mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	
							
								
								
									
										6
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -47,6 +47,9 @@ runs: | |||||||
|     - name: Build and push to ghcr by digest |     - name: Build and push to ghcr by digest | ||||||
|       id: build-ghcr |       id: build-ghcr | ||||||
|       uses: docker/build-push-action@v6.7.0 |       uses: docker/build-push-action@v6.7.0 | ||||||
|  |       env: | ||||||
|  |         DOCKER_BUILD_SUMMARY: false | ||||||
|  |         DOCKER_BUILD_RECORD_UPLOAD: false | ||||||
|       with: |       with: | ||||||
|         context: . |         context: . | ||||||
|         file: ./docker/Dockerfile |         file: ./docker/Dockerfile | ||||||
| @@ -70,6 +73,9 @@ runs: | |||||||
|     - name: Build and push to dockerhub by digest |     - name: Build and push to dockerhub by digest | ||||||
|       id: build-dockerhub |       id: build-dockerhub | ||||||
|       uses: docker/build-push-action@v6.7.0 |       uses: docker/build-push-action@v6.7.0 | ||||||
|  |       env: | ||||||
|  |         DOCKER_BUILD_SUMMARY: false | ||||||
|  |         DOCKER_BUILD_RECORD_UPLOAD: false | ||||||
|       with: |       with: | ||||||
|         context: . |         context: . | ||||||
|         file: ./docker/Dockerfile |         file: ./docker/Dockerfile | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							| @@ -17,7 +17,7 @@ runs: | |||||||
|   steps: |   steps: | ||||||
|     - name: Set up Python ${{ inputs.python-version }} |     - name: Set up Python ${{ inputs.python-version }} | ||||||
|       id: python |       id: python | ||||||
|       uses: actions/setup-python@v5.1.1 |       uses: actions/setup-python@v5.2.0 | ||||||
|       with: |       with: | ||||||
|         python-version: ${{ inputs.python-version }} |         python-version: ${{ inputs.python-version }} | ||||||
|     - name: Restore Python virtual environment |     - name: Restore Python virtual environment | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							| @@ -23,7 +23,7 @@ jobs: | |||||||
|       - name: Checkout |       - name: Checkout | ||||||
|         uses: actions/checkout@v4.1.7 |         uses: actions/checkout@v4.1.7 | ||||||
|       - name: Set up Python |       - name: Set up Python | ||||||
|         uses: actions/setup-python@v5.1.0 |         uses: actions/setup-python@v5.2.0 | ||||||
|         with: |         with: | ||||||
|           python-version: "3.11" |           python-version: "3.11" | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -42,7 +42,7 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4.1.7 |       - uses: actions/checkout@v4.1.7 | ||||||
|       - name: Set up Python |       - name: Set up Python | ||||||
|         uses: actions/setup-python@v5.1.0 |         uses: actions/setup-python@v5.2.0 | ||||||
|         with: |         with: | ||||||
|           python-version: "3.9" |           python-version: "3.9" | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -41,7 +41,7 @@ jobs: | |||||||
|         run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT |         run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT | ||||||
|       - name: Set up Python ${{ env.DEFAULT_PYTHON }} |       - name: Set up Python ${{ env.DEFAULT_PYTHON }} | ||||||
|         id: python |         id: python | ||||||
|         uses: actions/setup-python@v5.1.0 |         uses: actions/setup-python@v5.2.0 | ||||||
|         with: |         with: | ||||||
|           python-version: ${{ env.DEFAULT_PYTHON }} |           python-version: ${{ env.DEFAULT_PYTHON }} | ||||||
|       - name: Restore Python virtual environment |       - name: Restore Python virtual environment | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -53,7 +53,7 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4.1.7 |       - uses: actions/checkout@v4.1.7 | ||||||
|       - name: Set up Python |       - name: Set up Python | ||||||
|         uses: actions/setup-python@v5.1.0 |         uses: actions/setup-python@v5.2.0 | ||||||
|         with: |         with: | ||||||
|           python-version: "3.x" |           python-version: "3.x" | ||||||
|       - name: Set up python environment |       - name: Set up python environment | ||||||
| @@ -65,7 +65,7 @@ jobs: | |||||||
|           pip3 install build |           pip3 install build | ||||||
|           python3 -m build |           python3 -m build | ||||||
|       - name: Publish |       - name: Publish | ||||||
|         uses: pypa/gh-action-pypi-publish@v1.9.0 |         uses: pypa/gh-action-pypi-publish@v1.10.1 | ||||||
|  |  | ||||||
|   deploy-docker: |   deploy-docker: | ||||||
|     name: Build ESPHome ${{ matrix.platform }} |     name: Build ESPHome ${{ matrix.platform }} | ||||||
| @@ -85,7 +85,7 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4.1.7 |       - uses: actions/checkout@v4.1.7 | ||||||
|       - name: Set up Python |       - name: Set up Python | ||||||
|         uses: actions/setup-python@v5.1.0 |         uses: actions/setup-python@v5.2.0 | ||||||
|         with: |         with: | ||||||
|           python-version: "3.9" |           python-version: "3.9" | ||||||
|  |  | ||||||
| @@ -141,7 +141,7 @@ jobs: | |||||||
|           echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT |           echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT | ||||||
|  |  | ||||||
|       - name: Upload digests |       - name: Upload digests | ||||||
|         uses: actions/upload-artifact@v4.3.4 |         uses: actions/upload-artifact@v4.4.0 | ||||||
|         with: |         with: | ||||||
|           name: digests-${{ steps.sanitize.outputs.name }} |           name: digests-${{ steps.sanitize.outputs.name }} | ||||||
|           path: /tmp/digests |           path: /tmp/digests | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							| @@ -22,7 +22,7 @@ jobs: | |||||||
|           path: lib/home-assistant |           path: lib/home-assistant | ||||||
|  |  | ||||||
|       - name: Setup Python |       - name: Setup Python | ||||||
|         uses: actions/setup-python@v5.1.0 |         uses: actions/setup-python@v5.2.0 | ||||||
|         with: |         with: | ||||||
|           python-version: 3.12 |           python-version: 3.12 | ||||||
|  |  | ||||||
| @@ -36,7 +36,7 @@ jobs: | |||||||
|           python ./script/sync-device_class.py |           python ./script/sync-device_class.py | ||||||
|  |  | ||||||
|       - name: Commit changes |       - name: Commit changes | ||||||
|         uses: peter-evans/create-pull-request@v6.1.0 |         uses: peter-evans/create-pull-request@v7.0.0 | ||||||
|         with: |         with: | ||||||
|           commit-message: "Synchronise Device Classes from Home Assistant" |           commit-message: "Synchronise Device Classes from Home Assistant" | ||||||
|           committer: esphomebot <esphome@nabucasa.com> |           committer: esphomebot <esphome@nabucasa.com> | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								CODEOWNERS
									
									
									
									
									
								
							| @@ -58,9 +58,10 @@ esphome/components/beken_spi_led_strip/* @Mat931 | |||||||
| esphome/components/bh1750/* @OttoWinter | esphome/components/bh1750/* @OttoWinter | ||||||
| esphome/components/binary_sensor/* @esphome/core | esphome/components/binary_sensor/* @esphome/core | ||||||
| esphome/components/bk72xx/* @kuba2k2 | esphome/components/bk72xx/* @kuba2k2 | ||||||
|  | esphome/components/bl0906/* @athom-tech @jesserockz @tarontop | ||||||
| esphome/components/bl0939/* @ziceva | esphome/components/bl0939/* @ziceva | ||||||
| esphome/components/bl0940/* @tobias- | esphome/components/bl0940/* @tobias- | ||||||
| esphome/components/bl0942/* @dbuezas | esphome/components/bl0942/* @dbuezas @dwmw2 | ||||||
| esphome/components/ble_client/* @buxtronix @clydebarrow | esphome/components/ble_client/* @buxtronix @clydebarrow | ||||||
| esphome/components/bluetooth_proxy/* @jesserockz | esphome/components/bluetooth_proxy/* @jesserockz | ||||||
| esphome/components/bme280_base/* @esphome/core | esphome/components/bme280_base/* @esphome/core | ||||||
| @@ -69,6 +70,9 @@ esphome/components/bme680_bsec/* @trvrnrth | |||||||
| esphome/components/bme68x_bsec2/* @kbx81 @neffs | esphome/components/bme68x_bsec2/* @kbx81 @neffs | ||||||
| esphome/components/bme68x_bsec2_i2c/* @kbx81 @neffs | esphome/components/bme68x_bsec2_i2c/* @kbx81 @neffs | ||||||
| esphome/components/bmi160/* @flaviut | esphome/components/bmi160/* @flaviut | ||||||
|  | esphome/components/bmp280_base/* @ademuri | ||||||
|  | esphome/components/bmp280_i2c/* @ademuri | ||||||
|  | esphome/components/bmp280_spi/* @ademuri | ||||||
| esphome/components/bmp3xx/* @latonita | esphome/components/bmp3xx/* @latonita | ||||||
| esphome/components/bmp3xx_base/* @latonita @martgras | esphome/components/bmp3xx_base/* @latonita @martgras | ||||||
| esphome/components/bmp3xx_i2c/* @latonita | esphome/components/bmp3xx_i2c/* @latonita | ||||||
| @@ -82,6 +86,7 @@ esphome/components/cap1188/* @mreditor97 | |||||||
| esphome/components/captive_portal/* @OttoWinter | esphome/components/captive_portal/* @OttoWinter | ||||||
| esphome/components/ccs811/* @habbie | esphome/components/ccs811/* @habbie | ||||||
| esphome/components/cd74hc4067/* @asoehlke | esphome/components/cd74hc4067/* @asoehlke | ||||||
|  | esphome/components/ch422g/* @jesterret | ||||||
| esphome/components/climate/* @esphome/core | esphome/components/climate/* @esphome/core | ||||||
| esphome/components/climate_ir/* @glmnet | esphome/components/climate_ir/* @glmnet | ||||||
| esphome/components/color_temperature/* @jesserockz | esphome/components/color_temperature/* @jesserockz | ||||||
| @@ -169,6 +174,7 @@ esphome/components/he60r/* @clydebarrow | |||||||
| esphome/components/heatpumpir/* @rob-deutsch | esphome/components/heatpumpir/* @rob-deutsch | ||||||
| esphome/components/hitachi_ac424/* @sourabhjaiswal | esphome/components/hitachi_ac424/* @sourabhjaiswal | ||||||
| esphome/components/hm3301/* @freekode | esphome/components/hm3301/* @freekode | ||||||
|  | esphome/components/hmac_md5/* @dwmw2 | ||||||
| esphome/components/homeassistant/* @OttoWinter @esphome/core | esphome/components/homeassistant/* @OttoWinter @esphome/core | ||||||
| esphome/components/homeassistant/number/* @landonr | esphome/components/homeassistant/number/* @landonr | ||||||
| esphome/components/homeassistant/switch/* @Links2004 | esphome/components/homeassistant/switch/* @Links2004 | ||||||
| @@ -221,6 +227,7 @@ esphome/components/lilygo_t5_47/touchscreen/* @jesserockz | |||||||
| esphome/components/lock/* @esphome/core | esphome/components/lock/* @esphome/core | ||||||
| esphome/components/logger/* @esphome/core | esphome/components/logger/* @esphome/core | ||||||
| esphome/components/ltr390/* @latonita @sjtrny | esphome/components/ltr390/* @latonita @sjtrny | ||||||
|  | esphome/components/ltr501/* @latonita | ||||||
| esphome/components/ltr_als_ps/* @latonita | esphome/components/ltr_als_ps/* @latonita | ||||||
| esphome/components/lvgl/* @clydebarrow | esphome/components/lvgl/* @clydebarrow | ||||||
| esphome/components/m5stack_8angle/* @rnauber | esphome/components/m5stack_8angle/* @rnauber | ||||||
| @@ -382,6 +389,7 @@ esphome/components/st7701s/* @clydebarrow | |||||||
| esphome/components/st7735/* @SenexCrenshaw | esphome/components/st7735/* @SenexCrenshaw | ||||||
| esphome/components/st7789v/* @kbx81 | esphome/components/st7789v/* @kbx81 | ||||||
| esphome/components/st7920/* @marsjan155 | esphome/components/st7920/* @marsjan155 | ||||||
|  | esphome/components/statsd/* @Links2004 | ||||||
| esphome/components/substitutions/* @esphome/core | esphome/components/substitutions/* @esphome/core | ||||||
| esphome/components/sun/* @OttoWinter | esphome/components/sun/* @OttoWinter | ||||||
| esphome/components/sun_gtil2/* @Mat931 | esphome/components/sun_gtil2/* @Mat931 | ||||||
| @@ -421,6 +429,7 @@ esphome/components/tuya/switch/* @jesserockz | |||||||
| esphome/components/tuya/text_sensor/* @dentra | esphome/components/tuya/text_sensor/* @dentra | ||||||
| esphome/components/uart/* @esphome/core | esphome/components/uart/* @esphome/core | ||||||
| esphome/components/uart/button/* @ssieb | esphome/components/uart/button/* @ssieb | ||||||
|  | esphome/components/udp/* @clydebarrow | ||||||
| esphome/components/ufire_ec/* @pvizeli | esphome/components/ufire_ec/* @pvizeli | ||||||
| esphome/components/ufire_ise/* @pvizeli | esphome/components/ufire_ise/* @pvizeli | ||||||
| esphome/components/ultrasonic/* @OttoWinter | esphome/components/ultrasonic/* @OttoWinter | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ RUN \ | |||||||
|         python3-venv=3.11.2-1+b1 \ |         python3-venv=3.11.2-1+b1 \ | ||||||
|         python3-wheel=0.38.4-2 \ |         python3-wheel=0.38.4-2 \ | ||||||
|         iputils-ping=3:20221126-1 \ |         iputils-ping=3:20221126-1 \ | ||||||
|         git=1:2.39.2-1.1 \ |         git=1:2.39.5-0+deb12u1 \ | ||||||
|         curl=7.88.1-10+deb12u7 \ |         curl=7.88.1-10+deb12u7 \ | ||||||
|         openssh-client=1:9.2p1-2+deb12u3 \ |         openssh-client=1:9.2p1-2+deb12u3 \ | ||||||
|         python3-cffi=1.15.1-5 \ |         python3-cffi=1.15.1-5 \ | ||||||
| @@ -49,7 +49,7 @@ RUN \ | |||||||
|                 zlib1g-dev=1:1.2.13.dfsg-1 \ |                 zlib1g-dev=1:1.2.13.dfsg-1 \ | ||||||
|                 libjpeg-dev=1:2.1.5-2 \ |                 libjpeg-dev=1:2.1.5-2 \ | ||||||
|                 libfreetype-dev=2.12.1+dfsg-5+deb12u3 \ |                 libfreetype-dev=2.12.1+dfsg-5+deb12u3 \ | ||||||
|                 libssl-dev=3.0.14-1~deb12u1 \ |                 libssl-dev=3.0.14-1~deb12u2 \ | ||||||
|                 libffi-dev=3.4.4-1 \ |                 libffi-dev=3.4.4-1 \ | ||||||
|                 libopenjp2-7=2.5.0-2 \ |                 libopenjp2-7=2.5.0-2 \ | ||||||
|                 libtiff6=4.5.0-6+deb12u1 \ |                 libtiff6=4.5.0-6+deb12u1 \ | ||||||
| @@ -96,14 +96,19 @@ RUN \ | |||||||
| # First install requirements to leverage caching when requirements don't change | # First install requirements to leverage caching when requirements don't change | ||||||
| # tmpfs is for https://github.com/rust-lang/cargo/issues/8719 | # tmpfs is for https://github.com/rust-lang/cargo/issues/8719 | ||||||
|  |  | ||||||
| COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini / | COPY requirements.txt requirements_optional.txt / | ||||||
| RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ | RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ | ||||||
|         export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ |         curl -L https://www.piwheels.org/cp311/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl -o /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \ | ||||||
|  |         && pip3 install --break-system-packages --no-cache-dir /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \ | ||||||
|  |         && rm /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \ | ||||||
|  |         && export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ | ||||||
|     fi; \ |     fi; \ | ||||||
|     CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo \ |     CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo \ | ||||||
|     pip3 install \ |     pip3 install \ | ||||||
|     --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ |     --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt | ||||||
|     && /platformio_install_deps.py /platformio.ini --libraries |  | ||||||
|  | COPY script/platformio_install_deps.py platformio.ini / | ||||||
|  | RUN /platformio_install_deps.py /platformio.ini --libraries | ||||||
|  |  | ||||||
| # Avoid unsafe git error when container user and file config volume permissions don't match | # Avoid unsafe git error when container user and file config volume permissions don't match | ||||||
| RUN git config --system --add safe.directory '*' | RUN git config --system --add safe.directory '*' | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ from esphome.const import ( | |||||||
|     SECRETS_FILES, |     SECRETS_FILES, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, EsphomeError, coroutine | from esphome.core import CORE, EsphomeError, coroutine | ||||||
| from esphome.helpers import indent, is_ip_address | from esphome.helpers import indent, is_ip_address, get_bool_env | ||||||
| from esphome.log import Fore, color, setup_log | from esphome.log import Fore, color, setup_log | ||||||
| from esphome.util import ( | from esphome.util import ( | ||||||
|     get_serial_ports, |     get_serial_ports, | ||||||
| @@ -731,7 +731,11 @@ POST_CONFIG_ACTIONS = { | |||||||
| def parse_args(argv): | def parse_args(argv): | ||||||
|     options_parser = argparse.ArgumentParser(add_help=False) |     options_parser = argparse.ArgumentParser(add_help=False) | ||||||
|     options_parser.add_argument( |     options_parser.add_argument( | ||||||
|         "-v", "--verbose", help="Enable verbose ESPHome logs.", action="store_true" |         "-v", | ||||||
|  |         "--verbose", | ||||||
|  |         help="Enable verbose ESPHome logs.", | ||||||
|  |         action="store_true", | ||||||
|  |         default=get_bool_env("ESPHOME_VERBOSE"), | ||||||
|     ) |     ) | ||||||
|     options_parser.add_argument( |     options_parser.add_argument( | ||||||
|         "-q", "--quiet", help="Disable all ESPHome logs.", action="store_true" |         "-q", "--quiet", help="Disable all ESPHome logs.", action="store_true" | ||||||
|   | |||||||
| @@ -62,6 +62,8 @@ service APIConnection { | |||||||
|   rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {} |   rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {} | ||||||
|  |  | ||||||
|   rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {} |   rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {} | ||||||
|  |   rpc voice_assistant_get_configuration(VoiceAssistantConfigurationRequest) returns (VoiceAssistantConfigurationResponse) {} | ||||||
|  |   rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {} | ||||||
|  |  | ||||||
|   rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {} |   rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {} | ||||||
| } | } | ||||||
| @@ -1107,6 +1109,19 @@ enum MediaPlayerCommand { | |||||||
|   MEDIA_PLAYER_COMMAND_MUTE = 3; |   MEDIA_PLAYER_COMMAND_MUTE = 3; | ||||||
|   MEDIA_PLAYER_COMMAND_UNMUTE = 4; |   MEDIA_PLAYER_COMMAND_UNMUTE = 4; | ||||||
| } | } | ||||||
|  | enum MediaPlayerFormatPurpose { | ||||||
|  |   MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0; | ||||||
|  |   MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1; | ||||||
|  | } | ||||||
|  | message MediaPlayerSupportedFormat { | ||||||
|  |   option (ifdef) = "USE_MEDIA_PLAYER"; | ||||||
|  |  | ||||||
|  |   string format = 1; | ||||||
|  |   uint32 sample_rate = 2; | ||||||
|  |   uint32 num_channels = 3; | ||||||
|  |   MediaPlayerFormatPurpose purpose = 4; | ||||||
|  |   uint32 sample_bytes = 5; | ||||||
|  | } | ||||||
| message ListEntitiesMediaPlayerResponse { | message ListEntitiesMediaPlayerResponse { | ||||||
|   option (id) = 63; |   option (id) = 63; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
| @@ -1122,6 +1137,8 @@ message ListEntitiesMediaPlayerResponse { | |||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|  |  | ||||||
|   bool supports_pause = 8; |   bool supports_pause = 8; | ||||||
|  |  | ||||||
|  |   repeated MediaPlayerSupportedFormat supported_formats = 9; | ||||||
| } | } | ||||||
| message MediaPlayerStateResponse { | message MediaPlayerStateResponse { | ||||||
|   option (id) = 64; |   option (id) = 64; | ||||||
| @@ -1539,6 +1556,53 @@ message VoiceAssistantTimerEventResponse { | |||||||
|   bool is_active = 6; |   bool is_active = 6; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | message VoiceAssistantAnnounceRequest { | ||||||
|  |   option (id) = 119; | ||||||
|  |   option (source) = SOURCE_CLIENT; | ||||||
|  |   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||||
|  |  | ||||||
|  |   string media_id = 1; | ||||||
|  |   string text = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message VoiceAssistantAnnounceFinished { | ||||||
|  |   option (id) = 120; | ||||||
|  |   option (source) = SOURCE_SERVER; | ||||||
|  |   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||||
|  |  | ||||||
|  |   bool success = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message VoiceAssistantWakeWord { | ||||||
|  |   string id = 1; | ||||||
|  |   string wake_word = 2; | ||||||
|  |   repeated string trained_languages = 3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message VoiceAssistantConfigurationRequest { | ||||||
|  |   option (id) = 121; | ||||||
|  |   option (source) = SOURCE_CLIENT; | ||||||
|  |   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message VoiceAssistantConfigurationResponse { | ||||||
|  |   option (id) = 122; | ||||||
|  |   option (source) = SOURCE_SERVER; | ||||||
|  |   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||||
|  |  | ||||||
|  |   repeated VoiceAssistantWakeWord available_wake_words = 1; | ||||||
|  |   repeated string active_wake_words = 2; | ||||||
|  |   uint32 max_active_wake_words = 3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message VoiceAssistantSetConfiguration { | ||||||
|  |   option (id) = 123; | ||||||
|  |   option (source) = SOURCE_CLIENT; | ||||||
|  |   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||||
|  |  | ||||||
|  |   repeated string active_wake_words = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
| // ==================== ALARM CONTROL PANEL ==================== | // ==================== ALARM CONTROL PANEL ==================== | ||||||
| enum AlarmControlPanelState { | enum AlarmControlPanelState { | ||||||
|   ALARM_STATE_DISARMED = 0; |   ALARM_STATE_DISARMED = 0; | ||||||
|   | |||||||
| @@ -1026,6 +1026,16 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play | |||||||
|   auto traits = media_player->get_traits(); |   auto traits = media_player->get_traits(); | ||||||
|   msg.supports_pause = traits.get_supports_pause(); |   msg.supports_pause = traits.get_supports_pause(); | ||||||
|  |  | ||||||
|  |   for (auto &supported_format : traits.get_supported_formats()) { | ||||||
|  |     MediaPlayerSupportedFormat media_format; | ||||||
|  |     media_format.format = supported_format.format; | ||||||
|  |     media_format.sample_rate = supported_format.sample_rate; | ||||||
|  |     media_format.num_channels = supported_format.num_channels; | ||||||
|  |     media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose); | ||||||
|  |     media_format.sample_bytes = supported_format.sample_bytes; | ||||||
|  |     msg.supported_formats.push_back(media_format); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   return this->send_list_entities_media_player_response(msg); |   return this->send_list_entities_media_player_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { | void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { | ||||||
| @@ -1204,6 +1214,49 @@ void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistant | |||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) { | ||||||
|  |   if (voice_assistant::global_voice_assistant != nullptr) { | ||||||
|  |     if (voice_assistant::global_voice_assistant->get_api_connection() != this) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     voice_assistant::global_voice_assistant->on_announce(msg); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration( | ||||||
|  |     const VoiceAssistantConfigurationRequest &msg) { | ||||||
|  |   VoiceAssistantConfigurationResponse resp; | ||||||
|  |   if (voice_assistant::global_voice_assistant != nullptr) { | ||||||
|  |     if (voice_assistant::global_voice_assistant->get_api_connection() != this) { | ||||||
|  |       return resp; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     auto &config = voice_assistant::global_voice_assistant->get_configuration(); | ||||||
|  |     for (auto &wake_word : config.available_wake_words) { | ||||||
|  |       VoiceAssistantWakeWord resp_wake_word; | ||||||
|  |       resp_wake_word.id = wake_word.id; | ||||||
|  |       resp_wake_word.wake_word = wake_word.wake_word; | ||||||
|  |       for (const auto &lang : wake_word.trained_languages) { | ||||||
|  |         resp_wake_word.trained_languages.push_back(lang); | ||||||
|  |       } | ||||||
|  |       resp.available_wake_words.push_back(std::move(resp_wake_word)); | ||||||
|  |     } | ||||||
|  |     resp.max_active_wake_words = config.max_active_wake_words; | ||||||
|  |   } | ||||||
|  |   return resp; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { | ||||||
|  |   if (voice_assistant::global_voice_assistant != nullptr) { | ||||||
|  |     if (voice_assistant::global_voice_assistant->get_api_connection() != this) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_ALARM_CONTROL_PANEL | #ifdef USE_ALARM_CONTROL_PANEL | ||||||
|   | |||||||
| @@ -151,6 +151,10 @@ class APIConnection : public APIServerConnection { | |||||||
|   void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; |   void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; | ||||||
|   void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override; |   void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override; | ||||||
|   void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override; |   void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override; | ||||||
|  |   void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override; | ||||||
|  |   VoiceAssistantConfigurationResponse voice_assistant_get_configuration( | ||||||
|  |       const VoiceAssistantConfigurationRequest &msg) override; | ||||||
|  |   void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_ALARM_CONTROL_PANEL | #ifdef USE_ALARM_CONTROL_PANEL | ||||||
|   | |||||||
| @@ -387,6 +387,18 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerCommand>(enums::Me | |||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | template<> const char *proto_enum_to_string<enums::MediaPlayerFormatPurpose>(enums::MediaPlayerFormatPurpose value) { | ||||||
|  |   switch (value) { | ||||||
|  |     case enums::MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT: | ||||||
|  |       return "MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT"; | ||||||
|  |     case enums::MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT: | ||||||
|  |       return "MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT"; | ||||||
|  |     default: | ||||||
|  |       return "UNKNOWN"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| template<> | template<> | ||||||
| const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::BluetoothDeviceRequestType value) { | const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::BluetoothDeviceRequestType value) { | ||||||
|   switch (value) { |   switch (value) { | ||||||
| @@ -5123,6 +5135,74 @@ void ButtonCommandRequest::dump_to(std::string &out) const { | |||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | bool MediaPlayerSupportedFormat::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 2: { | ||||||
|  |       this->sample_rate = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 3: { | ||||||
|  |       this->num_channels = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 4: { | ||||||
|  |       this->purpose = value.as_enum<enums::MediaPlayerFormatPurpose>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 5: { | ||||||
|  |       this->sample_bytes = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool MediaPlayerSupportedFormat::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->format = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void MediaPlayerSupportedFormat::encode(ProtoWriteBuffer buffer) const { | ||||||
|  |   buffer.encode_string(1, this->format); | ||||||
|  |   buffer.encode_uint32(2, this->sample_rate); | ||||||
|  |   buffer.encode_uint32(3, this->num_channels); | ||||||
|  |   buffer.encode_enum<enums::MediaPlayerFormatPurpose>(4, this->purpose); | ||||||
|  |   buffer.encode_uint32(5, this->sample_bytes); | ||||||
|  | } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void MediaPlayerSupportedFormat::dump_to(std::string &out) const { | ||||||
|  |   __attribute__((unused)) char buffer[64]; | ||||||
|  |   out.append("MediaPlayerSupportedFormat {\n"); | ||||||
|  |   out.append("  format: "); | ||||||
|  |   out.append("'").append(this->format).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  sample_rate: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->sample_rate); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  num_channels: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->num_channels); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  purpose: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::MediaPlayerFormatPurpose>(this->purpose)); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  sample_bytes: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->sample_bytes); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
| bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|   switch (field_id) { |   switch (field_id) { | ||||||
|     case 6: { |     case 6: { | ||||||
| @@ -5159,6 +5239,10 @@ bool ListEntitiesMediaPlayerResponse::decode_length(uint32_t field_id, ProtoLeng | |||||||
|       this->icon = value.as_string(); |       this->icon = value.as_string(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 9: { | ||||||
|  |       this->supported_formats.push_back(value.as_message<MediaPlayerSupportedFormat>()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -5182,6 +5266,9 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|   buffer.encode_bool(8, this->supports_pause); |   buffer.encode_bool(8, this->supports_pause); | ||||||
|  |   for (auto &it : this->supported_formats) { | ||||||
|  |     buffer.encode_message<MediaPlayerSupportedFormat>(9, it, true); | ||||||
|  |   } | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { | void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { | ||||||
| @@ -5219,6 +5306,12 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  supports_pause: "); |   out.append("  supports_pause: "); | ||||||
|   out.append(YESNO(this->supports_pause)); |   out.append(YESNO(this->supports_pause)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   for (const auto &it : this->supported_formats) { | ||||||
|  |     out.append("  supported_formats: "); | ||||||
|  |     it.dump_to(out); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -6978,6 +7071,193 @@ void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const { | |||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->media_id = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 2: { | ||||||
|  |       this->text = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void VoiceAssistantAnnounceRequest::encode(ProtoWriteBuffer buffer) const { | ||||||
|  |   buffer.encode_string(1, this->media_id); | ||||||
|  |   buffer.encode_string(2, this->text); | ||||||
|  | } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const { | ||||||
|  |   __attribute__((unused)) char buffer[64]; | ||||||
|  |   out.append("VoiceAssistantAnnounceRequest {\n"); | ||||||
|  |   out.append("  media_id: "); | ||||||
|  |   out.append("'").append(this->media_id).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  text: "); | ||||||
|  |   out.append("'").append(this->text).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | bool VoiceAssistantAnnounceFinished::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->success = value.as_bool(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void VoiceAssistantAnnounceFinished::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->success); } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { | ||||||
|  |   __attribute__((unused)) char buffer[64]; | ||||||
|  |   out.append("VoiceAssistantAnnounceFinished {\n"); | ||||||
|  |   out.append("  success: "); | ||||||
|  |   out.append(YESNO(this->success)); | ||||||
|  |   out.append("\n"); | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | bool VoiceAssistantWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->id = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 2: { | ||||||
|  |       this->wake_word = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 3: { | ||||||
|  |       this->trained_languages.push_back(value.as_string()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void VoiceAssistantWakeWord::encode(ProtoWriteBuffer buffer) const { | ||||||
|  |   buffer.encode_string(1, this->id); | ||||||
|  |   buffer.encode_string(2, this->wake_word); | ||||||
|  |   for (auto &it : this->trained_languages) { | ||||||
|  |     buffer.encode_string(3, it, true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void VoiceAssistantWakeWord::dump_to(std::string &out) const { | ||||||
|  |   __attribute__((unused)) char buffer[64]; | ||||||
|  |   out.append("VoiceAssistantWakeWord {\n"); | ||||||
|  |   out.append("  id: "); | ||||||
|  |   out.append("'").append(this->id).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  wake_word: "); | ||||||
|  |   out.append("'").append(this->wake_word).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   for (const auto &it : this->trained_languages) { | ||||||
|  |     out.append("  trained_languages: "); | ||||||
|  |     out.append("'").append(it).append("'"); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | void VoiceAssistantConfigurationRequest::encode(ProtoWriteBuffer buffer) const {} | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { | ||||||
|  |   out.append("VoiceAssistantConfigurationRequest {}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | bool VoiceAssistantConfigurationResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 3: { | ||||||
|  |       this->max_active_wake_words = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool VoiceAssistantConfigurationResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->available_wake_words.push_back(value.as_message<VoiceAssistantWakeWord>()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 2: { | ||||||
|  |       this->active_wake_words.push_back(value.as_string()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const { | ||||||
|  |   for (auto &it : this->available_wake_words) { | ||||||
|  |     buffer.encode_message<VoiceAssistantWakeWord>(1, it, true); | ||||||
|  |   } | ||||||
|  |   for (auto &it : this->active_wake_words) { | ||||||
|  |     buffer.encode_string(2, it, true); | ||||||
|  |   } | ||||||
|  |   buffer.encode_uint32(3, this->max_active_wake_words); | ||||||
|  | } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const { | ||||||
|  |   __attribute__((unused)) char buffer[64]; | ||||||
|  |   out.append("VoiceAssistantConfigurationResponse {\n"); | ||||||
|  |   for (const auto &it : this->available_wake_words) { | ||||||
|  |     out.append("  available_wake_words: "); | ||||||
|  |     it.dump_to(out); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (const auto &it : this->active_wake_words) { | ||||||
|  |     out.append("  active_wake_words: "); | ||||||
|  |     out.append("'").append(it).append("'"); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   out.append("  max_active_wake_words: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->max_active_wake_words); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->active_wake_words.push_back(value.as_string()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void VoiceAssistantSetConfiguration::encode(ProtoWriteBuffer buffer) const { | ||||||
|  |   for (auto &it : this->active_wake_words) { | ||||||
|  |     buffer.encode_string(1, it, true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void VoiceAssistantSetConfiguration::dump_to(std::string &out) const { | ||||||
|  |   __attribute__((unused)) char buffer[64]; | ||||||
|  |   out.append("VoiceAssistantSetConfiguration {\n"); | ||||||
|  |   for (const auto &it : this->active_wake_words) { | ||||||
|  |     out.append("  active_wake_words: "); | ||||||
|  |     out.append("'").append(it).append("'"); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
| bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|   switch (field_id) { |   switch (field_id) { | ||||||
|     case 6: { |     case 6: { | ||||||
|   | |||||||
| @@ -156,6 +156,10 @@ enum MediaPlayerCommand : uint32_t { | |||||||
|   MEDIA_PLAYER_COMMAND_MUTE = 3, |   MEDIA_PLAYER_COMMAND_MUTE = 3, | ||||||
|   MEDIA_PLAYER_COMMAND_UNMUTE = 4, |   MEDIA_PLAYER_COMMAND_UNMUTE = 4, | ||||||
| }; | }; | ||||||
|  | enum MediaPlayerFormatPurpose : uint32_t { | ||||||
|  |   MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0, | ||||||
|  |   MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1, | ||||||
|  | }; | ||||||
| enum BluetoothDeviceRequestType : uint32_t { | enum BluetoothDeviceRequestType : uint32_t { | ||||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0, |   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0, | ||||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1, |   BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1, | ||||||
| @@ -1267,6 +1271,22 @@ class ButtonCommandRequest : public ProtoMessage { | |||||||
|  protected: |  protected: | ||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
| }; | }; | ||||||
|  | class MediaPlayerSupportedFormat : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   std::string format{}; | ||||||
|  |   uint32_t sample_rate{0}; | ||||||
|  |   uint32_t num_channels{0}; | ||||||
|  |   enums::MediaPlayerFormatPurpose purpose{}; | ||||||
|  |   uint32_t sample_bytes{0}; | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|  |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
|  | }; | ||||||
| class ListEntitiesMediaPlayerResponse : public ProtoMessage { | class ListEntitiesMediaPlayerResponse : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   std::string object_id{}; |   std::string object_id{}; | ||||||
| @@ -1277,6 +1297,7 @@ class ListEntitiesMediaPlayerResponse : public ProtoMessage { | |||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|   enums::EntityCategory entity_category{}; |   enums::EntityCategory entity_category{}; | ||||||
|   bool supports_pause{false}; |   bool supports_pause{false}; | ||||||
|  |   std::vector<MediaPlayerSupportedFormat> supported_formats{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -1805,6 +1826,76 @@ class VoiceAssistantTimerEventResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | class VoiceAssistantAnnounceRequest : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   std::string media_id{}; | ||||||
|  |   std::string text{}; | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|  | }; | ||||||
|  | class VoiceAssistantAnnounceFinished : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   bool success{false}; | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
|  | }; | ||||||
|  | class VoiceAssistantWakeWord : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   std::string id{}; | ||||||
|  |   std::string wake_word{}; | ||||||
|  |   std::vector<std::string> trained_languages{}; | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|  | }; | ||||||
|  | class VoiceAssistantConfigurationRequest : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  | }; | ||||||
|  | class VoiceAssistantConfigurationResponse : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   std::vector<VoiceAssistantWakeWord> available_wake_words{}; | ||||||
|  |   std::vector<std::string> active_wake_words{}; | ||||||
|  |   uint32_t max_active_wake_words{0}; | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|  |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
|  | }; | ||||||
|  | class VoiceAssistantSetConfiguration : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   std::vector<std::string> active_wake_words{}; | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|  | }; | ||||||
| class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { | class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   std::string object_id{}; |   std::string object_id{}; | ||||||
|   | |||||||
| @@ -486,6 +486,29 @@ bool APIServerConnectionBase::send_voice_assistant_audio(const VoiceAssistantAud | |||||||
| #endif | #endif | ||||||
| #ifdef USE_VOICE_ASSISTANT | #ifdef USE_VOICE_ASSISTANT | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  | bool APIServerConnectionBase::send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg) { | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   ESP_LOGVV(TAG, "send_voice_assistant_announce_finished: %s", msg.dump().c_str()); | ||||||
|  | #endif | ||||||
|  |   return this->send_message_<VoiceAssistantAnnounceFinished>(msg, 120); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  | bool APIServerConnectionBase::send_voice_assistant_configuration_response( | ||||||
|  |     const VoiceAssistantConfigurationResponse &msg) { | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   ESP_LOGVV(TAG, "send_voice_assistant_configuration_response: %s", msg.dump().c_str()); | ||||||
|  | #endif | ||||||
|  |   return this->send_message_<VoiceAssistantConfigurationResponse>(msg, 122); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  | #endif | ||||||
| #ifdef USE_ALARM_CONTROL_PANEL | #ifdef USE_ALARM_CONTROL_PANEL | ||||||
| bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response( | bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response( | ||||||
|     const ListEntitiesAlarmControlPanelResponse &msg) { |     const ListEntitiesAlarmControlPanelResponse &msg) { | ||||||
| @@ -1135,6 +1158,39 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | |||||||
|       ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str()); |       ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str()); | ||||||
| #endif | #endif | ||||||
|       this->on_update_command_request(msg); |       this->on_update_command_request(msg); | ||||||
|  | #endif | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     case 119: { | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  |       VoiceAssistantAnnounceRequest msg; | ||||||
|  |       msg.decode(msg_data, msg_size); | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |       ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str()); | ||||||
|  | #endif | ||||||
|  |       this->on_voice_assistant_announce_request(msg); | ||||||
|  | #endif | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     case 121: { | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  |       VoiceAssistantConfigurationRequest msg; | ||||||
|  |       msg.decode(msg_data, msg_size); | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |       ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str()); | ||||||
|  | #endif | ||||||
|  |       this->on_voice_assistant_configuration_request(msg); | ||||||
|  | #endif | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     case 123: { | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  |       VoiceAssistantSetConfiguration msg; | ||||||
|  |       msg.decode(msg_data, msg_size); | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |       ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str()); | ||||||
|  | #endif | ||||||
|  |       this->on_voice_assistant_set_configuration(msg); | ||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
| @@ -1625,6 +1681,35 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo | |||||||
|   this->subscribe_voice_assistant(msg); |   this->subscribe_voice_assistant(msg); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  | void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { | ||||||
|  |   if (!this->is_connection_setup()) { | ||||||
|  |     this->on_no_setup_connection(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (!this->is_authenticated()) { | ||||||
|  |     this->on_unauthenticated_access(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   VoiceAssistantConfigurationResponse ret = this->voice_assistant_get_configuration(msg); | ||||||
|  |   if (!this->send_voice_assistant_configuration_response(ret)) { | ||||||
|  |     this->on_fatal_error(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  | void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { | ||||||
|  |   if (!this->is_connection_setup()) { | ||||||
|  |     this->on_no_setup_connection(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (!this->is_authenticated()) { | ||||||
|  |     this->on_unauthenticated_access(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->voice_assistant_set_configuration(msg); | ||||||
|  | } | ||||||
|  | #endif | ||||||
| #ifdef USE_ALARM_CONTROL_PANEL | #ifdef USE_ALARM_CONTROL_PANEL | ||||||
| void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { | void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (!this->is_connection_setup()) { | ||||||
|   | |||||||
| @@ -247,6 +247,21 @@ class APIServerConnectionBase : public ProtoService { | |||||||
| #ifdef USE_VOICE_ASSISTANT | #ifdef USE_VOICE_ASSISTANT | ||||||
|   virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){}; |   virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){}; | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  |   virtual void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){}; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  |   bool send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg); | ||||||
|  | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  |   virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){}; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  |   bool send_voice_assistant_configuration_response(const VoiceAssistantConfigurationResponse &msg); | ||||||
|  | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  |   virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){}; | ||||||
|  | #endif | ||||||
| #ifdef USE_ALARM_CONTROL_PANEL | #ifdef USE_ALARM_CONTROL_PANEL | ||||||
|   bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg); |   bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg); | ||||||
| #endif | #endif | ||||||
| @@ -419,6 +434,13 @@ class APIServerConnection : public APIServerConnectionBase { | |||||||
| #ifdef USE_VOICE_ASSISTANT | #ifdef USE_VOICE_ASSISTANT | ||||||
|   virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; |   virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  |   virtual VoiceAssistantConfigurationResponse voice_assistant_get_configuration( | ||||||
|  |       const VoiceAssistantConfigurationRequest &msg) = 0; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  |   virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0; | ||||||
|  | #endif | ||||||
| #ifdef USE_ALARM_CONTROL_PANEL | #ifdef USE_ALARM_CONTROL_PANEL | ||||||
|   virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; |   virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; | ||||||
| #endif | #endif | ||||||
| @@ -520,6 +542,12 @@ class APIServerConnection : public APIServerConnectionBase { | |||||||
| #ifdef USE_VOICE_ASSISTANT | #ifdef USE_VOICE_ASSISTANT | ||||||
|   void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; |   void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  |   void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_VOICE_ASSISTANT | ||||||
|  |   void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; | ||||||
|  | #endif | ||||||
| #ifdef USE_ALARM_CONTROL_PANEL | #ifdef USE_ALARM_CONTROL_PANEL | ||||||
|   void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; |   void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -1,13 +1,13 @@ | |||||||
| # Dummy integration to allow relying on AsyncTCP | # Dummy integration to allow relying on AsyncTCP | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.core import CORE, coroutine_with_priority |  | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|  |     PLATFORM_BK72XX, | ||||||
|     PLATFORM_ESP32, |     PLATFORM_ESP32, | ||||||
|     PLATFORM_ESP8266, |     PLATFORM_ESP8266, | ||||||
|     PLATFORM_BK72XX, |  | ||||||
|     PLATFORM_RTL87XX, |     PLATFORM_RTL87XX, | ||||||
| ) | ) | ||||||
|  | from esphome.core import CORE, coroutine_with_priority | ||||||
|  |  | ||||||
| CODEOWNERS = ["@OttoWinter"] | CODEOWNERS = ["@OttoWinter"] | ||||||
|  |  | ||||||
| @@ -22,7 +22,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     if CORE.is_esp32 or CORE.is_libretiny: |     if CORE.is_esp32 or CORE.is_libretiny: | ||||||
|         # https://github.com/esphome/AsyncTCP/blob/master/library.json |         # https://github.com/esphome/AsyncTCP/blob/master/library.json | ||||||
|         cg.add_library("esphome/AsyncTCP-esphome", "2.1.3") |         cg.add_library("esphome/AsyncTCP-esphome", "2.1.4") | ||||||
|     elif CORE.is_esp8266: |     elif CORE.is_esp8266: | ||||||
|         # https://github.com/esphome/ESPAsyncTCP |         # https://github.com/esphome/ESPAsyncTCP | ||||||
|         cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") |         cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") | ||||||
|   | |||||||
| @@ -1,34 +1,34 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome.components import sensor, spi | from esphome.components import sensor, spi | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ID, |  | ||||||
|     CONF_REACTIVE_POWER, |  | ||||||
|     CONF_VOLTAGE, |  | ||||||
|     CONF_CURRENT, |     CONF_CURRENT, | ||||||
|  |     CONF_FORWARD_ACTIVE_ENERGY, | ||||||
|  |     CONF_FREQUENCY, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_LINE_FREQUENCY, | ||||||
|     CONF_POWER, |     CONF_POWER, | ||||||
|     CONF_POWER_FACTOR, |     CONF_POWER_FACTOR, | ||||||
|     CONF_FREQUENCY, |     CONF_REACTIVE_POWER, | ||||||
|     CONF_FORWARD_ACTIVE_ENERGY, |  | ||||||
|     CONF_REVERSE_ACTIVE_ENERGY, |     CONF_REVERSE_ACTIVE_ENERGY, | ||||||
|  |     CONF_VOLTAGE, | ||||||
|     DEVICE_CLASS_CURRENT, |     DEVICE_CLASS_CURRENT, | ||||||
|     DEVICE_CLASS_ENERGY, |     DEVICE_CLASS_ENERGY, | ||||||
|     DEVICE_CLASS_POWER, |     DEVICE_CLASS_POWER, | ||||||
|     DEVICE_CLASS_POWER_FACTOR, |     DEVICE_CLASS_POWER_FACTOR, | ||||||
|     DEVICE_CLASS_VOLTAGE, |     DEVICE_CLASS_VOLTAGE, | ||||||
|     ICON_LIGHTBULB, |  | ||||||
|     ICON_CURRENT_AC, |     ICON_CURRENT_AC, | ||||||
|  |     ICON_LIGHTBULB, | ||||||
|     STATE_CLASS_MEASUREMENT, |     STATE_CLASS_MEASUREMENT, | ||||||
|     STATE_CLASS_TOTAL_INCREASING, |     STATE_CLASS_TOTAL_INCREASING, | ||||||
|  |     UNIT_AMPERE, | ||||||
|     UNIT_HERTZ, |     UNIT_HERTZ, | ||||||
|     UNIT_VOLT, |     UNIT_VOLT, | ||||||
|     UNIT_AMPERE, |  | ||||||
|     UNIT_WATT, |  | ||||||
|     UNIT_VOLT_AMPS_REACTIVE, |     UNIT_VOLT_AMPS_REACTIVE, | ||||||
|  |     UNIT_WATT, | ||||||
|     UNIT_WATT_HOURS, |     UNIT_WATT_HOURS, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CONF_LINE_FREQUENCY = "line_frequency" |  | ||||||
| CONF_METER_CONSTANT = "meter_constant" | CONF_METER_CONSTANT = "meter_constant" | ||||||
| CONF_PL_CONST = "pl_const" | CONF_PL_CONST = "pl_const" | ||||||
| CONF_GAIN_PGA = "gain_pga" | CONF_GAIN_PGA = "gain_pga" | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ from esphome.const import ( | |||||||
|     CONF_FORWARD_ACTIVE_ENERGY, |     CONF_FORWARD_ACTIVE_ENERGY, | ||||||
|     CONF_FREQUENCY, |     CONF_FREQUENCY, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|  |     CONF_LINE_FREQUENCY, | ||||||
|     CONF_PHASE_A, |     CONF_PHASE_A, | ||||||
|     CONF_PHASE_ANGLE, |     CONF_PHASE_ANGLE, | ||||||
|     CONF_PHASE_B, |     CONF_PHASE_B, | ||||||
| @@ -39,7 +40,6 @@ from esphome.const import ( | |||||||
|  |  | ||||||
| from . import atm90e32_ns | from . import atm90e32_ns | ||||||
|  |  | ||||||
| CONF_LINE_FREQUENCY = "line_frequency" |  | ||||||
| CONF_CHIP_TEMPERATURE = "chip_temperature" | CONF_CHIP_TEMPERATURE = "chip_temperature" | ||||||
| CONF_GAIN_PGA = "gain_pga" | CONF_GAIN_PGA = "gain_pga" | ||||||
| CONF_CURRENT_PHASES = "current_phases" | CONF_CURRENT_PHASES = "current_phases" | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								esphome/components/bl0906/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/bl0906/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ["@athom-tech", "@tarontop", "@jesserockz"] | ||||||
							
								
								
									
										238
									
								
								esphome/components/bl0906/bl0906.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								esphome/components/bl0906/bl0906.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,238 @@ | |||||||
|  | #include "bl0906.h" | ||||||
|  | #include "constants.h" | ||||||
|  |  | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bl0906 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "bl0906"; | ||||||
|  |  | ||||||
|  | constexpr uint32_t to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; } | ||||||
|  |  | ||||||
|  | constexpr int32_t to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; } | ||||||
|  |  | ||||||
|  | // The SUM byte is (Addr+Data_L+Data_M+Data_H)&0xFF negated; | ||||||
|  | constexpr uint8_t bl0906_checksum(const uint8_t address, const DataPacket *data) { | ||||||
|  |   return (address + data->l + data->m + data->h) ^ 0xFF; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BL0906::loop() { | ||||||
|  |   if (this->current_channel_ == UINT8_MAX) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   while (this->available()) | ||||||
|  |     this->flush(); | ||||||
|  |  | ||||||
|  |   if (this->current_channel_ == 0) { | ||||||
|  |     // Temperature | ||||||
|  |     this->read_data_(BL0906_TEMPERATURE, BL0906_TREF, this->temperature_sensor_); | ||||||
|  |   } else if (this->current_channel_ == 1) { | ||||||
|  |     this->read_data_(BL0906_I_1_RMS, BL0906_IREF, this->current_1_sensor_); | ||||||
|  |     this->read_data_(BL0906_WATT_1, BL0906_PREF, this->power_1_sensor_); | ||||||
|  |     this->read_data_(BL0906_CF_1_CNT, BL0906_EREF, this->energy_1_sensor_); | ||||||
|  |   } else if (this->current_channel_ == 2) { | ||||||
|  |     this->read_data_(BL0906_I_2_RMS, BL0906_IREF, this->current_2_sensor_); | ||||||
|  |     this->read_data_(BL0906_WATT_2, BL0906_PREF, this->power_2_sensor_); | ||||||
|  |     this->read_data_(BL0906_CF_2_CNT, BL0906_EREF, this->energy_2_sensor_); | ||||||
|  |   } else if (this->current_channel_ == 3) { | ||||||
|  |     this->read_data_(BL0906_I_3_RMS, BL0906_IREF, this->current_3_sensor_); | ||||||
|  |     this->read_data_(BL0906_WATT_3, BL0906_PREF, this->power_3_sensor_); | ||||||
|  |     this->read_data_(BL0906_CF_3_CNT, BL0906_EREF, this->energy_3_sensor_); | ||||||
|  |   } else if (this->current_channel_ == 4) { | ||||||
|  |     this->read_data_(BL0906_I_4_RMS, BL0906_IREF, this->current_4_sensor_); | ||||||
|  |     this->read_data_(BL0906_WATT_4, BL0906_PREF, this->power_4_sensor_); | ||||||
|  |     this->read_data_(BL0906_CF_4_CNT, BL0906_EREF, this->energy_4_sensor_); | ||||||
|  |   } else if (this->current_channel_ == 5) { | ||||||
|  |     this->read_data_(BL0906_I_5_RMS, BL0906_IREF, this->current_5_sensor_); | ||||||
|  |     this->read_data_(BL0906_WATT_5, BL0906_PREF, this->power_5_sensor_); | ||||||
|  |     this->read_data_(BL0906_CF_5_CNT, BL0906_EREF, this->energy_5_sensor_); | ||||||
|  |   } else if (this->current_channel_ == 6) { | ||||||
|  |     this->read_data_(BL0906_I_6_RMS, BL0906_IREF, this->current_6_sensor_); | ||||||
|  |     this->read_data_(BL0906_WATT_6, BL0906_PREF, this->power_6_sensor_); | ||||||
|  |     this->read_data_(BL0906_CF_6_CNT, BL0906_EREF, this->energy_6_sensor_); | ||||||
|  |   } else if (this->current_channel_ == UINT8_MAX - 2) { | ||||||
|  |     // Frequency | ||||||
|  |     this->read_data_(BL0906_FREQUENCY, BL0906_FREF, frequency_sensor_); | ||||||
|  |     // Voltage | ||||||
|  |     this->read_data_(BL0906_V_RMS, BL0906_UREF, voltage_sensor_); | ||||||
|  |   } else if (this->current_channel_ == UINT8_MAX - 1) { | ||||||
|  |     // Total power | ||||||
|  |     this->read_data_(BL0906_WATT_SUM, BL0906_WATT, this->total_power_sensor_); | ||||||
|  |     // Total Energy | ||||||
|  |     this->read_data_(BL0906_CF_SUM_CNT, BL0906_CF, this->total_energy_sensor_); | ||||||
|  |   } else { | ||||||
|  |     this->current_channel_ = UINT8_MAX - 2;  // Go to frequency and voltage | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->current_channel_++; | ||||||
|  |   this->handle_actions_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BL0906::setup() { | ||||||
|  |   while (this->available()) | ||||||
|  |     this->flush(); | ||||||
|  |   this->write_array(USR_WRPROT_WITABLE, sizeof(USR_WRPROT_WITABLE)); | ||||||
|  |   // Calibration (1: register address; 2: value before calibration; 3: value after calibration) | ||||||
|  |   this->bias_correction_(BL0906_RMSOS_1, 0.01600, 0);  // Calibration current_1 | ||||||
|  |   this->bias_correction_(BL0906_RMSOS_2, 0.01500, 0); | ||||||
|  |   this->bias_correction_(BL0906_RMSOS_3, 0.01400, 0); | ||||||
|  |   this->bias_correction_(BL0906_RMSOS_4, 0.01300, 0); | ||||||
|  |   this->bias_correction_(BL0906_RMSOS_5, 0.01200, 0); | ||||||
|  |   this->bias_correction_(BL0906_RMSOS_6, 0.01200, 0);  // Calibration current_6 | ||||||
|  |  | ||||||
|  |   this->write_array(USR_WRPROT_ONLYREAD, sizeof(USR_WRPROT_ONLYREAD)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BL0906::update() { this->current_channel_ = 0; } | ||||||
|  |  | ||||||
|  | size_t BL0906::enqueue_action_(ActionCallbackFuncPtr function) { | ||||||
|  |   this->action_queue_.push_back(function); | ||||||
|  |   return this->action_queue_.size(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BL0906::handle_actions_() { | ||||||
|  |   if (this->action_queue_.empty()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   ActionCallbackFuncPtr ptr_func = nullptr; | ||||||
|  |   for (int i = 0; i < this->action_queue_.size(); i++) { | ||||||
|  |     ptr_func = this->action_queue_[i]; | ||||||
|  |     if (ptr_func) { | ||||||
|  |       ESP_LOGI(TAG, "HandleActionCallback[%d]...", i); | ||||||
|  |       (this->*ptr_func)(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   while (this->available()) { | ||||||
|  |     this->read(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->action_queue_.clear(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Reset energy | ||||||
|  | void BL0906::reset_energy_() { | ||||||
|  |   this->write_array(BL0906_INIT[0], 6); | ||||||
|  |   delay(1); | ||||||
|  |   this->flush(); | ||||||
|  |  | ||||||
|  |   ESP_LOGW(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_INIT[0][0], BL0906_INIT[0][1], BL0906_INIT[0][2], | ||||||
|  |            BL0906_INIT[0][3], BL0906_INIT[0][4], BL0906_INIT[0][5]); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Read data | ||||||
|  | void BL0906::read_data_(const uint8_t address, const float reference, sensor::Sensor *sensor) { | ||||||
|  |   if (sensor == nullptr) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   DataPacket buffer; | ||||||
|  |   ube24_t data_u24; | ||||||
|  |   sbe24_t data_s24; | ||||||
|  |   float value = 0; | ||||||
|  |  | ||||||
|  |   bool signed_result = reference == BL0906_TREF || reference == BL0906_WATT || reference == BL0906_PREF; | ||||||
|  |  | ||||||
|  |   this->write_byte(BL0906_READ_COMMAND); | ||||||
|  |   this->write_byte(address); | ||||||
|  |   if (this->read_array((uint8_t *) &buffer, sizeof(buffer) - 1)) { | ||||||
|  |     if (bl0906_checksum(address, &buffer) == buffer.checksum) { | ||||||
|  |       if (signed_result) { | ||||||
|  |         data_s24.l = buffer.l; | ||||||
|  |         data_s24.m = buffer.m; | ||||||
|  |         data_s24.h = buffer.h; | ||||||
|  |       } else { | ||||||
|  |         data_u24.l = buffer.l; | ||||||
|  |         data_u24.m = buffer.m; | ||||||
|  |         data_u24.h = buffer.h; | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); | ||||||
|  |       while (read() >= 0) | ||||||
|  |         ; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   // Power | ||||||
|  |   if (reference == BL0906_PREF) { | ||||||
|  |     value = (float) to_int32_t(data_s24) * reference; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Total power | ||||||
|  |   if (reference == BL0906_WATT) { | ||||||
|  |     value = (float) to_int32_t(data_s24) * reference; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Voltage, current, power, total power | ||||||
|  |   if (reference == BL0906_UREF || reference == BL0906_IREF || reference == BL0906_EREF || reference == BL0906_CF) { | ||||||
|  |     value = (float) to_uint32_t(data_u24) * reference; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Frequency | ||||||
|  |   if (reference == BL0906_FREF) { | ||||||
|  |     value = reference / (float) to_uint32_t(data_u24); | ||||||
|  |   } | ||||||
|  |   // Chip temperature | ||||||
|  |   if (reference == BL0906_TREF) { | ||||||
|  |     value = (float) to_int32_t(data_s24); | ||||||
|  |     value = (value - 64) * 12.5 / 59 - 40; | ||||||
|  |   } | ||||||
|  |   sensor->publish_state(value); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RMS offset correction | ||||||
|  | void BL0906::bias_correction_(uint8_t address, float measurements, float correction) { | ||||||
|  |   DataPacket data; | ||||||
|  |   float ki = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097;  // Current coefficient | ||||||
|  |   float i_rms0 = measurements * ki; | ||||||
|  |   float i_rms = correction * ki; | ||||||
|  |   int32_t value = (i_rms * i_rms - i_rms0 * i_rms0) / 256; | ||||||
|  |   data.l = value << 24 >> 24; | ||||||
|  |   data.m = value << 16 >> 24; | ||||||
|  |   if (value < 0) { | ||||||
|  |     data.h = (value << 8 >> 24) | 0b10000000; | ||||||
|  |   } | ||||||
|  |   data.address = bl0906_checksum(address, &data); | ||||||
|  |   ESP_LOGV(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_WRITE_COMMAND, address, data.l, data.m, data.h, data.address); | ||||||
|  |   this->write_byte(BL0906_WRITE_COMMAND); | ||||||
|  |   this->write_byte(address); | ||||||
|  |   this->write_byte(data.l); | ||||||
|  |   this->write_byte(data.m); | ||||||
|  |   this->write_byte(data.h); | ||||||
|  |   this->write_byte(data.address); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BL0906::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "BL0906:"); | ||||||
|  |   LOG_SENSOR("  ", "Voltage", this->voltage_sensor_); | ||||||
|  |  | ||||||
|  |   LOG_SENSOR("  ", "Current1", this->current_1_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Current2", this->current_2_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Current3", this->current_3_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Current4", this->current_4_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Current5", this->current_5_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Current6", this->current_6_sensor_); | ||||||
|  |  | ||||||
|  |   LOG_SENSOR("  ", "Power1", this->power_1_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Power2", this->power_2_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Power3", this->power_3_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Power4", this->power_4_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Power5", this->power_5_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Power6", this->power_6_sensor_); | ||||||
|  |  | ||||||
|  |   LOG_SENSOR("  ", "Energy1", this->energy_1_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Energy2", this->energy_2_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Energy3", this->energy_3_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Energy4", this->energy_4_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Energy5", this->energy_5_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Energy6", this->energy_6_sensor_); | ||||||
|  |  | ||||||
|  |   LOG_SENSOR("  ", "Total Power", this->total_power_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Total Energy", this->total_energy_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Frequency", this->frequency_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace bl0906 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										96
									
								
								esphome/components/bl0906/bl0906.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								esphome/components/bl0906/bl0906.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/uart/uart.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/datatypes.h" | ||||||
|  |  | ||||||
|  | // https://www.belling.com.cn/media/file_object/bel_product/BL0906/datasheet/BL0906_V1.02_cn.pdf | ||||||
|  | // https://www.belling.com.cn/media/file_object/bel_product/BL0906/guide/BL0906%20APP%20Note_V1.02.pdf | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bl0906 { | ||||||
|  |  | ||||||
|  | struct DataPacket {  // NOLINT(altera-struct-pack-align) | ||||||
|  |   uint8_t l{0}; | ||||||
|  |   uint8_t m{0}; | ||||||
|  |   uint8_t h{0}; | ||||||
|  |   uint8_t checksum;  // checksum | ||||||
|  |   uint8_t address; | ||||||
|  | } __attribute__((packed)); | ||||||
|  |  | ||||||
|  | struct ube24_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align) | ||||||
|  |   uint8_t l{0}; | ||||||
|  |   uint8_t m{0}; | ||||||
|  |   uint8_t h{0}; | ||||||
|  | } __attribute__((packed)); | ||||||
|  |  | ||||||
|  | struct sbe24_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align) | ||||||
|  |   uint8_t l{0}; | ||||||
|  |   uint8_t m{0}; | ||||||
|  |   int8_t h{0}; | ||||||
|  | } __attribute__((packed)); | ||||||
|  |  | ||||||
|  | template<typename... Ts> class ResetEnergyAction; | ||||||
|  |  | ||||||
|  | class BL0906; | ||||||
|  |  | ||||||
|  | using ActionCallbackFuncPtr = void (BL0906::*)(); | ||||||
|  |  | ||||||
|  | class BL0906 : public PollingComponent, public uart::UARTDevice { | ||||||
|  |   SUB_SENSOR(voltage) | ||||||
|  |   SUB_SENSOR(current_1) | ||||||
|  |   SUB_SENSOR(current_2) | ||||||
|  |   SUB_SENSOR(current_3) | ||||||
|  |   SUB_SENSOR(current_4) | ||||||
|  |   SUB_SENSOR(current_5) | ||||||
|  |   SUB_SENSOR(current_6) | ||||||
|  |   SUB_SENSOR(power_1) | ||||||
|  |   SUB_SENSOR(power_2) | ||||||
|  |   SUB_SENSOR(power_3) | ||||||
|  |   SUB_SENSOR(power_4) | ||||||
|  |   SUB_SENSOR(power_5) | ||||||
|  |   SUB_SENSOR(power_6) | ||||||
|  |   SUB_SENSOR(total_power) | ||||||
|  |   SUB_SENSOR(energy_1) | ||||||
|  |   SUB_SENSOR(energy_2) | ||||||
|  |   SUB_SENSOR(energy_3) | ||||||
|  |   SUB_SENSOR(energy_4) | ||||||
|  |   SUB_SENSOR(energy_5) | ||||||
|  |   SUB_SENSOR(energy_6) | ||||||
|  |   SUB_SENSOR(total_energy) | ||||||
|  |   SUB_SENSOR(frequency) | ||||||
|  |   SUB_SENSOR(temperature) | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   void loop() override; | ||||||
|  |  | ||||||
|  |   void update() override; | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   template<typename... Ts> friend class ResetEnergyAction; | ||||||
|  |  | ||||||
|  |   void reset_energy_(); | ||||||
|  |  | ||||||
|  |   void read_data_(uint8_t address, float reference, sensor::Sensor *sensor); | ||||||
|  |  | ||||||
|  |   void bias_correction_(uint8_t address, float measurements, float correction); | ||||||
|  |  | ||||||
|  |   uint8_t current_channel_{0}; | ||||||
|  |   size_t enqueue_action_(ActionCallbackFuncPtr function); | ||||||
|  |   void handle_actions_(); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   std::vector<ActionCallbackFuncPtr> action_queue_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class ResetEnergyAction : public Action<Ts...>, public Parented<BL0906> { | ||||||
|  |  public: | ||||||
|  |   void play(Ts... x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace bl0906 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										4
									
								
								esphome/components/bl0906/const.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								esphome/components/bl0906/const.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | # const.py | ||||||
|  | ICON_ENERGY = "mdi:lightning-bolt" | ||||||
|  | ICON_FREQUENCY = "mdi:cosine-wave" | ||||||
|  | ICON_VOLTAGE = "mdi:sine-wave" | ||||||
							
								
								
									
										122
									
								
								esphome/components/bl0906/constants.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								esphome/components/bl0906/constants.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <cstdint> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bl0906 { | ||||||
|  |  | ||||||
|  | // Total power conversion | ||||||
|  | static const float BL0906_WATT = 16 * 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / | ||||||
|  |                                  (40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000); | ||||||
|  | // Total Energy conversion | ||||||
|  | static const float BL0906_CF = 16 * 4194304 * 0.032768 * 16 / | ||||||
|  |                                (3600000 * 16 * | ||||||
|  |                                 (40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / | ||||||
|  |                                  (1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000)))); | ||||||
|  | // Frequency conversion | ||||||
|  | static const float BL0906_FREF = 10000000; | ||||||
|  | // Temperature conversion | ||||||
|  | static const float BL0906_TREF = 12.5 / 59 - 40; | ||||||
|  | // Current conversion | ||||||
|  | static const float BL0906_IREF = 1.097 / (12875 * 1 * (5.1 + 5.1) * 1000 / 2000); | ||||||
|  | // Voltage conversion | ||||||
|  | static const float BL0906_UREF = 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / (13162 * 1 * 100 * 1000); | ||||||
|  | // Power conversion | ||||||
|  | static const float BL0906_PREF = 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / | ||||||
|  |                                  (40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000); | ||||||
|  | // Energy conversion | ||||||
|  | static const float BL0906_EREF = 4194304 * 0.032768 * 16 / | ||||||
|  |                                  (3600000 * 16 * | ||||||
|  |                                   (40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / | ||||||
|  |                                    (1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000)))); | ||||||
|  | // Current coefficient | ||||||
|  | static const float BL0906_KI = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097; | ||||||
|  | // Power coefficient | ||||||
|  | static const float BL0906_KP = 40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / 1.097 / 1.097 / | ||||||
|  |                                (20000 + 20000 + 20000 + 20000 + 20000); | ||||||
|  |  | ||||||
|  | static const uint8_t USR_WRPROT_WITABLE[6] = {0xCA, 0x9E, 0x55, 0x55, 0x00, 0xB7}; | ||||||
|  | static const uint8_t USR_WRPROT_ONLYREAD[6] = {0xCA, 0x9E, 0x00, 0x00, 0x00, 0x61}; | ||||||
|  |  | ||||||
|  | static const uint8_t BL0906_READ_COMMAND = 0x35; | ||||||
|  | static const uint8_t BL0906_WRITE_COMMAND = 0xCA; | ||||||
|  |  | ||||||
|  | // Register address | ||||||
|  | // Voltage | ||||||
|  | static const uint8_t BL0906_V_RMS = 0x16; | ||||||
|  |  | ||||||
|  | // Total power | ||||||
|  | static const uint8_t BL0906_WATT_SUM = 0X2C; | ||||||
|  |  | ||||||
|  | // Current1~6 | ||||||
|  | static const uint8_t BL0906_I_1_RMS = 0x0D;  // current_1 | ||||||
|  | static const uint8_t BL0906_I_2_RMS = 0x0E; | ||||||
|  | static const uint8_t BL0906_I_3_RMS = 0x0F; | ||||||
|  | static const uint8_t BL0906_I_4_RMS = 0x10; | ||||||
|  | static const uint8_t BL0906_I_5_RMS = 0x13; | ||||||
|  | static const uint8_t BL0906_I_6_RMS = 0x14;  // current_6 | ||||||
|  |  | ||||||
|  | // Power1~6 | ||||||
|  | static const uint8_t BL0906_WATT_1 = 0X23;  // power_1 | ||||||
|  | static const uint8_t BL0906_WATT_2 = 0X24; | ||||||
|  | static const uint8_t BL0906_WATT_3 = 0X25; | ||||||
|  | static const uint8_t BL0906_WATT_4 = 0X26; | ||||||
|  | static const uint8_t BL0906_WATT_5 = 0X29; | ||||||
|  | static const uint8_t BL0906_WATT_6 = 0X2A;  // power_6 | ||||||
|  |  | ||||||
|  | // Active pulse count, unsigned | ||||||
|  | static const uint8_t BL0906_CF_1_CNT = 0X30;  // Channel_1 | ||||||
|  | static const uint8_t BL0906_CF_2_CNT = 0X31; | ||||||
|  | static const uint8_t BL0906_CF_3_CNT = 0X32; | ||||||
|  | static const uint8_t BL0906_CF_4_CNT = 0X33; | ||||||
|  | static const uint8_t BL0906_CF_5_CNT = 0X36; | ||||||
|  | static const uint8_t BL0906_CF_6_CNT = 0X37;  // Channel_6 | ||||||
|  |  | ||||||
|  | // Total active pulse count, unsigned | ||||||
|  | static const uint8_t BL0906_CF_SUM_CNT = 0X39; | ||||||
|  |  | ||||||
|  | // Voltage frequency cycle | ||||||
|  | static const uint8_t BL0906_FREQUENCY = 0X4E; | ||||||
|  |  | ||||||
|  | // Internal temperature | ||||||
|  | static const uint8_t BL0906_TEMPERATURE = 0X5E; | ||||||
|  |  | ||||||
|  | // Calibration register | ||||||
|  | // RMS gain adjustment register | ||||||
|  | static const uint8_t BL0906_RMSGN_1 = 0x6D;  // Channel_1 | ||||||
|  | static const uint8_t BL0906_RMSGN_2 = 0x6E; | ||||||
|  | static const uint8_t BL0906_RMSGN_3 = 0x6F; | ||||||
|  | static const uint8_t BL0906_RMSGN_4 = 0x70; | ||||||
|  | static const uint8_t BL0906_RMSGN_5 = 0x73; | ||||||
|  | static const uint8_t BL0906_RMSGN_6 = 0x74;  // Channel_6 | ||||||
|  |  | ||||||
|  | // RMS offset correction register | ||||||
|  | static const uint8_t BL0906_RMSOS_1 = 0x78;  // Channel_1 | ||||||
|  | static const uint8_t BL0906_RMSOS_2 = 0x79; | ||||||
|  | static const uint8_t BL0906_RMSOS_3 = 0x7A; | ||||||
|  | static const uint8_t BL0906_RMSOS_4 = 0x7B; | ||||||
|  | static const uint8_t BL0906_RMSOS_5 = 0x7E; | ||||||
|  | static const uint8_t BL0906_RMSOS_6 = 0x7F;  // Channel_6 | ||||||
|  |  | ||||||
|  | // Active power gain adjustment register | ||||||
|  | static const uint8_t BL0906_WATTGN_1 = 0xB7;  // Channel_1 | ||||||
|  | static const uint8_t BL0906_WATTGN_2 = 0xB8; | ||||||
|  | static const uint8_t BL0906_WATTGN_3 = 0xB9; | ||||||
|  | static const uint8_t BL0906_WATTGN_4 = 0xBA; | ||||||
|  | static const uint8_t BL0906_WATTGN_5 = 0xBD; | ||||||
|  | static const uint8_t BL0906_WATTGN_6 = 0xBE;  // Channel_6 | ||||||
|  |  | ||||||
|  | // User write protection setting register, | ||||||
|  | // You must first write 0x5555 to the write protection setting register before writing to other registers. | ||||||
|  | static const uint8_t BL0906_USR_WRPROT = 0x9E; | ||||||
|  |  | ||||||
|  | // Reset Register | ||||||
|  | static const uint8_t BL0906_SOFT_RESET = 0x9F; | ||||||
|  |  | ||||||
|  | const uint8_t BL0906_INIT[2][6] = { | ||||||
|  |     // Reset to default | ||||||
|  |     {BL0906_WRITE_COMMAND, BL0906_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x52}, | ||||||
|  |     // Enable User Operation Write | ||||||
|  |     {BL0906_WRITE_COMMAND, BL0906_USR_WRPROT, 0x55, 0x55, 0x00, 0xB7}}; | ||||||
|  |  | ||||||
|  | }  // namespace bl0906 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										184
									
								
								esphome/components/bl0906/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								esphome/components/bl0906/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,184 @@ | |||||||
|  | from esphome import automation | ||||||
|  | from esphome.automation import maybe_simple_id | ||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import sensor, uart | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_CHANNEL, | ||||||
|  |     CONF_CURRENT, | ||||||
|  |     CONF_ENERGY, | ||||||
|  |     CONF_FREQUENCY, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_NAME, | ||||||
|  |     CONF_POWER, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     CONF_TOTAL_POWER, | ||||||
|  |     CONF_VOLTAGE, | ||||||
|  |     DEVICE_CLASS_CURRENT, | ||||||
|  |     DEVICE_CLASS_ENERGY, | ||||||
|  |     DEVICE_CLASS_FREQUENCY, | ||||||
|  |     DEVICE_CLASS_POWER, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_VOLTAGE, | ||||||
|  |     ICON_CURRENT_AC, | ||||||
|  |     ICON_POWER, | ||||||
|  |     ICON_THERMOMETER, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     STATE_CLASS_TOTAL_INCREASING, | ||||||
|  |     UNIT_AMPERE, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_HERTZ, | ||||||
|  |     UNIT_KILOWATT_HOURS, | ||||||
|  |     UNIT_VOLT, | ||||||
|  |     UNIT_WATT, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | # Import ICONS not included in esphome's const.py, from the local components const.py | ||||||
|  | from .const import ICON_ENERGY, ICON_FREQUENCY, ICON_VOLTAGE | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["uart"] | ||||||
|  | AUTO_LOAD = ["bl0906"] | ||||||
|  | CONF_TOTAL_ENERGY = "total_energy" | ||||||
|  |  | ||||||
|  | bl0906_ns = cg.esphome_ns.namespace("bl0906") | ||||||
|  | BL0906 = bl0906_ns.class_("BL0906", cg.PollingComponent, uart.UARTDevice) | ||||||
|  | ResetEnergyAction = bl0906_ns.class_("ResetEnergyAction", automation.Action) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(BL0906), | ||||||
|  |             cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( | ||||||
|  |                 icon=ICON_FREQUENCY, | ||||||
|  |                 accuracy_decimals=0, | ||||||
|  |                 device_class=DEVICE_CLASS_FREQUENCY, | ||||||
|  |                 unit_of_measurement=UNIT_HERTZ, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |                 icon=ICON_THERMOMETER, | ||||||
|  |                 accuracy_decimals=0, | ||||||
|  |                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |                 unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( | ||||||
|  |                 icon=ICON_VOLTAGE, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_VOLTAGE, | ||||||
|  |                 unit_of_measurement=UNIT_VOLT, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_TOTAL_POWER): sensor.sensor_schema( | ||||||
|  |                 icon=ICON_POWER, | ||||||
|  |                 accuracy_decimals=3, | ||||||
|  |                 device_class=DEVICE_CLASS_POWER, | ||||||
|  |                 unit_of_measurement=UNIT_WATT, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_TOTAL_ENERGY): sensor.sensor_schema( | ||||||
|  |                 icon=ICON_ENERGY, | ||||||
|  |                 accuracy_decimals=3, | ||||||
|  |                 device_class=DEVICE_CLASS_ENERGY, | ||||||
|  |                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||||
|  |                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend( | ||||||
|  |         cv.Schema( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(f"{CONF_CHANNEL}_{i + 1}"): cv.Schema( | ||||||
|  |                     { | ||||||
|  |                         cv.Optional(CONF_CURRENT): cv.maybe_simple_value( | ||||||
|  |                             sensor.sensor_schema( | ||||||
|  |                                 icon=ICON_CURRENT_AC, | ||||||
|  |                                 accuracy_decimals=3, | ||||||
|  |                                 device_class=DEVICE_CLASS_CURRENT, | ||||||
|  |                                 unit_of_measurement=UNIT_AMPERE, | ||||||
|  |                                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |                             ), | ||||||
|  |                             key=CONF_NAME, | ||||||
|  |                         ), | ||||||
|  |                         cv.Optional(CONF_POWER): cv.maybe_simple_value( | ||||||
|  |                             sensor.sensor_schema( | ||||||
|  |                                 icon=ICON_POWER, | ||||||
|  |                                 accuracy_decimals=0, | ||||||
|  |                                 device_class=DEVICE_CLASS_POWER, | ||||||
|  |                                 unit_of_measurement=UNIT_WATT, | ||||||
|  |                                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |                             ), | ||||||
|  |                             key=CONF_NAME, | ||||||
|  |                         ), | ||||||
|  |                         cv.Optional(CONF_ENERGY): cv.maybe_simple_value( | ||||||
|  |                             sensor.sensor_schema( | ||||||
|  |                                 icon=ICON_ENERGY, | ||||||
|  |                                 accuracy_decimals=3, | ||||||
|  |                                 device_class=DEVICE_CLASS_ENERGY, | ||||||
|  |                                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||||
|  |                                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||||
|  |                             ), | ||||||
|  |                             key=CONF_NAME, | ||||||
|  |                         ), | ||||||
|  |                     } | ||||||
|  |                 ) | ||||||
|  |                 for i in range(6) | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  |     .extend(uart.UART_DEVICE_SCHEMA) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( | ||||||
|  |     "bl0906", baud_rate=19200, require_tx=True, require_rx=True | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "bl0906.reset_energy", | ||||||
|  |     ResetEnergyAction, | ||||||
|  |     maybe_simple_id( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_ID): cv.use_id(BL0906), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | async def reset_energy_to_code(config, action_id, template_arg, args): | ||||||
|  |     paren = await cg.get_variable(config[CONF_ID]) | ||||||
|  |     return cg.new_Pvariable(action_id, template_arg, paren) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  |     if frequency_config := config.get(CONF_FREQUENCY): | ||||||
|  |         sens = await sensor.new_sensor(frequency_config) | ||||||
|  |         cg.add(var.set_frequency_sensor(sens)) | ||||||
|  |     if temperature_config := config.get(CONF_TEMPERATURE): | ||||||
|  |         sens = await sensor.new_sensor(temperature_config) | ||||||
|  |         cg.add(var.set_temperature_sensor(sens)) | ||||||
|  |     if voltage_config := config.get(CONF_VOLTAGE): | ||||||
|  |         sens = await sensor.new_sensor(voltage_config) | ||||||
|  |         cg.add(var.set_voltage_sensor(sens)) | ||||||
|  |  | ||||||
|  |     for i in range(6): | ||||||
|  |         if channel_config := config.get(f"{CONF_CHANNEL}_{i + 1}"): | ||||||
|  |             if current_config := channel_config.get(CONF_CURRENT): | ||||||
|  |                 sens = await sensor.new_sensor(current_config) | ||||||
|  |                 cg.add(getattr(var, f"set_current_{i + 1}_sensor")(sens)) | ||||||
|  |             if power_config := channel_config.get(CONF_POWER): | ||||||
|  |                 sens = await sensor.new_sensor(power_config) | ||||||
|  |                 cg.add(getattr(var, f"set_power_{i + 1}_sensor")(sens)) | ||||||
|  |             if energy_config := channel_config.get(CONF_ENERGY): | ||||||
|  |                 sens = await sensor.new_sensor(energy_config) | ||||||
|  |                 cg.add(getattr(var, f"set_energy_{i + 1}_sensor")(sens)) | ||||||
|  |  | ||||||
|  |     if total_power_config := config.get(CONF_TOTAL_POWER): | ||||||
|  |         sens = await sensor.new_sensor(total_power_config) | ||||||
|  |         cg.add(var.set_total_power_sensor(sens)) | ||||||
|  |  | ||||||
|  |     if total_energy_config := config.get(CONF_TOTAL_ENERGY): | ||||||
|  |         sens = await sensor.new_sensor(total_energy_config) | ||||||
|  |         cg.add(var.set_total_energy_sensor(sens)) | ||||||
| @@ -1 +1 @@ | |||||||
| CODEOWNERS = ["@dbuezas"] | CODEOWNERS = ["@dbuezas", "@dwmw2"] | ||||||
|   | |||||||
| @@ -2,6 +2,8 @@ | |||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| #include <cinttypes> | #include <cinttypes> | ||||||
|  |  | ||||||
|  | // Datasheet: https://www.belling.com.cn/media/file_object/bel_product/BL0942/datasheet/BL0942_V1.06_en.pdf | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace bl0942 { | namespace bl0942 { | ||||||
|  |  | ||||||
| @@ -12,43 +14,64 @@ static const uint8_t BL0942_FULL_PACKET = 0xAA; | |||||||
| static const uint8_t BL0942_PACKET_HEADER = 0x55; | static const uint8_t BL0942_PACKET_HEADER = 0x55; | ||||||
|  |  | ||||||
| static const uint8_t BL0942_WRITE_COMMAND = 0xA8; | static const uint8_t BL0942_WRITE_COMMAND = 0xA8; | ||||||
| static const uint8_t BL0942_REG_I_FAST_RMS_CTRL = 0x10; |  | ||||||
| static const uint8_t BL0942_REG_MODE = 0x18; | static const uint8_t BL0942_REG_I_RMSOS = 0x12; | ||||||
| static const uint8_t BL0942_REG_SOFT_RESET = 0x19; | static const uint8_t BL0942_REG_WA_CREEP = 0x14; | ||||||
| static const uint8_t BL0942_REG_USR_WRPROT = 0x1A; | static const uint8_t BL0942_REG_I_FAST_RMS_TH = 0x15; | ||||||
|  | static const uint8_t BL0942_REG_I_FAST_RMS_CYC = 0x16; | ||||||
|  | static const uint8_t BL0942_REG_FREQ_CYC = 0x17; | ||||||
|  | static const uint8_t BL0942_REG_OT_FUNX = 0x18; | ||||||
|  | static const uint8_t BL0942_REG_MODE = 0x19; | ||||||
|  | static const uint8_t BL0942_REG_SOFT_RESET = 0x1C; | ||||||
|  | static const uint8_t BL0942_REG_USR_WRPROT = 0x1D; | ||||||
| static const uint8_t BL0942_REG_TPS_CTRL = 0x1B; | static const uint8_t BL0942_REG_TPS_CTRL = 0x1B; | ||||||
|  |  | ||||||
| // TODO: Confirm insialisation works as intended | static const uint32_t BL0942_REG_MODE_RESV = 0x03; | ||||||
| const uint8_t BL0942_INIT[5][6] = { | static const uint32_t BL0942_REG_MODE_CF_EN = 0x04; | ||||||
|     // Reset to default | static const uint32_t BL0942_REG_MODE_RMS_UPDATE_SEL = 0x08; | ||||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, | static const uint32_t BL0942_REG_MODE_FAST_RMS_SEL = 0x10; | ||||||
|     // Enable User Operation Write | static const uint32_t BL0942_REG_MODE_AC_FREQ_SEL = 0x20; | ||||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, | static const uint32_t BL0942_REG_MODE_CF_CNT_CLR_SEL = 0x40; | ||||||
|     // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS | static const uint32_t BL0942_REG_MODE_CF_CNT_ADD_SEL = 0x80; | ||||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_MODE, 0x00, 0x10, 0x00, 0x37}, | static const uint32_t BL0942_REG_MODE_UART_RATE_19200 = 0x200; | ||||||
|     // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS | static const uint32_t BL0942_REG_MODE_UART_RATE_38400 = 0x300; | ||||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, | static const uint32_t BL0942_REG_MODE_DEFAULT = | ||||||
|     // 0x181C = Half cycle, Fast RMS threshold 6172 |     BL0942_REG_MODE_RESV | BL0942_REG_MODE_CF_EN | BL0942_REG_MODE_CF_CNT_ADD_SEL; | ||||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}}; |  | ||||||
|  | static const uint32_t BL0942_REG_SOFT_RESET_MAGIC = 0x5a5a5a; | ||||||
|  | static const uint32_t BL0942_REG_USR_WRPROT_MAGIC = 0x55; | ||||||
|  |  | ||||||
|  | // 23-byte packet, 11 bits per byte, 2400 baud: about 105ms | ||||||
|  | static const uint32_t PKT_TIMEOUT_MS = 200; | ||||||
|  |  | ||||||
| void BL0942::loop() { | void BL0942::loop() { | ||||||
|   DataPacket buffer; |   DataPacket buffer; | ||||||
|   if (!this->available()) { |   int avail = this->available(); | ||||||
|  |  | ||||||
|  |   if (!avail) { | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (read_array((uint8_t *) &buffer, sizeof(buffer))) { |   if (avail < sizeof(buffer)) { | ||||||
|     if (validate_checksum(&buffer)) { |     if (!this->rx_start_) { | ||||||
|       received_package_(&buffer); |       this->rx_start_ = millis(); | ||||||
|  |     } else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) { | ||||||
|  |       ESP_LOGW(TAG, "Junk on wire. Throwing away partial message (%d bytes)", avail); | ||||||
|  |       this->read_array((uint8_t *) &buffer, avail); | ||||||
|  |       this->rx_start_ = 0; | ||||||
|     } |     } | ||||||
|   } else { |     return; | ||||||
|     ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); |  | ||||||
|     while (read() >= 0) |  | ||||||
|       ; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (this->read_array((uint8_t *) &buffer, sizeof(buffer))) { | ||||||
|  |     if (this->validate_checksum_(&buffer)) { | ||||||
|  |       this->received_package_(&buffer); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   this->rx_start_ = 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool BL0942::validate_checksum(DataPacket *data) { | bool BL0942::validate_checksum_(DataPacket *data) { | ||||||
|   uint8_t checksum = BL0942_READ_COMMAND; |   uint8_t checksum = BL0942_READ_COMMAND | this->address_; | ||||||
|   // Whole package but checksum |   // Whole package but checksum | ||||||
|   uint8_t *raw = (uint8_t *) data; |   uint8_t *raw = (uint8_t *) data; | ||||||
|   for (uint32_t i = 0; i < sizeof(*data) - 1; i++) { |   for (uint32_t i = 0; i < sizeof(*data) - 1; i++) { | ||||||
| @@ -61,17 +84,73 @@ bool BL0942::validate_checksum(DataPacket *data) { | |||||||
|   return checksum == data->checksum; |   return checksum == data->checksum; | ||||||
| } | } | ||||||
|  |  | ||||||
| void BL0942::update() { | void BL0942::write_reg_(uint8_t reg, uint32_t val) { | ||||||
|  |   uint8_t pkt[6]; | ||||||
|  |  | ||||||
|   this->flush(); |   this->flush(); | ||||||
|   this->write_byte(BL0942_READ_COMMAND); |   pkt[0] = BL0942_WRITE_COMMAND | this->address_; | ||||||
|  |   pkt[1] = reg; | ||||||
|  |   pkt[2] = (val & 0xff); | ||||||
|  |   pkt[3] = (val >> 8) & 0xff; | ||||||
|  |   pkt[4] = (val >> 16) & 0xff; | ||||||
|  |   pkt[5] = (pkt[0] + pkt[1] + pkt[2] + pkt[3] + pkt[4]) ^ 0xff; | ||||||
|  |   this->write_array(pkt, 6); | ||||||
|  |   delay(1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int BL0942::read_reg_(uint8_t reg) { | ||||||
|  |   union { | ||||||
|  |     uint8_t b[4]; | ||||||
|  |     uint32_le_t le32; | ||||||
|  |   } resp; | ||||||
|  |  | ||||||
|  |   this->write_byte(BL0942_READ_COMMAND | this->address_); | ||||||
|  |   this->write_byte(reg); | ||||||
|  |   this->flush(); | ||||||
|  |   if (this->read_array(resp.b, 4) && | ||||||
|  |       resp.b[3] == | ||||||
|  |           (uint8_t) ((BL0942_READ_COMMAND + this->address_ + reg + resp.b[0] + resp.b[1] + resp.b[2]) ^ 0xff)) { | ||||||
|  |     resp.b[3] = 0; | ||||||
|  |     return resp.le32; | ||||||
|  |   } | ||||||
|  |   return -1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BL0942::update() { | ||||||
|  |   this->write_byte(BL0942_READ_COMMAND | this->address_); | ||||||
|   this->write_byte(BL0942_FULL_PACKET); |   this->write_byte(BL0942_FULL_PACKET); | ||||||
| } | } | ||||||
|  |  | ||||||
| void BL0942::setup() { | void BL0942::setup() { | ||||||
|   for (auto *i : BL0942_INIT) { |   // If either current or voltage references are set explicitly by the user, | ||||||
|     this->write_array(i, 6); |   // calculate the power reference from it unless that is also explicitly set. | ||||||
|     delay(1); |   if ((this->current_reference_set_ || this->voltage_reference_set_) && !this->power_reference_set_) { | ||||||
|  |     this->power_reference_ = (this->voltage_reference_ * this->current_reference_ * 3537.0 / 305978.0) / 73989.0; | ||||||
|  |     this->power_reference_set_ = true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Similarly for energy reference, if the power reference was set by the user | ||||||
|  |   // either implicitly or explicitly. | ||||||
|  |   if (this->power_reference_set_ && !this->energy_reference_set_) { | ||||||
|  |     this->energy_reference_ = this->power_reference_ * 3600000 / 419430.4; | ||||||
|  |     this->energy_reference_set_ = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->write_reg_(BL0942_REG_USR_WRPROT, BL0942_REG_USR_WRPROT_MAGIC); | ||||||
|  |   if (this->reset_) | ||||||
|  |     this->write_reg_(BL0942_REG_SOFT_RESET, BL0942_REG_SOFT_RESET_MAGIC); | ||||||
|  |  | ||||||
|  |   uint32_t mode = BL0942_REG_MODE_DEFAULT; | ||||||
|  |   mode |= BL0942_REG_MODE_RMS_UPDATE_SEL; /* 800ms refresh time */ | ||||||
|  |   if (this->line_freq_ == LINE_FREQUENCY_60HZ) | ||||||
|  |     mode |= BL0942_REG_MODE_AC_FREQ_SEL; | ||||||
|  |   this->write_reg_(BL0942_REG_MODE, mode); | ||||||
|  |  | ||||||
|  |   this->write_reg_(BL0942_REG_USR_WRPROT, 0); | ||||||
|  |  | ||||||
|  |   if (this->read_reg_(BL0942_REG_MODE) != mode) | ||||||
|  |     this->status_set_warning("BL0942 setup failed!"); | ||||||
|  |  | ||||||
|   this->flush(); |   this->flush(); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -82,10 +161,17 @@ void BL0942::received_package_(DataPacket *data) { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // cf_cnt is only 24 bits, so track overflows | ||||||
|  |   uint32_t cf_cnt = (uint24_t) data->cf_cnt; | ||||||
|  |   cf_cnt |= this->prev_cf_cnt_ & 0xff000000; | ||||||
|  |   if (cf_cnt < this->prev_cf_cnt_) { | ||||||
|  |     cf_cnt += 0x1000000; | ||||||
|  |   } | ||||||
|  |   this->prev_cf_cnt_ = cf_cnt; | ||||||
|  |  | ||||||
|   float v_rms = (uint24_t) data->v_rms / voltage_reference_; |   float v_rms = (uint24_t) data->v_rms / voltage_reference_; | ||||||
|   float i_rms = (uint24_t) data->i_rms / current_reference_; |   float i_rms = (uint24_t) data->i_rms / current_reference_; | ||||||
|   float watt = (int24_t) data->watt / power_reference_; |   float watt = (int24_t) data->watt / power_reference_; | ||||||
|   uint32_t cf_cnt = (uint24_t) data->cf_cnt; |  | ||||||
|   float total_energy_consumption = cf_cnt / energy_reference_; |   float total_energy_consumption = cf_cnt / energy_reference_; | ||||||
|   float frequency = 1000000.0f / data->frequency; |   float frequency = 1000000.0f / data->frequency; | ||||||
|  |  | ||||||
| @@ -104,18 +190,25 @@ void BL0942::received_package_(DataPacket *data) { | |||||||
|   if (frequency_sensor_ != nullptr) { |   if (frequency_sensor_ != nullptr) { | ||||||
|     frequency_sensor_->publish_state(frequency); |     frequency_sensor_->publish_state(frequency); | ||||||
|   } |   } | ||||||
|  |   this->status_clear_warning(); | ||||||
|   ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, frequency %fHz, status 0x%08X", v_rms, i_rms, |   ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, frequency %fHz, status 0x%08X", v_rms, i_rms, | ||||||
|            watt, cf_cnt, total_energy_consumption, frequency, data->status); |            watt, cf_cnt, total_energy_consumption, frequency, data->status); | ||||||
| } | } | ||||||
|  |  | ||||||
| void BL0942::dump_config() {  // NOLINT(readability-function-cognitive-complexity) | void BL0942::dump_config() {  // NOLINT(readability-function-cognitive-complexity) | ||||||
|   ESP_LOGCONFIG(TAG, "BL0942:"); |   ESP_LOGCONFIG(TAG, "BL0942:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Reset: %s", TRUEFALSE(this->reset_)); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Address: %d", this->address_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Nominal line frequency: %d Hz", this->line_freq_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Current reference: %f", this->current_reference_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Energy reference: %f", this->energy_reference_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Power reference: %f", this->power_reference_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Voltage reference: %f", this->voltage_reference_); | ||||||
|   LOG_SENSOR("", "Voltage", this->voltage_sensor_); |   LOG_SENSOR("", "Voltage", this->voltage_sensor_); | ||||||
|   LOG_SENSOR("", "Current", this->current_sensor_); |   LOG_SENSOR("", "Current", this->current_sensor_); | ||||||
|   LOG_SENSOR("", "Power", this->power_sensor_); |   LOG_SENSOR("", "Power", this->power_sensor_); | ||||||
|   LOG_SENSOR("", "Energy", this->energy_sensor_); |   LOG_SENSOR("", "Energy", this->energy_sensor_); | ||||||
|   LOG_SENSOR("", "frequency", this->frequency_sensor_); |   LOG_SENSOR("", "Frequency", this->frequency_sensor_); | ||||||
| } | } | ||||||
|  |  | ||||||
| }  // namespace bl0942 | }  // namespace bl0942 | ||||||
|   | |||||||
| @@ -8,6 +8,57 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace bl0942 { | namespace bl0942 { | ||||||
|  |  | ||||||
|  | // The BL0942 IC is "calibration-free", which means that it doesn't care | ||||||
|  | // at all about calibration, and that's left to software. It measures a | ||||||
|  | // voltage differential on its IP/IN pins which linearly proportional to | ||||||
|  | // the current flow, and another on its VP pin which is proportional to | ||||||
|  | // the line voltage. It never knows the actual calibration; the values | ||||||
|  | // it reports are solely in terms of those inputs. | ||||||
|  | // | ||||||
|  | // The datasheet refers to the input voltages as I(A) and V(V), both | ||||||
|  | // in millivolts. It measures them against a reference voltage Vref, | ||||||
|  | // which is typically 1.218V (but that absolute value is meaningless | ||||||
|  | // without the actual calibration anyway). | ||||||
|  | // | ||||||
|  | // The reported I_RMS value is 305978 I(A)/Vref, and the reported V_RMS | ||||||
|  | // value is 73989 V(V)/Vref. So we can calibrate those by applying a | ||||||
|  | // simple meter with a resistive load. | ||||||
|  | // | ||||||
|  | // The chip also measures the phase difference between voltage and | ||||||
|  | // current, and uses it to calculate the power factor (cos φ). It | ||||||
|  | // reports the WATT value of 3537 * I_RMS * V_RMS * cos φ). | ||||||
|  | // | ||||||
|  | // It also integrates total energy based on the WATT value. The time for | ||||||
|  | // one CF_CNT pulse is 1638.4*256 / WATT. | ||||||
|  | // | ||||||
|  | // So... how do we calibrate that? | ||||||
|  | // | ||||||
|  | // Using a simple resistive load and an external meter, we can measure | ||||||
|  | // the true voltage and current for a given V_RMS and I_RMS reading, | ||||||
|  | // to calculate BL0942_UREF and BL0942_IREF. Those are in units of | ||||||
|  | // "305978 counts per amp" or "73989 counts per volt" respectively. | ||||||
|  | // | ||||||
|  | // We can derive BL0942_PREF from those. Let's eliminate the weird | ||||||
|  | // factors and express the calibration in plain counts per volt/amp: | ||||||
|  | // UREF1 = UREF/73989, IREF1 = IREF/305978. | ||||||
|  | // | ||||||
|  | // Next... the true power in Watts is V * I * cos φ, so that's equal | ||||||
|  | // to WATT/3537 * IREF1 * UREF1. Which means | ||||||
|  | // BL0942_PREF = BL0942_UREF * BL0942_IREF * 3537 / 305978 / 73989. | ||||||
|  | // | ||||||
|  | // Finally the accumulated energy. The period of a CF_CNT count is | ||||||
|  | // 1638.4*256 / WATT seconds, or 419230.4 / WATT seconds. Which means | ||||||
|  | // the energy represented by a CN_CNT pulse is 419230.4 WATT-seconds. | ||||||
|  | // Factoring in the calibration, that's 419230.4 / BL0942_PREF actual | ||||||
|  | // Watt-seconds (or Joules, as the physicists like to call them). | ||||||
|  | // | ||||||
|  | // But we're not being physicists today; we we're being engineers, so | ||||||
|  | // we want to convert to kWh instead. Which we do by dividing by 1000 | ||||||
|  | // and then by 3600, so the energy in kWh is | ||||||
|  | // CF_CNT * 419230.4 / BL0942_PREF / 3600000 | ||||||
|  | // | ||||||
|  | // Which makes BL0952_EREF = BL0942_PREF * 3600000 / 419430.4 | ||||||
|  |  | ||||||
| static const float BL0942_PREF = 596;              // taken from tasmota | static const float BL0942_PREF = 596;              // taken from tasmota | ||||||
| static const float BL0942_UREF = 15873.35944299;   // should be 73989/1.218 | static const float BL0942_UREF = 15873.35944299;   // should be 73989/1.218 | ||||||
| static const float BL0942_IREF = 251213.46469622;  // 305978/1.218 | static const float BL0942_IREF = 251213.46469622;  // 305978/1.218 | ||||||
| @@ -28,6 +79,11 @@ struct DataPacket { | |||||||
|   uint8_t checksum; |   uint8_t checksum; | ||||||
| } __attribute__((packed)); | } __attribute__((packed)); | ||||||
|  |  | ||||||
|  | enum LineFrequency : uint8_t { | ||||||
|  |   LINE_FREQUENCY_50HZ = 50, | ||||||
|  |   LINE_FREQUENCY_60HZ = 60, | ||||||
|  | }; | ||||||
|  |  | ||||||
| class BL0942 : public PollingComponent, public uart::UARTDevice { | class BL0942 : public PollingComponent, public uart::UARTDevice { | ||||||
|  public: |  public: | ||||||
|   void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } |   void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } | ||||||
| @@ -35,9 +91,27 @@ class BL0942 : public PollingComponent, public uart::UARTDevice { | |||||||
|   void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } |   void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } | ||||||
|   void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } |   void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } | ||||||
|   void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; } |   void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; } | ||||||
|  |   void set_line_freq(LineFrequency freq) { this->line_freq_ = freq; } | ||||||
|  |   void set_address(uint8_t address) { this->address_ = address; } | ||||||
|  |   void set_reset(bool reset) { this->reset_ = reset; } | ||||||
|  |   void set_current_reference(float current_ref) { | ||||||
|  |     this->current_reference_ = current_ref; | ||||||
|  |     this->current_reference_set_ = true; | ||||||
|  |   } | ||||||
|  |   void set_energy_reference(float energy_ref) { | ||||||
|  |     this->energy_reference_ = energy_ref; | ||||||
|  |     this->energy_reference_set_ = true; | ||||||
|  |   } | ||||||
|  |   void set_power_reference(float power_ref) { | ||||||
|  |     this->power_reference_ = power_ref; | ||||||
|  |     this->power_reference_set_ = true; | ||||||
|  |   } | ||||||
|  |   void set_voltage_reference(float voltage_ref) { | ||||||
|  |     this->voltage_reference_ = voltage_ref; | ||||||
|  |     this->voltage_reference_set_ = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void loop() override; |   void loop() override; | ||||||
|  |  | ||||||
|   void update() override; |   void update() override; | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
| @@ -53,15 +127,25 @@ class BL0942 : public PollingComponent, public uart::UARTDevice { | |||||||
|  |  | ||||||
|   // Divide by this to turn into Watt |   // Divide by this to turn into Watt | ||||||
|   float power_reference_ = BL0942_PREF; |   float power_reference_ = BL0942_PREF; | ||||||
|  |   bool power_reference_set_ = false; | ||||||
|   // Divide by this to turn into Volt |   // Divide by this to turn into Volt | ||||||
|   float voltage_reference_ = BL0942_UREF; |   float voltage_reference_ = BL0942_UREF; | ||||||
|  |   bool voltage_reference_set_ = false; | ||||||
|   // Divide by this to turn into Ampere |   // Divide by this to turn into Ampere | ||||||
|   float current_reference_ = BL0942_IREF; |   float current_reference_ = BL0942_IREF; | ||||||
|  |   bool current_reference_set_ = false; | ||||||
|   // Divide by this to turn into kWh |   // Divide by this to turn into kWh | ||||||
|   float energy_reference_ = BL0942_EREF; |   float energy_reference_ = BL0942_EREF; | ||||||
|  |   bool energy_reference_set_ = false; | ||||||
|  |   uint8_t address_ = 0; | ||||||
|  |   bool reset_ = false; | ||||||
|  |   LineFrequency line_freq_ = LINE_FREQUENCY_50HZ; | ||||||
|  |   uint32_t rx_start_ = 0; | ||||||
|  |   uint32_t prev_cf_cnt_ = 0; | ||||||
|  |  | ||||||
|   static bool validate_checksum(DataPacket *data); |   bool validate_checksum_(DataPacket *data); | ||||||
|  |   int read_reg_(uint8_t reg); | ||||||
|  |   void write_reg_(uint8_t reg, uint32_t val); | ||||||
|   void received_package_(DataPacket *data); |   void received_package_(DataPacket *data); | ||||||
| }; | }; | ||||||
| }  // namespace bl0942 | }  // namespace bl0942 | ||||||
|   | |||||||
| @@ -1,32 +1,46 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome.components import sensor, uart | from esphome.components import sensor, uart | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|  |     CONF_ADDRESS, | ||||||
|     CONF_CURRENT, |     CONF_CURRENT, | ||||||
|     CONF_ENERGY, |     CONF_ENERGY, | ||||||
|  |     CONF_FREQUENCY, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|  |     CONF_LINE_FREQUENCY, | ||||||
|     CONF_POWER, |     CONF_POWER, | ||||||
|     CONF_VOLTAGE, |     CONF_VOLTAGE, | ||||||
|     CONF_FREQUENCY, |  | ||||||
|     DEVICE_CLASS_CURRENT, |     DEVICE_CLASS_CURRENT, | ||||||
|     DEVICE_CLASS_ENERGY, |     DEVICE_CLASS_ENERGY, | ||||||
|  |     DEVICE_CLASS_FREQUENCY, | ||||||
|     DEVICE_CLASS_POWER, |     DEVICE_CLASS_POWER, | ||||||
|     DEVICE_CLASS_VOLTAGE, |     DEVICE_CLASS_VOLTAGE, | ||||||
|     DEVICE_CLASS_FREQUENCY, |  | ||||||
|     STATE_CLASS_MEASUREMENT, |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     STATE_CLASS_TOTAL_INCREASING, | ||||||
|     UNIT_AMPERE, |     UNIT_AMPERE, | ||||||
|  |     UNIT_HERTZ, | ||||||
|     UNIT_KILOWATT_HOURS, |     UNIT_KILOWATT_HOURS, | ||||||
|     UNIT_VOLT, |     UNIT_VOLT, | ||||||
|     UNIT_WATT, |     UNIT_WATT, | ||||||
|     UNIT_HERTZ, |  | ||||||
|     STATE_CLASS_TOTAL_INCREASING, |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | CONF_CURRENT_REFERENCE = "current_reference" | ||||||
|  | CONF_ENERGY_REFERENCE = "energy_reference" | ||||||
|  | CONF_POWER_REFERENCE = "power_reference" | ||||||
|  | CONF_RESET = "reset" | ||||||
|  | CONF_VOLTAGE_REFERENCE = "voltage_reference" | ||||||
|  |  | ||||||
| DEPENDENCIES = ["uart"] | DEPENDENCIES = ["uart"] | ||||||
|  |  | ||||||
| bl0942_ns = cg.esphome_ns.namespace("bl0942") | bl0942_ns = cg.esphome_ns.namespace("bl0942") | ||||||
| BL0942 = bl0942_ns.class_("BL0942", cg.PollingComponent, uart.UARTDevice) | BL0942 = bl0942_ns.class_("BL0942", cg.PollingComponent, uart.UARTDevice) | ||||||
|  |  | ||||||
|  | LineFrequency = bl0942_ns.enum("LineFrequency") | ||||||
|  | LINE_FREQS = { | ||||||
|  |     50: LineFrequency.LINE_FREQUENCY_50HZ, | ||||||
|  |     60: LineFrequency.LINE_FREQUENCY_60HZ, | ||||||
|  | } | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( | CONFIG_SCHEMA = ( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
| @@ -45,22 +59,35 @@ CONFIG_SCHEMA = ( | |||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_POWER): sensor.sensor_schema( |             cv.Optional(CONF_POWER): sensor.sensor_schema( | ||||||
|                 unit_of_measurement=UNIT_WATT, |                 unit_of_measurement=UNIT_WATT, | ||||||
|                 accuracy_decimals=0, |                 accuracy_decimals=1, | ||||||
|                 device_class=DEVICE_CLASS_POWER, |                 device_class=DEVICE_CLASS_POWER, | ||||||
|                 state_class=STATE_CLASS_MEASUREMENT, |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_ENERGY): sensor.sensor_schema( |             cv.Optional(CONF_ENERGY): sensor.sensor_schema( | ||||||
|                 unit_of_measurement=UNIT_KILOWATT_HOURS, |                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||||
|                 accuracy_decimals=0, |                 accuracy_decimals=3, | ||||||
|                 device_class=DEVICE_CLASS_ENERGY, |                 device_class=DEVICE_CLASS_ENERGY, | ||||||
|                 state_class=STATE_CLASS_TOTAL_INCREASING, |                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( |             cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( | ||||||
|                 unit_of_measurement=UNIT_HERTZ, |                 unit_of_measurement=UNIT_HERTZ, | ||||||
|                 accuracy_decimals=0, |                 accuracy_decimals=2, | ||||||
|                 device_class=DEVICE_CLASS_FREQUENCY, |                 device_class=DEVICE_CLASS_FREQUENCY, | ||||||
|                 state_class=STATE_CLASS_MEASUREMENT, |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|             ), |             ), | ||||||
|  |             cv.Optional(CONF_LINE_FREQUENCY, default="50HZ"): cv.All( | ||||||
|  |                 cv.frequency, | ||||||
|  |                 cv.enum( | ||||||
|  |                     LINE_FREQS, | ||||||
|  |                     int=True, | ||||||
|  |                 ), | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_ADDRESS, default=0): cv.int_range(min=0, max=3), | ||||||
|  |             cv.Optional(CONF_RESET, default=True): cv.boolean, | ||||||
|  |             cv.Optional(CONF_CURRENT_REFERENCE): cv.float_, | ||||||
|  |             cv.Optional(CONF_ENERGY_REFERENCE): cv.float_, | ||||||
|  |             cv.Optional(CONF_POWER_REFERENCE): cv.float_, | ||||||
|  |             cv.Optional(CONF_VOLTAGE_REFERENCE): cv.float_, | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|     .extend(cv.polling_component_schema("60s")) |     .extend(cv.polling_component_schema("60s")) | ||||||
| @@ -88,3 +115,14 @@ async def to_code(config): | |||||||
|     if frequency_config := config.get(CONF_FREQUENCY): |     if frequency_config := config.get(CONF_FREQUENCY): | ||||||
|         sens = await sensor.new_sensor(frequency_config) |         sens = await sensor.new_sensor(frequency_config) | ||||||
|         cg.add(var.set_frequency_sensor(sens)) |         cg.add(var.set_frequency_sensor(sens)) | ||||||
|  |     cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY])) | ||||||
|  |     cg.add(var.set_address(config[CONF_ADDRESS])) | ||||||
|  |     cg.add(var.set_reset(config[CONF_RESET])) | ||||||
|  |     if (current_reference := config.get(CONF_CURRENT_REFERENCE, None)) is not None: | ||||||
|  |         cg.add(var.set_current_reference(current_reference)) | ||||||
|  |     if (voltage_reference := config.get(CONF_VOLTAGE_REFERENCE, None)) is not None: | ||||||
|  |         cg.add(var.set_voltage_reference(voltage_reference)) | ||||||
|  |     if (power_reference := config.get(CONF_POWER_REFERENCE, None)) is not None: | ||||||
|  |         cg.add(var.set_power_reference(power_reference)) | ||||||
|  |     if (energy_reference := config.get(CONF_ENERGY_REFERENCE, None)) is not None: | ||||||
|  |         cg.add(var.set_energy_reference(energy_reference)) | ||||||
|   | |||||||
| @@ -65,9 +65,7 @@ CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification" | |||||||
| CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request" | CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request" | ||||||
| CONF_AUTO_CONNECT = "auto_connect" | CONF_AUTO_CONNECT = "auto_connect" | ||||||
|  |  | ||||||
| # Espressif platformio framework is built with MAX_BLE_CONN to 3, so | MULTI_CONF = True | ||||||
| # enforce this in yaml checks. |  | ||||||
| MULTI_CONF = 3 |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( | CONFIG_SCHEMA = ( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, |             cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, | ||||||
|             cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, |             cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, | ||||||
|             cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, |             cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, | ||||||
|             cv.Optional(CONF_IBEACON_UUID): cv.uuid, |             cv.Optional(CONF_IBEACON_UUID): esp32_ble_tracker.bt_uuid, | ||||||
|             cv.Optional(CONF_TIMEOUT, default="5min"): cv.positive_time_period, |             cv.Optional(CONF_TIMEOUT, default="5min"): cv.positive_time_period, | ||||||
|             cv.Optional(CONF_MIN_RSSI): cv.All( |             cv.Optional(CONF_MIN_RSSI): cv.All( | ||||||
|                 cv.decibel, cv.int_range(min=-100, max=-30) |                 cv.decibel, cv.int_range(min=-100, max=-30) | ||||||
| @@ -83,7 +83,7 @@ async def to_code(config): | |||||||
|             cg.add(var.set_service_uuid128(uuid128)) |             cg.add(var.set_service_uuid128(uuid128)) | ||||||
|  |  | ||||||
|     if ibeacon_uuid := config.get(CONF_IBEACON_UUID): |     if ibeacon_uuid := config.get(CONF_IBEACON_UUID): | ||||||
|         ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid)) |         ibeacon_uuid = esp32_ble_tracker.as_reversed_hex_array(ibeacon_uuid) | ||||||
|         cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) |         cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) | ||||||
|  |  | ||||||
|         if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None: |         if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None: | ||||||
|   | |||||||
| @@ -54,6 +54,9 @@ bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_p | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     resp.advertisements.push_back(std::move(adv)); |     resp.advertisements.push_back(std::move(adv)); | ||||||
|  |  | ||||||
|  |     ESP_LOGV(TAG, "Proxying raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0], | ||||||
|  |              result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi); | ||||||
|   } |   } | ||||||
|   ESP_LOGV(TAG, "Proxying %d packets", count); |   ESP_LOGV(TAG, "Proxying %d packets", count); | ||||||
|   this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp); |   this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp); | ||||||
| @@ -87,6 +90,8 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi | |||||||
| void BluetoothProxy::dump_config() { | void BluetoothProxy::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); |   ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); | ||||||
|   ESP_LOGCONFIG(TAG, "  Active: %s", YESNO(this->active_)); |   ESP_LOGCONFIG(TAG, "  Active: %s", YESNO(this->active_)); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Connections: %d", this->connections_.size()); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Raw advertisements: %s", YESNO(this->raw_advertisements_)); | ||||||
| } | } | ||||||
|  |  | ||||||
| int BluetoothProxy::get_bluetooth_connections_free() { | int BluetoothProxy::get_bluetooth_connections_free() { | ||||||
|   | |||||||
| @@ -1,96 +1,5 @@ | |||||||
| import esphome.codegen as cg |  | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import i2c, sensor |  | ||||||
| from esphome.const import ( | CONFIG_SCHEMA = cv.invalid( | ||||||
|     CONF_ID, |     "The bmp280 sensor component has been renamed to bmp280_i2c." | ||||||
|     CONF_PRESSURE, |  | ||||||
|     CONF_TEMPERATURE, |  | ||||||
|     DEVICE_CLASS_PRESSURE, |  | ||||||
|     DEVICE_CLASS_TEMPERATURE, |  | ||||||
|     STATE_CLASS_MEASUREMENT, |  | ||||||
|     UNIT_CELSIUS, |  | ||||||
|     UNIT_HECTOPASCAL, |  | ||||||
|     CONF_IIR_FILTER, |  | ||||||
|     CONF_OVERSAMPLING, |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ["i2c"] |  | ||||||
|  |  | ||||||
| bmp280_ns = cg.esphome_ns.namespace("bmp280") |  | ||||||
| BMP280Oversampling = bmp280_ns.enum("BMP280Oversampling") |  | ||||||
| OVERSAMPLING_OPTIONS = { |  | ||||||
|     "NONE": BMP280Oversampling.BMP280_OVERSAMPLING_NONE, |  | ||||||
|     "1X": BMP280Oversampling.BMP280_OVERSAMPLING_1X, |  | ||||||
|     "2X": BMP280Oversampling.BMP280_OVERSAMPLING_2X, |  | ||||||
|     "4X": BMP280Oversampling.BMP280_OVERSAMPLING_4X, |  | ||||||
|     "8X": BMP280Oversampling.BMP280_OVERSAMPLING_8X, |  | ||||||
|     "16X": BMP280Oversampling.BMP280_OVERSAMPLING_16X, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| BMP280IIRFilter = bmp280_ns.enum("BMP280IIRFilter") |  | ||||||
| IIR_FILTER_OPTIONS = { |  | ||||||
|     "OFF": BMP280IIRFilter.BMP280_IIR_FILTER_OFF, |  | ||||||
|     "2X": BMP280IIRFilter.BMP280_IIR_FILTER_2X, |  | ||||||
|     "4X": BMP280IIRFilter.BMP280_IIR_FILTER_4X, |  | ||||||
|     "8X": BMP280IIRFilter.BMP280_IIR_FILTER_8X, |  | ||||||
|     "16X": BMP280IIRFilter.BMP280_IIR_FILTER_16X, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| BMP280Component = bmp280_ns.class_( |  | ||||||
|     "BMP280Component", cg.PollingComponent, i2c.I2CDevice |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( |  | ||||||
|     cv.Schema( |  | ||||||
|         { |  | ||||||
|             cv.GenerateID(): cv.declare_id(BMP280Component), |  | ||||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( |  | ||||||
|                 unit_of_measurement=UNIT_CELSIUS, |  | ||||||
|                 accuracy_decimals=1, |  | ||||||
|                 device_class=DEVICE_CLASS_TEMPERATURE, |  | ||||||
|                 state_class=STATE_CLASS_MEASUREMENT, |  | ||||||
|             ).extend( |  | ||||||
|                 { |  | ||||||
|                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( |  | ||||||
|                         OVERSAMPLING_OPTIONS, upper=True |  | ||||||
|                     ), |  | ||||||
|                 } |  | ||||||
|             ), |  | ||||||
|             cv.Optional(CONF_PRESSURE): sensor.sensor_schema( |  | ||||||
|                 unit_of_measurement=UNIT_HECTOPASCAL, |  | ||||||
|                 accuracy_decimals=1, |  | ||||||
|                 device_class=DEVICE_CLASS_PRESSURE, |  | ||||||
|                 state_class=STATE_CLASS_MEASUREMENT, |  | ||||||
|             ).extend( |  | ||||||
|                 { |  | ||||||
|                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( |  | ||||||
|                         OVERSAMPLING_OPTIONS, upper=True |  | ||||||
|                     ), |  | ||||||
|                 } |  | ||||||
|             ), |  | ||||||
|             cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( |  | ||||||
|                 IIR_FILTER_OPTIONS, upper=True |  | ||||||
|             ), |  | ||||||
|         } |  | ||||||
|     ) |  | ||||||
|     .extend(cv.polling_component_schema("60s")) |  | ||||||
|     .extend(i2c.i2c_device_schema(0x77)) |  | ||||||
| ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): |  | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |  | ||||||
|     await cg.register_component(var, config) |  | ||||||
|     await i2c.register_i2c_device(var, config) |  | ||||||
|  |  | ||||||
|     if temperature_config := config.get(CONF_TEMPERATURE): |  | ||||||
|         sens = await sensor.new_sensor(temperature_config) |  | ||||||
|         cg.add(var.set_temperature_sensor(sens)) |  | ||||||
|         cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING])) |  | ||||||
|  |  | ||||||
|     if pressure_config := config.get(CONF_PRESSURE): |  | ||||||
|         sens = await sensor.new_sensor(pressure_config) |  | ||||||
|         cg.add(var.set_pressure_sensor(sens)) |  | ||||||
|         cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING])) |  | ||||||
|  |  | ||||||
|     cg.add(var.set_iir_filter(config[CONF_IIR_FILTER])) |  | ||||||
|   | |||||||
							
								
								
									
										88
									
								
								esphome/components/bmp280_base/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								esphome/components/bmp280_base/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_IIR_FILTER, | ||||||
|  |     CONF_OVERSAMPLING, | ||||||
|  |     CONF_PRESSURE, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_PRESSURE, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_HECTOPASCAL, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@ademuri"] | ||||||
|  |  | ||||||
|  | bmp280_ns = cg.esphome_ns.namespace("bmp280_base") | ||||||
|  | BMP280Oversampling = bmp280_ns.enum("BMP280Oversampling") | ||||||
|  | OVERSAMPLING_OPTIONS = { | ||||||
|  |     "NONE": BMP280Oversampling.BMP280_OVERSAMPLING_NONE, | ||||||
|  |     "1X": BMP280Oversampling.BMP280_OVERSAMPLING_1X, | ||||||
|  |     "2X": BMP280Oversampling.BMP280_OVERSAMPLING_2X, | ||||||
|  |     "4X": BMP280Oversampling.BMP280_OVERSAMPLING_4X, | ||||||
|  |     "8X": BMP280Oversampling.BMP280_OVERSAMPLING_8X, | ||||||
|  |     "16X": BMP280Oversampling.BMP280_OVERSAMPLING_16X, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | BMP280IIRFilter = bmp280_ns.enum("BMP280IIRFilter") | ||||||
|  | IIR_FILTER_OPTIONS = { | ||||||
|  |     "OFF": BMP280IIRFilter.BMP280_IIR_FILTER_OFF, | ||||||
|  |     "2X": BMP280IIRFilter.BMP280_IIR_FILTER_2X, | ||||||
|  |     "4X": BMP280IIRFilter.BMP280_IIR_FILTER_4X, | ||||||
|  |     "8X": BMP280IIRFilter.BMP280_IIR_FILTER_8X, | ||||||
|  |     "16X": BMP280IIRFilter.BMP280_IIR_FILTER_16X, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA_BASE = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |             unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |             accuracy_decimals=1, | ||||||
|  |             device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         ).extend( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||||
|  |                     OVERSAMPLING_OPTIONS, upper=True | ||||||
|  |                 ), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_PRESSURE): sensor.sensor_schema( | ||||||
|  |             unit_of_measurement=UNIT_HECTOPASCAL, | ||||||
|  |             accuracy_decimals=1, | ||||||
|  |             device_class=DEVICE_CLASS_PRESSURE, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         ).extend( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||||
|  |                     OVERSAMPLING_OPTIONS, upper=True | ||||||
|  |                 ), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( | ||||||
|  |             IIR_FILTER_OPTIONS, upper=True | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ).extend(cv.polling_component_schema("60s")) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code_base(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|  |     if temperature_config := config.get(CONF_TEMPERATURE): | ||||||
|  |         sens = await sensor.new_sensor(temperature_config) | ||||||
|  |         cg.add(var.set_temperature_sensor(sens)) | ||||||
|  |         cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING])) | ||||||
|  |  | ||||||
|  |     if pressure_config := config.get(CONF_PRESSURE): | ||||||
|  |         sens = await sensor.new_sensor(pressure_config) | ||||||
|  |         cg.add(var.set_pressure_sensor(sens)) | ||||||
|  |         cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING])) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_iir_filter(config[CONF_IIR_FILTER])) | ||||||
|  |  | ||||||
|  |     return var | ||||||
| @@ -1,9 +1,9 @@ | |||||||
| #include "bmp280.h" | #include "bmp280_base.h" | ||||||
| #include "esphome/core/hal.h" | #include "esphome/core/hal.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| 
 | 
 | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace bmp280 { | namespace bmp280_base { | ||||||
| 
 | 
 | ||||||
| static const char *const TAG = "bmp280.sensor"; | static const char *const TAG = "bmp280.sensor"; | ||||||
| 
 | 
 | ||||||
| @@ -59,6 +59,14 @@ static const char *iir_filter_to_str(BMP280IIRFilter filter) { | |||||||
| void BMP280Component::setup() { | void BMP280Component::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up BMP280..."); |   ESP_LOGCONFIG(TAG, "Setting up BMP280..."); | ||||||
|   uint8_t chip_id = 0; |   uint8_t chip_id = 0; | ||||||
|  | 
 | ||||||
|  |   // Read the chip id twice, to work around a bug where the first read is 0.
 | ||||||
|  |   // https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
 | ||||||
|  |   if (!this->read_byte(0xD0, &chip_id)) { | ||||||
|  |     this->error_code_ = COMMUNICATION_FAILED; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|   if (!this->read_byte(0xD0, &chip_id)) { |   if (!this->read_byte(0xD0, &chip_id)) { | ||||||
|     this->error_code_ = COMMUNICATION_FAILED; |     this->error_code_ = COMMUNICATION_FAILED; | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
| @@ -122,7 +130,6 @@ void BMP280Component::setup() { | |||||||
| } | } | ||||||
| void BMP280Component::dump_config() { | void BMP280Component::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "BMP280:"); |   ESP_LOGCONFIG(TAG, "BMP280:"); | ||||||
|   LOG_I2C_DEVICE(this); |  | ||||||
|   switch (this->error_code_) { |   switch (this->error_code_) { | ||||||
|     case COMMUNICATION_FAILED: |     case COMMUNICATION_FAILED: | ||||||
|       ESP_LOGE(TAG, "Communication with BMP280 failed!"); |       ESP_LOGE(TAG, "Communication with BMP280 failed!"); | ||||||
| @@ -262,5 +269,5 @@ uint16_t BMP280Component::read_u16_le_(uint8_t a_register) { | |||||||
| } | } | ||||||
| int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); } | int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); } | ||||||
| 
 | 
 | ||||||
| }  // namespace bmp280
 | }  // namespace bmp280_base
 | ||||||
| }  // namespace esphome
 | }  // namespace esphome
 | ||||||
| @@ -2,10 +2,9 @@ | |||||||
| 
 | 
 | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
| #include "esphome/components/i2c/i2c.h" |  | ||||||
| 
 | 
 | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace bmp280 { | namespace bmp280_base { | ||||||
| 
 | 
 | ||||||
| /// Internal struct storing the calibration values of an BMP280.
 | /// Internal struct storing the calibration values of an BMP280.
 | ||||||
| struct BMP280CalibrationData { | struct BMP280CalibrationData { | ||||||
| @@ -50,8 +49,8 @@ enum BMP280IIRFilter { | |||||||
|   BMP280_IIR_FILTER_16X = 0b100, |   BMP280_IIR_FILTER_16X = 0b100, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /// This class implements support for the BMP280 Temperature+Pressure i2c sensor.
 | /// This class implements support for the BMP280 Temperature+Pressure sensor.
 | ||||||
| class BMP280Component : public PollingComponent, public i2c::I2CDevice { | class BMP280Component : public PollingComponent { | ||||||
|  public: |  public: | ||||||
|   void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } |   void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } | ||||||
|   void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } |   void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } | ||||||
| @@ -68,6 +67,11 @@ class BMP280Component : public PollingComponent, public i2c::I2CDevice { | |||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
|   void update() override; |   void update() override; | ||||||
| 
 | 
 | ||||||
|  |   virtual bool read_byte(uint8_t a_register, uint8_t *data) = 0; | ||||||
|  |   virtual bool write_byte(uint8_t a_register, uint8_t data) = 0; | ||||||
|  |   virtual bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0; | ||||||
|  |   virtual bool read_byte_16(uint8_t a_register, uint16_t *data) = 0; | ||||||
|  | 
 | ||||||
|  protected: |  protected: | ||||||
|   /// Read the temperature value and store the calculated ambient temperature in t_fine.
 |   /// Read the temperature value and store the calculated ambient temperature in t_fine.
 | ||||||
|   float read_temperature_(int32_t *t_fine); |   float read_temperature_(int32_t *t_fine); | ||||||
| @@ -90,5 +94,5 @@ class BMP280Component : public PollingComponent, public i2c::I2CDevice { | |||||||
|   } error_code_{NONE}; |   } error_code_{NONE}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| }  // namespace bmp280
 | }  // namespace bmp280_base
 | ||||||
| }  // namespace esphome
 | }  // namespace esphome
 | ||||||
							
								
								
									
										0
									
								
								esphome/components/bmp280_i2c/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/bmp280_i2c/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										27
									
								
								esphome/components/bmp280_i2c/bmp280_i2c.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/bmp280_i2c/bmp280_i2c.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | #include "bmp280_i2c.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bmp280_i2c { | ||||||
|  |  | ||||||
|  | bool BMP280I2CComponent::read_byte(uint8_t a_register, uint8_t *data) { | ||||||
|  |   return I2CDevice::read_byte(a_register, data); | ||||||
|  | }; | ||||||
|  | bool BMP280I2CComponent::write_byte(uint8_t a_register, uint8_t data) { | ||||||
|  |   return I2CDevice::write_byte(a_register, data); | ||||||
|  | }; | ||||||
|  | bool BMP280I2CComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) { | ||||||
|  |   return I2CDevice::read_bytes(a_register, data, len); | ||||||
|  | }; | ||||||
|  | bool BMP280I2CComponent::read_byte_16(uint8_t a_register, uint16_t *data) { | ||||||
|  |   return I2CDevice::read_byte_16(a_register, data); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | void BMP280I2CComponent::dump_config() { | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   BMP280Component::dump_config(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace bmp280_i2c | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										22
									
								
								esphome/components/bmp280_i2c/bmp280_i2c.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								esphome/components/bmp280_i2c/bmp280_i2c.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/bmp280_base/bmp280_base.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bmp280_i2c { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "bmp280_i2c.sensor"; | ||||||
|  |  | ||||||
|  | /// This class implements support for the BMP280 Temperature+Pressure i2c sensor. | ||||||
|  | class BMP280I2CComponent : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   bool read_byte(uint8_t a_register, uint8_t *data) override; | ||||||
|  |   bool write_byte(uint8_t a_register, uint8_t data) override; | ||||||
|  |   bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override; | ||||||
|  |   bool read_byte_16(uint8_t a_register, uint16_t *data) override; | ||||||
|  |   void dump_config() override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace bmp280_i2c | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										22
									
								
								esphome/components/bmp280_i2c/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								esphome/components/bmp280_i2c/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import i2c | ||||||
|  | from ..bmp280_base import to_code_base, CONFIG_SCHEMA_BASE | ||||||
|  |  | ||||||
|  | AUTO_LOAD = ["bmp280_base"] | ||||||
|  | CODEOWNERS = ["@ademuri"] | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | bmp280_ns = cg.esphome_ns.namespace("bmp280_i2c") | ||||||
|  | BMP280I2CComponent = bmp280_ns.class_( | ||||||
|  |     "BMP280I2CComponent", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend( | ||||||
|  |     i2c.i2c_device_schema(default_address=0x77) | ||||||
|  | ).extend({cv.GenerateID(): cv.declare_id(BMP280I2CComponent)}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = await to_code_base(config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
							
								
								
									
										0
									
								
								esphome/components/bmp280_spi/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/bmp280_spi/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										65
									
								
								esphome/components/bmp280_spi/bmp280_spi.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								esphome/components/bmp280_spi/bmp280_spi.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | #include <cstdint> | ||||||
|  | #include <cstddef> | ||||||
|  |  | ||||||
|  | #include "bmp280_spi.h" | ||||||
|  | #include <esphome/components/bmp280_base/bmp280_base.h> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bmp280_spi { | ||||||
|  |  | ||||||
|  | uint8_t set_bit(uint8_t num, uint8_t position) { | ||||||
|  |   uint8_t mask = 1 << position; | ||||||
|  |   return num | mask; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint8_t clear_bit(uint8_t num, uint8_t position) { | ||||||
|  |   uint8_t mask = 1 << position; | ||||||
|  |   return num & ~mask; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BMP280SPIComponent::setup() { | ||||||
|  |   this->spi_setup(); | ||||||
|  |   BMP280Component::setup(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // In SPI mode, only 7 bits of the register addresses are used; the MSB of register address is not used | ||||||
|  | // and replaced by a read/write bit (RW = ‘0’ for write and RW = ‘1’ for read). | ||||||
|  | // Example: address 0xF7 is accessed by using SPI register address 0x77. For write access, the byte | ||||||
|  | // 0x77 is transferred, for read access, the byte 0xF7 is transferred. | ||||||
|  | // https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf | ||||||
|  |  | ||||||
|  | bool BMP280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) { | ||||||
|  |   this->enable(); | ||||||
|  |   this->transfer_byte(set_bit(a_register, 7)); | ||||||
|  |   *data = this->transfer_byte(0); | ||||||
|  |   this->disable(); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool BMP280SPIComponent::write_byte(uint8_t a_register, uint8_t data) { | ||||||
|  |   this->enable(); | ||||||
|  |   this->transfer_byte(clear_bit(a_register, 7)); | ||||||
|  |   this->transfer_byte(data); | ||||||
|  |   this->disable(); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool BMP280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) { | ||||||
|  |   this->enable(); | ||||||
|  |   this->transfer_byte(set_bit(a_register, 7)); | ||||||
|  |   this->read_array(data, len); | ||||||
|  |   this->disable(); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool BMP280SPIComponent::read_byte_16(uint8_t a_register, uint16_t *data) { | ||||||
|  |   this->enable(); | ||||||
|  |   this->transfer_byte(set_bit(a_register, 7)); | ||||||
|  |   ((uint8_t *) data)[1] = this->transfer_byte(0); | ||||||
|  |   ((uint8_t *) data)[0] = this->transfer_byte(0); | ||||||
|  |   this->disable(); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace bmp280_spi | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										20
									
								
								esphome/components/bmp280_spi/bmp280_spi.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								esphome/components/bmp280_spi/bmp280_spi.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/bmp280_base/bmp280_base.h" | ||||||
|  | #include "esphome/components/spi/spi.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bmp280_spi { | ||||||
|  |  | ||||||
|  | class BMP280SPIComponent : public esphome::bmp280_base::BMP280Component, | ||||||
|  |                            public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, | ||||||
|  |                                                  spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> { | ||||||
|  |   void setup() override; | ||||||
|  |   bool read_byte(uint8_t a_register, uint8_t *data) override; | ||||||
|  |   bool write_byte(uint8_t a_register, uint8_t data) override; | ||||||
|  |   bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override; | ||||||
|  |   bool read_byte_16(uint8_t a_register, uint16_t *data) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace bmp280_spi | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										22
									
								
								esphome/components/bmp280_spi/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								esphome/components/bmp280_spi/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import spi | ||||||
|  | from ..bmp280_base import to_code_base, CONFIG_SCHEMA_BASE | ||||||
|  |  | ||||||
|  | AUTO_LOAD = ["bmp280_base"] | ||||||
|  | CODEOWNERS = ["@ademuri"] | ||||||
|  | DEPENDENCIES = ["spi"] | ||||||
|  |  | ||||||
|  | bmp280_ns = cg.esphome_ns.namespace("bmp280_spi") | ||||||
|  | BMP280SPIComponent = bmp280_ns.class_( | ||||||
|  |     "BMP280SPIComponent", cg.PollingComponent, spi.SPIDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend( | ||||||
|  |     spi.spi_device_schema(default_mode="mode3") | ||||||
|  | ).extend({cv.GenerateID(): cv.declare_id(BMP280SPIComponent)}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = await to_code_base(config) | ||||||
|  |     await spi.register_spi_device(var, config) | ||||||
| @@ -1,4 +1,5 @@ | |||||||
| #include "captive_portal.h" | #include "captive_portal.h" | ||||||
|  | #ifdef USE_CAPTIVE_PORTAL | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| #include "esphome/core/application.h" | #include "esphome/core/application.h" | ||||||
| #include "esphome/components/wifi/wifi_component.h" | #include "esphome/components/wifi/wifi_component.h" | ||||||
| @@ -91,3 +92,4 @@ CaptivePortal *global_captive_portal = nullptr;  // NOLINT(cppcoreguidelines-avo | |||||||
|  |  | ||||||
| }  // namespace captive_portal | }  // namespace captive_portal | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  | #endif | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #ifdef USE_CAPTIVE_PORTAL | ||||||
| #include <memory> | #include <memory> | ||||||
| #ifdef USE_ARDUINO | #ifdef USE_ARDUINO | ||||||
| #include <DNSServer.h> | #include <DNSServer.h> | ||||||
| @@ -71,3 +72,4 @@ extern CaptivePortal *global_captive_portal;  // NOLINT(cppcoreguidelines-avoid- | |||||||
|  |  | ||||||
| }  // namespace captive_portal | }  // namespace captive_portal | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  | #endif | ||||||
|   | |||||||
							
								
								
									
										67
									
								
								esphome/components/ch422g/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								esphome/components/ch422g/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | from esphome import pins | ||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import i2c | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_INPUT, | ||||||
|  |     CONF_INVERTED, | ||||||
|  |     CONF_MODE, | ||||||
|  |     CONF_NUMBER, | ||||||
|  |     CONF_OUTPUT, | ||||||
|  |     CONF_RESTORE_VALUE, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@jesterret"] | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  | MULTI_CONF = True | ||||||
|  | ch422g_ns = cg.esphome_ns.namespace("ch422g") | ||||||
|  |  | ||||||
|  | CH422GComponent = ch422g_ns.class_("CH422GComponent", cg.Component, i2c.I2CDevice) | ||||||
|  | CH422GGPIOPin = ch422g_ns.class_( | ||||||
|  |     "CH422GGPIOPin", cg.GPIOPin, cg.Parented.template(CH422GComponent) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONF_CH422G = "ch422g" | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_ID): cv.declare_id(CH422GComponent), | ||||||
|  |             cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x24)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE])) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CH422G_PIN_SCHEMA = pins.gpio_base_schema( | ||||||
|  |     CH422GGPIOPin, | ||||||
|  |     cv.int_range(min=0, max=7), | ||||||
|  |     modes=[CONF_INPUT, CONF_OUTPUT], | ||||||
|  | ).extend( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_CH422G): cv.use_id(CH422GComponent), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pins.PIN_SCHEMA_REGISTRY.register(CONF_CH422G, CH422G_PIN_SCHEMA) | ||||||
|  | async def ch422g_pin_to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     parent = await cg.get_variable(config[CONF_CH422G]) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_parent(parent)) | ||||||
|  |  | ||||||
|  |     num = config[CONF_NUMBER] | ||||||
|  |     cg.add(var.set_pin(num)) | ||||||
|  |     cg.add(var.set_inverted(config[CONF_INVERTED])) | ||||||
|  |     cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) | ||||||
|  |     return var | ||||||
							
								
								
									
										122
									
								
								esphome/components/ch422g/ch422g.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								esphome/components/ch422g/ch422g.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | |||||||
|  | #include "ch422g.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ch422g { | ||||||
|  |  | ||||||
|  | const uint8_t CH422G_REG_IN = 0x26; | ||||||
|  | const uint8_t CH422G_REG_OUT = 0x38; | ||||||
|  | const uint8_t OUT_REG_DEFAULT_VAL = 0xdf; | ||||||
|  |  | ||||||
|  | static const char *const TAG = "ch422g"; | ||||||
|  |  | ||||||
|  | void CH422GComponent::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up CH422G..."); | ||||||
|  |   // Test to see if device exists | ||||||
|  |   if (!this->read_inputs_()) { | ||||||
|  |     ESP_LOGE(TAG, "CH422G not detected at 0x%02X", this->address_); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // restore defaults over whatever got saved on last boot | ||||||
|  |   if (!this->restore_value_) { | ||||||
|  |     this->write_output_(OUT_REG_DEFAULT_VAL); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(), | ||||||
|  |            this->status_has_error()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CH422GComponent::loop() { | ||||||
|  |   // Clear all the previously read flags. | ||||||
|  |   this->pin_read_cache_ = 0x00; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CH422GComponent::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "CH422G:"); | ||||||
|  |   LOG_I2C_DEVICE(this) | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Communication with CH422G failed!"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ch422g doesn't have any flag support (needs docs?) | ||||||
|  | void CH422GComponent::pin_mode(uint8_t pin, gpio::Flags flags) {} | ||||||
|  |  | ||||||
|  | bool CH422GComponent::digital_read(uint8_t pin) { | ||||||
|  |   if (this->pin_read_cache_ == 0 || this->pin_read_cache_ & (1 << pin)) { | ||||||
|  |     // Read values on first access or in case it's being read again in the same loop | ||||||
|  |     this->read_inputs_(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->pin_read_cache_ |= (1 << pin); | ||||||
|  |   return this->state_mask_ & (1 << pin); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CH422GComponent::digital_write(uint8_t pin, bool value) { | ||||||
|  |   if (value) { | ||||||
|  |     this->write_output_(this->state_mask_ | (1 << pin)); | ||||||
|  |   } else { | ||||||
|  |     this->write_output_(this->state_mask_ & ~(1 << pin)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CH422GComponent::read_inputs_() { | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t temp = 0; | ||||||
|  |   if ((this->last_error_ = this->read(&temp, 1)) != esphome::i2c::ERROR_OK) { | ||||||
|  |     this->status_set_warning(str_sprintf("read_inputs_(): I2C I/O error: %d", (int) this->last_error_).c_str()); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t output = 0; | ||||||
|  |   if ((this->last_error_ = this->bus_->read(CH422G_REG_IN, &output, 1)) != esphome::i2c::ERROR_OK) { | ||||||
|  |     this->status_set_warning(str_sprintf("read_inputs_(): I2C I/O error: %d", (int) this->last_error_).c_str()); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->state_mask_ = output; | ||||||
|  |   this->status_clear_warning(); | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CH422GComponent::write_output_(uint8_t value) { | ||||||
|  |   const uint8_t temp = 1; | ||||||
|  |   if ((this->last_error_ = this->write(&temp, 1, false)) != esphome::i2c::ERROR_OK) { | ||||||
|  |     this->status_set_warning(str_sprintf("write_output_(): I2C I/O error: %d", (int) this->last_error_).c_str()); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t write_mask = value; | ||||||
|  |   if ((this->last_error_ = this->bus_->write(CH422G_REG_OUT, &write_mask, 1)) != esphome::i2c::ERROR_OK) { | ||||||
|  |     this->status_set_warning( | ||||||
|  |         str_sprintf("write_output_(): I2C I/O error: %d for write_mask: %d", (int) this->last_error_, (int) write_mask) | ||||||
|  |             .c_str()); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->state_mask_ = value; | ||||||
|  |   this->status_clear_warning(); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float CH422GComponent::get_setup_priority() const { return setup_priority::IO; } | ||||||
|  |  | ||||||
|  | // Run our loop() method very early in the loop, so that we cache read values | ||||||
|  | // before other components call our digital_read() method. | ||||||
|  | float CH422GComponent::get_loop_priority() const { return 9.0f; }  // Just after WIFI | ||||||
|  |  | ||||||
|  | void CH422GGPIOPin::setup() { pin_mode(flags_); } | ||||||
|  | void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } | ||||||
|  | bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } | ||||||
|  |  | ||||||
|  | void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } | ||||||
|  | std::string CH422GGPIOPin::dump_summary() const { return str_sprintf("EXIO%u via CH422G", pin_); } | ||||||
|  |  | ||||||
|  | }  // namespace ch422g | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										70
									
								
								esphome/components/ch422g/ch422g.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								esphome/components/ch422g/ch422g.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ch422g { | ||||||
|  |  | ||||||
|  | class CH422GComponent : public Component, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   CH422GComponent() = default; | ||||||
|  |  | ||||||
|  |   /// Check i2c availability and setup masks | ||||||
|  |   void setup() override; | ||||||
|  |   /// Poll for input changes periodically | ||||||
|  |   void loop() override; | ||||||
|  |   /// Helper function to read the value of a pin. | ||||||
|  |   bool digital_read(uint8_t pin); | ||||||
|  |   /// Helper function to write the value of a pin. | ||||||
|  |   void digital_write(uint8_t pin, bool value); | ||||||
|  |   /// Helper function to set the pin mode of a pin. | ||||||
|  |   void pin_mode(uint8_t pin, gpio::Flags flags); | ||||||
|  |  | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|  |   float get_loop_priority() const override; | ||||||
|  |  | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |   void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool read_inputs_(); | ||||||
|  |  | ||||||
|  |   bool write_output_(uint8_t value); | ||||||
|  |  | ||||||
|  |   /// The mask to write as output state - 1 means HIGH, 0 means LOW | ||||||
|  |   uint8_t state_mask_{0x00}; | ||||||
|  |   /// Flags to check if read previously during this loop | ||||||
|  |   uint8_t pin_read_cache_ = {0x00}; | ||||||
|  |   /// Storage for last I2C error seen | ||||||
|  |   esphome::i2c::ErrorCode last_error_; | ||||||
|  |   /// Whether we want to override stored values on expander | ||||||
|  |   bool restore_value_{false}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// Helper class to expose a CH422G pin as an internal input GPIO pin. | ||||||
|  | class CH422GGPIOPin : public GPIOPin { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void pin_mode(gpio::Flags flags) override; | ||||||
|  |   bool digital_read() override; | ||||||
|  |   void digital_write(bool value) override; | ||||||
|  |   std::string dump_summary() const override; | ||||||
|  |  | ||||||
|  |   void set_parent(CH422GComponent *parent) { parent_ = parent; } | ||||||
|  |   void set_pin(uint8_t pin) { pin_ = pin; } | ||||||
|  |   void set_inverted(bool inverted) { inverted_ = inverted; } | ||||||
|  |   void set_flags(gpio::Flags flags) { flags_ = flags; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   CH422GComponent *parent_; | ||||||
|  |   uint8_t pin_; | ||||||
|  |   bool inverted_; | ||||||
|  |   gpio::Flags flags_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ch422g | ||||||
|  | }  // namespace esphome | ||||||
| @@ -16,6 +16,8 @@ | |||||||
| #include <esp32s2/rom/rtc.h> | #include <esp32s2/rom/rtc.h> | ||||||
| #elif defined(USE_ESP32_VARIANT_ESP32S3) | #elif defined(USE_ESP32_VARIANT_ESP32S3) | ||||||
| #include <esp32s3/rom/rtc.h> | #include <esp32s3/rom/rtc.h> | ||||||
|  | #elif defined(USE_ESP32_VARIANT_ESP32H2) | ||||||
|  | #include <esp32h2/rom/rtc.h> | ||||||
| #endif | #endif | ||||||
| #ifdef USE_ARDUINO | #ifdef USE_ARDUINO | ||||||
| #include <Esp.h> | #include <Esp.h> | ||||||
| @@ -61,7 +63,7 @@ std::string DebugComponent::get_reset_reason_() { | |||||||
|     case RTCWDT_SYS_RESET: |     case RTCWDT_SYS_RESET: | ||||||
|       reset_reason = "RTC Watch Dog Reset Digital Core"; |       reset_reason = "RTC Watch Dog Reset Digital Core"; | ||||||
|       break; |       break; | ||||||
| #if !defined(USE_ESP32_VARIANT_ESP32C6) | #if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2) | ||||||
|     case INTRUSION_RESET: |     case INTRUSION_RESET: | ||||||
|       reset_reason = "Intrusion Reset CPU"; |       reset_reason = "Intrusion Reset CPU"; | ||||||
|       break; |       break; | ||||||
|   | |||||||
| @@ -1,15 +1,15 @@ | |||||||
|  | from esphome import automation, core | ||||||
|  | from esphome.automation import maybe_simple_id | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import core, automation |  | ||||||
| from esphome.automation import maybe_simple_id |  | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_AUTO_CLEAR_ENABLED, |     CONF_AUTO_CLEAR_ENABLED, | ||||||
|  |     CONF_FROM, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_LAMBDA, |     CONF_LAMBDA, | ||||||
|     CONF_PAGES, |  | ||||||
|     CONF_PAGE_ID, |     CONF_PAGE_ID, | ||||||
|  |     CONF_PAGES, | ||||||
|     CONF_ROTATION, |     CONF_ROTATION, | ||||||
|     CONF_FROM, |  | ||||||
|     CONF_TO, |     CONF_TO, | ||||||
|     CONF_TRIGGER_ID, |     CONF_TRIGGER_ID, | ||||||
| ) | ) | ||||||
| @@ -195,3 +195,4 @@ async def display_is_displaying_page_to_code(config, condition_id, template_arg, | |||||||
| @coroutine_with_priority(100.0) | @coroutine_with_priority(100.0) | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     cg.add_global(display_ns.using) |     cg.add_global(display_ns.using) | ||||||
|  |     cg.add_define("USE_DISPLAY") | ||||||
|   | |||||||
| @@ -256,6 +256,7 @@ bool Dsmr::parse_telegram() { | |||||||
|   MyData data; |   MyData data; | ||||||
|   ESP_LOGV(TAG, "Trying to parse telegram"); |   ESP_LOGV(TAG, "Trying to parse telegram"); | ||||||
|   this->stop_requesting_data_(); |   this->stop_requesting_data_(); | ||||||
|  |  | ||||||
|   ::dsmr::ParseResult<void> res = |   ::dsmr::ParseResult<void> res = | ||||||
|       ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false, |       ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false, | ||||||
|                               this->crc_check_);  // Parse telegram according to data definition. Ignore unknown values. |                               this->crc_check_);  // Parse telegram according to data definition. Ignore unknown values. | ||||||
| @@ -267,6 +268,11 @@ bool Dsmr::parse_telegram() { | |||||||
|   } else { |   } else { | ||||||
|     this->status_clear_warning(); |     this->status_clear_warning(); | ||||||
|     this->publish_sensors(data); |     this->publish_sensors(data); | ||||||
|  |  | ||||||
|  |     // publish the telegram, after publishing the sensors so it can also trigger action based on latest values | ||||||
|  |     if (this->s_telegram_ != nullptr) { | ||||||
|  |       this->s_telegram_->publish_state(std::string(this->telegram_, this->bytes_read_)); | ||||||
|  |     } | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -85,6 +85,9 @@ class Dsmr : public Component, public uart::UARTDevice { | |||||||
|   void set_##s(text_sensor::TextSensor *sensor) { s_##s##_ = sensor; } |   void set_##s(text_sensor::TextSensor *sensor) { s_##s##_ = sensor; } | ||||||
|   DSMR_TEXT_SENSOR_LIST(DSMR_SET_TEXT_SENSOR, ) |   DSMR_TEXT_SENSOR_LIST(DSMR_SET_TEXT_SENSOR, ) | ||||||
|  |  | ||||||
|  |   // handled outside dsmr | ||||||
|  |   void set_telegram(text_sensor::TextSensor *sensor) { s_telegram_ = sensor; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void receive_telegram_(); |   void receive_telegram_(); | ||||||
|   void receive_encrypted_telegram_(); |   void receive_encrypted_telegram_(); | ||||||
| @@ -124,6 +127,9 @@ class Dsmr : public Component, public uart::UARTDevice { | |||||||
|   bool header_found_{false}; |   bool header_found_{false}; | ||||||
|   bool footer_found_{false}; |   bool footer_found_{false}; | ||||||
|  |  | ||||||
|  |   // handled outside dsmr | ||||||
|  |   text_sensor::TextSensor *s_telegram_{nullptr}; | ||||||
|  |  | ||||||
| // Sensor member pointers | // Sensor member pointers | ||||||
| #define DSMR_DECLARE_SENSOR(s) sensor::Sensor *s_##s##_{nullptr}; | #define DSMR_DECLARE_SENSOR(s) sensor::Sensor *s_##s##_{nullptr}; | ||||||
|   DSMR_SENSOR_LIST(DSMR_DECLARE_SENSOR, ) |   DSMR_SENSOR_LIST(DSMR_DECLARE_SENSOR, ) | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import text_sensor | from esphome.components import text_sensor | ||||||
|  | from esphome.const import CONF_INTERNAL | ||||||
| from . import Dsmr, CONF_DSMR_ID | from . import Dsmr, CONF_DSMR_ID | ||||||
|  |  | ||||||
| AUTO_LOAD = ["dsmr"] | AUTO_LOAD = ["dsmr"] | ||||||
| @@ -22,6 +22,9 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|         cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(), |         cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(), | ||||||
|         cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(), |         cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(), | ||||||
|         cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(), |         cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(), | ||||||
|  |         cv.Optional("telegram"): text_sensor.text_sensor_schema().extend( | ||||||
|  |             {cv.Optional(CONF_INTERNAL, default=True): cv.boolean} | ||||||
|  |         ), | ||||||
|     } |     } | ||||||
| ).extend(cv.COMPONENT_SCHEMA) | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
| @@ -37,7 +40,9 @@ async def to_code(config): | |||||||
|         if id and id.type == text_sensor.TextSensor: |         if id and id.type == text_sensor.TextSensor: | ||||||
|             var = await text_sensor.new_text_sensor(conf) |             var = await text_sensor.new_text_sensor(conf) | ||||||
|             cg.add(getattr(hub, f"set_{key}")(var)) |             cg.add(getattr(hub, f"set_{key}")(var)) | ||||||
|             text_sensors.append(f"F({key})") |             if key != "telegram": | ||||||
|  |                 # telegram is not handled by dsmr | ||||||
|  |                 text_sensors.append(f"F({key})") | ||||||
|  |  | ||||||
|     if text_sensors: |     if text_sensors: | ||||||
|         cg.add_define( |         cg.add_define( | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| #include "e131.h" | #include "e131.h" | ||||||
|  | #ifdef USE_NETWORK | ||||||
| #include "e131_addressable_light_effect.h" | #include "e131_addressable_light_effect.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| @@ -118,3 +119,4 @@ bool E131Component::process_(int universe, const E131Packet &packet) { | |||||||
|  |  | ||||||
| }  // namespace e131 | }  // namespace e131 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  | #endif | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #ifdef USE_NETWORK | ||||||
| #include "esphome/components/socket/socket.h" | #include "esphome/components/socket/socket.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
| @@ -53,3 +54,4 @@ class E131Component : public esphome::Component { | |||||||
|  |  | ||||||
| }  // namespace e131 | }  // namespace e131 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  | #endif | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| #include "e131_addressable_light_effect.h" | #include "e131_addressable_light_effect.h" | ||||||
| #include "e131.h" | #include "e131.h" | ||||||
|  | #ifdef USE_NETWORK | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| @@ -90,3 +91,4 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet | |||||||
|  |  | ||||||
| }  // namespace e131 | }  // namespace e131 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  | #endif | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/components/light/addressable_light_effect.h" | #include "esphome/components/light/addressable_light_effect.h" | ||||||
|  | #ifdef USE_NETWORK | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace e131 { | namespace e131 { | ||||||
|  |  | ||||||
| @@ -42,3 +42,4 @@ class E131AddressableLightEffect : public light::AddressableLightEffect { | |||||||
|  |  | ||||||
| }  // namespace e131 | }  // namespace e131 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  | #endif | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| #include <cstring> | #include <cstring> | ||||||
| #include "e131.h" | #include "e131.h" | ||||||
|  | #ifdef USE_NETWORK | ||||||
| #include "esphome/components/network/ip_address.h" | #include "esphome/components/network/ip_address.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| #include "esphome/core/util.h" | #include "esphome/core/util.h" | ||||||
| @@ -137,3 +138,4 @@ bool E131Component::packet_(const std::vector<uint8_t> &data, int &universe, E13 | |||||||
|  |  | ||||||
| }  // namespace e131 | }  // namespace e131 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  | #endif | ||||||
|   | |||||||
| @@ -31,6 +31,13 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) { | |||||||
|   memcpy(ret.uuid_.uuid.uuid128, data, ESP_UUID_LEN_128); |   memcpy(ret.uuid_.uuid.uuid128, data, ESP_UUID_LEN_128); | ||||||
|   return ret; |   return ret; | ||||||
| } | } | ||||||
|  | ESPBTUUID ESPBTUUID::from_raw_reversed(const uint8_t *data) { | ||||||
|  |   ESPBTUUID ret; | ||||||
|  |   ret.uuid_.len = ESP_UUID_LEN_128; | ||||||
|  |   for (int i = 0; i < ESP_UUID_LEN_128; i++) | ||||||
|  |     ret.uuid_.uuid.uuid128[ESP_UUID_LEN_128 - 1 - i] = data[i]; | ||||||
|  |   return ret; | ||||||
|  | } | ||||||
| ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { | ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { | ||||||
|   ESPBTUUID ret; |   ESPBTUUID ret; | ||||||
|   if (data.length() == 4) { |   if (data.length() == 4) { | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ class ESPBTUUID { | |||||||
|   static ESPBTUUID from_uint32(uint32_t uuid); |   static ESPBTUUID from_uint32(uint32_t uuid); | ||||||
|  |  | ||||||
|   static ESPBTUUID from_raw(const uint8_t *data); |   static ESPBTUUID from_raw(const uint8_t *data); | ||||||
|  |   static ESPBTUUID from_raw_reversed(const uint8_t *data); | ||||||
|  |  | ||||||
|   static ESPBTUUID from_raw(const std::string &data); |   static ESPBTUUID from_raw(const std::string &data); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -462,14 +462,16 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e | |||||||
|     ESP_LOGVV(TAG, "  Service UUID: %s", uuid.to_string().c_str()); |     ESP_LOGVV(TAG, "  Service UUID: %s", uuid.to_string().c_str()); | ||||||
|   } |   } | ||||||
|   for (auto &data : this->manufacturer_datas_) { |   for (auto &data : this->manufacturer_datas_) { | ||||||
|     ESP_LOGVV(TAG, "  Manufacturer data: %s", format_hex_pretty(data.data).c_str()); |     auto ibeacon = ESPBLEiBeacon::from_manufacturer_data(data); | ||||||
|     if (this->get_ibeacon().has_value()) { |     if (ibeacon.has_value()) { | ||||||
|       auto ibeacon = this->get_ibeacon().value(); |       ESP_LOGVV(TAG, "  Manufacturer iBeacon:"); | ||||||
|       ESP_LOGVV(TAG, "    iBeacon data:"); |       ESP_LOGVV(TAG, "    UUID: %s", ibeacon.value().get_uuid().to_string().c_str()); | ||||||
|       ESP_LOGVV(TAG, "      UUID: %s", ibeacon.get_uuid().to_string().c_str()); |       ESP_LOGVV(TAG, "    Major: %u", ibeacon.value().get_major()); | ||||||
|       ESP_LOGVV(TAG, "      Major: %u", ibeacon.get_major()); |       ESP_LOGVV(TAG, "    Minor: %u", ibeacon.value().get_minor()); | ||||||
|       ESP_LOGVV(TAG, "      Minor: %u", ibeacon.get_minor()); |       ESP_LOGVV(TAG, "    TXPower: %d", ibeacon.value().get_signal_power()); | ||||||
|       ESP_LOGVV(TAG, "      TXPower: %d", ibeacon.get_signal_power()); |     } else { | ||||||
|  |       ESP_LOGVV(TAG, "  Manufacturer ID: %s, data: %s", data.uuid.to_string().c_str(), | ||||||
|  |                 format_hex_pretty(data.data).c_str()); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   for (auto &data : this->service_datas_) { |   for (auto &data : this->service_datas_) { | ||||||
| @@ -478,7 +480,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e | |||||||
|     ESP_LOGVV(TAG, "    Data: %s", format_hex_pretty(data.data).c_str()); |     ESP_LOGVV(TAG, "    Data: %s", format_hex_pretty(data.data).c_str()); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   ESP_LOGVV(TAG, "Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str()); |   ESP_LOGVV(TAG, "  Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str()); | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { | void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { | ||||||
|   | |||||||
| @@ -44,10 +44,10 @@ class ESPBLEiBeacon { | |||||||
|   ESPBLEiBeacon(const uint8_t *data); |   ESPBLEiBeacon(const uint8_t *data); | ||||||
|   static optional<ESPBLEiBeacon> from_manufacturer_data(const ServiceData &data); |   static optional<ESPBLEiBeacon> from_manufacturer_data(const ServiceData &data); | ||||||
|  |  | ||||||
|   uint16_t get_major() { return ((this->beacon_data_.major & 0xFF) << 8) | (this->beacon_data_.major >> 8); } |   uint16_t get_major() { return byteswap(this->beacon_data_.major); } | ||||||
|   uint16_t get_minor() { return ((this->beacon_data_.minor & 0xFF) << 8) | (this->beacon_data_.minor >> 8); } |   uint16_t get_minor() { return byteswap(this->beacon_data_.minor); } | ||||||
|   int8_t get_signal_power() { return this->beacon_data_.signal_power; } |   int8_t get_signal_power() { return this->beacon_data_.signal_power; } | ||||||
|   ESPBTUUID get_uuid() { return ESPBTUUID::from_raw(this->beacon_data_.proximity_uuid); } |   ESPBTUUID get_uuid() { return ESPBTUUID::from_raw_reversed(this->beacon_data_.proximity_uuid); } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   struct { |   struct { | ||||||
|   | |||||||
| @@ -140,6 +140,8 @@ CONF_TEST_PATTERN = "test_pattern" | |||||||
| # framerates | # framerates | ||||||
| CONF_MAX_FRAMERATE = "max_framerate" | CONF_MAX_FRAMERATE = "max_framerate" | ||||||
| CONF_IDLE_FRAMERATE = "idle_framerate" | CONF_IDLE_FRAMERATE = "idle_framerate" | ||||||
|  | # frame buffer | ||||||
|  | CONF_FRAME_BUFFER_COUNT = "frame_buffer_count" | ||||||
|  |  | ||||||
| # stream trigger | # stream trigger | ||||||
| CONF_ON_STREAM_START = "on_stream_start" | CONF_ON_STREAM_START = "on_stream_start" | ||||||
| @@ -213,6 +215,7 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( | |||||||
|         cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( |         cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( | ||||||
|             cv.framerate, cv.Range(min=0, max=1) |             cv.framerate, cv.Range(min=0, max=1) | ||||||
|         ), |         ), | ||||||
|  |         cv.Optional(CONF_FRAME_BUFFER_COUNT, default=1): cv.int_range(min=1, max=2), | ||||||
|         cv.Optional(CONF_ON_STREAM_START): automation.validate_automation( |         cv.Optional(CONF_ON_STREAM_START): automation.validate_automation( | ||||||
|             { |             { | ||||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||||
| @@ -285,6 +288,7 @@ async def to_code(config): | |||||||
|         cg.add(var.set_idle_update_interval(0)) |         cg.add(var.set_idle_update_interval(0)) | ||||||
|     else: |     else: | ||||||
|         cg.add(var.set_idle_update_interval(1000 / config[CONF_IDLE_FRAMERATE])) |         cg.add(var.set_idle_update_interval(1000 / config[CONF_IDLE_FRAMERATE])) | ||||||
|  |     cg.add(var.set_frame_buffer_count(config[CONF_FRAME_BUFFER_COUNT])) | ||||||
|     cg.add(var.set_frame_size(config[CONF_RESOLUTION])) |     cg.add(var.set_frame_size(config[CONF_RESOLUTION])) | ||||||
|  |  | ||||||
|     cg.add_define("USE_ESP32_CAMERA") |     cg.add_define("USE_ESP32_CAMERA") | ||||||
|   | |||||||
| @@ -127,7 +127,7 @@ void ESP32Camera::dump_config() { | |||||||
|   sensor_t *s = esp_camera_sensor_get(); |   sensor_t *s = esp_camera_sensor_get(); | ||||||
|   auto st = s->status; |   auto st = s->status; | ||||||
|   ESP_LOGCONFIG(TAG, "  JPEG Quality: %u", st.quality); |   ESP_LOGCONFIG(TAG, "  JPEG Quality: %u", st.quality); | ||||||
|   // ESP_LOGCONFIG(TAG, "  Framebuffer Count: %u", conf.fb_count); |   ESP_LOGCONFIG(TAG, "  Framebuffer Count: %u", conf.fb_count); | ||||||
|   ESP_LOGCONFIG(TAG, "  Contrast: %d", st.contrast); |   ESP_LOGCONFIG(TAG, "  Contrast: %d", st.contrast); | ||||||
|   ESP_LOGCONFIG(TAG, "  Brightness: %d", st.brightness); |   ESP_LOGCONFIG(TAG, "  Brightness: %d", st.brightness); | ||||||
|   ESP_LOGCONFIG(TAG, "  Saturation: %d", st.saturation); |   ESP_LOGCONFIG(TAG, "  Saturation: %d", st.saturation); | ||||||
| @@ -212,6 +212,8 @@ ESP32Camera::ESP32Camera() { | |||||||
|   this->config_.frame_size = FRAMESIZE_VGA;  // 640x480 |   this->config_.frame_size = FRAMESIZE_VGA;  // 640x480 | ||||||
|   this->config_.jpeg_quality = 10; |   this->config_.jpeg_quality = 10; | ||||||
|   this->config_.fb_count = 1; |   this->config_.fb_count = 1; | ||||||
|  |   this->config_.grab_mode = CAMERA_GRAB_WHEN_EMPTY; | ||||||
|  |   this->config_.fb_location = CAMERA_FB_IN_PSRAM; | ||||||
|  |  | ||||||
|   global_esp32_camera = this; |   global_esp32_camera = this; | ||||||
| } | } | ||||||
| @@ -333,6 +335,12 @@ void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) { | |||||||
| void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) { | void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) { | ||||||
|   this->idle_update_interval_ = idle_update_interval; |   this->idle_update_interval_ = idle_update_interval; | ||||||
| } | } | ||||||
|  | /* set frame buffer parameters */ | ||||||
|  | void ESP32Camera::set_frame_buffer_mode(camera_grab_mode_t mode) { this->config_.grab_mode = mode; } | ||||||
|  | void ESP32Camera::set_frame_buffer_count(uint8_t fb_count) { | ||||||
|  |   this->config_.fb_count = fb_count; | ||||||
|  |   this->set_frame_buffer_mode(fb_count > 1 ? CAMERA_GRAB_LATEST : CAMERA_GRAB_WHEN_EMPTY); | ||||||
|  | } | ||||||
|  |  | ||||||
| /* ---------------- public API (specific) ---------------- */ | /* ---------------- public API (specific) ---------------- */ | ||||||
| void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) { | void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) { | ||||||
|   | |||||||
| @@ -145,6 +145,9 @@ class ESP32Camera : public Component, public EntityBase { | |||||||
|   /* -- framerates */ |   /* -- framerates */ | ||||||
|   void set_max_update_interval(uint32_t max_update_interval); |   void set_max_update_interval(uint32_t max_update_interval); | ||||||
|   void set_idle_update_interval(uint32_t idle_update_interval); |   void set_idle_update_interval(uint32_t idle_update_interval); | ||||||
|  |   /* -- frame buffer */ | ||||||
|  |   void set_frame_buffer_mode(camera_grab_mode_t mode); | ||||||
|  |   void set_frame_buffer_count(uint8_t fb_count); | ||||||
|  |  | ||||||
|   /* public API (derivated) */ |   /* public API (derivated) */ | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   | |||||||
| @@ -1,18 +1,23 @@ | |||||||
| import esphome.codegen as cg |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome import pins | from esphome import pins | ||||||
|  | import esphome.codegen as cg | ||||||
| from esphome.components import canbus | from esphome.components import canbus | ||||||
| from esphome.const import CONF_ID, CONF_RX_PIN, CONF_TX_PIN | from esphome.components.canbus import CONF_BIT_RATE, CanbusComponent, CanSpeed | ||||||
| from esphome.components.canbus import CanbusComponent, CanSpeed, CONF_BIT_RATE |  | ||||||
|  |  | ||||||
| from esphome.components.esp32 import get_esp32_variant | from esphome.components.esp32 import get_esp32_variant | ||||||
| from esphome.components.esp32.const import ( | from esphome.components.esp32.const import ( | ||||||
|     VARIANT_ESP32, |     VARIANT_ESP32, | ||||||
|     VARIANT_ESP32S2, |  | ||||||
|     VARIANT_ESP32S3, |  | ||||||
|     VARIANT_ESP32C3, |     VARIANT_ESP32C3, | ||||||
|     VARIANT_ESP32C6, |     VARIANT_ESP32C6, | ||||||
|     VARIANT_ESP32H2, |     VARIANT_ESP32H2, | ||||||
|  |     VARIANT_ESP32S2, | ||||||
|  |     VARIANT_ESP32S3, | ||||||
|  | ) | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_RX_PIN, | ||||||
|  |     CONF_RX_QUEUE_LEN, | ||||||
|  |     CONF_TX_PIN, | ||||||
|  |     CONF_TX_QUEUE_LEN, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CODEOWNERS = ["@Sympatron"] | CODEOWNERS = ["@Sympatron"] | ||||||
| @@ -77,6 +82,8 @@ CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend( | |||||||
|         cv.Optional(CONF_BIT_RATE, default="125KBPS"): validate_bit_rate, |         cv.Optional(CONF_BIT_RATE, default="125KBPS"): validate_bit_rate, | ||||||
|         cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number, |         cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number, | ||||||
|         cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number, |         cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number, | ||||||
|  |         cv.Optional(CONF_RX_QUEUE_LEN): cv.uint32_t, | ||||||
|  |         cv.Optional(CONF_TX_QUEUE_LEN): cv.uint32_t, | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -87,3 +94,7 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     cg.add(var.set_rx(config[CONF_RX_PIN])) |     cg.add(var.set_rx(config[CONF_RX_PIN])) | ||||||
|     cg.add(var.set_tx(config[CONF_TX_PIN])) |     cg.add(var.set_tx(config[CONF_TX_PIN])) | ||||||
|  |     if (rx_queue_len := config.get(CONF_RX_QUEUE_LEN)) is not None: | ||||||
|  |         cg.add(var.set_rx_queue_len(rx_queue_len)) | ||||||
|  |     if (tx_queue_len := config.get(CONF_TX_QUEUE_LEN)) is not None: | ||||||
|  |         cg.add(var.set_tx_queue_len(tx_queue_len)) | ||||||
|   | |||||||
| @@ -69,6 +69,13 @@ static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config | |||||||
| bool ESP32Can::setup_internal() { | bool ESP32Can::setup_internal() { | ||||||
|   twai_general_config_t g_config = |   twai_general_config_t g_config = | ||||||
|       TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL); |       TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL); | ||||||
|  |   if (this->tx_queue_len_.has_value()) { | ||||||
|  |     g_config.tx_queue_len = this->tx_queue_len_.value(); | ||||||
|  |   } | ||||||
|  |   if (this->rx_queue_len_.has_value()) { | ||||||
|  |     g_config.rx_queue_len = this->rx_queue_len_.value(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); |   twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); | ||||||
|   twai_timing_config_t t_config; |   twai_timing_config_t t_config; | ||||||
|  |  | ||||||
| @@ -111,6 +118,7 @@ canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) { | |||||||
|       .flags = flags, |       .flags = flags, | ||||||
|       .identifier = frame->can_id, |       .identifier = frame->can_id, | ||||||
|       .data_length_code = frame->can_data_length_code, |       .data_length_code = frame->can_data_length_code, | ||||||
|  |       .data = {},  // to suppress warning, data is initialized properly below | ||||||
|   }; |   }; | ||||||
|   if (!frame->remote_transmission_request) { |   if (!frame->remote_transmission_request) { | ||||||
|     memcpy(message.data, frame->data, frame->can_data_length_code); |     memcpy(message.data, frame->data, frame->can_data_length_code); | ||||||
|   | |||||||
| @@ -12,6 +12,8 @@ class ESP32Can : public canbus::Canbus { | |||||||
|  public: |  public: | ||||||
|   void set_rx(int rx) { rx_ = rx; } |   void set_rx(int rx) { rx_ = rx; } | ||||||
|   void set_tx(int tx) { tx_ = tx; } |   void set_tx(int tx) { tx_ = tx; } | ||||||
|  |   void set_tx_queue_len(uint32_t tx_queue_len) { this->tx_queue_len_ = tx_queue_len; } | ||||||
|  |   void set_rx_queue_len(uint32_t rx_queue_len) { this->rx_queue_len_ = rx_queue_len; } | ||||||
|   ESP32Can(){}; |   ESP32Can(){}; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
| @@ -21,6 +23,8 @@ class ESP32Can : public canbus::Canbus { | |||||||
|  |  | ||||||
|   int rx_{-1}; |   int rx_{-1}; | ||||||
|   int tx_{-1}; |   int tx_{-1}; | ||||||
|  |   optional<uint32_t> tx_queue_len_{}; | ||||||
|  |   optional<uint32_t> rx_queue_len_{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace esp32_can | }  // namespace esp32_can | ||||||
|   | |||||||
| @@ -38,7 +38,8 @@ void ESP32RMTLEDStripLightOutput::setup() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   ExternalRAMAllocator<rmt_item32_t> rmt_allocator(ExternalRAMAllocator<rmt_item32_t>::ALLOW_FAILURE); |   ExternalRAMAllocator<rmt_item32_t> rmt_allocator(ExternalRAMAllocator<rmt_item32_t>::ALLOW_FAILURE); | ||||||
|   this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8);  // 8 bits per byte, 1 rmt_item32_t per bit |   this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + | ||||||
|  |                                           1);  // 8 bits per byte, 1 rmt_item32_t per bit + 1 rmt_item32_t for reset | ||||||
|  |  | ||||||
|   rmt_config_t config; |   rmt_config_t config; | ||||||
|   memset(&config, 0, sizeof(config)); |   memset(&config, 0, sizeof(config)); | ||||||
| @@ -66,7 +67,7 @@ void ESP32RMTLEDStripLightOutput::setup() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, | void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, | ||||||
|                                                  uint32_t bit1_low) { |                                                  uint32_t bit1_low, uint32_t reset_time_high, uint32_t reset_time_low) { | ||||||
|   float ratio = (float) RMT_CLK_FREQ / RMT_CLK_DIV / 1e09f; |   float ratio = (float) RMT_CLK_FREQ / RMT_CLK_DIV / 1e09f; | ||||||
|  |  | ||||||
|   // 0-bit |   // 0-bit | ||||||
| @@ -79,6 +80,11 @@ void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bi | |||||||
|   this->bit1_.level0 = 1; |   this->bit1_.level0 = 1; | ||||||
|   this->bit1_.duration1 = (uint32_t) (ratio * bit1_low); |   this->bit1_.duration1 = (uint32_t) (ratio * bit1_low); | ||||||
|   this->bit1_.level1 = 0; |   this->bit1_.level1 = 0; | ||||||
|  |   // reset | ||||||
|  |   this->reset_.duration0 = (uint32_t) (ratio * reset_time_high); | ||||||
|  |   this->reset_.level0 = 1; | ||||||
|  |   this->reset_.duration1 = (uint32_t) (ratio * reset_time_low); | ||||||
|  |   this->reset_.level1 = 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | ||||||
| @@ -118,6 +124,12 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | |||||||
|     psrc++; |     psrc++; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (this->reset_.duration0 > 0 || this->reset_.duration1 > 0) { | ||||||
|  |     pdest->val = this->reset_.val; | ||||||
|  |     pdest++; | ||||||
|  |     len++; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   if (rmt_write_items(this->channel_, this->rmt_buf_, len, false) != ESP_OK) { |   if (rmt_write_items(this->channel_, this->rmt_buf_, len, false) != ESP_OK) { | ||||||
|     ESP_LOGE(TAG, "RMT TX error"); |     ESP_LOGE(TAG, "RMT TX error"); | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|   | |||||||
| @@ -49,7 +49,8 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | |||||||
|   /// Set a maximum refresh rate in µs as some lights do not like being updated too often. |   /// Set a maximum refresh rate in µs as some lights do not like being updated too often. | ||||||
|   void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; } |   void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; } | ||||||
|  |  | ||||||
|   void set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low); |   void set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low, | ||||||
|  |                       uint32_t reset_time_high, uint32_t reset_time_low); | ||||||
|  |  | ||||||
|   void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; } |   void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; } | ||||||
|   void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; } |   void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; } | ||||||
| @@ -75,7 +76,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | |||||||
|   bool is_rgbw_; |   bool is_rgbw_; | ||||||
|   bool is_wrgb_; |   bool is_wrgb_; | ||||||
|  |  | ||||||
|   rmt_item32_t bit0_, bit1_; |   rmt_item32_t bit0_, bit1_, reset_; | ||||||
|   RGBOrder rgb_order_; |   RGBOrder rgb_order_; | ||||||
|   rmt_channel_t channel_; |   rmt_channel_t channel_; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -43,13 +43,16 @@ class LEDStripTimings: | |||||||
|     bit0_low: int |     bit0_low: int | ||||||
|     bit1_high: int |     bit1_high: int | ||||||
|     bit1_low: int |     bit1_low: int | ||||||
|  |     reset_high: int | ||||||
|  |     reset_low: int | ||||||
|  |  | ||||||
|  |  | ||||||
| CHIPSETS = { | CHIPSETS = { | ||||||
|     "WS2812": LEDStripTimings(400, 1000, 1000, 400), |     "WS2811": LEDStripTimings(300, 1090, 1090, 320, 0, 300000), | ||||||
|     "SK6812": LEDStripTimings(300, 900, 600, 600), |     "WS2812": LEDStripTimings(400, 1000, 1000, 400, 0, 0), | ||||||
|     "APA106": LEDStripTimings(350, 1360, 1360, 350), |     "SK6812": LEDStripTimings(300, 900, 600, 600, 0, 0), | ||||||
|     "SM16703": LEDStripTimings(300, 900, 900, 300), |     "APA106": LEDStripTimings(350, 1360, 1360, 350, 0, 0), | ||||||
|  |     "SM16703": LEDStripTimings(300, 900, 900, 300, 0, 0), | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -58,6 +61,8 @@ CONF_BIT0_HIGH = "bit0_high" | |||||||
| CONF_BIT0_LOW = "bit0_low" | CONF_BIT0_LOW = "bit0_low" | ||||||
| CONF_BIT1_HIGH = "bit1_high" | CONF_BIT1_HIGH = "bit1_high" | ||||||
| CONF_BIT1_LOW = "bit1_low" | CONF_BIT1_LOW = "bit1_low" | ||||||
|  | CONF_RESET_HIGH = "reset_high" | ||||||
|  | CONF_RESET_LOW = "reset_low" | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | CONFIG_SCHEMA = cv.All( | ||||||
| @@ -88,6 +93,14 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                 CONF_BIT1_LOW, |                 CONF_BIT1_LOW, | ||||||
|                 "custom", |                 "custom", | ||||||
|             ): cv.positive_time_period_nanoseconds, |             ): cv.positive_time_period_nanoseconds, | ||||||
|  |             cv.Optional( | ||||||
|  |                 CONF_RESET_HIGH, | ||||||
|  |                 default="0 us", | ||||||
|  |             ): cv.positive_time_period_nanoseconds, | ||||||
|  |             cv.Optional( | ||||||
|  |                 CONF_RESET_LOW, | ||||||
|  |                 default="0 us", | ||||||
|  |             ): cv.positive_time_period_nanoseconds, | ||||||
|         } |         } | ||||||
|     ), |     ), | ||||||
|     cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH), |     cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH), | ||||||
| @@ -113,6 +126,8 @@ async def to_code(config): | |||||||
|                 chipset.bit0_low, |                 chipset.bit0_low, | ||||||
|                 chipset.bit1_high, |                 chipset.bit1_high, | ||||||
|                 chipset.bit1_low, |                 chipset.bit1_low, | ||||||
|  |                 chipset.reset_high, | ||||||
|  |                 chipset.reset_low, | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|     else: |     else: | ||||||
| @@ -122,6 +137,8 @@ async def to_code(config): | |||||||
|                 config[CONF_BIT0_LOW], |                 config[CONF_BIT0_LOW], | ||||||
|                 config[CONF_BIT1_HIGH], |                 config[CONF_BIT1_HIGH], | ||||||
|                 config[CONF_BIT1_LOW], |                 config[CONF_BIT1_LOW], | ||||||
|  |                 config[CONF_RESET_HIGH], | ||||||
|  |                 config[CONF_RESET_LOW], | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| #include "ota_esphome.h" | #include "ota_esphome.h" | ||||||
|  | #ifdef USE_OTA | ||||||
| #include "esphome/components/md5/md5.h" | #include "esphome/components/md5/md5.h" | ||||||
| #include "esphome/components/network/util.h" | #include "esphome/components/network/util.h" | ||||||
| #include "esphome/components/ota/ota_backend.h" | #include "esphome/components/ota/ota_backend.h" | ||||||
| @@ -410,3 +410,4 @@ float ESPHomeOTAComponent::get_setup_priority() const { return setup_priority::A | |||||||
| uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; } | uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; } | ||||||
| void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; } | void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; } | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  | #endif | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
|  | #ifdef USE_OTA | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
| #include "esphome/core/preferences.h" | #include "esphome/core/preferences.h" | ||||||
| #include "esphome/components/ota/ota_backend.h" | #include "esphome/components/ota/ota_backend.h" | ||||||
| @@ -41,3 +42,4 @@ class ESPHomeOTAComponent : public ota::OTAComponent { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  | #endif | ||||||
|   | |||||||
| @@ -1,43 +1,35 @@ | |||||||
|  | import functools | ||||||
| import hashlib | import hashlib | ||||||
| import logging | import logging | ||||||
|  |  | ||||||
| import functools |  | ||||||
| from pathlib import Path |  | ||||||
| import os | import os | ||||||
|  | from pathlib import Path | ||||||
| import re | import re | ||||||
|  |  | ||||||
| from packaging import version | from packaging import version | ||||||
| import requests | import requests | ||||||
|  |  | ||||||
| from esphome import core | from esphome import core, external_files | ||||||
| from esphome import external_files |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.helpers import ( | import esphome.config_validation as cv | ||||||
|     copy_file_if_changed, |  | ||||||
|     cpp_string_escape, |  | ||||||
| ) |  | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_FAMILY, |     CONF_FAMILY, | ||||||
|     CONF_FILE, |     CONF_FILE, | ||||||
|     CONF_GLYPHS, |     CONF_GLYPHS, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|  |     CONF_PATH, | ||||||
|     CONF_RAW_DATA_ID, |     CONF_RAW_DATA_ID, | ||||||
|     CONF_TYPE, |  | ||||||
|     CONF_REFRESH, |     CONF_REFRESH, | ||||||
|     CONF_SIZE, |     CONF_SIZE, | ||||||
|     CONF_PATH, |     CONF_TYPE, | ||||||
|     CONF_WEIGHT, |  | ||||||
|     CONF_URL, |     CONF_URL, | ||||||
|  |     CONF_WEIGHT, | ||||||
| ) | ) | ||||||
| from esphome.core import ( | from esphome.core import CORE, HexInt | ||||||
|     CORE, | from esphome.helpers import copy_file_if_changed, cpp_string_escape | ||||||
|     HexInt, |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| DOMAIN = "font" | DOMAIN = "font" | ||||||
| DEPENDENCIES = ["display"] |  | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core", "@clydebarrow"] | CODEOWNERS = ["@esphome/core", "@clydebarrow"] | ||||||
| @@ -400,10 +392,7 @@ class EFont: | |||||||
|  |  | ||||||
|  |  | ||||||
| def convert_bitmap_to_pillow_font(filepath): | def convert_bitmap_to_pillow_font(filepath): | ||||||
|     from PIL import ( |     from PIL import BdfFontFile, PcfFontFile | ||||||
|         PcfFontFile, |  | ||||||
|         BdfFontFile, |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     local_bitmap_font_file = external_files.compute_local_file_dir( |     local_bitmap_font_file = external_files.compute_local_file_dir( | ||||||
|         DOMAIN, |         DOMAIN, | ||||||
|   | |||||||
| @@ -1,9 +1,8 @@ | |||||||
| #include "font.h" | #include "font.h" | ||||||
|  |  | ||||||
|  | #include "esphome/core/color.h" | ||||||
| #include "esphome/core/hal.h" | #include "esphome/core/hal.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| #include "esphome/core/color.h" |  | ||||||
| #include "esphome/components/display/display_buffer.h" |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace font { | namespace font { | ||||||
| @@ -68,6 +67,7 @@ int Font::match_next_glyph(const uint8_t *str, int *match_length) { | |||||||
|     return -1; |     return -1; | ||||||
|   return lo; |   return lo; | ||||||
| } | } | ||||||
|  | #ifdef USE_DISPLAY | ||||||
| void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) { | void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) { | ||||||
|   *baseline = this->baseline_; |   *baseline = this->baseline_; | ||||||
|   *height = this->height_; |   *height = this->height_; | ||||||
| @@ -164,6 +164,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo | |||||||
|     i += match_length; |     i += match_length; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| }  // namespace font | }  // namespace font | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -1,8 +1,11 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/core/datatypes.h" |  | ||||||
| #include "esphome/core/color.h" | #include "esphome/core/color.h" | ||||||
| #include "esphome/components/display/display_buffer.h" | #include "esphome/core/datatypes.h" | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #ifdef USE_DISPLAY | ||||||
|  | #include "esphome/components/display/display.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace font { | namespace font { | ||||||
| @@ -38,7 +41,11 @@ class Glyph { | |||||||
|   const GlyphData *glyph_data_; |   const GlyphData *glyph_data_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class Font : public display::BaseFont { | class Font | ||||||
|  | #ifdef USE_DISPLAY | ||||||
|  |     : public display::BaseFont | ||||||
|  | #endif | ||||||
|  | { | ||||||
|  public: |  public: | ||||||
|   /** Construct the font with the given glyphs. |   /** Construct the font with the given glyphs. | ||||||
|    * |    * | ||||||
| @@ -50,9 +57,11 @@ class Font : public display::BaseFont { | |||||||
|  |  | ||||||
|   int match_next_glyph(const uint8_t *str, int *match_length); |   int match_next_glyph(const uint8_t *str, int *match_length); | ||||||
|  |  | ||||||
|  | #ifdef USE_DISPLAY | ||||||
|   void print(int x_start, int y_start, display::Display *display, Color color, const char *text, |   void print(int x_start, int y_start, display::Display *display, Color color, const char *text, | ||||||
|              Color background) override; |              Color background) override; | ||||||
|   void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override; |   void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override; | ||||||
|  | #endif | ||||||
|   inline int get_baseline() { return this->baseline_; } |   inline int get_baseline() { return this->baseline_; } | ||||||
|   inline int get_height() { return this->height_; } |   inline int get_height() { return this->height_; } | ||||||
|   inline int get_bpp() { return this->bpp_; } |   inline int get_bpp() { return this->bpp_; } | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome.components import climate_ir | from esphome.components import climate_ir | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_ID, CONF_MODEL | from esphome.const import CONF_ID, CONF_MODEL | ||||||
|  |  | ||||||
| CODEOWNERS = ["@orestismers"] | CODEOWNERS = ["@orestismers"] | ||||||
| @@ -17,6 +17,7 @@ MODELS = { | |||||||
|     "yaa": Model.GREE_YAA, |     "yaa": Model.GREE_YAA, | ||||||
|     "yac": Model.GREE_YAC, |     "yac": Model.GREE_YAC, | ||||||
|     "yac1fb9": Model.GREE_YAC1FB9, |     "yac1fb9": Model.GREE_YAC1FB9, | ||||||
|  |     "yx1ff": Model.GREE_YX1FF, | ||||||
| } | } | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( | CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( | ||||||
|   | |||||||
| @@ -6,7 +6,15 @@ namespace gree { | |||||||
|  |  | ||||||
| static const char *const TAG = "gree.climate"; | static const char *const TAG = "gree.climate"; | ||||||
|  |  | ||||||
| void GreeClimate::set_model(Model model) { this->model_ = model; } | void GreeClimate::set_model(Model model) { | ||||||
|  |   if (model == GREE_YX1FF) { | ||||||
|  |     this->fan_modes_.insert(climate::CLIMATE_FAN_QUIET);   // YX1FF 4 speed | ||||||
|  |     this->presets_.insert(climate::CLIMATE_PRESET_NONE);   // YX1FF sleep mode | ||||||
|  |     this->presets_.insert(climate::CLIMATE_PRESET_SLEEP);  // YX1FF sleep mode | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->model_ = model; | ||||||
|  | } | ||||||
|  |  | ||||||
| void GreeClimate::transmit_state() { | void GreeClimate::transmit_state() { | ||||||
|   uint8_t remote_state[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00}; |   uint8_t remote_state[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00}; | ||||||
| @@ -14,7 +22,7 @@ void GreeClimate::transmit_state() { | |||||||
|   remote_state[0] = this->fan_speed_() | this->operation_mode_(); |   remote_state[0] = this->fan_speed_() | this->operation_mode_(); | ||||||
|   remote_state[1] = this->temperature_(); |   remote_state[1] = this->temperature_(); | ||||||
|  |  | ||||||
|   if (this->model_ == GREE_YAN) { |   if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF) { | ||||||
|     remote_state[2] = 0x60; |     remote_state[2] = 0x60; | ||||||
|     remote_state[3] = 0x50; |     remote_state[3] = 0x50; | ||||||
|     remote_state[4] = this->vertical_swing_(); |     remote_state[4] = this->vertical_swing_(); | ||||||
| @@ -36,8 +44,18 @@ void GreeClimate::transmit_state() { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (this->model_ == GREE_YX1FF) { | ||||||
|  |     if (this->fan_speed_() == GREE_FAN_TURBO) { | ||||||
|  |       remote_state[2] |= GREE_FAN_TURBO_BIT; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (this->preset_() == GREE_PRESET_SLEEP) { | ||||||
|  |       remote_state[0] |= GREE_PRESET_SLEEP_BIT; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // Calculate the checksum |   // Calculate the checksum | ||||||
|   if (this->model_ == GREE_YAN) { |   if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF) { | ||||||
|     remote_state[7] = ((remote_state[0] << 4) + (remote_state[1] << 4) + 0xC0); |     remote_state[7] = ((remote_state[0] << 4) + (remote_state[1] << 4) + 0xC0); | ||||||
|   } else { |   } else { | ||||||
|     remote_state[7] = |     remote_state[7] = | ||||||
| @@ -124,6 +142,23 @@ uint8_t GreeClimate::operation_mode_() { | |||||||
| } | } | ||||||
|  |  | ||||||
| uint8_t GreeClimate::fan_speed_() { | uint8_t GreeClimate::fan_speed_() { | ||||||
|  |   // YX1FF has 4 fan speeds -- we treat low as quiet and turbo as high | ||||||
|  |   if (this->model_ == GREE_YX1FF) { | ||||||
|  |     switch (this->fan_mode.value()) { | ||||||
|  |       case climate::CLIMATE_FAN_QUIET: | ||||||
|  |         return GREE_FAN_1; | ||||||
|  |       case climate::CLIMATE_FAN_LOW: | ||||||
|  |         return GREE_FAN_2; | ||||||
|  |       case climate::CLIMATE_FAN_MEDIUM: | ||||||
|  |         return GREE_FAN_3; | ||||||
|  |       case climate::CLIMATE_FAN_HIGH: | ||||||
|  |         return GREE_FAN_TURBO; | ||||||
|  |       case climate::CLIMATE_FAN_AUTO: | ||||||
|  |       default: | ||||||
|  |         return GREE_FAN_AUTO; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   switch (this->fan_mode.value()) { |   switch (this->fan_mode.value()) { | ||||||
|     case climate::CLIMATE_FAN_LOW: |     case climate::CLIMATE_FAN_LOW: | ||||||
|       return GREE_FAN_1; |       return GREE_FAN_1; | ||||||
| @@ -161,5 +196,21 @@ uint8_t GreeClimate::temperature_() { | |||||||
|   return (uint8_t) roundf(clamp<float>(this->target_temperature, GREE_TEMP_MIN, GREE_TEMP_MAX)); |   return (uint8_t) roundf(clamp<float>(this->target_temperature, GREE_TEMP_MIN, GREE_TEMP_MAX)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | uint8_t GreeClimate::preset_() { | ||||||
|  |   // YX1FF has sleep preset | ||||||
|  |   if (this->model_ == GREE_YX1FF) { | ||||||
|  |     switch (this->preset.value()) { | ||||||
|  |       case climate::CLIMATE_PRESET_NONE: | ||||||
|  |         return GREE_PRESET_NONE; | ||||||
|  |       case climate::CLIMATE_PRESET_SLEEP: | ||||||
|  |         return GREE_PRESET_SLEEP; | ||||||
|  |       default: | ||||||
|  |         return GREE_PRESET_NONE; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return GREE_PRESET_NONE; | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace gree | }  // namespace gree | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -25,7 +25,6 @@ const uint8_t GREE_FAN_AUTO = 0x00; | |||||||
| const uint8_t GREE_FAN_1 = 0x10; | const uint8_t GREE_FAN_1 = 0x10; | ||||||
| const uint8_t GREE_FAN_2 = 0x20; | const uint8_t GREE_FAN_2 = 0x20; | ||||||
| const uint8_t GREE_FAN_3 = 0x30; | const uint8_t GREE_FAN_3 = 0x30; | ||||||
| const uint8_t GREE_FAN_TURBO = 0x80; |  | ||||||
|  |  | ||||||
| // IR Transmission | // IR Transmission | ||||||
| const uint32_t GREE_IR_FREQUENCY = 38000; | const uint32_t GREE_IR_FREQUENCY = 38000; | ||||||
| @@ -70,8 +69,16 @@ const uint8_t GREE_HDIR_MIDDLE = 0x04; | |||||||
| const uint8_t GREE_HDIR_MRIGHT = 0x05; | const uint8_t GREE_HDIR_MRIGHT = 0x05; | ||||||
| const uint8_t GREE_HDIR_RIGHT = 0x06; | const uint8_t GREE_HDIR_RIGHT = 0x06; | ||||||
|  |  | ||||||
|  | // Only available on YX1FF | ||||||
|  | // Turbo (high) fan mode + sleep preset mode | ||||||
|  | const uint8_t GREE_FAN_TURBO = 0x80; | ||||||
|  | const uint8_t GREE_FAN_TURBO_BIT = 0x10; | ||||||
|  | const uint8_t GREE_PRESET_NONE = 0x00; | ||||||
|  | const uint8_t GREE_PRESET_SLEEP = 0x01; | ||||||
|  | const uint8_t GREE_PRESET_SLEEP_BIT = 0x80; | ||||||
|  |  | ||||||
| // Model codes | // Model codes | ||||||
| enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9 }; | enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9, GREE_YX1FF }; | ||||||
|  |  | ||||||
| class GreeClimate : public climate_ir::ClimateIR { | class GreeClimate : public climate_ir::ClimateIR { | ||||||
|  public: |  public: | ||||||
| @@ -93,6 +100,7 @@ class GreeClimate : public climate_ir::ClimateIR { | |||||||
|   uint8_t horizontal_swing_(); |   uint8_t horizontal_swing_(); | ||||||
|   uint8_t vertical_swing_(); |   uint8_t vertical_swing_(); | ||||||
|   uint8_t temperature_(); |   uint8_t temperature_(); | ||||||
|  |   uint8_t preset_(); | ||||||
|  |  | ||||||
|   Model model_{}; |   Model model_{}; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,11 +1,10 @@ | |||||||
| import esphome.codegen as cg |  | ||||||
| import esphome.config_validation as cv |  | ||||||
|  |  | ||||||
| from esphome import pins | from esphome import pins | ||||||
|  | import esphome.codegen as cg | ||||||
| from esphome.components import i2c, touchscreen | from esphome.components import i2c, touchscreen | ||||||
| from esphome.const import CONF_INTERRUPT_PIN, CONF_ID | import esphome.config_validation as cv | ||||||
| from .. import gt911_ns | from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN | ||||||
|  |  | ||||||
|  | from .. import gt911_ns | ||||||
|  |  | ||||||
| GT911ButtonListener = gt911_ns.class_("GT911ButtonListener") | GT911ButtonListener = gt911_ns.class_("GT911ButtonListener") | ||||||
| GT911Touchscreen = gt911_ns.class_( | GT911Touchscreen = gt911_ns.class_( | ||||||
| @@ -18,6 +17,7 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( | |||||||
|     { |     { | ||||||
|         cv.GenerateID(): cv.declare_id(GT911Touchscreen), |         cv.GenerateID(): cv.declare_id(GT911Touchscreen), | ||||||
|         cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, |         cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, | ||||||
|  |         cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, | ||||||
|     } |     } | ||||||
| ).extend(i2c.i2c_device_schema(0x5D)) | ).extend(i2c.i2c_device_schema(0x5D)) | ||||||
|  |  | ||||||
| @@ -29,3 +29,5 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     if interrupt_pin := config.get(CONF_INTERRUPT_PIN): |     if interrupt_pin := config.get(CONF_INTERRUPT_PIN): | ||||||
|         cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin))) |         cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin))) | ||||||
|  |     if reset_pin := config.get(CONF_RESET_PIN): | ||||||
|  |         cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin))) | ||||||
|   | |||||||
| @@ -26,6 +26,23 @@ static const size_t MAX_BUTTONS = 4;  // max number of buttons scanned | |||||||
| void GT911Touchscreen::setup() { | void GT911Touchscreen::setup() { | ||||||
|   i2c::ErrorCode err; |   i2c::ErrorCode err; | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up GT911 Touchscreen..."); |   ESP_LOGCONFIG(TAG, "Setting up GT911 Touchscreen..."); | ||||||
|  |   if (this->reset_pin_ != nullptr) { | ||||||
|  |     this->reset_pin_->setup(); | ||||||
|  |     this->reset_pin_->digital_write(false); | ||||||
|  |     if (this->interrupt_pin_ != nullptr) { | ||||||
|  |       // The interrupt pin is used as an input during reset to select the I2C address. | ||||||
|  |       this->interrupt_pin_->pin_mode(gpio::FLAG_OUTPUT); | ||||||
|  |       this->interrupt_pin_->setup(); | ||||||
|  |       this->interrupt_pin_->digital_write(false); | ||||||
|  |     } | ||||||
|  |     delay(2); | ||||||
|  |     this->reset_pin_->digital_write(true); | ||||||
|  |     delay(50);  // NOLINT | ||||||
|  |     if (this->interrupt_pin_ != nullptr) { | ||||||
|  |       this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT); | ||||||
|  |       this->interrupt_pin_->setup(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // check the configuration of the int line. |   // check the configuration of the int line. | ||||||
|   uint8_t data[4]; |   uint8_t data[4]; | ||||||
|   | |||||||
| @@ -19,12 +19,14 @@ class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice | |||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|  |  | ||||||
|   void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } |   void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } | ||||||
|  |   void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } | ||||||
|   void register_button_listener(GT911ButtonListener *listener) { this->button_listeners_.push_back(listener); } |   void register_button_listener(GT911ButtonListener *listener) { this->button_listeners_.push_back(listener); } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void update_touches() override; |   void update_touches() override; | ||||||
|  |  | ||||||
|   InternalGPIOPin *interrupt_pin_{}; |   InternalGPIOPin *interrupt_pin_{}; | ||||||
|  |   GPIOPin *reset_pin_{}; | ||||||
|   std::vector<GT911ButtonListener *> button_listeners_; |   std::vector<GT911ButtonListener *> button_listeners_; | ||||||
|   uint8_t button_state_{0xFF};  // last button state. Initial FF guarantees first update. |   uint8_t button_state_{0xFF};  // last button state. Initial FF guarantees first update. | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								esphome/components/hmac_md5/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								esphome/components/hmac_md5/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | AUTO_LOAD = ["md5"] | ||||||
|  | CODEOWNERS = ["@dwmw2"] | ||||||
							
								
								
									
										56
									
								
								esphome/components/hmac_md5/hmac_md5.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								esphome/components/hmac_md5/hmac_md5.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | #include <cstdio> | ||||||
|  | #include <cstring> | ||||||
|  | #include "hmac_md5.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace hmac_md5 { | ||||||
|  | void HmacMD5::init(const uint8_t *key, size_t len) { | ||||||
|  |   uint8_t ipad[64], opad[64]; | ||||||
|  |  | ||||||
|  |   memset(ipad, 0, sizeof(ipad)); | ||||||
|  |   if (len > 64) { | ||||||
|  |     md5::MD5Digest keymd5; | ||||||
|  |     keymd5.init(); | ||||||
|  |     keymd5.add(key, len); | ||||||
|  |     keymd5.calculate(); | ||||||
|  |     keymd5.get_bytes(ipad); | ||||||
|  |   } else { | ||||||
|  |     memcpy(ipad, key, len); | ||||||
|  |   } | ||||||
|  |   memcpy(opad, ipad, sizeof(opad)); | ||||||
|  |  | ||||||
|  |   for (int i = 0; i < 64; i++) { | ||||||
|  |     ipad[i] ^= 0x36; | ||||||
|  |     opad[i] ^= 0x5c; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->ihash_.init(); | ||||||
|  |   this->ihash_.add(ipad, sizeof(ipad)); | ||||||
|  |  | ||||||
|  |   this->ohash_.init(); | ||||||
|  |   this->ohash_.add(opad, sizeof(opad)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HmacMD5::add(const uint8_t *data, size_t len) { this->ihash_.add(data, len); } | ||||||
|  |  | ||||||
|  | void HmacMD5::calculate() { | ||||||
|  |   uint8_t ibytes[16]; | ||||||
|  |  | ||||||
|  |   this->ihash_.calculate(); | ||||||
|  |   this->ihash_.get_bytes(ibytes); | ||||||
|  |  | ||||||
|  |   this->ohash_.add(ibytes, sizeof(ibytes)); | ||||||
|  |   this->ohash_.calculate(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HmacMD5::get_bytes(uint8_t *output) { this->ohash_.get_bytes(output); } | ||||||
|  |  | ||||||
|  | void HmacMD5::get_hex(char *output) { this->ohash_.get_hex(output); } | ||||||
|  |  | ||||||
|  | bool HmacMD5::equals_bytes(const uint8_t *expected) { return this->ohash_.equals_bytes(expected); } | ||||||
|  |  | ||||||
|  | bool HmacMD5::equals_hex(const char *expected) { return this->ohash_.equals_hex(expected); } | ||||||
|  |  | ||||||
|  | }  // namespace hmac_md5 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										48
									
								
								esphome/components/hmac_md5/hmac_md5.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								esphome/components/hmac_md5/hmac_md5.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #include "esphome/components/md5/md5.h" | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace hmac_md5 { | ||||||
|  |  | ||||||
|  | class HmacMD5 { | ||||||
|  |  public: | ||||||
|  |   HmacMD5() = default; | ||||||
|  |   ~HmacMD5() = default; | ||||||
|  |  | ||||||
|  |   /// Initialize a new MD5 digest computation. | ||||||
|  |   void init(const uint8_t *key, size_t len); | ||||||
|  |   void init(const char *key, size_t len) { this->init((const uint8_t *) key, len); } | ||||||
|  |   void init(const std::string &key) { this->init(key.c_str(), key.length()); } | ||||||
|  |  | ||||||
|  |   /// Add bytes of data for the digest. | ||||||
|  |   void add(const uint8_t *data, size_t len); | ||||||
|  |   void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); } | ||||||
|  |  | ||||||
|  |   /// Compute the digest, based on the provided data. | ||||||
|  |   void calculate(); | ||||||
|  |  | ||||||
|  |   /// Retrieve the HMAC-MD5 digest as bytes. | ||||||
|  |   /// The output must be able to hold 16 bytes or more. | ||||||
|  |   void get_bytes(uint8_t *output); | ||||||
|  |  | ||||||
|  |   /// Retrieve the HMAC-MD5 digest as hex characters. | ||||||
|  |   /// The output must be able to hold 32 bytes or more. | ||||||
|  |   void get_hex(char *output); | ||||||
|  |  | ||||||
|  |   /// Compare the digest against a provided byte-encoded digest (16 bytes). | ||||||
|  |   bool equals_bytes(const uint8_t *expected); | ||||||
|  |  | ||||||
|  |   /// Compare the digest against a provided hex-encoded digest (32 bytes). | ||||||
|  |   bool equals_hex(const char *expected); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   md5::MD5Digest ihash_; | ||||||
|  |   md5::MD5Digest ohash_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace hmac_md5 | ||||||
|  | }  // namespace esphome | ||||||
| @@ -5,6 +5,19 @@ from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_INTERNAL | |||||||
| CODEOWNERS = ["@OttoWinter", "@esphome/core"] | CODEOWNERS = ["@OttoWinter", "@esphome/core"] | ||||||
| homeassistant_ns = cg.esphome_ns.namespace("homeassistant") | homeassistant_ns = cg.esphome_ns.namespace("homeassistant") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_entity_domain(platform, supported_domains): | ||||||
|  |     def validator(config): | ||||||
|  |         domain = config[CONF_ENTITY_ID].split(".", 1)[0] | ||||||
|  |         if domain not in supported_domains: | ||||||
|  |             raise cv.Invalid( | ||||||
|  |                 f"Entity ID {config[CONF_ENTITY_ID]} is not supported by the {platform} platform." | ||||||
|  |             ) | ||||||
|  |         return config | ||||||
|  |  | ||||||
|  |     return validator | ||||||
|  |  | ||||||
|  |  | ||||||
| HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema( | HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.Required(CONF_ENTITY_ID): cv.entity_id, |         cv.Required(CONF_ENTITY_ID): cv.entity_id, | ||||||
|   | |||||||
| @@ -7,19 +7,32 @@ from .. import ( | |||||||
|     HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA, |     HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA, | ||||||
|     homeassistant_ns, |     homeassistant_ns, | ||||||
|     setup_home_assistant_entity, |     setup_home_assistant_entity, | ||||||
|  |     validate_entity_domain, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CODEOWNERS = ["@Links2004"] | CODEOWNERS = ["@Links2004"] | ||||||
| DEPENDENCIES = ["api"] | DEPENDENCIES = ["api"] | ||||||
|  |  | ||||||
|  | SUPPORTED_DOMAINS = [ | ||||||
|  |     "automation", | ||||||
|  |     "fan", | ||||||
|  |     "humidifier", | ||||||
|  |     "input_boolean", | ||||||
|  |     "light", | ||||||
|  |     "remote", | ||||||
|  |     "siren", | ||||||
|  |     "switch", | ||||||
|  | ] | ||||||
|  |  | ||||||
| HomeassistantSwitch = homeassistant_ns.class_( | HomeassistantSwitch = homeassistant_ns.class_( | ||||||
|     "HomeassistantSwitch", switch.Switch, cg.Component |     "HomeassistantSwitch", switch.Switch, cg.Component | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( | CONFIG_SCHEMA = cv.All( | ||||||
|     switch.switch_schema(HomeassistantSwitch) |     switch.switch_schema(HomeassistantSwitch) | ||||||
|     .extend(cv.COMPONENT_SCHEMA) |  | ||||||
|     .extend(HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA) |     .extend(HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA), | ||||||
|  |     validate_entity_domain("switch", SUPPORTED_DOMAINS), | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -42,9 +42,9 @@ void HomeassistantSwitch::write_state(bool state) { | |||||||
|  |  | ||||||
|   api::HomeassistantServiceResponse resp; |   api::HomeassistantServiceResponse resp; | ||||||
|   if (state) { |   if (state) { | ||||||
|     resp.service = "switch.turn_on"; |     resp.service = "homeassistant.turn_on"; | ||||||
|   } else { |   } else { | ||||||
|     resp.service = "switch.turn_off"; |     resp.service = "homeassistant.turn_off"; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   api::HomeassistantServiceMap entity_id_kv; |   api::HomeassistantServiceMap entity_id_kv; | ||||||
|   | |||||||
| @@ -1,16 +1,16 @@ | |||||||
| import esphome.config_validation as cv |  | ||||||
| import esphome.final_validate as fv |  | ||||||
| import esphome.codegen as cg |  | ||||||
|  |  | ||||||
| from esphome import pins | from esphome import pins | ||||||
| from esphome.const import CONF_ID | import esphome.codegen as cg | ||||||
| from esphome.components.esp32 import get_esp32_variant | from esphome.components.esp32 import get_esp32_variant | ||||||
| from esphome.components.esp32.const import ( | from esphome.components.esp32.const import ( | ||||||
|     VARIANT_ESP32, |     VARIANT_ESP32, | ||||||
|  |     VARIANT_ESP32C3, | ||||||
|     VARIANT_ESP32S2, |     VARIANT_ESP32S2, | ||||||
|     VARIANT_ESP32S3, |     VARIANT_ESP32S3, | ||||||
|     VARIANT_ESP32C3, |  | ||||||
| ) | ) | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE | ||||||
|  | from esphome.cpp_generator import MockObjClass | ||||||
|  | import esphome.final_validate as fv | ||||||
|  |  | ||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
| DEPENDENCIES = ["esp32"] | DEPENDENCIES = ["esp32"] | ||||||
| @@ -25,16 +25,26 @@ CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin" | |||||||
| CONF_I2S_AUDIO = "i2s_audio" | CONF_I2S_AUDIO = "i2s_audio" | ||||||
| CONF_I2S_AUDIO_ID = "i2s_audio_id" | CONF_I2S_AUDIO_ID = "i2s_audio_id" | ||||||
|  |  | ||||||
|  | CONF_BITS_PER_SAMPLE = "bits_per_sample" | ||||||
| CONF_I2S_MODE = "i2s_mode" | CONF_I2S_MODE = "i2s_mode" | ||||||
| CONF_PRIMARY = "primary" | CONF_PRIMARY = "primary" | ||||||
| CONF_SECONDARY = "secondary" | CONF_SECONDARY = "secondary" | ||||||
|  |  | ||||||
|  | CONF_USE_APLL = "use_apll" | ||||||
|  | CONF_BITS_PER_SAMPLE = "bits_per_sample" | ||||||
|  | CONF_BITS_PER_CHANNEL = "bits_per_channel" | ||||||
|  | CONF_MONO = "mono" | ||||||
|  | CONF_LEFT = "left" | ||||||
|  | CONF_RIGHT = "right" | ||||||
|  | CONF_STEREO = "stereo" | ||||||
|  |  | ||||||
| i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio") | i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio") | ||||||
| I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component) | I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component) | ||||||
| I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", cg.Parented.template(I2SAudioComponent)) | I2SAudioBase = i2s_audio_ns.class_( | ||||||
| I2SAudioOut = i2s_audio_ns.class_( |     "I2SAudioBase", cg.Parented.template(I2SAudioComponent) | ||||||
|     "I2SAudioOut", cg.Parented.template(I2SAudioComponent) |  | ||||||
| ) | ) | ||||||
|  | I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", I2SAudioBase) | ||||||
|  | I2SAudioOut = i2s_audio_ns.class_("I2SAudioOut", I2SAudioBase) | ||||||
|  |  | ||||||
| i2s_mode_t = cg.global_ns.enum("i2s_mode_t") | i2s_mode_t = cg.global_ns.enum("i2s_mode_t") | ||||||
| I2S_MODE_OPTIONS = { | I2S_MODE_OPTIONS = { | ||||||
| @@ -50,6 +60,75 @@ I2S_PORTS = { | |||||||
|     VARIANT_ESP32C3: 1, |     VARIANT_ESP32C3: 1, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t") | ||||||
|  | I2S_CHANNELS = { | ||||||
|  |     CONF_MONO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ALL_LEFT, | ||||||
|  |     CONF_LEFT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT, | ||||||
|  |     CONF_RIGHT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT, | ||||||
|  |     CONF_STEREO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_RIGHT_LEFT, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | i2s_bits_per_sample_t = cg.global_ns.enum("i2s_bits_per_sample_t") | ||||||
|  | I2S_BITS_PER_SAMPLE = { | ||||||
|  |     8: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_8BIT, | ||||||
|  |     16: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_16BIT, | ||||||
|  |     24: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_24BIT, | ||||||
|  |     32: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_32BIT, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | i2s_bits_per_chan_t = cg.global_ns.enum("i2s_bits_per_chan_t") | ||||||
|  | I2S_BITS_PER_CHANNEL = { | ||||||
|  |     "default": i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_DEFAULT, | ||||||
|  |     8: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_8BIT, | ||||||
|  |     16: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_16BIT, | ||||||
|  |     24: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_24BIT, | ||||||
|  |     32: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_32BIT, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | _validate_bits = cv.float_with_unit("bits", "bit") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def i2s_audio_component_schema( | ||||||
|  |     class_: MockObjClass, | ||||||
|  |     *, | ||||||
|  |     default_sample_rate: int, | ||||||
|  |     default_channel: str, | ||||||
|  |     default_bits_per_sample: str, | ||||||
|  | ): | ||||||
|  |     return cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(class_), | ||||||
|  |             cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), | ||||||
|  |             cv.Optional(CONF_CHANNEL, default=default_channel): cv.enum(I2S_CHANNELS), | ||||||
|  |             cv.Optional(CONF_SAMPLE_RATE, default=default_sample_rate): cv.int_range( | ||||||
|  |                 min=1 | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_BITS_PER_SAMPLE, default=default_bits_per_sample): cv.All( | ||||||
|  |                 _validate_bits, cv.enum(I2S_BITS_PER_SAMPLE) | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.enum( | ||||||
|  |                 I2S_MODE_OPTIONS, lower=True | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_USE_APLL, default=False): cv.boolean, | ||||||
|  |             cv.Optional(CONF_BITS_PER_CHANNEL, default="default"): cv.All( | ||||||
|  |                 cv.Any(cv.float_with_unit("bits", "bit"), "default"), | ||||||
|  |                 cv.enum(I2S_BITS_PER_CHANNEL), | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def register_i2s_audio_component(var, config): | ||||||
|  |     await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_i2s_mode(config[CONF_I2S_MODE])) | ||||||
|  |     cg.add(var.set_channel(config[CONF_CHANNEL])) | ||||||
|  |     cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) | ||||||
|  |     cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) | ||||||
|  |     cg.add(var.set_bits_per_channel(config[CONF_BITS_PER_CHANNEL])) | ||||||
|  |     cg.add(var.set_use_apll(config[CONF_USE_APLL])) | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema( | CONFIG_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.GenerateID(): cv.declare_id(I2SAudioComponent), |         cv.GenerateID(): cv.declare_id(I2SAudioComponent), | ||||||
|   | |||||||
| @@ -11,9 +11,27 @@ namespace i2s_audio { | |||||||
|  |  | ||||||
| class I2SAudioComponent; | class I2SAudioComponent; | ||||||
|  |  | ||||||
| class I2SAudioIn : public Parented<I2SAudioComponent> {}; | class I2SAudioBase : public Parented<I2SAudioComponent> { | ||||||
|  |  public: | ||||||
|  |   void set_i2s_mode(i2s_mode_t mode) { this->i2s_mode_ = mode; } | ||||||
|  |   void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; } | ||||||
|  |   void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; } | ||||||
|  |   void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; } | ||||||
|  |   void set_bits_per_channel(i2s_bits_per_chan_t bits_per_channel) { this->bits_per_channel_ = bits_per_channel; } | ||||||
|  |   void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; } | ||||||
|  |  | ||||||
| class I2SAudioOut : public Parented<I2SAudioComponent> {}; |  protected: | ||||||
|  |   i2s_mode_t i2s_mode_{}; | ||||||
|  |   i2s_channel_fmt_t channel_; | ||||||
|  |   uint32_t sample_rate_; | ||||||
|  |   i2s_bits_per_sample_t bits_per_sample_; | ||||||
|  |   i2s_bits_per_chan_t bits_per_channel_; | ||||||
|  |   bool use_apll_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class I2SAudioIn : public I2SAudioBase {}; | ||||||
|  |  | ||||||
|  | class I2SAudioOut : public I2SAudioBase {}; | ||||||
|  |  | ||||||
| class I2SAudioComponent : public Component { | class I2SAudioComponent : public Component { | ||||||
|  public: |  public: | ||||||
|   | |||||||
| @@ -12,6 +12,10 @@ from .. import ( | |||||||
|     I2SAudioOut, |     I2SAudioOut, | ||||||
|     CONF_I2S_AUDIO_ID, |     CONF_I2S_AUDIO_ID, | ||||||
|     CONF_I2S_DOUT_PIN, |     CONF_I2S_DOUT_PIN, | ||||||
|  |     CONF_LEFT, | ||||||
|  |     CONF_RIGHT, | ||||||
|  |     CONF_MONO, | ||||||
|  |     CONF_STEREO, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
| @@ -30,12 +34,12 @@ CONF_DAC_TYPE = "dac_type" | |||||||
| CONF_I2S_COMM_FMT = "i2s_comm_fmt" | CONF_I2S_COMM_FMT = "i2s_comm_fmt" | ||||||
|  |  | ||||||
| INTERNAL_DAC_OPTIONS = { | INTERNAL_DAC_OPTIONS = { | ||||||
|     "left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, |     CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, | ||||||
|     "right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN, |     CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN, | ||||||
|     "stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN, |     CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN, | ||||||
| } | } | ||||||
|  |  | ||||||
| EXTERNAL_DAC_OPTIONS = ["mono", "stereo"] | EXTERNAL_DAC_OPTIONS = [CONF_MONO, CONF_STEREO] | ||||||
|  |  | ||||||
| NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2] | NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2] | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ enum I2SState : uint8_t { | |||||||
|   I2S_STATE_STOPPING, |   I2S_STATE_STOPPING, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer, public I2SAudioOut { | class I2SAudioMediaPlayer : public Component, public Parented<I2SAudioComponent>, public media_player::MediaPlayer { | ||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   float get_setup_priority() const override { return esphome::setup_priority::LATE; } |   float get_setup_priority() const override { return esphome::setup_priority::LATE; } | ||||||
|   | |||||||
| @@ -1,20 +1,17 @@ | |||||||
| import esphome.config_validation as cv |  | ||||||
| import esphome.codegen as cg |  | ||||||
|  |  | ||||||
| from esphome import pins | from esphome import pins | ||||||
| from esphome.const import CONF_CHANNEL, CONF_ID, CONF_NUMBER, CONF_SAMPLE_RATE | import esphome.codegen as cg | ||||||
| from esphome.components import microphone, esp32 | from esphome.components import esp32, microphone | ||||||
| from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin | from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_ID, CONF_NUMBER | ||||||
|  |  | ||||||
| from .. import ( | from .. import ( | ||||||
|     CONF_I2S_MODE, |  | ||||||
|     CONF_PRIMARY, |  | ||||||
|     I2S_MODE_OPTIONS, |  | ||||||
|     i2s_audio_ns, |  | ||||||
|     I2SAudioComponent, |  | ||||||
|     I2SAudioIn, |  | ||||||
|     CONF_I2S_AUDIO_ID, |  | ||||||
|     CONF_I2S_DIN_PIN, |     CONF_I2S_DIN_PIN, | ||||||
|  |     CONF_RIGHT, | ||||||
|  |     I2SAudioIn, | ||||||
|  |     i2s_audio_component_schema, | ||||||
|  |     i2s_audio_ns, | ||||||
|  |     register_i2s_audio_component, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
| @@ -23,29 +20,14 @@ DEPENDENCIES = ["i2s_audio"] | |||||||
| CONF_ADC_PIN = "adc_pin" | CONF_ADC_PIN = "adc_pin" | ||||||
| CONF_ADC_TYPE = "adc_type" | CONF_ADC_TYPE = "adc_type" | ||||||
| CONF_PDM = "pdm" | CONF_PDM = "pdm" | ||||||
| CONF_BITS_PER_SAMPLE = "bits_per_sample" |  | ||||||
| CONF_USE_APLL = "use_apll" |  | ||||||
|  |  | ||||||
| I2SAudioMicrophone = i2s_audio_ns.class_( | I2SAudioMicrophone = i2s_audio_ns.class_( | ||||||
|     "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component |     "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component | ||||||
| ) | ) | ||||||
|  |  | ||||||
| i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t") |  | ||||||
| CHANNELS = { |  | ||||||
|     "left": i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT, |  | ||||||
|     "right": i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT, |  | ||||||
| } |  | ||||||
| i2s_bits_per_sample_t = cg.global_ns.enum("i2s_bits_per_sample_t") |  | ||||||
| BITS_PER_SAMPLE = { |  | ||||||
|     16: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_16BIT, |  | ||||||
|     32: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_32BIT, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32] | INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32] | ||||||
| PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3] | PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3] | ||||||
|  |  | ||||||
| _validate_bits = cv.float_with_unit("bits", "bit") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_esp32_variant(config): | def validate_esp32_variant(config): | ||||||
|     variant = esp32.get_esp32_variant() |     variant = esp32.get_esp32_variant() | ||||||
| @@ -62,21 +44,15 @@ def validate_esp32_variant(config): | |||||||
|  |  | ||||||
|  |  | ||||||
| BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend( | BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend( | ||||||
|     { |     i2s_audio_component_schema( | ||||||
|         cv.GenerateID(): cv.declare_id(I2SAudioMicrophone), |         I2SAudioMicrophone, | ||||||
|         cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), |         default_sample_rate=16000, | ||||||
|         cv.Optional(CONF_CHANNEL, default="right"): cv.enum(CHANNELS), |         default_channel=CONF_RIGHT, | ||||||
|         cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1), |         default_bits_per_sample="32bit", | ||||||
|         cv.Optional(CONF_BITS_PER_SAMPLE, default="32bit"): cv.All( |     ) | ||||||
|             _validate_bits, cv.enum(BITS_PER_SAMPLE) |  | ||||||
|         ), |  | ||||||
|         cv.Optional(CONF_USE_APLL, default=False): cv.boolean, |  | ||||||
|         cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.enum( |  | ||||||
|             I2S_MODE_OPTIONS, lower=True |  | ||||||
|         ), |  | ||||||
|     } |  | ||||||
| ).extend(cv.COMPONENT_SCHEMA) | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.typed_schema( |     cv.typed_schema( | ||||||
|         { |         { | ||||||
| @@ -88,7 +64,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             "external": BASE_SCHEMA.extend( |             "external": BASE_SCHEMA.extend( | ||||||
|                 { |                 { | ||||||
|                     cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number, |                     cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number, | ||||||
|                     cv.Required(CONF_PDM): cv.boolean, |                     cv.Optional(CONF_PDM, default=False): cv.boolean, | ||||||
|                 } |                 } | ||||||
|             ), |             ), | ||||||
|         }, |         }, | ||||||
| @@ -101,8 +77,8 @@ CONFIG_SCHEMA = cv.All( | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |     await register_i2s_audio_component(var, config) | ||||||
|     await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) |     await microphone.register_microphone(var, config) | ||||||
|  |  | ||||||
|     if config[CONF_ADC_TYPE] == "internal": |     if config[CONF_ADC_TYPE] == "internal": | ||||||
|         variant = esp32.get_esp32_variant() |         variant = esp32.get_esp32_variant() | ||||||
| @@ -112,11 +88,3 @@ async def to_code(config): | |||||||
|     else: |     else: | ||||||
|         cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN])) |         cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN])) | ||||||
|         cg.add(var.set_pdm(config[CONF_PDM])) |         cg.add(var.set_pdm(config[CONF_PDM])) | ||||||
|  |  | ||||||
|     cg.add(var.set_i2s_mode(config[CONF_I2S_MODE])) |  | ||||||
|     cg.add(var.set_channel(config[CONF_CHANNEL])) |  | ||||||
|     cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) |  | ||||||
|     cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) |  | ||||||
|     cg.add(var.set_use_apll(config[CONF_USE_APLL])) |  | ||||||
|  |  | ||||||
|     await microphone.register_microphone(var, config) |  | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ void I2SAudioMicrophone::start_() { | |||||||
|       .tx_desc_auto_clear = false, |       .tx_desc_auto_clear = false, | ||||||
|       .fixed_mclk = 0, |       .fixed_mclk = 0, | ||||||
|       .mclk_multiple = I2S_MCLK_MULTIPLE_256, |       .mclk_multiple = I2S_MCLK_MULTIPLE_256, | ||||||
|       .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, |       .bits_per_chan = this->bits_per_channel_, | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   esp_err_t err; |   esp_err_t err; | ||||||
| @@ -167,21 +167,24 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) { | |||||||
|     return 0; |     return 0; | ||||||
|   } |   } | ||||||
|   this->status_clear_warning(); |   this->status_clear_warning(); | ||||||
|   if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) { |   // ESP-IDF I2S implementation right-extends 8-bit data to 16 bits, | ||||||
|     return bytes_read; |   // and 24-bit data to 32 bits. | ||||||
|   } else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) { |   switch (this->bits_per_sample_) { | ||||||
|     std::vector<int16_t> samples; |     case I2S_BITS_PER_SAMPLE_8BIT: | ||||||
|     size_t samples_read = bytes_read / sizeof(int32_t); |     case I2S_BITS_PER_SAMPLE_16BIT: | ||||||
|     samples.resize(samples_read); |       return bytes_read; | ||||||
|     for (size_t i = 0; i < samples_read; i++) { |     case I2S_BITS_PER_SAMPLE_24BIT: | ||||||
|       int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14; |     case I2S_BITS_PER_SAMPLE_32BIT: { | ||||||
|       samples[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX); |       size_t samples_read = bytes_read / sizeof(int32_t); | ||||||
|  |       for (size_t i = 0; i < samples_read; i++) { | ||||||
|  |         int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14; | ||||||
|  |         buf[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX); | ||||||
|  |       } | ||||||
|  |       return samples_read * sizeof(int16_t); | ||||||
|     } |     } | ||||||
|     memcpy(buf, samples.data(), samples_read * sizeof(int16_t)); |     default: | ||||||
|     return samples_read * sizeof(int16_t); |       ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_); | ||||||
|   } else { |       return 0; | ||||||
|     ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_); |  | ||||||
|     return 0; |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,13 +30,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub | |||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   void set_i2s_mode(i2s_mode_t mode) { this->i2s_mode_ = mode; } |  | ||||||
|  |  | ||||||
|   void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; } |  | ||||||
|   void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; } |  | ||||||
|   void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; } |  | ||||||
|   void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; } |  | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void start_(); |   void start_(); | ||||||
|   void stop_(); |   void stop_(); | ||||||
| @@ -48,11 +41,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub | |||||||
|   bool adc_{false}; |   bool adc_{false}; | ||||||
| #endif | #endif | ||||||
|   bool pdm_{false}; |   bool pdm_{false}; | ||||||
|   i2s_mode_t i2s_mode_{}; |  | ||||||
|   i2s_channel_fmt_t channel_; |  | ||||||
|   uint32_t sample_rate_; |  | ||||||
|   i2s_bits_per_sample_t bits_per_sample_; |  | ||||||
|   bool use_apll_; |  | ||||||
|  |  | ||||||
|   HighFrequencyLoopRequester high_freq_; |   HighFrequencyLoopRequester high_freq_; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,15 +1,19 @@ | |||||||
| import esphome.codegen as cg |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome import pins | from esphome import pins | ||||||
| from esphome.const import CONF_ID, CONF_MODE | import esphome.codegen as cg | ||||||
| from esphome.components import esp32, speaker | from esphome.components import esp32, speaker | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_CHANNEL, CONF_ID, CONF_MODE, CONF_TIMEOUT | ||||||
|  |  | ||||||
| from .. import ( | from .. import ( | ||||||
|     CONF_I2S_AUDIO_ID, |  | ||||||
|     CONF_I2S_DOUT_PIN, |     CONF_I2S_DOUT_PIN, | ||||||
|     I2SAudioComponent, |     CONF_LEFT, | ||||||
|  |     CONF_MONO, | ||||||
|  |     CONF_RIGHT, | ||||||
|  |     CONF_STEREO, | ||||||
|     I2SAudioOut, |     I2SAudioOut, | ||||||
|  |     i2s_audio_component_schema, | ||||||
|     i2s_audio_ns, |     i2s_audio_ns, | ||||||
|  |     register_i2s_audio_component, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
| @@ -19,19 +23,16 @@ I2SAudioSpeaker = i2s_audio_ns.class_( | |||||||
|     "I2SAudioSpeaker", cg.Component, speaker.Speaker, I2SAudioOut |     "I2SAudioSpeaker", cg.Component, speaker.Speaker, I2SAudioOut | ||||||
| ) | ) | ||||||
|  |  | ||||||
| i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t") |  | ||||||
|  |  | ||||||
| CONF_MUTE_PIN = "mute_pin" |  | ||||||
| CONF_DAC_TYPE = "dac_type" | CONF_DAC_TYPE = "dac_type" | ||||||
|  |  | ||||||
|  | i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t") | ||||||
| INTERNAL_DAC_OPTIONS = { | INTERNAL_DAC_OPTIONS = { | ||||||
|     "left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, |     CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, | ||||||
|     "right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN, |     CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN, | ||||||
|     "stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN, |     CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN, | ||||||
| } | } | ||||||
|  |  | ||||||
| EXTERNAL_DAC_OPTIONS = ["mono", "stereo"] |  | ||||||
|  |  | ||||||
| NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2] | NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2] | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -44,28 +45,40 @@ def validate_esp32_variant(config): | |||||||
|     return config |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | BASE_SCHEMA = ( | ||||||
|  |     speaker.SPEAKER_SCHEMA.extend( | ||||||
|  |         i2s_audio_component_schema( | ||||||
|  |             I2SAudioSpeaker, | ||||||
|  |             default_sample_rate=16000, | ||||||
|  |             default_channel=CONF_MONO, | ||||||
|  |             default_bits_per_sample="16bit", | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  |     .extend( | ||||||
|  |         { | ||||||
|  |             cv.Optional( | ||||||
|  |                 CONF_TIMEOUT, default="100ms" | ||||||
|  |             ): cv.positive_time_period_milliseconds, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.typed_schema( |     cv.typed_schema( | ||||||
|         { |         { | ||||||
|             "internal": speaker.SPEAKER_SCHEMA.extend( |             "internal": BASE_SCHEMA.extend( | ||||||
|                 { |                 { | ||||||
|                     cv.GenerateID(): cv.declare_id(I2SAudioSpeaker), |  | ||||||
|                     cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), |  | ||||||
|                     cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True), |                     cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True), | ||||||
|                 } |                 } | ||||||
|             ).extend(cv.COMPONENT_SCHEMA), |             ), | ||||||
|             "external": speaker.SPEAKER_SCHEMA.extend( |             "external": BASE_SCHEMA.extend( | ||||||
|                 { |                 { | ||||||
|                     cv.GenerateID(): cv.declare_id(I2SAudioSpeaker), |  | ||||||
|                     cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), |  | ||||||
|                     cv.Required( |                     cv.Required( | ||||||
|                         CONF_I2S_DOUT_PIN |                         CONF_I2S_DOUT_PIN | ||||||
|                     ): pins.internal_gpio_output_pin_number, |                     ): pins.internal_gpio_output_pin_number, | ||||||
|                     cv.Optional(CONF_MODE, default="mono"): cv.one_of( |  | ||||||
|                         *EXTERNAL_DAC_OPTIONS, lower=True |  | ||||||
|                     ), |  | ||||||
|                 } |                 } | ||||||
|             ).extend(cv.COMPONENT_SCHEMA), |             ), | ||||||
|         }, |         }, | ||||||
|         key=CONF_DAC_TYPE, |         key=CONF_DAC_TYPE, | ||||||
|     ), |     ), | ||||||
| @@ -76,12 +89,11 @@ CONFIG_SCHEMA = cv.All( | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |     await register_i2s_audio_component(var, config) | ||||||
|     await speaker.register_speaker(var, config) |     await speaker.register_speaker(var, config) | ||||||
|  |  | ||||||
|     await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) |  | ||||||
|  |  | ||||||
|     if config[CONF_DAC_TYPE] == "internal": |     if config[CONF_DAC_TYPE] == "internal": | ||||||
|         cg.add(var.set_internal_dac_mode(config[CONF_MODE])) |         cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL])) | ||||||
|     else: |     else: | ||||||
|         cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN])) |         cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN])) | ||||||
|         cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1)) |     cg.add(var.set_timeout(config[CONF_TIMEOUT])) | ||||||
|   | |||||||
| @@ -56,6 +56,21 @@ void I2SAudioSpeaker::start_() { | |||||||
|   this->task_created_ = true; |   this->task_created_ = true; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | template<typename a, typename b> const uint8_t *convert_data_format(const a *from, b *to, size_t &bytes, bool repeat) { | ||||||
|  |   if (sizeof(a) == sizeof(b) && !repeat) { | ||||||
|  |     return reinterpret_cast<const uint8_t *>(from); | ||||||
|  |   } | ||||||
|  |   const b *result = to; | ||||||
|  |   for (size_t i = 0; i < bytes; i += sizeof(a)) { | ||||||
|  |     b value = static_cast<b>(*from++) << (sizeof(b) - sizeof(a)) * 8; | ||||||
|  |     *to++ = value; | ||||||
|  |     if (repeat) | ||||||
|  |       *to++ = value; | ||||||
|  |   } | ||||||
|  |   bytes *= (sizeof(b) / sizeof(a)) * (repeat ? 2 : 1);  // NOLINT | ||||||
|  |   return reinterpret_cast<const uint8_t *>(result); | ||||||
|  | } | ||||||
|  |  | ||||||
| void I2SAudioSpeaker::player_task(void *params) { | void I2SAudioSpeaker::player_task(void *params) { | ||||||
|   I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params; |   I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params; | ||||||
|  |  | ||||||
| @@ -64,19 +79,19 @@ void I2SAudioSpeaker::player_task(void *params) { | |||||||
|   xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); |   xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); | ||||||
|  |  | ||||||
|   i2s_driver_config_t config = { |   i2s_driver_config_t config = { | ||||||
|       .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX), |       .mode = (i2s_mode_t) (this_speaker->i2s_mode_ | I2S_MODE_TX), | ||||||
|       .sample_rate = 16000, |       .sample_rate = this_speaker->sample_rate_, | ||||||
|       .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, |       .bits_per_sample = this_speaker->bits_per_sample_, | ||||||
|       .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, |       .channel_format = this_speaker->channel_, | ||||||
|       .communication_format = I2S_COMM_FORMAT_STAND_I2S, |       .communication_format = I2S_COMM_FORMAT_STAND_I2S, | ||||||
|       .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, |       .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, | ||||||
|       .dma_buf_count = 8, |       .dma_buf_count = 8, | ||||||
|       .dma_buf_len = 128, |       .dma_buf_len = 256, | ||||||
|       .use_apll = false, |       .use_apll = this_speaker->use_apll_, | ||||||
|       .tx_desc_auto_clear = true, |       .tx_desc_auto_clear = true, | ||||||
|       .fixed_mclk = I2S_PIN_NO_CHANGE, |       .fixed_mclk = 0, | ||||||
|       .mclk_multiple = I2S_MCLK_MULTIPLE_256, |       .mclk_multiple = I2S_MCLK_MULTIPLE_256, | ||||||
|       .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, |       .bits_per_chan = this_speaker->bits_per_channel_, | ||||||
|   }; |   }; | ||||||
| #if SOC_I2S_SUPPORTS_DAC | #if SOC_I2S_SUPPORTS_DAC | ||||||
|   if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) { |   if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) { | ||||||
| @@ -114,10 +129,11 @@ void I2SAudioSpeaker::player_task(void *params) { | |||||||
|   event.type = TaskEventType::STARTED; |   event.type = TaskEventType::STARTED; | ||||||
|   xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); |   xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); | ||||||
|  |  | ||||||
|   int16_t buffer[BUFFER_SIZE / 2]; |   int32_t buffer[BUFFER_SIZE]; | ||||||
|  |  | ||||||
|   while (true) { |   while (true) { | ||||||
|     if (xQueueReceive(this_speaker->buffer_queue_, &data_event, 100 / portTICK_PERIOD_MS) != pdTRUE) { |     if (xQueueReceive(this_speaker->buffer_queue_, &data_event, this_speaker->timeout_ / portTICK_PERIOD_MS) != | ||||||
|  |         pdTRUE) { | ||||||
|       break;  // End of audio from main thread |       break;  // End of audio from main thread | ||||||
|     } |     } | ||||||
|     if (data_event.stop) { |     if (data_event.stop) { | ||||||
| @@ -125,17 +141,28 @@ void I2SAudioSpeaker::player_task(void *params) { | |||||||
|       xQueueReset(this_speaker->buffer_queue_);  // Flush queue |       xQueueReset(this_speaker->buffer_queue_);  // Flush queue | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     size_t bytes_written; |  | ||||||
|  |  | ||||||
|     memmove(buffer, data_event.data, data_event.len); |     const uint8_t *data = data_event.data; | ||||||
|     size_t remaining = data_event.len / 2; |     size_t remaining = data_event.len; | ||||||
|     size_t current = 0; |     switch (this_speaker->bits_per_sample_) { | ||||||
|  |       case I2S_BITS_PER_SAMPLE_8BIT: | ||||||
|  |       case I2S_BITS_PER_SAMPLE_16BIT: { | ||||||
|  |         data = convert_data_format(reinterpret_cast<const int16_t *>(data), reinterpret_cast<int16_t *>(buffer), | ||||||
|  |                                    remaining, this_speaker->channel_ == I2S_CHANNEL_FMT_ALL_LEFT); | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |       case I2S_BITS_PER_SAMPLE_24BIT: | ||||||
|  |       case I2S_BITS_PER_SAMPLE_32BIT: { | ||||||
|  |         data = convert_data_format(reinterpret_cast<const int16_t *>(data), reinterpret_cast<int32_t *>(buffer), | ||||||
|  |                                    remaining, this_speaker->channel_ == I2S_CHANNEL_FMT_ALL_LEFT); | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     while (remaining > 0) { |     while (remaining != 0) { | ||||||
|       uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF); |       size_t bytes_written; | ||||||
|  |       esp_err_t err = | ||||||
|       esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written, |           i2s_write(this_speaker->parent_->get_port(), data, remaining, &bytes_written, (32 / portTICK_PERIOD_MS)); | ||||||
|                                 (10 / portTICK_PERIOD_MS)); |  | ||||||
|       if (err != ESP_OK) { |       if (err != ESP_OK) { | ||||||
|         event = {.type = TaskEventType::WARNING, .err = err}; |         event = {.type = TaskEventType::WARNING, .err = err}; | ||||||
|         if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) { |         if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) { | ||||||
| @@ -143,21 +170,8 @@ void I2SAudioSpeaker::player_task(void *params) { | |||||||
|         } |         } | ||||||
|         continue; |         continue; | ||||||
|       } |       } | ||||||
|       if (bytes_written != sizeof(sample)) { |       data += bytes_written; | ||||||
|         event = {.type = TaskEventType::WARNING, .err = ESP_FAIL}; |       remaining -= bytes_written; | ||||||
|         if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) { |  | ||||||
|           ESP_LOGW(TAG, "Failed to send WARNING event"); |  | ||||||
|         } |  | ||||||
|         continue; |  | ||||||
|       } |  | ||||||
|       remaining--; |  | ||||||
|       current++; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     event.type = TaskEventType::PLAYING; |  | ||||||
|     event.err = current; |  | ||||||
|     if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) { |  | ||||||
|       ESP_LOGW(TAG, "Failed to send PLAYING event"); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -213,13 +227,11 @@ void I2SAudioSpeaker::watch_() { | |||||||
|       case TaskEventType::STARTED: |       case TaskEventType::STARTED: | ||||||
|         ESP_LOGD(TAG, "Started I2S Audio Speaker"); |         ESP_LOGD(TAG, "Started I2S Audio Speaker"); | ||||||
|         this->state_ = speaker::STATE_RUNNING; |         this->state_ = speaker::STATE_RUNNING; | ||||||
|  |         this->status_clear_warning(); | ||||||
|         break; |         break; | ||||||
|       case TaskEventType::STOPPING: |       case TaskEventType::STOPPING: | ||||||
|         ESP_LOGD(TAG, "Stopping I2S Audio Speaker"); |         ESP_LOGD(TAG, "Stopping I2S Audio Speaker"); | ||||||
|         break; |         break; | ||||||
|       case TaskEventType::PLAYING: |  | ||||||
|         this->status_clear_warning(); |  | ||||||
|         break; |  | ||||||
|       case TaskEventType::STOPPED: |       case TaskEventType::STOPPED: | ||||||
|         this->state_ = speaker::STATE_STOPPED; |         this->state_ = speaker::STATE_STOPPED; | ||||||
|         vTaskDelete(this->player_task_handle_); |         vTaskDelete(this->player_task_handle_); | ||||||
|   | |||||||
| @@ -21,7 +21,6 @@ static const size_t BUFFER_SIZE = 1024; | |||||||
| enum class TaskEventType : uint8_t { | enum class TaskEventType : uint8_t { | ||||||
|   STARTING = 0, |   STARTING = 0, | ||||||
|   STARTED, |   STARTED, | ||||||
|   PLAYING, |  | ||||||
|   STOPPING, |   STOPPING, | ||||||
|   STOPPED, |   STOPPED, | ||||||
|   WARNING = 255, |   WARNING = 255, | ||||||
| @@ -38,18 +37,18 @@ struct DataEvent { | |||||||
|   uint8_t data[BUFFER_SIZE]; |   uint8_t data[BUFFER_SIZE]; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAudioOut { | class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Component { | ||||||
|  public: |  public: | ||||||
|   float get_setup_priority() const override { return esphome::setup_priority::LATE; } |   float get_setup_priority() const override { return esphome::setup_priority::LATE; } | ||||||
|  |  | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void loop() override; |   void loop() override; | ||||||
|  |  | ||||||
|  |   void set_timeout(uint32_t ms) { this->timeout_ = ms; } | ||||||
|   void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; } |   void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; } | ||||||
| #if SOC_I2S_SUPPORTS_DAC | #if SOC_I2S_SUPPORTS_DAC | ||||||
|   void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; } |   void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; } | ||||||
| #endif | #endif | ||||||
|   void set_external_dac_channels(uint8_t channels) { this->external_dac_channels_ = channels; } |  | ||||||
|  |  | ||||||
|   void start() override; |   void start() override; | ||||||
|   void stop() override; |   void stop() override; | ||||||
| @@ -70,13 +69,13 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud | |||||||
|   QueueHandle_t buffer_queue_; |   QueueHandle_t buffer_queue_; | ||||||
|   QueueHandle_t event_queue_; |   QueueHandle_t event_queue_; | ||||||
|  |  | ||||||
|  |   uint32_t timeout_{0}; | ||||||
|   uint8_t dout_pin_{0}; |   uint8_t dout_pin_{0}; | ||||||
|   bool task_created_{false}; |   bool task_created_{false}; | ||||||
|  |  | ||||||
| #if SOC_I2S_SUPPORTS_DAC | #if SOC_I2S_SUPPORTS_DAC | ||||||
|   i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE}; |   i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE}; | ||||||
| #endif | #endif | ||||||
|   uint8_t external_dac_channels_; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace i2s_audio | }  // namespace i2s_audio | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user