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 | ||||
|       id: build-ghcr | ||||
|       uses: docker/build-push-action@v6.7.0 | ||||
|       env: | ||||
|         DOCKER_BUILD_SUMMARY: false | ||||
|         DOCKER_BUILD_RECORD_UPLOAD: false | ||||
|       with: | ||||
|         context: . | ||||
|         file: ./docker/Dockerfile | ||||
| @@ -70,6 +73,9 @@ runs: | ||||
|     - name: Build and push to dockerhub by digest | ||||
|       id: build-dockerhub | ||||
|       uses: docker/build-push-action@v6.7.0 | ||||
|       env: | ||||
|         DOCKER_BUILD_SUMMARY: false | ||||
|         DOCKER_BUILD_RECORD_UPLOAD: false | ||||
|       with: | ||||
|         context: . | ||||
|         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: | ||||
|     - name: Set up Python ${{ inputs.python-version }} | ||||
|       id: python | ||||
|       uses: actions/setup-python@v5.1.1 | ||||
|       uses: actions/setup-python@v5.2.0 | ||||
|       with: | ||||
|         python-version: ${{ inputs.python-version }} | ||||
|     - 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 | ||||
|         uses: actions/checkout@v4.1.7 | ||||
|       - name: Set up Python | ||||
|         uses: actions/setup-python@v5.1.0 | ||||
|         uses: actions/setup-python@v5.2.0 | ||||
|         with: | ||||
|           python-version: "3.11" | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -42,7 +42,7 @@ jobs: | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4.1.7 | ||||
|       - name: Set up Python | ||||
|         uses: actions/setup-python@v5.1.0 | ||||
|         uses: actions/setup-python@v5.2.0 | ||||
|         with: | ||||
|           python-version: "3.9" | ||||
|       - 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 | ||||
|       - name: Set up Python ${{ env.DEFAULT_PYTHON }} | ||||
|         id: python | ||||
|         uses: actions/setup-python@v5.1.0 | ||||
|         uses: actions/setup-python@v5.2.0 | ||||
|         with: | ||||
|           python-version: ${{ env.DEFAULT_PYTHON }} | ||||
|       - name: Restore Python virtual environment | ||||
|   | ||||
							
								
								
									
										8
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -53,7 +53,7 @@ jobs: | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4.1.7 | ||||
|       - name: Set up Python | ||||
|         uses: actions/setup-python@v5.1.0 | ||||
|         uses: actions/setup-python@v5.2.0 | ||||
|         with: | ||||
|           python-version: "3.x" | ||||
|       - name: Set up python environment | ||||
| @@ -65,7 +65,7 @@ jobs: | ||||
|           pip3 install build | ||||
|           python3 -m build | ||||
|       - name: Publish | ||||
|         uses: pypa/gh-action-pypi-publish@v1.9.0 | ||||
|         uses: pypa/gh-action-pypi-publish@v1.10.1 | ||||
|  | ||||
|   deploy-docker: | ||||
|     name: Build ESPHome ${{ matrix.platform }} | ||||
| @@ -85,7 +85,7 @@ jobs: | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4.1.7 | ||||
|       - name: Set up Python | ||||
|         uses: actions/setup-python@v5.1.0 | ||||
|         uses: actions/setup-python@v5.2.0 | ||||
|         with: | ||||
|           python-version: "3.9" | ||||
|  | ||||
| @@ -141,7 +141,7 @@ jobs: | ||||
|           echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT | ||||
|  | ||||
|       - name: Upload digests | ||||
|         uses: actions/upload-artifact@v4.3.4 | ||||
|         uses: actions/upload-artifact@v4.4.0 | ||||
|         with: | ||||
|           name: digests-${{ steps.sanitize.outputs.name }} | ||||
|           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 | ||||
|  | ||||
|       - name: Setup Python | ||||
|         uses: actions/setup-python@v5.1.0 | ||||
|         uses: actions/setup-python@v5.2.0 | ||||
|         with: | ||||
|           python-version: 3.12 | ||||
|  | ||||
| @@ -36,7 +36,7 @@ jobs: | ||||
|           python ./script/sync-device_class.py | ||||
|  | ||||
|       - name: Commit changes | ||||
|         uses: peter-evans/create-pull-request@v6.1.0 | ||||
|         uses: peter-evans/create-pull-request@v7.0.0 | ||||
|         with: | ||||
|           commit-message: "Synchronise Device Classes from Home Assistant" | ||||
|           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/binary_sensor/* @esphome/core | ||||
| esphome/components/bk72xx/* @kuba2k2 | ||||
| esphome/components/bl0906/* @athom-tech @jesserockz @tarontop | ||||
| esphome/components/bl0939/* @ziceva | ||||
| esphome/components/bl0940/* @tobias- | ||||
| esphome/components/bl0942/* @dbuezas | ||||
| esphome/components/bl0942/* @dbuezas @dwmw2 | ||||
| esphome/components/ble_client/* @buxtronix @clydebarrow | ||||
| esphome/components/bluetooth_proxy/* @jesserockz | ||||
| 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_i2c/* @kbx81 @neffs | ||||
| 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_base/* @latonita @martgras | ||||
| esphome/components/bmp3xx_i2c/* @latonita | ||||
| @@ -82,6 +86,7 @@ esphome/components/cap1188/* @mreditor97 | ||||
| esphome/components/captive_portal/* @OttoWinter | ||||
| esphome/components/ccs811/* @habbie | ||||
| esphome/components/cd74hc4067/* @asoehlke | ||||
| esphome/components/ch422g/* @jesterret | ||||
| esphome/components/climate/* @esphome/core | ||||
| esphome/components/climate_ir/* @glmnet | ||||
| esphome/components/color_temperature/* @jesserockz | ||||
| @@ -169,6 +174,7 @@ esphome/components/he60r/* @clydebarrow | ||||
| esphome/components/heatpumpir/* @rob-deutsch | ||||
| esphome/components/hitachi_ac424/* @sourabhjaiswal | ||||
| esphome/components/hm3301/* @freekode | ||||
| esphome/components/hmac_md5/* @dwmw2 | ||||
| esphome/components/homeassistant/* @OttoWinter @esphome/core | ||||
| esphome/components/homeassistant/number/* @landonr | ||||
| esphome/components/homeassistant/switch/* @Links2004 | ||||
| @@ -221,6 +227,7 @@ esphome/components/lilygo_t5_47/touchscreen/* @jesserockz | ||||
| esphome/components/lock/* @esphome/core | ||||
| esphome/components/logger/* @esphome/core | ||||
| esphome/components/ltr390/* @latonita @sjtrny | ||||
| esphome/components/ltr501/* @latonita | ||||
| esphome/components/ltr_als_ps/* @latonita | ||||
| esphome/components/lvgl/* @clydebarrow | ||||
| esphome/components/m5stack_8angle/* @rnauber | ||||
| @@ -382,6 +389,7 @@ esphome/components/st7701s/* @clydebarrow | ||||
| esphome/components/st7735/* @SenexCrenshaw | ||||
| esphome/components/st7789v/* @kbx81 | ||||
| esphome/components/st7920/* @marsjan155 | ||||
| esphome/components/statsd/* @Links2004 | ||||
| esphome/components/substitutions/* @esphome/core | ||||
| esphome/components/sun/* @OttoWinter | ||||
| esphome/components/sun_gtil2/* @Mat931 | ||||
| @@ -421,6 +429,7 @@ esphome/components/tuya/switch/* @jesserockz | ||||
| esphome/components/tuya/text_sensor/* @dentra | ||||
| esphome/components/uart/* @esphome/core | ||||
| esphome/components/uart/button/* @ssieb | ||||
| esphome/components/udp/* @clydebarrow | ||||
| esphome/components/ufire_ec/* @pvizeli | ||||
| esphome/components/ufire_ise/* @pvizeli | ||||
| esphome/components/ultrasonic/* @OttoWinter | ||||
|   | ||||
| @@ -33,7 +33,7 @@ RUN \ | ||||
|         python3-venv=3.11.2-1+b1 \ | ||||
|         python3-wheel=0.38.4-2 \ | ||||
|         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 \ | ||||
|         openssh-client=1:9.2p1-2+deb12u3 \ | ||||
|         python3-cffi=1.15.1-5 \ | ||||
| @@ -49,7 +49,7 @@ RUN \ | ||||
|                 zlib1g-dev=1:1.2.13.dfsg-1 \ | ||||
|                 libjpeg-dev=1:2.1.5-2 \ | ||||
|                 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 \ | ||||
|                 libopenjp2-7=2.5.0-2 \ | ||||
|                 libtiff6=4.5.0-6+deb12u1 \ | ||||
| @@ -96,14 +96,19 @@ RUN \ | ||||
| # First install requirements to leverage caching when requirements don't change | ||||
| # 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 \ | ||||
|         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; \ | ||||
|     CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo \ | ||||
|     pip3 install \ | ||||
|     --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ | ||||
|     && /platformio_install_deps.py /platformio.ini --libraries | ||||
|     --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt | ||||
|  | ||||
| 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 | ||||
| RUN git config --system --add safe.directory '*' | ||||
|   | ||||
| @@ -38,7 +38,7 @@ from esphome.const import ( | ||||
|     SECRETS_FILES, | ||||
| ) | ||||
| 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.util import ( | ||||
|     get_serial_ports, | ||||
| @@ -731,7 +731,11 @@ POST_CONFIG_ACTIONS = { | ||||
| def parse_args(argv): | ||||
|     options_parser = argparse.ArgumentParser(add_help=False) | ||||
|     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( | ||||
|         "-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 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) {} | ||||
| } | ||||
| @@ -1107,6 +1109,19 @@ enum MediaPlayerCommand { | ||||
|   MEDIA_PLAYER_COMMAND_MUTE = 3; | ||||
|   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 { | ||||
|   option (id) = 63; | ||||
|   option (source) = SOURCE_SERVER; | ||||
| @@ -1122,6 +1137,8 @@ message ListEntitiesMediaPlayerResponse { | ||||
|   EntityCategory entity_category = 7; | ||||
|  | ||||
|   bool supports_pause = 8; | ||||
|  | ||||
|   repeated MediaPlayerSupportedFormat supported_formats = 9; | ||||
| } | ||||
| message MediaPlayerStateResponse { | ||||
|   option (id) = 64; | ||||
| @@ -1539,6 +1556,53 @@ message VoiceAssistantTimerEventResponse { | ||||
|   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 ==================== | ||||
| enum AlarmControlPanelState { | ||||
|   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(); | ||||
|   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); | ||||
| } | ||||
| 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 | ||||
|  | ||||
| #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_audio(const VoiceAssistantAudio &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 | ||||
|  | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|   | ||||
| @@ -387,6 +387,18 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerCommand>(enums::Me | ||||
| } | ||||
| #endif | ||||
| #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<> | ||||
| const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::BluetoothDeviceRequestType value) { | ||||
|   switch (value) { | ||||
| @@ -5123,6 +5135,74 @@ void ButtonCommandRequest::dump_to(std::string &out) const { | ||||
|   out.append("}"); | ||||
| } | ||||
| #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) { | ||||
|   switch (field_id) { | ||||
|     case 6: { | ||||
| @@ -5159,6 +5239,10 @@ bool ListEntitiesMediaPlayerResponse::decode_length(uint32_t field_id, ProtoLeng | ||||
|       this->icon = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     case 9: { | ||||
|       this->supported_formats.push_back(value.as_message<MediaPlayerSupportedFormat>()); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -5182,6 +5266,9 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(6, this->disabled_by_default); | ||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||
|   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 | ||||
| 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(YESNO(this->supports_pause)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   for (const auto &it : this->supported_formats) { | ||||
|     out.append("  supported_formats: "); | ||||
|     it.dump_to(out); | ||||
|     out.append("\n"); | ||||
|   } | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -6978,6 +7071,193 @@ void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const { | ||||
|   out.append("}"); | ||||
| } | ||||
| #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) { | ||||
|   switch (field_id) { | ||||
|     case 6: { | ||||
|   | ||||
| @@ -156,6 +156,10 @@ enum MediaPlayerCommand : uint32_t { | ||||
|   MEDIA_PLAYER_COMMAND_MUTE = 3, | ||||
|   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 { | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0, | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1, | ||||
| @@ -1267,6 +1271,22 @@ class ButtonCommandRequest : public ProtoMessage { | ||||
|  protected: | ||||
|   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 { | ||||
|  public: | ||||
|   std::string object_id{}; | ||||
| @@ -1277,6 +1297,7 @@ class ListEntitiesMediaPlayerResponse : public ProtoMessage { | ||||
|   bool disabled_by_default{false}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   bool supports_pause{false}; | ||||
|   std::vector<MediaPlayerSupportedFormat> supported_formats{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   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_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 { | ||||
|  public: | ||||
|   std::string object_id{}; | ||||
|   | ||||
| @@ -486,6 +486,29 @@ bool APIServerConnectionBase::send_voice_assistant_audio(const VoiceAssistantAud | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| #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 | ||||
| bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response( | ||||
|     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()); | ||||
| #endif | ||||
|       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 | ||||
|       break; | ||||
|     } | ||||
| @@ -1625,6 +1681,35 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo | ||||
|   this->subscribe_voice_assistant(msg); | ||||
| } | ||||
| #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 | ||||
| void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|   | ||||
| @@ -247,6 +247,21 @@ class APIServerConnectionBase : public ProtoService { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){}; | ||||
| #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 | ||||
|   bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg); | ||||
| #endif | ||||
| @@ -419,6 +434,13 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; | ||||
| #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 | ||||
|   virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; | ||||
| #endif | ||||
| @@ -520,6 +542,12 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; | ||||
| #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 | ||||
|   void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; | ||||
| #endif | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| # Dummy integration to allow relying on AsyncTCP | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| from esphome.const import ( | ||||
|     PLATFORM_BK72XX, | ||||
|     PLATFORM_ESP32, | ||||
|     PLATFORM_ESP8266, | ||||
|     PLATFORM_BK72XX, | ||||
|     PLATFORM_RTL87XX, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
|  | ||||
| CODEOWNERS = ["@OttoWinter"] | ||||
|  | ||||
| @@ -22,7 +22,7 @@ CONFIG_SCHEMA = cv.All( | ||||
| async def to_code(config): | ||||
|     if CORE.is_esp32 or CORE.is_libretiny: | ||||
|         # 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: | ||||
|         # https://github.com/esphome/ESPAsyncTCP | ||||
|         cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") | ||||
|   | ||||
| @@ -1,34 +1,34 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, spi | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_REACTIVE_POWER, | ||||
|     CONF_VOLTAGE, | ||||
|     CONF_CURRENT, | ||||
|     CONF_FORWARD_ACTIVE_ENERGY, | ||||
|     CONF_FREQUENCY, | ||||
|     CONF_ID, | ||||
|     CONF_LINE_FREQUENCY, | ||||
|     CONF_POWER, | ||||
|     CONF_POWER_FACTOR, | ||||
|     CONF_FREQUENCY, | ||||
|     CONF_FORWARD_ACTIVE_ENERGY, | ||||
|     CONF_REACTIVE_POWER, | ||||
|     CONF_REVERSE_ACTIVE_ENERGY, | ||||
|     CONF_VOLTAGE, | ||||
|     DEVICE_CLASS_CURRENT, | ||||
|     DEVICE_CLASS_ENERGY, | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_POWER_FACTOR, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     ICON_LIGHTBULB, | ||||
|     ICON_CURRENT_AC, | ||||
|     ICON_LIGHTBULB, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     STATE_CLASS_TOTAL_INCREASING, | ||||
|     UNIT_AMPERE, | ||||
|     UNIT_HERTZ, | ||||
|     UNIT_VOLT, | ||||
|     UNIT_AMPERE, | ||||
|     UNIT_WATT, | ||||
|     UNIT_VOLT_AMPS_REACTIVE, | ||||
|     UNIT_WATT, | ||||
|     UNIT_WATT_HOURS, | ||||
| ) | ||||
|  | ||||
| CONF_LINE_FREQUENCY = "line_frequency" | ||||
| CONF_METER_CONSTANT = "meter_constant" | ||||
| CONF_PL_CONST = "pl_const" | ||||
| CONF_GAIN_PGA = "gain_pga" | ||||
|   | ||||
| @@ -7,6 +7,7 @@ from esphome.const import ( | ||||
|     CONF_FORWARD_ACTIVE_ENERGY, | ||||
|     CONF_FREQUENCY, | ||||
|     CONF_ID, | ||||
|     CONF_LINE_FREQUENCY, | ||||
|     CONF_PHASE_A, | ||||
|     CONF_PHASE_ANGLE, | ||||
|     CONF_PHASE_B, | ||||
| @@ -39,7 +40,6 @@ from esphome.const import ( | ||||
|  | ||||
| from . import atm90e32_ns | ||||
|  | ||||
| CONF_LINE_FREQUENCY = "line_frequency" | ||||
| CONF_CHIP_TEMPERATURE = "chip_temperature" | ||||
| CONF_GAIN_PGA = "gain_pga" | ||||
| 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 <cinttypes> | ||||
|  | ||||
| // Datasheet: https://www.belling.com.cn/media/file_object/bel_product/BL0942/datasheet/BL0942_V1.06_en.pdf | ||||
|  | ||||
| namespace esphome { | ||||
| 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_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_SOFT_RESET = 0x19; | ||||
| static const uint8_t BL0942_REG_USR_WRPROT = 0x1A; | ||||
|  | ||||
| static const uint8_t BL0942_REG_I_RMSOS = 0x12; | ||||
| static const uint8_t BL0942_REG_WA_CREEP = 0x14; | ||||
| 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; | ||||
|  | ||||
| // TODO: Confirm insialisation works as intended | ||||
| const uint8_t BL0942_INIT[5][6] = { | ||||
|     // Reset to default | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, | ||||
|     // Enable User Operation Write | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, | ||||
|     // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_MODE, 0x00, 0x10, 0x00, 0x37}, | ||||
|     // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, | ||||
|     // 0x181C = Half cycle, Fast RMS threshold 6172 | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}}; | ||||
| static const uint32_t BL0942_REG_MODE_RESV = 0x03; | ||||
| static const uint32_t BL0942_REG_MODE_CF_EN = 0x04; | ||||
| static const uint32_t BL0942_REG_MODE_RMS_UPDATE_SEL = 0x08; | ||||
| static const uint32_t BL0942_REG_MODE_FAST_RMS_SEL = 0x10; | ||||
| static const uint32_t BL0942_REG_MODE_AC_FREQ_SEL = 0x20; | ||||
| static const uint32_t BL0942_REG_MODE_CF_CNT_CLR_SEL = 0x40; | ||||
| static const uint32_t BL0942_REG_MODE_CF_CNT_ADD_SEL = 0x80; | ||||
| static const uint32_t BL0942_REG_MODE_UART_RATE_19200 = 0x200; | ||||
| static const uint32_t BL0942_REG_MODE_UART_RATE_38400 = 0x300; | ||||
| static const uint32_t BL0942_REG_MODE_DEFAULT = | ||||
|     BL0942_REG_MODE_RESV | BL0942_REG_MODE_CF_EN | BL0942_REG_MODE_CF_CNT_ADD_SEL; | ||||
|  | ||||
| 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() { | ||||
|   DataPacket buffer; | ||||
|   if (!this->available()) { | ||||
|   int avail = this->available(); | ||||
|  | ||||
|   if (!avail) { | ||||
|     return; | ||||
|   } | ||||
|   if (read_array((uint8_t *) &buffer, sizeof(buffer))) { | ||||
|     if (validate_checksum(&buffer)) { | ||||
|       received_package_(&buffer); | ||||
|   if (avail < sizeof(buffer)) { | ||||
|     if (!this->rx_start_) { | ||||
|       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 { | ||||
|     ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); | ||||
|     while (read() >= 0) | ||||
|       ; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   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) { | ||||
|   uint8_t checksum = BL0942_READ_COMMAND; | ||||
| bool BL0942::validate_checksum_(DataPacket *data) { | ||||
|   uint8_t checksum = BL0942_READ_COMMAND | this->address_; | ||||
|   // Whole package but checksum | ||||
|   uint8_t *raw = (uint8_t *) data; | ||||
|   for (uint32_t i = 0; i < sizeof(*data) - 1; i++) { | ||||
| @@ -61,17 +84,73 @@ bool BL0942::validate_checksum(DataPacket *data) { | ||||
|   return checksum == data->checksum; | ||||
| } | ||||
|  | ||||
| void BL0942::update() { | ||||
| void BL0942::write_reg_(uint8_t reg, uint32_t val) { | ||||
|   uint8_t pkt[6]; | ||||
|  | ||||
|   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); | ||||
| } | ||||
|  | ||||
| void BL0942::setup() { | ||||
|   for (auto *i : BL0942_INIT) { | ||||
|     this->write_array(i, 6); | ||||
|     delay(1); | ||||
|   // If either current or voltage references are set explicitly by the user, | ||||
|   // calculate the power reference from it unless that is also explicitly set. | ||||
|   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(); | ||||
| } | ||||
|  | ||||
| @@ -82,10 +161,17 @@ void BL0942::received_package_(DataPacket *data) { | ||||
|     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 i_rms = (uint24_t) data->i_rms / current_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 frequency = 1000000.0f / data->frequency; | ||||
|  | ||||
| @@ -104,18 +190,25 @@ void BL0942::received_package_(DataPacket *data) { | ||||
|   if (frequency_sensor_ != nullptr) { | ||||
|     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, | ||||
|            watt, cf_cnt, total_energy_consumption, frequency, data->status); | ||||
| } | ||||
|  | ||||
| void BL0942::dump_config() {  // NOLINT(readability-function-cognitive-complexity) | ||||
|   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("", "Current", this->current_sensor_); | ||||
|   LOG_SENSOR("", "Power", this->power_sensor_); | ||||
|   LOG_SENSOR("", "Energy", this->energy_sensor_); | ||||
|   LOG_SENSOR("", "frequency", this->frequency_sensor_); | ||||
|   LOG_SENSOR("", "Frequency", this->frequency_sensor_); | ||||
| } | ||||
|  | ||||
| }  // namespace bl0942 | ||||
|   | ||||
| @@ -8,6 +8,57 @@ | ||||
| namespace esphome { | ||||
| 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_UREF = 15873.35944299;   // should be 73989/1.218 | ||||
| static const float BL0942_IREF = 251213.46469622;  // 305978/1.218 | ||||
| @@ -28,6 +79,11 @@ struct DataPacket { | ||||
|   uint8_t checksum; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| enum LineFrequency : uint8_t { | ||||
|   LINE_FREQUENCY_50HZ = 50, | ||||
|   LINE_FREQUENCY_60HZ = 60, | ||||
| }; | ||||
|  | ||||
| class BL0942 : public PollingComponent, public uart::UARTDevice { | ||||
|  public: | ||||
|   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_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_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 update() override; | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
| @@ -53,15 +127,25 @@ class BL0942 : public PollingComponent, public uart::UARTDevice { | ||||
|  | ||||
|   // Divide by this to turn into Watt | ||||
|   float power_reference_ = BL0942_PREF; | ||||
|   bool power_reference_set_ = false; | ||||
|   // Divide by this to turn into Volt | ||||
|   float voltage_reference_ = BL0942_UREF; | ||||
|   bool voltage_reference_set_ = false; | ||||
|   // Divide by this to turn into Ampere | ||||
|   float current_reference_ = BL0942_IREF; | ||||
|   bool current_reference_set_ = false; | ||||
|   // Divide by this to turn into kWh | ||||
|   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); | ||||
| }; | ||||
| }  // namespace bl0942 | ||||
|   | ||||
| @@ -1,32 +1,46 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, uart | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ADDRESS, | ||||
|     CONF_CURRENT, | ||||
|     CONF_ENERGY, | ||||
|     CONF_FREQUENCY, | ||||
|     CONF_ID, | ||||
|     CONF_LINE_FREQUENCY, | ||||
|     CONF_POWER, | ||||
|     CONF_VOLTAGE, | ||||
|     CONF_FREQUENCY, | ||||
|     DEVICE_CLASS_CURRENT, | ||||
|     DEVICE_CLASS_ENERGY, | ||||
|     DEVICE_CLASS_FREQUENCY, | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     DEVICE_CLASS_FREQUENCY, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     STATE_CLASS_TOTAL_INCREASING, | ||||
|     UNIT_AMPERE, | ||||
|     UNIT_HERTZ, | ||||
|     UNIT_KILOWATT_HOURS, | ||||
|     UNIT_VOLT, | ||||
|     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"] | ||||
|  | ||||
| bl0942_ns = cg.esphome_ns.namespace("bl0942") | ||||
| 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 = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
| @@ -45,22 +59,35 @@ CONFIG_SCHEMA = ( | ||||
|             ), | ||||
|             cv.Optional(CONF_POWER): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_WATT, | ||||
|                 accuracy_decimals=0, | ||||
|                 accuracy_decimals=1, | ||||
|                 device_class=DEVICE_CLASS_POWER, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_ENERGY): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||
|                 accuracy_decimals=0, | ||||
|                 accuracy_decimals=3, | ||||
|                 device_class=DEVICE_CLASS_ENERGY, | ||||
|                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||
|             ), | ||||
|             cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_HERTZ, | ||||
|                 accuracy_decimals=0, | ||||
|                 accuracy_decimals=2, | ||||
|                 device_class=DEVICE_CLASS_FREQUENCY, | ||||
|                 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")) | ||||
| @@ -88,3 +115,14 @@ async def to_code(config): | ||||
|     if frequency_config := config.get(CONF_FREQUENCY): | ||||
|         sens = await sensor.new_sensor(frequency_config) | ||||
|         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_AUTO_CONNECT = "auto_connect" | ||||
|  | ||||
| # Espressif platformio framework is built with MAX_BLE_CONN to 3, so | ||||
| # enforce this in yaml checks. | ||||
| MULTI_CONF = 3 | ||||
| MULTI_CONF = True | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|   | ||||
| @@ -41,7 +41,7 @@ CONFIG_SCHEMA = cv.All( | ||||
|             cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, | ||||
|             cv.Optional(CONF_IBEACON_MAJOR): 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_MIN_RSSI): cv.All( | ||||
|                 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)) | ||||
|  | ||||
|     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)) | ||||
|  | ||||
|         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)); | ||||
|  | ||||
|     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); | ||||
|   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() { | ||||
|   ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); | ||||
|   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() { | ||||
|   | ||||
| @@ -1,96 +1,5 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_PRESSURE, | ||||
|     CONF_TEMPERATURE, | ||||
|     DEVICE_CLASS_PRESSURE, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_HECTOPASCAL, | ||||
|     CONF_IIR_FILTER, | ||||
|     CONF_OVERSAMPLING, | ||||
|  | ||||
| CONFIG_SCHEMA = cv.invalid( | ||||
|     "The bmp280 sensor component has been renamed to bmp280_i2c." | ||||
| ) | ||||
|  | ||||
| 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/log.h" | ||||
| 
 | ||||
| namespace esphome { | ||||
| namespace bmp280 { | ||||
| namespace bmp280_base { | ||||
| 
 | ||||
| static const char *const TAG = "bmp280.sensor"; | ||||
| 
 | ||||
| @@ -59,6 +59,14 @@ static const char *iir_filter_to_str(BMP280IIRFilter filter) { | ||||
| void BMP280Component::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up BMP280..."); | ||||
|   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)) { | ||||
|     this->error_code_ = COMMUNICATION_FAILED; | ||||
|     this->mark_failed(); | ||||
| @@ -122,7 +130,6 @@ void BMP280Component::setup() { | ||||
| } | ||||
| void BMP280Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "BMP280:"); | ||||
|   LOG_I2C_DEVICE(this); | ||||
|   switch (this->error_code_) { | ||||
|     case COMMUNICATION_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); } | ||||
| 
 | ||||
| }  // namespace bmp280
 | ||||
| }  // namespace bmp280_base
 | ||||
| }  // namespace esphome
 | ||||
| @@ -2,10 +2,9 @@ | ||||
| 
 | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
| 
 | ||||
| namespace esphome { | ||||
| namespace bmp280 { | ||||
| namespace bmp280_base { | ||||
| 
 | ||||
| /// Internal struct storing the calibration values of an BMP280.
 | ||||
| struct BMP280CalibrationData { | ||||
| @@ -50,8 +49,8 @@ enum BMP280IIRFilter { | ||||
|   BMP280_IIR_FILTER_16X = 0b100, | ||||
| }; | ||||
| 
 | ||||
| /// This class implements support for the BMP280 Temperature+Pressure i2c sensor.
 | ||||
| class BMP280Component : public PollingComponent, public i2c::I2CDevice { | ||||
| /// This class implements support for the BMP280 Temperature+Pressure sensor.
 | ||||
| class BMP280Component : public PollingComponent { | ||||
|  public: | ||||
|   void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_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; | ||||
|   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: | ||||
|   /// Read the temperature value and store the calculated ambient temperature in t_fine.
 | ||||
|   float read_temperature_(int32_t *t_fine); | ||||
| @@ -90,5 +94,5 @@ class BMP280Component : public PollingComponent, public i2c::I2CDevice { | ||||
|   } error_code_{NONE}; | ||||
| }; | ||||
| 
 | ||||
| }  // namespace bmp280
 | ||||
| }  // namespace bmp280_base
 | ||||
| }  // 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" | ||||
| #ifdef USE_CAPTIVE_PORTAL | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/components/wifi/wifi_component.h" | ||||
| @@ -91,3 +92,4 @@ CaptivePortal *global_captive_portal = nullptr;  // NOLINT(cppcoreguidelines-avo | ||||
|  | ||||
| }  // namespace captive_portal | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/defines.h" | ||||
| #ifdef USE_CAPTIVE_PORTAL | ||||
| #include <memory> | ||||
| #ifdef USE_ARDUINO | ||||
| #include <DNSServer.h> | ||||
| @@ -71,3 +72,4 @@ extern CaptivePortal *global_captive_portal;  // NOLINT(cppcoreguidelines-avoid- | ||||
|  | ||||
| }  // namespace captive_portal | ||||
| }  // 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> | ||||
| #elif defined(USE_ESP32_VARIANT_ESP32S3) | ||||
| #include <esp32s3/rom/rtc.h> | ||||
| #elif defined(USE_ESP32_VARIANT_ESP32H2) | ||||
| #include <esp32h2/rom/rtc.h> | ||||
| #endif | ||||
| #ifdef USE_ARDUINO | ||||
| #include <Esp.h> | ||||
| @@ -61,7 +63,7 @@ std::string DebugComponent::get_reset_reason_() { | ||||
|     case RTCWDT_SYS_RESET: | ||||
|       reset_reason = "RTC Watch Dog Reset Digital Core"; | ||||
|       break; | ||||
| #if !defined(USE_ESP32_VARIANT_ESP32C6) | ||||
| #if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2) | ||||
|     case INTRUSION_RESET: | ||||
|       reset_reason = "Intrusion Reset CPU"; | ||||
|       break; | ||||
|   | ||||
| @@ -1,15 +1,15 @@ | ||||
| from esphome import automation, core | ||||
| from esphome.automation import maybe_simple_id | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import core, automation | ||||
| from esphome.automation import maybe_simple_id | ||||
| from esphome.const import ( | ||||
|     CONF_AUTO_CLEAR_ENABLED, | ||||
|     CONF_FROM, | ||||
|     CONF_ID, | ||||
|     CONF_LAMBDA, | ||||
|     CONF_PAGES, | ||||
|     CONF_PAGE_ID, | ||||
|     CONF_PAGES, | ||||
|     CONF_ROTATION, | ||||
|     CONF_FROM, | ||||
|     CONF_TO, | ||||
|     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) | ||||
| async def to_code(config): | ||||
|     cg.add_global(display_ns.using) | ||||
|     cg.add_define("USE_DISPLAY") | ||||
|   | ||||
| @@ -256,6 +256,7 @@ bool Dsmr::parse_telegram() { | ||||
|   MyData data; | ||||
|   ESP_LOGV(TAG, "Trying to parse telegram"); | ||||
|   this->stop_requesting_data_(); | ||||
|  | ||||
|   ::dsmr::ParseResult<void> res = | ||||
|       ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false, | ||||
|                               this->crc_check_);  // Parse telegram according to data definition. Ignore unknown values. | ||||
| @@ -267,6 +268,11 @@ bool Dsmr::parse_telegram() { | ||||
|   } else { | ||||
|     this->status_clear_warning(); | ||||
|     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; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -85,6 +85,9 @@ class Dsmr : public Component, public uart::UARTDevice { | ||||
|   void set_##s(text_sensor::TextSensor *sensor) { s_##s##_ = sensor; } | ||||
|   DSMR_TEXT_SENSOR_LIST(DSMR_SET_TEXT_SENSOR, ) | ||||
|  | ||||
|   // handled outside dsmr | ||||
|   void set_telegram(text_sensor::TextSensor *sensor) { s_telegram_ = sensor; } | ||||
|  | ||||
|  protected: | ||||
|   void receive_telegram_(); | ||||
|   void receive_encrypted_telegram_(); | ||||
| @@ -124,6 +127,9 @@ class Dsmr : public Component, public uart::UARTDevice { | ||||
|   bool header_found_{false}; | ||||
|   bool footer_found_{false}; | ||||
|  | ||||
|   // handled outside dsmr | ||||
|   text_sensor::TextSensor *s_telegram_{nullptr}; | ||||
|  | ||||
| // Sensor member pointers | ||||
| #define DSMR_DECLARE_SENSOR(s) sensor::Sensor *s_##s##_{nullptr}; | ||||
|   DSMR_SENSOR_LIST(DSMR_DECLARE_SENSOR, ) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import text_sensor | ||||
|  | ||||
| from esphome.const import CONF_INTERNAL | ||||
| from . import Dsmr, CONF_DSMR_ID | ||||
|  | ||||
| AUTO_LOAD = ["dsmr"] | ||||
| @@ -22,6 +22,9 @@ CONFIG_SCHEMA = cv.Schema( | ||||
|         cv.Optional("water_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("telegram"): text_sensor.text_sensor_schema().extend( | ||||
|             {cv.Optional(CONF_INTERNAL, default=True): cv.boolean} | ||||
|         ), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
| @@ -37,6 +40,8 @@ async def to_code(config): | ||||
|         if id and id.type == text_sensor.TextSensor: | ||||
|             var = await text_sensor.new_text_sensor(conf) | ||||
|             cg.add(getattr(hub, f"set_{key}")(var)) | ||||
|             if key != "telegram": | ||||
|                 # telegram is not handled by dsmr | ||||
|                 text_sensors.append(f"F({key})") | ||||
|  | ||||
|     if text_sensors: | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| #include "e131.h" | ||||
| #ifdef USE_NETWORK | ||||
| #include "e131_addressable_light_effect.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| @@ -118,3 +119,4 @@ bool E131Component::process_(int universe, const E131Packet &packet) { | ||||
|  | ||||
| }  // namespace e131 | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/defines.h" | ||||
| #ifdef USE_NETWORK | ||||
| #include "esphome/components/socket/socket.h" | ||||
| #include "esphome/core/component.h" | ||||
|  | ||||
| @@ -53,3 +54,4 @@ class E131Component : public esphome::Component { | ||||
|  | ||||
| }  // namespace e131 | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #include "e131_addressable_light_effect.h" | ||||
| #include "e131.h" | ||||
| #ifdef USE_NETWORK | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| @@ -90,3 +91,4 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet | ||||
|  | ||||
| }  // namespace e131 | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/light/addressable_light_effect.h" | ||||
|  | ||||
| #ifdef USE_NETWORK | ||||
| namespace esphome { | ||||
| namespace e131 { | ||||
|  | ||||
| @@ -42,3 +42,4 @@ class E131AddressableLightEffect : public light::AddressableLightEffect { | ||||
|  | ||||
| }  // namespace e131 | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #include <cstring> | ||||
| #include "e131.h" | ||||
| #ifdef USE_NETWORK | ||||
| #include "esphome/components/network/ip_address.h" | ||||
| #include "esphome/core/log.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 esphome | ||||
| #endif | ||||
|   | ||||
| @@ -31,6 +31,13 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) { | ||||
|   memcpy(ret.uuid_.uuid.uuid128, data, ESP_UUID_LEN_128); | ||||
|   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 ret; | ||||
|   if (data.length() == 4) { | ||||
|   | ||||
| @@ -20,6 +20,7 @@ class ESPBTUUID { | ||||
|   static ESPBTUUID from_uint32(uint32_t uuid); | ||||
|  | ||||
|   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); | ||||
|  | ||||
|   | ||||
| @@ -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()); | ||||
|   } | ||||
|   for (auto &data : this->manufacturer_datas_) { | ||||
|     ESP_LOGVV(TAG, "  Manufacturer data: %s", format_hex_pretty(data.data).c_str()); | ||||
|     if (this->get_ibeacon().has_value()) { | ||||
|       auto ibeacon = this->get_ibeacon().value(); | ||||
|       ESP_LOGVV(TAG, "    iBeacon data:"); | ||||
|       ESP_LOGVV(TAG, "      UUID: %s", ibeacon.get_uuid().to_string().c_str()); | ||||
|       ESP_LOGVV(TAG, "      Major: %u", ibeacon.get_major()); | ||||
|       ESP_LOGVV(TAG, "      Minor: %u", ibeacon.get_minor()); | ||||
|       ESP_LOGVV(TAG, "      TXPower: %d", ibeacon.get_signal_power()); | ||||
|     auto ibeacon = ESPBLEiBeacon::from_manufacturer_data(data); | ||||
|     if (ibeacon.has_value()) { | ||||
|       ESP_LOGVV(TAG, "  Manufacturer iBeacon:"); | ||||
|       ESP_LOGVV(TAG, "    UUID: %s", ibeacon.value().get_uuid().to_string().c_str()); | ||||
|       ESP_LOGVV(TAG, "    Major: %u", ibeacon.value().get_major()); | ||||
|       ESP_LOGVV(TAG, "    Minor: %u", ibeacon.value().get_minor()); | ||||
|       ESP_LOGVV(TAG, "    TXPower: %d", ibeacon.value().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_) { | ||||
| @@ -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, "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 | ||||
| } | ||||
| 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); | ||||
|   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_minor() { return ((this->beacon_data_.minor & 0xFF) << 8) | (this->beacon_data_.minor >> 8); } | ||||
|   uint16_t get_major() { return byteswap(this->beacon_data_.major); } | ||||
|   uint16_t get_minor() { return byteswap(this->beacon_data_.minor); } | ||||
|   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: | ||||
|   struct { | ||||
|   | ||||
| @@ -140,6 +140,8 @@ CONF_TEST_PATTERN = "test_pattern" | ||||
| # framerates | ||||
| CONF_MAX_FRAMERATE = "max_framerate" | ||||
| CONF_IDLE_FRAMERATE = "idle_framerate" | ||||
| # frame buffer | ||||
| CONF_FRAME_BUFFER_COUNT = "frame_buffer_count" | ||||
|  | ||||
| # stream trigger | ||||
| 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.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.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||
| @@ -285,6 +288,7 @@ async def to_code(config): | ||||
|         cg.add(var.set_idle_update_interval(0)) | ||||
|     else: | ||||
|         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_define("USE_ESP32_CAMERA") | ||||
|   | ||||
| @@ -127,7 +127,7 @@ void ESP32Camera::dump_config() { | ||||
|   sensor_t *s = esp_camera_sensor_get(); | ||||
|   auto st = s->status; | ||||
|   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, "  Brightness: %d", st.brightness); | ||||
|   ESP_LOGCONFIG(TAG, "  Saturation: %d", st.saturation); | ||||
| @@ -212,6 +212,8 @@ ESP32Camera::ESP32Camera() { | ||||
|   this->config_.frame_size = FRAMESIZE_VGA;  // 640x480 | ||||
|   this->config_.jpeg_quality = 10; | ||||
|   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; | ||||
| } | ||||
| @@ -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) { | ||||
|   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) ---------------- */ | ||||
| void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) { | ||||
|   | ||||
| @@ -145,6 +145,9 @@ class ESP32Camera : public Component, public EntityBase { | ||||
|   /* -- framerates */ | ||||
|   void set_max_update_interval(uint32_t max_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) */ | ||||
|   void setup() override; | ||||
|   | ||||
| @@ -1,18 +1,23 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import canbus | ||||
| from esphome.const import CONF_ID, CONF_RX_PIN, CONF_TX_PIN | ||||
| from esphome.components.canbus import CanbusComponent, CanSpeed, CONF_BIT_RATE | ||||
|  | ||||
| from esphome.components.canbus import CONF_BIT_RATE, CanbusComponent, CanSpeed | ||||
| from esphome.components.esp32 import get_esp32_variant | ||||
| from esphome.components.esp32.const import ( | ||||
|     VARIANT_ESP32, | ||||
|     VARIANT_ESP32S2, | ||||
|     VARIANT_ESP32S3, | ||||
|     VARIANT_ESP32C3, | ||||
|     VARIANT_ESP32C6, | ||||
|     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"] | ||||
| @@ -77,6 +82,8 @@ CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend( | ||||
|         cv.Optional(CONF_BIT_RATE, default="125KBPS"): validate_bit_rate, | ||||
|         cv.Required(CONF_RX_PIN): pins.internal_gpio_input_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_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() { | ||||
|   twai_general_config_t g_config = | ||||
|       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_timing_config_t t_config; | ||||
|  | ||||
| @@ -111,6 +118,7 @@ canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) { | ||||
|       .flags = flags, | ||||
|       .identifier = frame->can_id, | ||||
|       .data_length_code = frame->can_data_length_code, | ||||
|       .data = {},  // to suppress warning, data is initialized properly below | ||||
|   }; | ||||
|   if (!frame->remote_transmission_request) { | ||||
|     memcpy(message.data, frame->data, frame->can_data_length_code); | ||||
|   | ||||
| @@ -12,6 +12,8 @@ class ESP32Can : public canbus::Canbus { | ||||
|  public: | ||||
|   void set_rx(int rx) { rx_ = rx; } | ||||
|   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(){}; | ||||
|  | ||||
|  protected: | ||||
| @@ -21,6 +23,8 @@ class ESP32Can : public canbus::Canbus { | ||||
|  | ||||
|   int rx_{-1}; | ||||
|   int tx_{-1}; | ||||
|   optional<uint32_t> tx_queue_len_{}; | ||||
|   optional<uint32_t> rx_queue_len_{}; | ||||
| }; | ||||
|  | ||||
| }  // namespace esp32_can | ||||
|   | ||||
| @@ -38,7 +38,8 @@ void ESP32RMTLEDStripLightOutput::setup() { | ||||
|   } | ||||
|  | ||||
|   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; | ||||
|   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, | ||||
|                                                  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; | ||||
|  | ||||
|   // 0-bit | ||||
| @@ -79,6 +80,11 @@ void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bi | ||||
|   this->bit1_.level0 = 1; | ||||
|   this->bit1_.duration1 = (uint32_t) (ratio * bit1_low); | ||||
|   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) { | ||||
| @@ -118,6 +124,12 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | ||||
|     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) { | ||||
|     ESP_LOGE(TAG, "RMT TX error"); | ||||
|     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. | ||||
|   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_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; } | ||||
| @@ -75,7 +76,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | ||||
|   bool is_rgbw_; | ||||
|   bool is_wrgb_; | ||||
|  | ||||
|   rmt_item32_t bit0_, bit1_; | ||||
|   rmt_item32_t bit0_, bit1_, reset_; | ||||
|   RGBOrder rgb_order_; | ||||
|   rmt_channel_t channel_; | ||||
|  | ||||
|   | ||||
| @@ -43,13 +43,16 @@ class LEDStripTimings: | ||||
|     bit0_low: int | ||||
|     bit1_high: int | ||||
|     bit1_low: int | ||||
|     reset_high: int | ||||
|     reset_low: int | ||||
|  | ||||
|  | ||||
| CHIPSETS = { | ||||
|     "WS2812": LEDStripTimings(400, 1000, 1000, 400), | ||||
|     "SK6812": LEDStripTimings(300, 900, 600, 600), | ||||
|     "APA106": LEDStripTimings(350, 1360, 1360, 350), | ||||
|     "SM16703": LEDStripTimings(300, 900, 900, 300), | ||||
|     "WS2811": LEDStripTimings(300, 1090, 1090, 320, 0, 300000), | ||||
|     "WS2812": LEDStripTimings(400, 1000, 1000, 400, 0, 0), | ||||
|     "SK6812": LEDStripTimings(300, 900, 600, 600, 0, 0), | ||||
|     "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_BIT1_HIGH = "bit1_high" | ||||
| CONF_BIT1_LOW = "bit1_low" | ||||
| CONF_RESET_HIGH = "reset_high" | ||||
| CONF_RESET_LOW = "reset_low" | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
| @@ -88,6 +93,14 @@ CONFIG_SCHEMA = cv.All( | ||||
|                 CONF_BIT1_LOW, | ||||
|                 "custom", | ||||
|             ): 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), | ||||
| @@ -113,6 +126,8 @@ async def to_code(config): | ||||
|                 chipset.bit0_low, | ||||
|                 chipset.bit1_high, | ||||
|                 chipset.bit1_low, | ||||
|                 chipset.reset_high, | ||||
|                 chipset.reset_low, | ||||
|             ) | ||||
|         ) | ||||
|     else: | ||||
| @@ -122,6 +137,8 @@ async def to_code(config): | ||||
|                 config[CONF_BIT0_LOW], | ||||
|                 config[CONF_BIT1_HIGH], | ||||
|                 config[CONF_BIT1_LOW], | ||||
|                 config[CONF_RESET_HIGH], | ||||
|                 config[CONF_RESET_LOW], | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| #include "ota_esphome.h" | ||||
|  | ||||
| #ifdef USE_OTA | ||||
| #include "esphome/components/md5/md5.h" | ||||
| #include "esphome/components/network/util.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_; } | ||||
| void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; } | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/defines.h" | ||||
| #ifdef USE_OTA | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/preferences.h" | ||||
| #include "esphome/components/ota/ota_backend.h" | ||||
| @@ -41,3 +42,4 @@ class ESPHomeOTAComponent : public ota::OTAComponent { | ||||
| }; | ||||
|  | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -1,43 +1,35 @@ | ||||
| import functools | ||||
| import hashlib | ||||
| import logging | ||||
|  | ||||
| import functools | ||||
| from pathlib import Path | ||||
| import os | ||||
| from pathlib import Path | ||||
| import re | ||||
|  | ||||
| from packaging import version | ||||
| import requests | ||||
|  | ||||
| from esphome import core | ||||
| from esphome import external_files | ||||
| import esphome.config_validation as cv | ||||
| from esphome import core, external_files | ||||
| import esphome.codegen as cg | ||||
| from esphome.helpers import ( | ||||
|     copy_file_if_changed, | ||||
|     cpp_string_escape, | ||||
| ) | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_FAMILY, | ||||
|     CONF_FILE, | ||||
|     CONF_GLYPHS, | ||||
|     CONF_ID, | ||||
|     CONF_PATH, | ||||
|     CONF_RAW_DATA_ID, | ||||
|     CONF_TYPE, | ||||
|     CONF_REFRESH, | ||||
|     CONF_SIZE, | ||||
|     CONF_PATH, | ||||
|     CONF_WEIGHT, | ||||
|     CONF_TYPE, | ||||
|     CONF_URL, | ||||
|     CONF_WEIGHT, | ||||
| ) | ||||
| from esphome.core import ( | ||||
|     CORE, | ||||
|     HexInt, | ||||
| ) | ||||
| from esphome.core import CORE, HexInt | ||||
| from esphome.helpers import copy_file_if_changed, cpp_string_escape | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| DOMAIN = "font" | ||||
| DEPENDENCIES = ["display"] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| CODEOWNERS = ["@esphome/core", "@clydebarrow"] | ||||
| @@ -400,10 +392,7 @@ class EFont: | ||||
|  | ||||
|  | ||||
| def convert_bitmap_to_pillow_font(filepath): | ||||
|     from PIL import ( | ||||
|         PcfFontFile, | ||||
|         BdfFontFile, | ||||
|     ) | ||||
|     from PIL import BdfFontFile, PcfFontFile | ||||
|  | ||||
|     local_bitmap_font_file = external_files.compute_local_file_dir( | ||||
|         DOMAIN, | ||||
|   | ||||
| @@ -1,9 +1,8 @@ | ||||
| #include "font.h" | ||||
|  | ||||
| #include "esphome/core/color.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/color.h" | ||||
| #include "esphome/components/display/display_buffer.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace font { | ||||
| @@ -68,6 +67,7 @@ int Font::match_next_glyph(const uint8_t *str, int *match_length) { | ||||
|     return -1; | ||||
|   return lo; | ||||
| } | ||||
| #ifdef USE_DISPLAY | ||||
| void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) { | ||||
|   *baseline = this->baseline_; | ||||
|   *height = this->height_; | ||||
| @@ -164,6 +164,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo | ||||
|     i += match_length; | ||||
|   } | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace font | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -1,8 +1,11 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/datatypes.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 font { | ||||
| @@ -38,7 +41,11 @@ class Glyph { | ||||
|   const GlyphData *glyph_data_; | ||||
| }; | ||||
|  | ||||
| class Font : public display::BaseFont { | ||||
| class Font | ||||
| #ifdef USE_DISPLAY | ||||
|     : public display::BaseFont | ||||
| #endif | ||||
| { | ||||
|  public: | ||||
|   /** 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); | ||||
|  | ||||
| #ifdef USE_DISPLAY | ||||
|   void print(int x_start, int y_start, display::Display *display, Color color, const char *text, | ||||
|              Color background) 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_height() { return this->height_; } | ||||
|   inline int get_bpp() { return this->bpp_; } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import climate_ir | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_ID, CONF_MODEL | ||||
|  | ||||
| CODEOWNERS = ["@orestismers"] | ||||
| @@ -17,6 +17,7 @@ MODELS = { | ||||
|     "yaa": Model.GREE_YAA, | ||||
|     "yac": Model.GREE_YAC, | ||||
|     "yac1fb9": Model.GREE_YAC1FB9, | ||||
|     "yx1ff": Model.GREE_YX1FF, | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( | ||||
|   | ||||
| @@ -6,7 +6,15 @@ namespace gree { | ||||
|  | ||||
| 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() { | ||||
|   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[1] = this->temperature_(); | ||||
|  | ||||
|   if (this->model_ == GREE_YAN) { | ||||
|   if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF) { | ||||
|     remote_state[2] = 0x60; | ||||
|     remote_state[3] = 0x50; | ||||
|     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 | ||||
|   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); | ||||
|   } else { | ||||
|     remote_state[7] = | ||||
| @@ -124,6 +142,23 @@ uint8_t GreeClimate::operation_mode_() { | ||||
| } | ||||
|  | ||||
| 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()) { | ||||
|     case climate::CLIMATE_FAN_LOW: | ||||
|       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)); | ||||
| } | ||||
|  | ||||
| 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 esphome | ||||
|   | ||||
| @@ -25,7 +25,6 @@ const uint8_t GREE_FAN_AUTO = 0x00; | ||||
| const uint8_t GREE_FAN_1 = 0x10; | ||||
| const uint8_t GREE_FAN_2 = 0x20; | ||||
| const uint8_t GREE_FAN_3 = 0x30; | ||||
| const uint8_t GREE_FAN_TURBO = 0x80; | ||||
|  | ||||
| // IR Transmission | ||||
| 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_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 | ||||
| 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 { | ||||
|  public: | ||||
| @@ -93,6 +100,7 @@ class GreeClimate : public climate_ir::ClimateIR { | ||||
|   uint8_t horizontal_swing_(); | ||||
|   uint8_t vertical_swing_(); | ||||
|   uint8_t temperature_(); | ||||
|   uint8_t preset_(); | ||||
|  | ||||
|   Model model_{}; | ||||
| }; | ||||
|   | ||||
| @@ -1,11 +1,10 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
|  | ||||
| from esphome import pins | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import i2c, touchscreen | ||||
| from esphome.const import CONF_INTERRUPT_PIN, CONF_ID | ||||
| from .. import gt911_ns | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN | ||||
|  | ||||
| from .. import gt911_ns | ||||
|  | ||||
| GT911ButtonListener = gt911_ns.class_("GT911ButtonListener") | ||||
| GT911Touchscreen = gt911_ns.class_( | ||||
| @@ -18,6 +17,7 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(GT911Touchscreen), | ||||
|         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)) | ||||
|  | ||||
| @@ -29,3 +29,5 @@ async def to_code(config): | ||||
|  | ||||
|     if interrupt_pin := config.get(CONF_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() { | ||||
|   i2c::ErrorCode err; | ||||
|   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. | ||||
|   uint8_t data[4]; | ||||
|   | ||||
| @@ -19,12 +19,14 @@ class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice | ||||
|   void dump_config() override; | ||||
|  | ||||
|   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); } | ||||
|  | ||||
|  protected: | ||||
|   void update_touches() override; | ||||
|  | ||||
|   InternalGPIOPin *interrupt_pin_{}; | ||||
|   GPIOPin *reset_pin_{}; | ||||
|   std::vector<GT911ButtonListener *> button_listeners_; | ||||
|   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"] | ||||
| 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( | ||||
|     { | ||||
|         cv.Required(CONF_ENTITY_ID): cv.entity_id, | ||||
|   | ||||
| @@ -7,19 +7,32 @@ from .. import ( | ||||
|     HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA, | ||||
|     homeassistant_ns, | ||||
|     setup_home_assistant_entity, | ||||
|     validate_entity_domain, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@Links2004"] | ||||
| DEPENDENCIES = ["api"] | ||||
|  | ||||
| SUPPORTED_DOMAINS = [ | ||||
|     "automation", | ||||
|     "fan", | ||||
|     "humidifier", | ||||
|     "input_boolean", | ||||
|     "light", | ||||
|     "remote", | ||||
|     "siren", | ||||
|     "switch", | ||||
| ] | ||||
|  | ||||
| HomeassistantSwitch = homeassistant_ns.class_( | ||||
|     "HomeassistantSwitch", switch.Switch, cg.Component | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     switch.switch_schema(HomeassistantSwitch) | ||||
|     .extend(cv.COMPONENT_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; | ||||
|   if (state) { | ||||
|     resp.service = "switch.turn_on"; | ||||
|     resp.service = "homeassistant.turn_on"; | ||||
|   } else { | ||||
|     resp.service = "switch.turn_off"; | ||||
|     resp.service = "homeassistant.turn_off"; | ||||
|   } | ||||
|  | ||||
|   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.const import CONF_ID | ||||
| import esphome.codegen as cg | ||||
| from esphome.components.esp32 import get_esp32_variant | ||||
| from esphome.components.esp32.const import ( | ||||
|     VARIANT_ESP32, | ||||
|     VARIANT_ESP32C3, | ||||
|     VARIANT_ESP32S2, | ||||
|     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"] | ||||
| DEPENDENCIES = ["esp32"] | ||||
| @@ -25,16 +25,26 @@ CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin" | ||||
| CONF_I2S_AUDIO = "i2s_audio" | ||||
| CONF_I2S_AUDIO_ID = "i2s_audio_id" | ||||
|  | ||||
| CONF_BITS_PER_SAMPLE = "bits_per_sample" | ||||
| CONF_I2S_MODE = "i2s_mode" | ||||
| CONF_PRIMARY = "primary" | ||||
| 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") | ||||
| I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component) | ||||
| I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", cg.Parented.template(I2SAudioComponent)) | ||||
| I2SAudioOut = i2s_audio_ns.class_( | ||||
|     "I2SAudioOut", cg.Parented.template(I2SAudioComponent) | ||||
| I2SAudioBase = i2s_audio_ns.class_( | ||||
|     "I2SAudioBase", 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_OPTIONS = { | ||||
| @@ -50,6 +60,75 @@ I2S_PORTS = { | ||||
|     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( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(I2SAudioComponent), | ||||
|   | ||||
| @@ -11,9 +11,27 @@ namespace i2s_audio { | ||||
|  | ||||
| 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 { | ||||
|  public: | ||||
|   | ||||
| @@ -12,6 +12,10 @@ from .. import ( | ||||
|     I2SAudioOut, | ||||
|     CONF_I2S_AUDIO_ID, | ||||
|     CONF_I2S_DOUT_PIN, | ||||
|     CONF_LEFT, | ||||
|     CONF_RIGHT, | ||||
|     CONF_MONO, | ||||
|     CONF_STEREO, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@jesserockz"] | ||||
| @@ -30,12 +34,12 @@ CONF_DAC_TYPE = "dac_type" | ||||
| CONF_I2S_COMM_FMT = "i2s_comm_fmt" | ||||
|  | ||||
| INTERNAL_DAC_OPTIONS = { | ||||
|     "left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, | ||||
|     "right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN, | ||||
|     "stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN, | ||||
|     CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, | ||||
|     CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_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] | ||||
|  | ||||
|   | ||||
| @@ -23,7 +23,7 @@ enum I2SState : uint8_t { | ||||
|   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: | ||||
|   void setup() override; | ||||
|   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.const import CONF_CHANNEL, CONF_ID, CONF_NUMBER, CONF_SAMPLE_RATE | ||||
| from esphome.components import microphone, esp32 | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import esp32, microphone | ||||
| 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 ( | ||||
|     CONF_I2S_MODE, | ||||
|     CONF_PRIMARY, | ||||
|     I2S_MODE_OPTIONS, | ||||
|     i2s_audio_ns, | ||||
|     I2SAudioComponent, | ||||
|     I2SAudioIn, | ||||
|     CONF_I2S_AUDIO_ID, | ||||
|     CONF_I2S_DIN_PIN, | ||||
|     CONF_RIGHT, | ||||
|     I2SAudioIn, | ||||
|     i2s_audio_component_schema, | ||||
|     i2s_audio_ns, | ||||
|     register_i2s_audio_component, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@jesserockz"] | ||||
| @@ -23,29 +20,14 @@ DEPENDENCIES = ["i2s_audio"] | ||||
| CONF_ADC_PIN = "adc_pin" | ||||
| CONF_ADC_TYPE = "adc_type" | ||||
| CONF_PDM = "pdm" | ||||
| CONF_BITS_PER_SAMPLE = "bits_per_sample" | ||||
| CONF_USE_APLL = "use_apll" | ||||
|  | ||||
| I2SAudioMicrophone = i2s_audio_ns.class_( | ||||
|     "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] | ||||
| PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3] | ||||
|  | ||||
| _validate_bits = cv.float_with_unit("bits", "bit") | ||||
|  | ||||
|  | ||||
| def validate_esp32_variant(config): | ||||
|     variant = esp32.get_esp32_variant() | ||||
| @@ -62,21 +44,15 @@ def validate_esp32_variant(config): | ||||
|  | ||||
|  | ||||
| BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(I2SAudioMicrophone), | ||||
|         cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), | ||||
|         cv.Optional(CONF_CHANNEL, default="right"): cv.enum(CHANNELS), | ||||
|         cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1), | ||||
|         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 | ||||
|         ), | ||||
|     } | ||||
|     i2s_audio_component_schema( | ||||
|         I2SAudioMicrophone, | ||||
|         default_sample_rate=16000, | ||||
|         default_channel=CONF_RIGHT, | ||||
|         default_bits_per_sample="32bit", | ||||
|     ) | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.typed_schema( | ||||
|         { | ||||
| @@ -88,7 +64,7 @@ CONFIG_SCHEMA = cv.All( | ||||
|             "external": BASE_SCHEMA.extend( | ||||
|                 { | ||||
|                     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): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) | ||||
|     await register_i2s_audio_component(var, config) | ||||
|     await microphone.register_microphone(var, config) | ||||
|  | ||||
|     if config[CONF_ADC_TYPE] == "internal": | ||||
|         variant = esp32.get_esp32_variant() | ||||
| @@ -112,11 +88,3 @@ async def to_code(config): | ||||
|     else: | ||||
|         cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN])) | ||||
|         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, | ||||
|       .fixed_mclk = 0, | ||||
|       .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; | ||||
| @@ -167,19 +167,22 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) { | ||||
|     return 0; | ||||
|   } | ||||
|   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, | ||||
|   // and 24-bit data to 32 bits. | ||||
|   switch (this->bits_per_sample_) { | ||||
|     case I2S_BITS_PER_SAMPLE_8BIT: | ||||
|     case I2S_BITS_PER_SAMPLE_16BIT: | ||||
|       return bytes_read; | ||||
|   } else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) { | ||||
|     std::vector<int16_t> samples; | ||||
|     case I2S_BITS_PER_SAMPLE_24BIT: | ||||
|     case I2S_BITS_PER_SAMPLE_32BIT: { | ||||
|       size_t samples_read = bytes_read / sizeof(int32_t); | ||||
|     samples.resize(samples_read); | ||||
|       for (size_t i = 0; i < samples_read; i++) { | ||||
|         int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14; | ||||
|       samples[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX); | ||||
|         buf[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX); | ||||
|       } | ||||
|     memcpy(buf, samples.data(), samples_read * sizeof(int16_t)); | ||||
|       return samples_read * sizeof(int16_t); | ||||
|   } else { | ||||
|     } | ||||
|     default: | ||||
|       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 | ||||
|  | ||||
|   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: | ||||
|   void start_(); | ||||
|   void stop_(); | ||||
| @@ -48,11 +41,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub | ||||
|   bool adc_{false}; | ||||
| #endif | ||||
|   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_; | ||||
| }; | ||||
|   | ||||
| @@ -1,15 +1,19 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins | ||||
| from esphome.const import CONF_ID, CONF_MODE | ||||
| import esphome.codegen as cg | ||||
| 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 ( | ||||
|     CONF_I2S_AUDIO_ID, | ||||
|     CONF_I2S_DOUT_PIN, | ||||
|     I2SAudioComponent, | ||||
|     CONF_LEFT, | ||||
|     CONF_MONO, | ||||
|     CONF_RIGHT, | ||||
|     CONF_STEREO, | ||||
|     I2SAudioOut, | ||||
|     i2s_audio_component_schema, | ||||
|     i2s_audio_ns, | ||||
|     register_i2s_audio_component, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@jesserockz"] | ||||
| @@ -19,19 +23,16 @@ I2SAudioSpeaker = i2s_audio_ns.class_( | ||||
|     "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" | ||||
|  | ||||
| i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t") | ||||
| INTERNAL_DAC_OPTIONS = { | ||||
|     "left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, | ||||
|     "right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN, | ||||
|     "stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN, | ||||
|     CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, | ||||
|     CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_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] | ||||
|  | ||||
|  | ||||
| @@ -44,28 +45,40 @@ def validate_esp32_variant(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( | ||||
|     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), | ||||
|                 } | ||||
|             ).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( | ||||
|                         CONF_I2S_DOUT_PIN | ||||
|                     ): 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, | ||||
|     ), | ||||
| @@ -76,12 +89,11 @@ CONFIG_SCHEMA = cv.All( | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await register_i2s_audio_component(var, config) | ||||
|     await speaker.register_speaker(var, config) | ||||
|  | ||||
|     await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) | ||||
|  | ||||
|     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: | ||||
|         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; | ||||
| } | ||||
|  | ||||
| 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) { | ||||
|   I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params; | ||||
|  | ||||
| @@ -64,19 +79,19 @@ void I2SAudioSpeaker::player_task(void *params) { | ||||
|   xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); | ||||
|  | ||||
|   i2s_driver_config_t config = { | ||||
|       .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX), | ||||
|       .sample_rate = 16000, | ||||
|       .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, | ||||
|       .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, | ||||
|       .mode = (i2s_mode_t) (this_speaker->i2s_mode_ | I2S_MODE_TX), | ||||
|       .sample_rate = this_speaker->sample_rate_, | ||||
|       .bits_per_sample = this_speaker->bits_per_sample_, | ||||
|       .channel_format = this_speaker->channel_, | ||||
|       .communication_format = I2S_COMM_FORMAT_STAND_I2S, | ||||
|       .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, | ||||
|       .dma_buf_count = 8, | ||||
|       .dma_buf_len = 128, | ||||
|       .use_apll = false, | ||||
|       .dma_buf_len = 256, | ||||
|       .use_apll = this_speaker->use_apll_, | ||||
|       .tx_desc_auto_clear = true, | ||||
|       .fixed_mclk = I2S_PIN_NO_CHANGE, | ||||
|       .fixed_mclk = 0, | ||||
|       .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 (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) { | ||||
| @@ -114,10 +129,11 @@ void I2SAudioSpeaker::player_task(void *params) { | ||||
|   event.type = TaskEventType::STARTED; | ||||
|   xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); | ||||
|  | ||||
|   int16_t buffer[BUFFER_SIZE / 2]; | ||||
|   int32_t buffer[BUFFER_SIZE]; | ||||
|  | ||||
|   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 | ||||
|     } | ||||
|     if (data_event.stop) { | ||||
| @@ -125,17 +141,28 @@ void I2SAudioSpeaker::player_task(void *params) { | ||||
|       xQueueReset(this_speaker->buffer_queue_);  // Flush queue | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     const uint8_t *data = data_event.data; | ||||
|     size_t remaining = data_event.len; | ||||
|     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) { | ||||
|       size_t bytes_written; | ||||
|  | ||||
|     memmove(buffer, data_event.data, data_event.len); | ||||
|     size_t remaining = data_event.len / 2; | ||||
|     size_t current = 0; | ||||
|  | ||||
|     while (remaining > 0) { | ||||
|       uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF); | ||||
|  | ||||
|       esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written, | ||||
|                                 (10 / portTICK_PERIOD_MS)); | ||||
|       esp_err_t err = | ||||
|           i2s_write(this_speaker->parent_->get_port(), data, remaining, &bytes_written, (32 / portTICK_PERIOD_MS)); | ||||
|       if (err != ESP_OK) { | ||||
|         event = {.type = TaskEventType::WARNING, .err = err}; | ||||
|         if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) { | ||||
| @@ -143,21 +170,8 @@ void I2SAudioSpeaker::player_task(void *params) { | ||||
|         } | ||||
|         continue; | ||||
|       } | ||||
|       if (bytes_written != sizeof(sample)) { | ||||
|         event = {.type = TaskEventType::WARNING, .err = ESP_FAIL}; | ||||
|         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"); | ||||
|       data += bytes_written; | ||||
|       remaining -= bytes_written; | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -213,13 +227,11 @@ void I2SAudioSpeaker::watch_() { | ||||
|       case TaskEventType::STARTED: | ||||
|         ESP_LOGD(TAG, "Started I2S Audio Speaker"); | ||||
|         this->state_ = speaker::STATE_RUNNING; | ||||
|         this->status_clear_warning(); | ||||
|         break; | ||||
|       case TaskEventType::STOPPING: | ||||
|         ESP_LOGD(TAG, "Stopping I2S Audio Speaker"); | ||||
|         break; | ||||
|       case TaskEventType::PLAYING: | ||||
|         this->status_clear_warning(); | ||||
|         break; | ||||
|       case TaskEventType::STOPPED: | ||||
|         this->state_ = speaker::STATE_STOPPED; | ||||
|         vTaskDelete(this->player_task_handle_); | ||||
|   | ||||
| @@ -21,7 +21,6 @@ static const size_t BUFFER_SIZE = 1024; | ||||
| enum class TaskEventType : uint8_t { | ||||
|   STARTING = 0, | ||||
|   STARTED, | ||||
|   PLAYING, | ||||
|   STOPPING, | ||||
|   STOPPED, | ||||
|   WARNING = 255, | ||||
| @@ -38,18 +37,18 @@ struct DataEvent { | ||||
|   uint8_t data[BUFFER_SIZE]; | ||||
| }; | ||||
|  | ||||
| class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAudioOut { | ||||
| class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Component { | ||||
|  public: | ||||
|   float get_setup_priority() const override { return esphome::setup_priority::LATE; } | ||||
|  | ||||
|   void setup() override; | ||||
|   void loop() override; | ||||
|  | ||||
|   void set_timeout(uint32_t ms) { this->timeout_ = ms; } | ||||
|   void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; } | ||||
| #if SOC_I2S_SUPPORTS_DAC | ||||
|   void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; } | ||||
| #endif | ||||
|   void set_external_dac_channels(uint8_t channels) { this->external_dac_channels_ = channels; } | ||||
|  | ||||
|   void start() override; | ||||
|   void stop() override; | ||||
| @@ -70,13 +69,13 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud | ||||
|   QueueHandle_t buffer_queue_; | ||||
|   QueueHandle_t event_queue_; | ||||
|  | ||||
|   uint32_t timeout_{0}; | ||||
|   uint8_t dout_pin_{0}; | ||||
|   bool task_created_{false}; | ||||
|  | ||||
| #if SOC_I2S_SUPPORTS_DAC | ||||
|   i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE}; | ||||
| #endif | ||||
|   uint8_t external_dac_channels_; | ||||
| }; | ||||
|  | ||||
| }  // 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