mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'dev' into bump-2023.4.0b1
This commit is contained in:
		| @@ -4,12 +4,17 @@ | ||||
|   "postCreateCommand": [ | ||||
|     "script/devcontainer-post-create" | ||||
|   ], | ||||
|   "containerEnv": { | ||||
|     "DEVCONTAINER": "1" | ||||
|   }, | ||||
|   "runArgs": [ | ||||
|     "--privileged", | ||||
|     "-e", | ||||
|     "ESPHOME_DASHBOARD_USE_PING=1" | ||||
|   ], | ||||
|   "appPort": 6052, | ||||
|   "customizations": { | ||||
|     "vscode": { | ||||
|       "extensions": [ | ||||
|         // python | ||||
|         "ms-python.python", | ||||
| @@ -51,6 +56,8 @@ | ||||
|         "files.associations": { | ||||
|           "**/.vscode/*.json": "jsonc" | ||||
|         }, | ||||
|     "C_Cpp.clang_format_path": "/usr/bin/clang-format-11", | ||||
|         "C_Cpp.clang_format_path": "/usr/bin/clang-format-13" | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -41,6 +41,10 @@ jobs: | ||||
|             file: tests/test3.yaml | ||||
|             name: Test tests/test3.yaml | ||||
|             pio_cache_key: test3 | ||||
|           - id: test | ||||
|             file: tests/test3.1.yaml | ||||
|             name: Test tests/test3.1.yaml | ||||
|             pio_cache_key: test3.1 | ||||
|           - id: test | ||||
|             file: tests/test4.yaml | ||||
|             name: Test tests/test4.yaml | ||||
| @@ -129,7 +133,7 @@ jobs: | ||||
|       - name: Install clang tools | ||||
|         run: | | ||||
|           sudo apt-get install \ | ||||
|               clang-format-11 \ | ||||
|               clang-format-13 \ | ||||
|               clang-tidy-11 | ||||
|         if: matrix.id == 'clang-tidy' || matrix.id == 'clang-format' | ||||
|  | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							| @@ -18,7 +18,7 @@ jobs: | ||||
|   stale: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/stale@v7 | ||||
|       - uses: actions/stale@v8 | ||||
|         with: | ||||
|           days-before-pr-stale: 90 | ||||
|           days-before-pr-close: 7 | ||||
| @@ -38,7 +38,7 @@ jobs: | ||||
|   close-issues: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/stale@v7 | ||||
|       - uses: actions/stale@v8 | ||||
|         with: | ||||
|           days-before-pr-stale: -1 | ||||
|           days-before-pr-close: -1 | ||||
|   | ||||
							
								
								
									
										60
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| --- | ||||
| name: Synchronise Device Classes from Home Assistant | ||||
|  | ||||
| on: | ||||
|   workflow_dispatch: | ||||
|   schedule: | ||||
|     - cron: '45 6 * * *' | ||||
|  | ||||
| permissions: | ||||
|   contents: write | ||||
|   pull-requests: write | ||||
|  | ||||
| jobs: | ||||
|   sync: | ||||
|     name: Sync Device Classes | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
|  | ||||
|       - name: Checkout Home Assistant | ||||
|         uses: actions/checkout@v3 | ||||
|         with: | ||||
|           repository: home-assistant/core | ||||
|           path: lib/home-assistant | ||||
|  | ||||
|       - name: Setup Python | ||||
|         uses: actions/setup-python@v4 | ||||
|         with: | ||||
|           python-version: 3.11 | ||||
|  | ||||
|       - name: Install Home Assistant | ||||
|         run: | | ||||
|           python -m pip install --upgrade pip | ||||
|           pip install -e lib/home-assistant | ||||
|  | ||||
|       - name: Sync | ||||
|         run: | | ||||
|           python ./script/sync-device_class.py | ||||
|  | ||||
|       - name: Get PR template | ||||
|         id: pr-template-body | ||||
|         run: | | ||||
|           body=$(cat .github/PULL_REQUEST_TEMPLATE.md) | ||||
|           delimiter="$(openssl rand -hex 8)" | ||||
|           echo "body<<$delimiter" >> $GITHUB_OUTPUT | ||||
|           echo "$body" >> $GITHUB_OUTPUT | ||||
|           echo "$delimiter" >> $GITHUB_OUTPUT | ||||
|  | ||||
|       - name: Commit changes | ||||
|         uses: peter-evans/create-pull-request@v4 | ||||
|         with: | ||||
|           commit-message: "Synchronise Device Classes from Home Assistant" | ||||
|           committer: esphomebot <esphome@nabucasa.com> | ||||
|           author: esphomebot <esphome@nabucasa.com> | ||||
|           branch: sync/device-classes/ | ||||
|           branch-suffix: timestamp | ||||
|           delete-branch: true | ||||
|           title: "Synchronise Device Classes from Home Assistant" | ||||
|           body: ${{ steps.pr-template-body.outputs.body }} | ||||
| @@ -2,8 +2,8 @@ | ||||
| # See https://pre-commit.com for more information | ||||
| # See https://pre-commit.com/hooks.html for more hooks | ||||
| repos: | ||||
|   - repo: https://github.com/ambv/black | ||||
|     rev: 23.1.0 | ||||
|   - repo: https://github.com/psf/black | ||||
|     rev: 23.3.0 | ||||
|     hooks: | ||||
|       - id: black | ||||
|         args: | ||||
|   | ||||
							
								
								
									
										15
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							| @@ -2,15 +2,24 @@ | ||||
|   "version": "2.0.0", | ||||
|   "tasks": [ | ||||
|     { | ||||
|       "label": "run", | ||||
|       "label": "Run Dashboard", | ||||
|       "type": "shell", | ||||
|       "command": "python3 -m esphome dashboard config/", | ||||
|       "command": "${command:python.interpreterPath}", | ||||
|       "args": [ | ||||
|         "-m", | ||||
|         "esphome", | ||||
|         "dashboard", | ||||
|         "config/" | ||||
|       ], | ||||
|       "problemMatcher": [] | ||||
|     }, | ||||
|     { | ||||
|       "label": "clang-tidy", | ||||
|       "type": "shell", | ||||
|       "command": "./script/clang-tidy", | ||||
|       "command": "${command:python.interpreterPath}", | ||||
|       "args": [ | ||||
|         "./script/clang-tidy" | ||||
|       ], | ||||
|       "problemMatcher": [ | ||||
|         { | ||||
|           "owner": "clang-tidy", | ||||
|   | ||||
| @@ -111,6 +111,8 @@ esphome/components/hte501/* @Stock-M | ||||
| esphome/components/hydreon_rgxx/* @functionpointer | ||||
| esphome/components/i2c/* @esphome/core | ||||
| esphome/components/i2s_audio/* @jesserockz | ||||
| esphome/components/i2s_audio/media_player/* @jesserockz | ||||
| esphome/components/i2s_audio/microphone/* @jesserockz | ||||
| esphome/components/ili9xxx/* @nielsnl68 | ||||
| esphome/components/improv_base/* @esphome/core | ||||
| esphome/components/improv_serial/* @esphome/core | ||||
| @@ -154,11 +156,13 @@ esphome/components/mcp9808/* @k7hpn | ||||
| esphome/components/md5/* @esphome/core | ||||
| esphome/components/mdns/* @esphome/core | ||||
| esphome/components/media_player/* @jesserockz | ||||
| esphome/components/microphone/* @jesserockz | ||||
| esphome/components/mics_4514/* @jesserockz | ||||
| esphome/components/midea/* @dudanov | ||||
| esphome/components/midea_ir/* @dudanov | ||||
| esphome/components/mitsubishi/* @RubyBailey | ||||
| esphome/components/mlx90393/* @functionpointer | ||||
| esphome/components/mmc5603/* @benhoff | ||||
| esphome/components/modbus_controller/* @martgras | ||||
| esphome/components/modbus_controller/binary_sensor/* @martgras | ||||
| esphome/components/modbus_controller/number/* @martgras | ||||
| @@ -286,6 +290,7 @@ esphome/components/ufire_ise/* @pvizeli | ||||
| esphome/components/ultrasonic/* @OttoWinter | ||||
| esphome/components/vbus/* @ssieb | ||||
| esphome/components/version/* @esphome/core | ||||
| esphome/components/voice_assistant/* @jesserockz | ||||
| esphome/components/wake_on_lan/* @willwill2will54 | ||||
| esphome/components/web_server_base/* @OttoWinter | ||||
| esphome/components/whirlpool/* @glmnet | ||||
|   | ||||
| @@ -135,7 +135,7 @@ RUN \ | ||||
|     apt-get update \ | ||||
|     # Use pinned versions so that we get updates with build caching | ||||
|     && apt-get install -y --no-install-recommends \ | ||||
|         clang-format-11=1:11.0.1-2 \ | ||||
|         clang-format-13=1:13.0.1-6~deb11u1 \ | ||||
|         clang-tidy-11=1:11.0.1-2 \ | ||||
|         patch=2.7.6-7 \ | ||||
|         software-properties-common=0.96.20.2-2.1 \ | ||||
|   | ||||
| @@ -152,6 +152,8 @@ def run_miniterm(config, port): | ||||
|         _LOGGER.error("Could not connect to serial port %s", port) | ||||
|         return 1 | ||||
|  | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def wrap_to_code(name, comp): | ||||
|     coro = coroutine(comp.to_code) | ||||
|   | ||||
| @@ -76,8 +76,6 @@ async def to_code(config): | ||||
|         pos = 0 | ||||
|         for frameIndex in range(frames): | ||||
|             image.seek(frameIndex) | ||||
|             if CONF_RESIZE in config: | ||||
|                 image.thumbnail(config[CONF_RESIZE]) | ||||
|             frame = image.convert("RGB") | ||||
|             if CONF_RESIZE in config: | ||||
|                 frame = frame.resize([width, height]) | ||||
|   | ||||
| @@ -53,6 +53,9 @@ service APIConnection { | ||||
|   rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {} | ||||
|   rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {} | ||||
|   rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {} | ||||
|   rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {} | ||||
|  | ||||
|   rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {} | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -208,6 +211,8 @@ message DeviceInfoResponse { | ||||
|   string manufacturer = 12; | ||||
|  | ||||
|   string friendly_name = 13; | ||||
|  | ||||
|   uint32 voice_assistant_version = 14; | ||||
| } | ||||
|  | ||||
| message ListEntitiesRequest { | ||||
| @@ -1125,6 +1130,7 @@ message MediaPlayerCommandRequest { | ||||
| message SubscribeBluetoothLEAdvertisementsRequest { | ||||
|   option (id) = 66; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_BLUETOOTH_PROXY"; | ||||
| } | ||||
|  | ||||
| message BluetoothServiceData { | ||||
| @@ -1156,6 +1162,7 @@ enum BluetoothDeviceRequestType { | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3; | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4; | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5; | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6; | ||||
| } | ||||
|  | ||||
| message BluetoothDeviceRequest { | ||||
| @@ -1359,3 +1366,71 @@ message BluetoothDeviceUnpairingResponse { | ||||
|   bool success = 2; | ||||
|   int32 error = 3; | ||||
| } | ||||
|  | ||||
| message UnsubscribeBluetoothLEAdvertisementsRequest { | ||||
|   option (id) = 87; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_BLUETOOTH_PROXY"; | ||||
| } | ||||
|  | ||||
| message BluetoothDeviceClearCacheResponse { | ||||
|   option (id) = 88; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_BLUETOOTH_PROXY"; | ||||
|  | ||||
|   uint64 address = 1; | ||||
|   bool success = 2; | ||||
|   int32 error = 3; | ||||
| } | ||||
|  | ||||
| // ==================== PUSH TO TALK ==================== | ||||
| message SubscribeVoiceAssistantRequest { | ||||
|   option (id) = 89; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
|  | ||||
|   bool subscribe = 1; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantRequest { | ||||
|   option (id) = 90; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
|  | ||||
|   bool start = 1; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantResponse { | ||||
|   option (id) = 91; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
|  | ||||
|   uint32 port = 1; | ||||
|   bool error = 2; | ||||
| } | ||||
|  | ||||
| enum VoiceAssistantEvent { | ||||
|   VOICE_ASSISTANT_ERROR = 0; | ||||
|   VOICE_ASSISTANT_RUN_START = 1; | ||||
|   VOICE_ASSISTANT_RUN_END = 2; | ||||
|   VOICE_ASSISTANT_STT_START = 3; | ||||
|   VOICE_ASSISTANT_STT_END = 4; | ||||
|   VOICE_ASSISTANT_INTENT_START = 5; | ||||
|   VOICE_ASSISTANT_INTENT_END = 6; | ||||
|   VOICE_ASSISTANT_TTS_START = 7; | ||||
|   VOICE_ASSISTANT_TTS_END = 8; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantEventData { | ||||
|   string name = 1; | ||||
|   string value = 2; | ||||
| } | ||||
|  | ||||
| message VoiceAssistantEventResponse { | ||||
|   option (id) = 92; | ||||
|   option (source) = SOURCE_CLIENT; | ||||
|   option (ifdef) = "USE_VOICE_ASSISTANT"; | ||||
|  | ||||
|   VoiceAssistantEvent event_type = 1; | ||||
|   repeated VoiceAssistantEventData data = 2; | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #include "api_connection.h" | ||||
| #include <cerrno> | ||||
| #include <cinttypes> | ||||
| #include "esphome/components/network/util.h" | ||||
| #include "esphome/core/entity_base.h" | ||||
| #include "esphome/core/hal.h" | ||||
| @@ -15,6 +16,9 @@ | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| #include "esphome/components/bluetooth_proxy/bluetooth_proxy.h" | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| #include "esphome/components/voice_assistant/voice_assistant.h" | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
| @@ -180,6 +184,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_ | ||||
|   ListEntitiesBinarySensorResponse msg; | ||||
|   msg.object_id = binary_sensor->get_object_id(); | ||||
|   msg.key = binary_sensor->get_object_id_hash(); | ||||
|   if (binary_sensor->has_own_name()) | ||||
|     msg.name = binary_sensor->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor); | ||||
|   msg.device_class = binary_sensor->get_device_class(); | ||||
| @@ -212,6 +217,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { | ||||
|   ListEntitiesCoverResponse msg; | ||||
|   msg.key = cover->get_object_id_hash(); | ||||
|   msg.object_id = cover->get_object_id(); | ||||
|   if (cover->has_own_name()) | ||||
|     msg.name = cover->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("cover", cover); | ||||
|   msg.assumed_state = traits.get_is_assumed_state(); | ||||
| @@ -275,6 +281,7 @@ bool APIConnection::send_fan_info(fan::Fan *fan) { | ||||
|   ListEntitiesFanResponse msg; | ||||
|   msg.key = fan->get_object_id_hash(); | ||||
|   msg.object_id = fan->get_object_id(); | ||||
|   if (fan->has_own_name()) | ||||
|     msg.name = fan->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("fan", fan); | ||||
|   msg.supports_oscillation = traits.supports_oscillation(); | ||||
| @@ -337,6 +344,7 @@ bool APIConnection::send_light_info(light::LightState *light) { | ||||
|   ListEntitiesLightResponse msg; | ||||
|   msg.key = light->get_object_id_hash(); | ||||
|   msg.object_id = light->get_object_id(); | ||||
|   if (light->has_own_name()) | ||||
|     msg.name = light->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("light", light); | ||||
|  | ||||
| @@ -418,6 +426,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { | ||||
|   ListEntitiesSensorResponse msg; | ||||
|   msg.key = sensor->get_object_id_hash(); | ||||
|   msg.object_id = sensor->get_object_id(); | ||||
|   if (sensor->has_own_name()) | ||||
|     msg.name = sensor->get_name(); | ||||
|   msg.unique_id = sensor->unique_id(); | ||||
|   if (msg.unique_id.empty()) | ||||
| @@ -448,6 +457,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) { | ||||
|   ListEntitiesSwitchResponse msg; | ||||
|   msg.key = a_switch->get_object_id_hash(); | ||||
|   msg.object_id = a_switch->get_object_id(); | ||||
|   if (a_switch->has_own_name()) | ||||
|     msg.name = a_switch->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("switch", a_switch); | ||||
|   msg.icon = a_switch->get_icon(); | ||||
| @@ -533,6 +543,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { | ||||
|   ListEntitiesClimateResponse msg; | ||||
|   msg.key = climate->get_object_id_hash(); | ||||
|   msg.object_id = climate->get_object_id(); | ||||
|   if (climate->has_own_name()) | ||||
|     msg.name = climate->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("climate", climate); | ||||
|  | ||||
| @@ -611,6 +622,7 @@ bool APIConnection::send_number_info(number::Number *number) { | ||||
|   ListEntitiesNumberResponse msg; | ||||
|   msg.key = number->get_object_id_hash(); | ||||
|   msg.object_id = number->get_object_id(); | ||||
|   if (number->has_own_name()) | ||||
|     msg.name = number->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("number", number); | ||||
|   msg.icon = number->get_icon(); | ||||
| @@ -652,6 +664,7 @@ bool APIConnection::send_select_info(select::Select *select) { | ||||
|   ListEntitiesSelectResponse msg; | ||||
|   msg.key = select->get_object_id_hash(); | ||||
|   msg.object_id = select->get_object_id(); | ||||
|   if (select->has_own_name()) | ||||
|     msg.name = select->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("select", select); | ||||
|   msg.icon = select->get_icon(); | ||||
| @@ -679,6 +692,7 @@ bool APIConnection::send_button_info(button::Button *button) { | ||||
|   ListEntitiesButtonResponse msg; | ||||
|   msg.key = button->get_object_id_hash(); | ||||
|   msg.object_id = button->get_object_id(); | ||||
|   if (button->has_own_name()) | ||||
|     msg.name = button->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("button", button); | ||||
|   msg.icon = button->get_icon(); | ||||
| @@ -710,6 +724,7 @@ bool APIConnection::send_lock_info(lock::Lock *a_lock) { | ||||
|   ListEntitiesLockResponse msg; | ||||
|   msg.key = a_lock->get_object_id_hash(); | ||||
|   msg.object_id = a_lock->get_object_id(); | ||||
|   if (a_lock->has_own_name()) | ||||
|     msg.name = a_lock->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("lock", a_lock); | ||||
|   msg.icon = a_lock->get_icon(); | ||||
| @@ -755,6 +770,7 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play | ||||
|   ListEntitiesMediaPlayerResponse msg; | ||||
|   msg.key = media_player->get_object_id_hash(); | ||||
|   msg.object_id = media_player->get_object_id(); | ||||
|   if (media_player->has_own_name()) | ||||
|     msg.name = media_player->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("media_player", media_player); | ||||
|   msg.icon = media_player->get_icon(); | ||||
| @@ -799,6 +815,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { | ||||
|   ListEntitiesCameraResponse msg; | ||||
|   msg.key = camera->get_object_id_hash(); | ||||
|   msg.object_id = camera->get_object_id(); | ||||
|   if (camera->has_own_name()) | ||||
|     msg.name = camera->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("camera", camera); | ||||
|   msg.disabled_by_default = camera->is_disabled_by_default(); | ||||
| @@ -879,6 +896,30 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_ | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| bool APIConnection::request_voice_assistant(bool start) { | ||||
|   if (!this->voice_assistant_subscription_) | ||||
|     return false; | ||||
|   VoiceAssistantRequest msg; | ||||
|   msg.start = start; | ||||
|   return this->send_voice_assistant_request(msg); | ||||
| } | ||||
| void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) { | ||||
|   if (voice_assistant::global_voice_assistant != nullptr) { | ||||
|     struct sockaddr_storage storage; | ||||
|     socklen_t len = sizeof(storage); | ||||
|     this->helper_->getpeername((struct sockaddr *) &storage, &len); | ||||
|     voice_assistant::global_voice_assistant->start(&storage, msg.port); | ||||
|   } | ||||
| }; | ||||
| void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) { | ||||
|   if (voice_assistant::global_voice_assistant != nullptr) { | ||||
|     voice_assistant::global_voice_assistant->on_event(msg); | ||||
|   } | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| bool APIConnection::send_log_message(int level, const char *tag, const char *line) { | ||||
|   if (this->log_subscription_ < level) | ||||
|     return false; | ||||
| @@ -898,7 +939,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { | ||||
|   this->helper_->set_log_info(client_info_); | ||||
|   this->client_api_version_major_ = msg.api_version_major; | ||||
|   this->client_api_version_minor_ = msg.api_version_minor; | ||||
|   ESP_LOGV(TAG, "Hello from client: '%s' | API Version %d.%d", this->client_info_.c_str(), | ||||
|   ESP_LOGV(TAG, "Hello from client: '%s' | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(), | ||||
|            this->client_api_version_major_, this->client_api_version_minor_); | ||||
|  | ||||
|   HelloResponse resp; | ||||
| @@ -953,7 +994,12 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { | ||||
|   resp.webserver_port = USE_WEBSERVER_PORT; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 4 : 1; | ||||
|   resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() | ||||
|                                      ? bluetooth_proxy::ACTIVE_CONNECTIONS_VERSION | ||||
|                                      : bluetooth_proxy::PASSIVE_ONLY_VERSION; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   resp.voice_assistant_version = 1; | ||||
| #endif | ||||
|   return resp; | ||||
| } | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| #include "api_server.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/defines.h" | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| @@ -97,6 +98,12 @@ class APIConnection : public APIServerConnection { | ||||
|     this->send_homeassistant_service_response(call); | ||||
|   } | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override { | ||||
|     this->bluetooth_le_advertisement_subscription_ = true; | ||||
|   } | ||||
|   void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override { | ||||
|     this->bluetooth_le_advertisement_subscription_ = false; | ||||
|   } | ||||
|   bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg); | ||||
|  | ||||
|   void bluetooth_device_request(const BluetoothDeviceRequest &msg) override; | ||||
| @@ -117,6 +124,15 @@ class APIConnection : public APIServerConnection { | ||||
|   } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override { | ||||
|     this->voice_assistant_subscription_ = msg.subscribe; | ||||
|   } | ||||
|   bool request_voice_assistant(bool start); | ||||
|   void on_voice_assistant_response(const VoiceAssistantResponse &msg) override; | ||||
|   void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; | ||||
| #endif | ||||
|  | ||||
|   void on_disconnect_response(const DisconnectResponse &value) override; | ||||
|   void on_ping_response(const PingResponse &value) override { | ||||
|     // we initiated ping | ||||
| @@ -150,9 +166,7 @@ class APIConnection : public APIServerConnection { | ||||
|     return {}; | ||||
|   } | ||||
|   void execute_service(const ExecuteServiceRequest &msg) override; | ||||
|   void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override { | ||||
|     this->bluetooth_le_advertisement_subscription_ = true; | ||||
|   } | ||||
|  | ||||
|   bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; } | ||||
|   bool is_connection_setup() override { | ||||
|     return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated(); | ||||
| @@ -197,7 +211,12 @@ class APIConnection : public APIServerConnection { | ||||
|   uint32_t last_traffic_; | ||||
|   bool sent_ping_{false}; | ||||
|   bool service_call_subscription_{false}; | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   bool bluetooth_le_advertisement_subscription_{false}; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   bool voice_assistant_subscription_{false}; | ||||
| #endif | ||||
|   bool next_close_ = false; | ||||
|   APIServer *parent_; | ||||
|   InitialStateIterator initial_state_iterator_; | ||||
|   | ||||
| @@ -10,8 +10,8 @@ | ||||
| #include "noise/protocol.h" | ||||
| #endif | ||||
|  | ||||
| #include "esphome/components/socket/socket.h" | ||||
| #include "api_noise_context.h" | ||||
| #include "esphome/components/socket/socket.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
| @@ -67,6 +67,7 @@ class APIFrameHelper { | ||||
|   virtual bool can_write_without_blocking() = 0; | ||||
|   virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0; | ||||
|   virtual std::string getpeername() = 0; | ||||
|   virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0; | ||||
|   virtual APIError close() = 0; | ||||
|   virtual APIError shutdown(int how) = 0; | ||||
|   // Give this helper a name for logging | ||||
| @@ -84,7 +85,10 @@ class APINoiseFrameHelper : public APIFrameHelper { | ||||
|   APIError read_packet(ReadPacketBuffer *buffer) override; | ||||
|   bool can_write_without_blocking() override; | ||||
|   APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override; | ||||
|   std::string getpeername() override { return socket_->getpeername(); } | ||||
|   std::string getpeername() override { return this->socket_->getpeername(); } | ||||
|   int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { | ||||
|     return this->socket_->getpeername(addr, addrlen); | ||||
|   } | ||||
|   APIError close() override; | ||||
|   APIError shutdown(int how) override; | ||||
|   // Give this helper a name for logging | ||||
| @@ -144,7 +148,10 @@ class APIPlaintextFrameHelper : public APIFrameHelper { | ||||
|   APIError read_packet(ReadPacketBuffer *buffer) override; | ||||
|   bool can_write_without_blocking() override; | ||||
|   APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override; | ||||
|   std::string getpeername() override { return socket_->getpeername(); } | ||||
|   std::string getpeername() override { return this->socket_->getpeername(); } | ||||
|   int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { | ||||
|     return this->socket_->getpeername(addr, addrlen); | ||||
|   } | ||||
|   APIError close() override; | ||||
|   APIError shutdown(int how) override; | ||||
|   // Give this helper a name for logging | ||||
|   | ||||
| @@ -400,6 +400,34 @@ const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::Bluet | ||||
|       return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE"; | ||||
|     case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE: | ||||
|       return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE"; | ||||
|     case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: | ||||
|       return "BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE"; | ||||
|     default: | ||||
|       return "UNKNOWN"; | ||||
|   } | ||||
| } | ||||
| #endif | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::VoiceAssistantEvent value) { | ||||
|   switch (value) { | ||||
|     case enums::VOICE_ASSISTANT_ERROR: | ||||
|       return "VOICE_ASSISTANT_ERROR"; | ||||
|     case enums::VOICE_ASSISTANT_RUN_START: | ||||
|       return "VOICE_ASSISTANT_RUN_START"; | ||||
|     case enums::VOICE_ASSISTANT_RUN_END: | ||||
|       return "VOICE_ASSISTANT_RUN_END"; | ||||
|     case enums::VOICE_ASSISTANT_STT_START: | ||||
|       return "VOICE_ASSISTANT_STT_START"; | ||||
|     case enums::VOICE_ASSISTANT_STT_END: | ||||
|       return "VOICE_ASSISTANT_STT_END"; | ||||
|     case enums::VOICE_ASSISTANT_INTENT_START: | ||||
|       return "VOICE_ASSISTANT_INTENT_START"; | ||||
|     case enums::VOICE_ASSISTANT_INTENT_END: | ||||
|       return "VOICE_ASSISTANT_INTENT_END"; | ||||
|     case enums::VOICE_ASSISTANT_TTS_START: | ||||
|       return "VOICE_ASSISTANT_TTS_START"; | ||||
|     case enums::VOICE_ASSISTANT_TTS_END: | ||||
|       return "VOICE_ASSISTANT_TTS_END"; | ||||
|     default: | ||||
|       return "UNKNOWN"; | ||||
|   } | ||||
| @@ -592,6 +620,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|       this->bluetooth_proxy_version = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     case 14: { | ||||
|       this->voice_assistant_version = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -652,6 +684,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_uint32(11, this->bluetooth_proxy_version); | ||||
|   buffer.encode_string(12, this->manufacturer); | ||||
|   buffer.encode_string(13, this->friendly_name); | ||||
|   buffer.encode_uint32(14, this->voice_assistant_version); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void DeviceInfoResponse::dump_to(std::string &out) const { | ||||
| @@ -710,6 +743,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const { | ||||
|   out.append("  friendly_name: "); | ||||
|   out.append("'").append(this->friendly_name).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  voice_assistant_version: "); | ||||
|   sprintf(buffer, "%u", this->voice_assistant_version); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -6060,6 +6098,204 @@ void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const { | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| void UnsubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {} | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void UnsubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const { | ||||
|   out.append("UnsubscribeBluetoothLEAdvertisementsRequest {}"); | ||||
| } | ||||
| #endif | ||||
| bool BluetoothDeviceClearCacheResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->address = value.as_uint64(); | ||||
|       return true; | ||||
|     } | ||||
|     case 2: { | ||||
|       this->success = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 3: { | ||||
|       this->error = value.as_int32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void BluetoothDeviceClearCacheResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_uint64(1, this->address); | ||||
|   buffer.encode_bool(2, this->success); | ||||
|   buffer.encode_int32(3, this->error); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("BluetoothDeviceClearCacheResponse {\n"); | ||||
|   out.append("  address: "); | ||||
|   sprintf(buffer, "%llu", this->address); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  success: "); | ||||
|   out.append(YESNO(this->success)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  error: "); | ||||
|   sprintf(buffer, "%d", this->error); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool SubscribeVoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->subscribe = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void SubscribeVoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->subscribe); } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("SubscribeVoiceAssistantRequest {\n"); | ||||
|   out.append("  subscribe: "); | ||||
|   out.append(YESNO(this->subscribe)); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->start = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->start); } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void VoiceAssistantRequest::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("VoiceAssistantRequest {\n"); | ||||
|   out.append("  start: "); | ||||
|   out.append(YESNO(this->start)); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->port = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     case 2: { | ||||
|       this->error = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void VoiceAssistantResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_uint32(1, this->port); | ||||
|   buffer.encode_bool(2, this->error); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void VoiceAssistantResponse::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("VoiceAssistantResponse {\n"); | ||||
|   out.append("  port: "); | ||||
|   sprintf(buffer, "%u", this->port); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  error: "); | ||||
|   out.append(YESNO(this->error)); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->name = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     case 2: { | ||||
|       this->value = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void VoiceAssistantEventData::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(1, this->name); | ||||
|   buffer.encode_string(2, this->value); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void VoiceAssistantEventData::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("VoiceAssistantEventData {\n"); | ||||
|   out.append("  name: "); | ||||
|   out.append("'").append(this->name).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  value: "); | ||||
|   out.append("'").append(this->value).append("'"); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool VoiceAssistantEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->event_type = value.as_enum<enums::VoiceAssistantEvent>(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool VoiceAssistantEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||
|   switch (field_id) { | ||||
|     case 2: { | ||||
|       this->data.push_back(value.as_message<VoiceAssistantEventData>()); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void VoiceAssistantEventResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_enum<enums::VoiceAssistantEvent>(1, this->event_type); | ||||
|   for (auto &it : this->data) { | ||||
|     buffer.encode_message<VoiceAssistantEventData>(2, it, true); | ||||
|   } | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void VoiceAssistantEventResponse::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("VoiceAssistantEventResponse {\n"); | ||||
|   out.append("  event_type: "); | ||||
|   out.append(proto_enum_to_string<enums::VoiceAssistantEvent>(this->event_type)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   for (const auto &it : this->data) { | ||||
|     out.append("  data: "); | ||||
|     it.dump_to(out); | ||||
|     out.append("\n"); | ||||
|   } | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -163,6 +163,18 @@ enum BluetoothDeviceRequestType : uint32_t { | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3, | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4, | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5, | ||||
|   BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6, | ||||
| }; | ||||
| enum VoiceAssistantEvent : uint32_t { | ||||
|   VOICE_ASSISTANT_ERROR = 0, | ||||
|   VOICE_ASSISTANT_RUN_START = 1, | ||||
|   VOICE_ASSISTANT_RUN_END = 2, | ||||
|   VOICE_ASSISTANT_STT_START = 3, | ||||
|   VOICE_ASSISTANT_STT_END = 4, | ||||
|   VOICE_ASSISTANT_INTENT_START = 5, | ||||
|   VOICE_ASSISTANT_INTENT_END = 6, | ||||
|   VOICE_ASSISTANT_TTS_START = 7, | ||||
|   VOICE_ASSISTANT_TTS_END = 8, | ||||
| }; | ||||
|  | ||||
| }  // namespace enums | ||||
| @@ -278,6 +290,7 @@ class DeviceInfoResponse : public ProtoMessage { | ||||
|   uint32_t bluetooth_proxy_version{0}; | ||||
|   std::string manufacturer{}; | ||||
|   std::string friendly_name{}; | ||||
|   uint32_t voice_assistant_version{0}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -1554,6 +1567,87 @@ class BluetoothDeviceUnpairingResponse : public ProtoMessage { | ||||
|  protected: | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class UnsubscribeBluetoothLEAdvertisementsRequest : 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 BluetoothDeviceClearCacheResponse : public ProtoMessage { | ||||
|  public: | ||||
|   uint64_t address{0}; | ||||
|   bool success{false}; | ||||
|   int32_t error{0}; | ||||
|   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 SubscribeVoiceAssistantRequest : public ProtoMessage { | ||||
|  public: | ||||
|   bool subscribe{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 VoiceAssistantRequest : public ProtoMessage { | ||||
|  public: | ||||
|   bool start{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 VoiceAssistantResponse : public ProtoMessage { | ||||
|  public: | ||||
|   uint32_t port{0}; | ||||
|   bool error{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 VoiceAssistantEventData : public ProtoMessage { | ||||
|  public: | ||||
|   std::string name{}; | ||||
|   std::string value{}; | ||||
|   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 VoiceAssistantEventResponse : public ProtoMessage { | ||||
|  public: | ||||
|   enums::VoiceAssistantEvent event_type{}; | ||||
|   std::vector<VoiceAssistantEventData> data{}; | ||||
|   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; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -329,6 +329,8 @@ bool APIServerConnectionBase::send_media_player_state_response(const MediaPlayer | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_bluetooth_le_advertisement_response: %s", msg.dump().c_str()); | ||||
| @@ -441,6 +443,30 @@ bool APIServerConnectionBase::send_bluetooth_device_unpairing_response(const Blu | ||||
|   return this->send_message_<BluetoothDeviceUnpairingResponse>(msg, 86); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| bool APIServerConnectionBase::send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_bluetooth_device_clear_cache_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<BluetoothDeviceClearCacheResponse>(msg, 88); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| bool APIServerConnectionBase::send_voice_assistant_request(const VoiceAssistantRequest &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_voice_assistant_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<VoiceAssistantRequest>(msg, 90); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| #endif | ||||
| bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { | ||||
|   switch (msg_type) { | ||||
|     case 1: { | ||||
| @@ -709,12 +735,14 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | ||||
|       break; | ||||
|     } | ||||
|     case 66: { | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|       SubscribeBluetoothLEAdvertisementsRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_subscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_subscribe_bluetooth_le_advertisements_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 68: { | ||||
| @@ -802,6 +830,50 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | ||||
|       ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_subscribe_bluetooth_connections_free_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 87: { | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|       UnsubscribeBluetoothLEAdvertisementsRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_unsubscribe_bluetooth_le_advertisements_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 89: { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|       SubscribeVoiceAssistantRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_subscribe_voice_assistant_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_subscribe_voice_assistant_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 91: { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|       VoiceAssistantResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_response(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 92: { | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|       VoiceAssistantEventResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_event_response(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
| @@ -1065,6 +1137,7 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma | ||||
|   this->media_player_command(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( | ||||
|     const SubscribeBluetoothLEAdvertisementsRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
| @@ -1077,6 +1150,7 @@ void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( | ||||
|   } | ||||
|   this->subscribe_bluetooth_le_advertisements(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
| @@ -1185,6 +1259,33 @@ void APIServerConnection::on_subscribe_bluetooth_connections_free_request( | ||||
|   } | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request( | ||||
|     const UnsubscribeBluetoothLEAdvertisementsRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->unsubscribe_bluetooth_le_advertisements(msg); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) { | ||||
|   if (!this->is_connection_setup()) { | ||||
|     this->on_no_setup_connection(); | ||||
|     return; | ||||
|   } | ||||
|   if (!this->is_authenticated()) { | ||||
|     this->on_unauthenticated_access(); | ||||
|     return; | ||||
|   } | ||||
|   this->subscribe_voice_assistant(msg); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -154,8 +154,10 @@ class APIServerConnectionBase : public ProtoService { | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   virtual void on_subscribe_bluetooth_le_advertisements_request( | ||||
|       const SubscribeBluetoothLEAdvertisementsRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg); | ||||
| #endif | ||||
| @@ -215,6 +217,25 @@ class APIServerConnectionBase : public ProtoService { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   bool send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   virtual void on_unsubscribe_bluetooth_le_advertisements_request( | ||||
|       const UnsubscribeBluetoothLEAdvertisementsRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){}; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   bool send_voice_assistant_request(const VoiceAssistantRequest &msg); | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void on_voice_assistant_response(const VoiceAssistantResponse &value){}; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){}; | ||||
| #endif | ||||
|  protected: | ||||
|   bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; | ||||
| @@ -267,7 +288,9 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0; | ||||
| #endif | ||||
| @@ -292,6 +315,12 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free( | ||||
|       const SubscribeBluetoothConnectionsFreeRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   virtual void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; | ||||
| #endif | ||||
|  protected: | ||||
|   void on_hello_request(const HelloRequest &msg) override; | ||||
| @@ -339,7 +368,9 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override; | ||||
| #endif | ||||
| @@ -364,6 +395,13 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   void on_unsubscribe_bluetooth_le_advertisements_request( | ||||
|       const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; | ||||
| #endif | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
|   | ||||
| @@ -45,7 +45,7 @@ void APIServer::setup() { | ||||
|  | ||||
|   struct sockaddr_storage server; | ||||
|  | ||||
|   socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_)); | ||||
|   socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_); | ||||
|   if (sl == 0) { | ||||
|     ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno); | ||||
|     this->mark_failed(); | ||||
| @@ -331,6 +331,17 @@ void APIServer::send_bluetooth_device_unpairing(uint64_t address, bool success, | ||||
|   } | ||||
| } | ||||
|  | ||||
| void APIServer::send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error) { | ||||
|   BluetoothDeviceClearCacheResponse call; | ||||
|   call.address = address; | ||||
|   call.success = success; | ||||
|   call.error = error; | ||||
|  | ||||
|   for (auto &client : this->clients_) { | ||||
|     client->send_bluetooth_device_clear_cache_response(call); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) { | ||||
|   BluetoothConnectionsFreeResponse call; | ||||
|   call.free = free; | ||||
| @@ -416,5 +427,18 @@ void APIServer::on_shutdown() { | ||||
|   delay(10); | ||||
| } | ||||
|  | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| void APIServer::start_voice_assistant() { | ||||
|   for (auto &c : this->clients_) { | ||||
|     c->request_voice_assistant(true); | ||||
|   } | ||||
| } | ||||
| void APIServer::stop_voice_assistant() { | ||||
|   for (auto &c : this->clients_) { | ||||
|     c->request_voice_assistant(false); | ||||
|   } | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -80,6 +80,7 @@ class APIServer : public Component, public Controller { | ||||
|   void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK); | ||||
|   void send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK); | ||||
|   void send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK); | ||||
|   void send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK); | ||||
|   void send_bluetooth_connections_free(uint8_t free, uint8_t limit); | ||||
|   void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call); | ||||
|   void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call); | ||||
| @@ -94,6 +95,11 @@ class APIServer : public Component, public Controller { | ||||
|   void request_time(); | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|   void start_voice_assistant(); | ||||
|   void stop_voice_assistant(); | ||||
| #endif | ||||
|  | ||||
|   bool is_connected() const; | ||||
|  | ||||
|   struct HomeAssistantStateSubscription { | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| #include "proto.h" | ||||
| #include <cinttypes> | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| @@ -13,7 +14,7 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) { | ||||
|     uint32_t consumed; | ||||
|     auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed); | ||||
|     if (!res.has_value()) { | ||||
|       ESP_LOGV(TAG, "Invalid field start at %u", i); | ||||
|       ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i); | ||||
|       break; | ||||
|     } | ||||
|  | ||||
| @@ -25,12 +26,12 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) { | ||||
|       case 0: {  // VarInt | ||||
|         res = ProtoVarInt::parse(&buffer[i], length - i, &consumed); | ||||
|         if (!res.has_value()) { | ||||
|           ESP_LOGV(TAG, "Invalid VarInt at %u", i); | ||||
|           ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i); | ||||
|           error = true; | ||||
|           break; | ||||
|         } | ||||
|         if (!this->decode_varint(field_id, *res)) { | ||||
|           ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, res->as_uint32()); | ||||
|           ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32()); | ||||
|         } | ||||
|         i += consumed; | ||||
|         break; | ||||
| @@ -38,38 +39,38 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) { | ||||
|       case 2: {  // Length-delimited | ||||
|         res = ProtoVarInt::parse(&buffer[i], length - i, &consumed); | ||||
|         if (!res.has_value()) { | ||||
|           ESP_LOGV(TAG, "Invalid Length Delimited at %u", i); | ||||
|           ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i); | ||||
|           error = true; | ||||
|           break; | ||||
|         } | ||||
|         uint32_t field_length = res->as_uint32(); | ||||
|         i += consumed; | ||||
|         if (field_length > length - i) { | ||||
|           ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i); | ||||
|           ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i); | ||||
|           error = true; | ||||
|           break; | ||||
|         } | ||||
|         if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) { | ||||
|           ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id); | ||||
|           ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id); | ||||
|         } | ||||
|         i += field_length; | ||||
|         break; | ||||
|       } | ||||
|       case 5: {  // 32-bit | ||||
|         if (length - i < 4) { | ||||
|           ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %u", i); | ||||
|           ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i); | ||||
|           error = true; | ||||
|           break; | ||||
|         } | ||||
|         uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]); | ||||
|         if (!this->decode_32bit(field_id, Proto32Bit(val))) { | ||||
|           ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val); | ||||
|           ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val); | ||||
|         } | ||||
|         i += 4; | ||||
|         break; | ||||
|       } | ||||
|       default: | ||||
|         ESP_LOGV(TAG, "Invalid field type at %u", i); | ||||
|         ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i); | ||||
|         error = true; | ||||
|         break; | ||||
|     } | ||||
|   | ||||
| @@ -12,7 +12,6 @@ from esphome.const import ( | ||||
|     CONF_CAPACITANCE, | ||||
| ) | ||||
|  | ||||
| AUTO_LOAD = ["sensor", "binary_sensor"] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| CONF_AS3935_ID = "as3935_id" | ||||
|   | ||||
| @@ -26,9 +26,13 @@ void AS3935Component::setup() { | ||||
| void AS3935Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "AS3935:"); | ||||
|   LOG_PIN("  Interrupt Pin: ", this->irq_pin_); | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   LOG_BINARY_SENSOR("  ", "Thunder alert", this->thunder_alert_binary_sensor_); | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   LOG_SENSOR("  ", "Distance", this->distance_sensor_); | ||||
|   LOG_SENSOR("  ", "Lightning energy", this->energy_sensor_); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| float AS3935Component::get_setup_priority() const { return setup_priority::DATA; } | ||||
| @@ -44,16 +48,22 @@ void AS3935Component::loop() { | ||||
|     ESP_LOGI(TAG, "Disturber was detected - try increasing the spike rejection value!"); | ||||
|   } else if (int_value == LIGHTNING_INT) { | ||||
|     ESP_LOGI(TAG, "Lightning has been detected!"); | ||||
|     if (this->thunder_alert_binary_sensor_ != nullptr) | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|     if (this->thunder_alert_binary_sensor_ != nullptr) { | ||||
|       this->thunder_alert_binary_sensor_->publish_state(true); | ||||
|       this->set_timeout(10, [this]() { this->thunder_alert_binary_sensor_->publish_state(false); }); | ||||
|     } | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|     uint8_t distance = this->get_distance_to_storm_(); | ||||
|     if (this->distance_sensor_ != nullptr) | ||||
|       this->distance_sensor_->publish_state(distance); | ||||
|  | ||||
|     uint32_t energy = this->get_lightning_energy_(); | ||||
|     if (this->energy_sensor_ != nullptr) | ||||
|       this->energy_sensor_->publish_state(energy); | ||||
| #endif | ||||
|   } | ||||
|   this->thunder_alert_binary_sensor_->publish_state(false); | ||||
| } | ||||
|  | ||||
| void AS3935Component::write_indoor(bool indoor) { | ||||
|   | ||||
| @@ -1,9 +1,14 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #ifdef USE_SENSOR | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #endif | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace as3935 { | ||||
| @@ -52,6 +57,15 @@ enum AS3935Values { | ||||
| }; | ||||
|  | ||||
| class AS3935Component : public Component { | ||||
| #ifdef USE_SENSOR | ||||
|   SUB_SENSOR(distance) | ||||
|   SUB_SENSOR(energy) | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   SUB_BINARY_SENSOR(thunder_alert) | ||||
| #endif | ||||
|  | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
| @@ -59,11 +73,7 @@ class AS3935Component : public Component { | ||||
|   void loop() override; | ||||
|  | ||||
|   void set_irq_pin(GPIOPin *irq_pin) { irq_pin_ = irq_pin; } | ||||
|   void set_distance_sensor(sensor::Sensor *distance_sensor) { distance_sensor_ = distance_sensor; } | ||||
|   void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } | ||||
|   void set_thunder_alert_binary_sensor(binary_sensor::BinarySensor *thunder_alert_binary_sensor) { | ||||
|     thunder_alert_binary_sensor_ = thunder_alert_binary_sensor; | ||||
|   } | ||||
|  | ||||
|   void set_indoor(bool indoor) { indoor_ = indoor; } | ||||
|   void write_indoor(bool indoor); | ||||
|   void set_noise_level(uint8_t noise_level) { noise_level_ = noise_level; } | ||||
| @@ -92,9 +102,6 @@ class AS3935Component : public Component { | ||||
|  | ||||
|   virtual void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) = 0; | ||||
|  | ||||
|   sensor::Sensor *distance_sensor_{nullptr}; | ||||
|   sensor::Sensor *energy_sensor_{nullptr}; | ||||
|   binary_sensor::BinarySensor *thunder_alert_binary_sensor_{nullptr}; | ||||
|   GPIOPin *irq_pin_; | ||||
|  | ||||
|   bool indoor_; | ||||
|   | ||||
| @@ -116,7 +116,7 @@ class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingCompo | ||||
|   void update() override; | ||||
|   void dump_config() override; | ||||
|   void setup() override { this->codec_ = make_unique<BedjetCodec>(); } | ||||
|   float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } | ||||
|   float get_setup_priority() const override { return setup_priority::BLUETOOTH; } | ||||
|  | ||||
|   /** @return The BedJet's configured name, or the MAC address if not discovered yet. */ | ||||
|   std::string get_name() { | ||||
|   | ||||
| @@ -41,16 +41,13 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) { | ||||
|     this->state_callback_.call(state); | ||||
|   } | ||||
| } | ||||
| std::string BinarySensor::device_class() { return ""; } | ||||
|  | ||||
| BinarySensor::BinarySensor() : state(false) {} | ||||
| void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } | ||||
| std::string BinarySensor::get_device_class() { | ||||
|   if (this->device_class_.has_value()) | ||||
|     return *this->device_class_; | ||||
| #pragma GCC diagnostic push | ||||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | ||||
|   return this->device_class(); | ||||
| #pragma GCC diagnostic pop | ||||
|   return ""; | ||||
| } | ||||
| void BinarySensor::add_filter(Filter *filter) { | ||||
|   filter->parent_ = this; | ||||
|   | ||||
| @@ -80,14 +80,6 @@ class BinarySensor : public EntityBase { | ||||
|  | ||||
|   virtual bool is_status_binary_sensor() const; | ||||
|  | ||||
|   // ========== OVERRIDE METHODS ========== | ||||
|   // (You'll only need this when creating your own custom binary sensor) | ||||
|   /** Override this to set the default device class. | ||||
|    * | ||||
|    * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) | ||||
|    */ | ||||
|   virtual std::string device_class(); | ||||
|  | ||||
|  protected: | ||||
|   CallbackManager<void(bool)> state_callback_{}; | ||||
|   optional<std::string> device_class_{};  ///< Stores the override of the device class | ||||
|   | ||||
| @@ -30,7 +30,7 @@ void BinarySensorMap::process_group_() { | ||||
|     if (bs.binary_sensor->state) { | ||||
|       num_active_sensors++; | ||||
|       total_current_value += bs.sensor_value; | ||||
|       mask |= 1 << i; | ||||
|       mask |= 1ULL << i; | ||||
|     } | ||||
|   } | ||||
|   // check if the sensor map was touched | ||||
| @@ -38,12 +38,11 @@ void BinarySensorMap::process_group_() { | ||||
|     // did the bit_mask change or is it a new sensor touch | ||||
|     if (this->last_mask_ != mask) { | ||||
|       float publish_value = total_current_value / num_active_sensors; | ||||
|       ESP_LOGD(TAG, "'%s' - Publishing %.2f", this->name_.c_str(), publish_value); | ||||
|       this->publish_state(publish_value); | ||||
|     } | ||||
|   } else if (this->last_mask_ != 0ULL) { | ||||
|     // is this a new sensor release | ||||
|     ESP_LOGD(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str()); | ||||
|     ESP_LOGV(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str()); | ||||
|     this->publish_state(NAN); | ||||
|   } | ||||
|   this->last_mask_ = mask; | ||||
| @@ -52,28 +51,22 @@ void BinarySensorMap::process_group_() { | ||||
| void BinarySensorMap::process_sum_() { | ||||
|   float total_current_value = 0.0; | ||||
|   uint64_t mask = 0x00; | ||||
|   // check all binary_sensors for its state. when active add its value to total_current_value. | ||||
|   // create a bitmask for the binary_sensor status on all channels | ||||
|   // - check all binary_sensor states | ||||
|   // - if active, add its value to total_current_value | ||||
|   // - creates a bitmask for the binary_sensor status on all channels | ||||
|   for (size_t i = 0; i < this->channels_.size(); i++) { | ||||
|     auto bs = this->channels_[i]; | ||||
|     if (bs.binary_sensor->state) { | ||||
|       total_current_value += bs.sensor_value; | ||||
|       mask |= 1 << i; | ||||
|       mask |= 1ULL << i; | ||||
|     } | ||||
|   } | ||||
|   // check if the sensor map was touched | ||||
|   if (mask != 0ULL) { | ||||
|     // did the bit_mask change or is it a new sensor touch | ||||
|     if (this->last_mask_ != mask) { | ||||
|       float publish_value = total_current_value; | ||||
|       ESP_LOGD(TAG, "'%s' - Publishing %.2f", this->name_.c_str(), publish_value); | ||||
|       this->publish_state(publish_value); | ||||
|     } | ||||
|   } else if (this->last_mask_ != 0ULL) { | ||||
|     // is this a new sensor release | ||||
|     ESP_LOGD(TAG, "'%s' - No binary sensor active, publishing 0", this->name_.c_str()); | ||||
|     this->publish_state(0.0); | ||||
|  | ||||
|   // update state only if the binary sensor states have changed or if no state has ever been sent on boot | ||||
|   if ((this->last_mask_ != mask) || (!this->has_state())) { | ||||
|     this->publish_state(total_current_value); | ||||
|   } | ||||
|  | ||||
|   this->last_mask_ = mask; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -39,7 +39,7 @@ CONFIG_SCHEMA = cv.typed_schema( | ||||
|         ).extend( | ||||
|             { | ||||
|                 cv.Required(CONF_CHANNELS): cv.All( | ||||
|                     cv.ensure_list(entry), cv.Length(min=1) | ||||
|                     cv.ensure_list(entry), cv.Length(min=1, max=64) | ||||
|                 ), | ||||
|             } | ||||
|         ), | ||||
| @@ -50,7 +50,7 @@ CONFIG_SCHEMA = cv.typed_schema( | ||||
|         ).extend( | ||||
|             { | ||||
|                 cv.Required(CONF_CHANNELS): cv.All( | ||||
|                     cv.ensure_list(entry), cv.Length(min=1) | ||||
|                     cv.ensure_list(entry), cv.Length(min=1, max=64) | ||||
|                 ), | ||||
|             } | ||||
|         ), | ||||
|   | ||||
| @@ -306,6 +306,13 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest | ||||
|       api::global_api_server->send_bluetooth_device_unpairing(msg.address, ret == ESP_OK, ret); | ||||
|       break; | ||||
|     } | ||||
|     case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: { | ||||
|       esp_bd_addr_t address; | ||||
|       uint64_to_bd_addr(msg.address, address); | ||||
|       esp_err_t ret = esp_ble_gattc_cache_clean(address); | ||||
|       api::global_api_server->send_bluetooth_device_clear_cache(msg.address, ret == ESP_OK, ret); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -68,6 +68,14 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com | ||||
|  | ||||
| extern BluetoothProxy *global_bluetooth_proxy;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||
|  | ||||
| // Version 1: Initial version without active connections | ||||
| // Version 2: Support for active connections | ||||
| // Version 3: New connection API | ||||
| // Version 4: Pairing support | ||||
| // Version 5: Cache clear support | ||||
| static const uint32_t ACTIVE_CONNECTIONS_VERSION = 5; | ||||
| static const uint32_t PASSIVE_ONLY_VERSION = 1; | ||||
|  | ||||
| }  // namespace bluetooth_proxy | ||||
| }  // namespace esphome | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| #include "bme680.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bme680 { | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c | ||||
| from esphome.components import i2c, esp32 | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| CODEOWNERS = ["@trvrnrth"] | ||||
| @@ -32,7 +32,8 @@ BME680BSECComponent = bme680_bsec_ns.class_( | ||||
|     "BME680BSECComponent", cg.Component, i2c.I2CDevice | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BME680BSECComponent), | ||||
|             cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature, | ||||
| @@ -45,9 +46,17 @@ CONFIG_SCHEMA = cv.Schema( | ||||
|             cv.Optional( | ||||
|                 CONF_STATE_SAVE_INTERVAL, default="6hours" | ||||
|             ): cv.positive_time_period_minutes, | ||||
|     }, | ||||
|         } | ||||
|     ).extend(i2c.i2c_device_schema(0x76)), | ||||
|     cv.only_with_arduino, | ||||
| ).extend(i2c.i2c_device_schema(0x76)) | ||||
|     cv.Any( | ||||
|         cv.only_on_esp8266, | ||||
|         cv.All( | ||||
|             cv.only_on_esp32, | ||||
|             esp32.only_on_variant(supported=[esp32.const.VARIANT_ESP32]), | ||||
|         ), | ||||
|     ), | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|   | ||||
| @@ -6,9 +6,6 @@ namespace button { | ||||
|  | ||||
| static const char *const TAG = "button"; | ||||
|  | ||||
| Button::Button(const std::string &name) : EntityBase(name) {} | ||||
| Button::Button() : Button("") {} | ||||
|  | ||||
| void Button::press() { | ||||
|   ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str()); | ||||
|   this->press_action(); | ||||
|   | ||||
| @@ -28,9 +28,6 @@ namespace button { | ||||
|  */ | ||||
| class Button : public EntityBase { | ||||
|  public: | ||||
|   explicit Button(); | ||||
|   explicit Button(const std::string &name); | ||||
|  | ||||
|   /** Press this button. This is called by the front-end. | ||||
|    * | ||||
|    * For implementing buttons, please override press_action. | ||||
|   | ||||
| @@ -324,6 +324,10 @@ async def setup_climate_core_(var, config): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_CONTROL, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|  | ||||
| async def register_climate(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|   | ||||
| @@ -453,12 +453,7 @@ void Climate::set_visual_temperature_step_override(float target, float current) | ||||
|   this->visual_target_temperature_step_override_ = target; | ||||
|   this->visual_current_temperature_step_override_ = current; | ||||
| } | ||||
| #pragma GCC diagnostic push | ||||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | ||||
| Climate::Climate(const std::string &name) : EntityBase(name) {} | ||||
| #pragma GCC diagnostic pop | ||||
|  | ||||
| Climate::Climate() : Climate("") {} | ||||
| ClimateCall Climate::make_call() { return ClimateCall(this); } | ||||
|  | ||||
| ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) { | ||||
|   | ||||
| @@ -166,11 +166,6 @@ struct ClimateDeviceRestoreState { | ||||
|  */ | ||||
| class Climate : public EntityBase { | ||||
|  public: | ||||
|   /// Construct a climate device with empty name (will be set later). | ||||
|   Climate(); | ||||
|   /// Construct a climate device with a name. | ||||
|   Climate(const std::string &name); | ||||
|  | ||||
|   /// The active mode of the climate device. | ||||
|   ClimateMode mode{CLIMATE_MODE_OFF}; | ||||
|   /// The active state of the climate device. | ||||
|   | ||||
| @@ -14,12 +14,15 @@ from .. import copy_ns | ||||
| CopySelect = copy_ns.class_("CopySelect", select.Select, cg.Component) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = select.SELECT_SCHEMA.extend( | ||||
| CONFIG_SCHEMA = ( | ||||
|     select.select_schema(CopySelect) | ||||
|     .extend( | ||||
|         { | ||||
|         cv.GenerateID(): cv.declare_id(CopySelect), | ||||
|             cv.Required(CONF_SOURCE_ID): cv.use_id(select.Select), | ||||
|         } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| ) | ||||
|  | ||||
| FINAL_VALIDATE_SCHEMA = cv.All( | ||||
|     inherit_property_from(CONF_ICON, CONF_SOURCE_ID), | ||||
|   | ||||
| @@ -31,7 +31,7 @@ const char *cover_operation_to_str(CoverOperation op) { | ||||
|   } | ||||
| } | ||||
|  | ||||
| Cover::Cover(const std::string &name) : EntityBase(name), position{COVER_OPEN} {} | ||||
| Cover::Cover() : position{COVER_OPEN} {} | ||||
|  | ||||
| CoverCall::CoverCall(Cover *parent) : parent_(parent) {} | ||||
| CoverCall &CoverCall::set_command(const char *command) { | ||||
| @@ -204,18 +204,13 @@ optional<CoverRestoreState> Cover::restore_state_() { | ||||
|     return {}; | ||||
|   return recovered; | ||||
| } | ||||
| Cover::Cover() : Cover("") {} | ||||
| std::string Cover::get_device_class() { | ||||
|   if (this->device_class_override_.has_value()) | ||||
|     return *this->device_class_override_; | ||||
| #pragma GCC diagnostic push | ||||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | ||||
|   return this->device_class(); | ||||
| #pragma GCC diagnostic pop | ||||
|   return ""; | ||||
| } | ||||
| bool Cover::is_fully_open() const { return this->position == COVER_OPEN; } | ||||
| bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; } | ||||
| std::string Cover::device_class() { return ""; } | ||||
|  | ||||
| CoverCall CoverRestoreState::to_call(Cover *cover) { | ||||
|   auto call = cover->make_call(); | ||||
|   | ||||
| @@ -111,7 +111,6 @@ const char *cover_operation_to_str(CoverOperation op); | ||||
| class Cover : public EntityBase { | ||||
|  public: | ||||
|   explicit Cover(); | ||||
|   explicit Cover(const std::string &name); | ||||
|  | ||||
|   /// The current operation of the cover (idle, opening, closing). | ||||
|   CoverOperation current_operation{COVER_OPERATION_IDLE}; | ||||
| @@ -170,12 +169,6 @@ class Cover : public EntityBase { | ||||
|  | ||||
|   virtual void control(const CoverCall &call) = 0; | ||||
|  | ||||
|   /** Override this to set the default device class. | ||||
|    * | ||||
|    * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) | ||||
|    */ | ||||
|   virtual std::string device_class(); | ||||
|  | ||||
|   optional<CoverRestoreState> restore_state_(); | ||||
|  | ||||
|   CallbackManager<void()> state_callback_{}; | ||||
|   | ||||
| @@ -33,7 +33,10 @@ void CTClampSensor::update() { | ||||
|  | ||||
|     const float rms_ac_dc_squared = this->sample_squared_sum_ / this->num_samples_; | ||||
|     const float rms_dc = this->sample_sum_ / this->num_samples_; | ||||
|     const float rms_ac = std::sqrt(rms_ac_dc_squared - rms_dc * rms_dc); | ||||
|     const float rms_ac_squared = rms_ac_dc_squared - rms_dc * rms_dc; | ||||
|     float rms_ac = 0; | ||||
|     if (rms_ac_squared > 0) | ||||
|       rms_ac = std::sqrt(rms_ac_squared); | ||||
|     ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %d different samples (%d SPS)", this->name_.c_str(), rms_ac, | ||||
|              this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_); | ||||
|     this->publish_state(rms_ac); | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| #include "gpio.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include <cinttypes> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace esp32 { | ||||
| @@ -74,7 +75,7 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi | ||||
|  | ||||
| std::string ESP32InternalGPIOPin::dump_summary() const { | ||||
|   char buffer[32]; | ||||
|   snprintf(buffer, sizeof(buffer), "GPIO%u", static_cast<uint32_t>(pin_)); | ||||
|   snprintf(buffer, sizeof(buffer), "GPIO%" PRIu32, static_cast<uint32_t>(pin_)); | ||||
|   return buffer; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include "esphome/core/log.h" | ||||
| #include <nvs_flash.h> | ||||
| #include <cstring> | ||||
| #include <cinttypes> | ||||
| #include <vector> | ||||
| #include <string> | ||||
|  | ||||
| @@ -101,7 +102,7 @@ class ESP32Preferences : public ESPPreferences { | ||||
|     pref->nvs_handle = nvs_handle; | ||||
|  | ||||
|     uint32_t keyval = type; | ||||
|     pref->key = str_sprintf("%u", keyval); | ||||
|     pref->key = str_sprintf("%" PRIu32, keyval); | ||||
|  | ||||
|     return ESPPreferenceObject(pref); | ||||
|   } | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|  | ||||
| #include <esp_bt.h> | ||||
| #include <esp_bt_main.h> | ||||
| #include <esp_bt_device.h> | ||||
| #include <esp_gap_ble_api.h> | ||||
| #include <freertos/FreeRTOS.h> | ||||
| #include <freertos/FreeRTOSConfig.h> | ||||
| @@ -211,7 +212,16 @@ void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if | ||||
|  | ||||
| float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; } | ||||
|  | ||||
| void ESP32BLE::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE:"); } | ||||
| void ESP32BLE::dump_config() { | ||||
|   const uint8_t *mac_address = esp_bt_dev_get_address(); | ||||
|   if (mac_address) { | ||||
|     ESP_LOGCONFIG(TAG, "ESP32 BLE:"); | ||||
|     ESP_LOGCONFIG(TAG, "  MAC address: %02X:%02X:%02X:%02X:%02X:%02X", mac_address[0], mac_address[1], mac_address[2], | ||||
|                   mac_address[3], mac_address[4], mac_address[5]); | ||||
|   } else { | ||||
|     ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled"); | ||||
|   } | ||||
| } | ||||
|  | ||||
| ESP32BLE *global_ble = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||
|  | ||||
|   | ||||
| @@ -45,7 +45,8 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { | ||||
|       memset(this->remote_bda_, 0, sizeof(this->remote_bda_)); | ||||
|       this->address_str_ = ""; | ||||
|     } else { | ||||
|       this->address_str_ = str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, (uint8_t)(this->address_ >> 40) & 0xff, | ||||
|       this->address_str_ = | ||||
|           str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, (uint8_t) (this->address_ >> 40) & 0xff, | ||||
|                        (uint8_t) (this->address_ >> 32) & 0xff, (uint8_t) (this->address_ >> 24) & 0xff, | ||||
|                        (uint8_t) (this->address_ >> 16) & 0xff, (uint8_t) (this->address_ >> 8) & 0xff, | ||||
|                        (uint8_t) (this->address_ >> 0) & 0xff); | ||||
|   | ||||
| @@ -55,6 +55,22 @@ FRAME_SIZES = { | ||||
|     "SXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1280X1024, | ||||
|     "1600X1200": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, | ||||
|     "UXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, | ||||
|     "1920X1080": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1920X1080, | ||||
|     "FHD": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1920X1080, | ||||
|     "720X1280": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_720X1280, | ||||
|     "PHD": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_720X1280, | ||||
|     "864X1536": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_864X1536, | ||||
|     "P3MP": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_864X1536, | ||||
|     "2048X1536": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2048X1536, | ||||
|     "QXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2048X1536, | ||||
|     "2560X1440": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1440, | ||||
|     "QHD": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1440, | ||||
|     "2560X1600": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1600, | ||||
|     "WQXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1600, | ||||
|     "1080X1920": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1080X1920, | ||||
|     "PFHD": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1080X1920, | ||||
|     "2560X1920": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1920, | ||||
|     "QSXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1920, | ||||
| } | ||||
| ESP32GainControlMode = esp32_camera_ns.enum("ESP32GainControlMode") | ||||
| ENUM_GAIN_CONTROL_MODE = { | ||||
| @@ -140,7 +156,7 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( | ||||
|             { | ||||
|                 cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, | ||||
|                 cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( | ||||
|                     cv.frequency, cv.one_of(20e6, 10e6) | ||||
|                     cv.frequency, cv.Range(min=8e6, max=20e6) | ||||
|                 ), | ||||
|             } | ||||
|         ), | ||||
|   | ||||
| @@ -91,6 +91,30 @@ void ESP32Camera::dump_config() { | ||||
|     case FRAMESIZE_UXGA: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 1600x1200 (UXGA)"); | ||||
|       break; | ||||
|     case FRAMESIZE_FHD: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 1920x1080 (FHD)"); | ||||
|       break; | ||||
|     case FRAMESIZE_P_HD: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 720x1280 (P_HD)"); | ||||
|       break; | ||||
|     case FRAMESIZE_P_3MP: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 864x1536 (P_3MP)"); | ||||
|       break; | ||||
|     case FRAMESIZE_QXGA: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 2048x1536 (QXGA)"); | ||||
|       break; | ||||
|     case FRAMESIZE_QHD: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 2560x1440 (QHD)"); | ||||
|       break; | ||||
|     case FRAMESIZE_WQXGA: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 2560x1600 (WQXGA)"); | ||||
|       break; | ||||
|     case FRAMESIZE_P_FHD: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 1080x1920 (P_FHD)"); | ||||
|       break; | ||||
|     case FRAMESIZE_QSXGA: | ||||
|       ESP_LOGCONFIG(TAG, "  Resolution: 2560x1920 (QSXGA)"); | ||||
|       break; | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
| @@ -178,7 +202,7 @@ void ESP32Camera::loop() { | ||||
| float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; } | ||||
|  | ||||
| /* ---------------- constructors ---------------- */ | ||||
| ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { | ||||
| ESP32Camera::ESP32Camera() { | ||||
|   this->config_.pin_pwdn = -1; | ||||
|   this->config_.pin_reset = -1; | ||||
|   this->config_.pin_xclk = -1; | ||||
| @@ -191,7 +215,6 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { | ||||
|  | ||||
|   global_esp32_camera = this; | ||||
| } | ||||
| ESP32Camera::ESP32Camera() : ESP32Camera("") {} | ||||
|  | ||||
| /* ---------------- setters ---------------- */ | ||||
| /* set pin assignment */ | ||||
| @@ -257,6 +280,30 @@ void ESP32Camera::set_frame_size(ESP32CameraFrameSize size) { | ||||
|     case ESP32_CAMERA_SIZE_1600X1200: | ||||
|       this->config_.frame_size = FRAMESIZE_UXGA; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_1920X1080: | ||||
|       this->config_.frame_size = FRAMESIZE_FHD; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_720X1280: | ||||
|       this->config_.frame_size = FRAMESIZE_P_HD; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_864X1536: | ||||
|       this->config_.frame_size = FRAMESIZE_P_3MP; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_2048X1536: | ||||
|       this->config_.frame_size = FRAMESIZE_QXGA; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_2560X1440: | ||||
|       this->config_.frame_size = FRAMESIZE_QHD; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_2560X1600: | ||||
|       this->config_.frame_size = FRAMESIZE_WQXGA; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_1080X1920: | ||||
|       this->config_.frame_size = FRAMESIZE_P_FHD; | ||||
|       break; | ||||
|     case ESP32_CAMERA_SIZE_2560X1920: | ||||
|       this->config_.frame_size = FRAMESIZE_QSXGA; | ||||
|       break; | ||||
|   } | ||||
| } | ||||
| void ESP32Camera::set_jpeg_quality(uint8_t quality) { this->config_.jpeg_quality = quality; } | ||||
|   | ||||
| @@ -29,6 +29,14 @@ enum ESP32CameraFrameSize { | ||||
|   ESP32_CAMERA_SIZE_1024X768,   // XGA | ||||
|   ESP32_CAMERA_SIZE_1280X1024,  // SXGA | ||||
|   ESP32_CAMERA_SIZE_1600X1200,  // UXGA | ||||
|   ESP32_CAMERA_SIZE_1920X1080,  // FHD | ||||
|   ESP32_CAMERA_SIZE_720X1280,   // PHD | ||||
|   ESP32_CAMERA_SIZE_864X1536,   // P3MP | ||||
|   ESP32_CAMERA_SIZE_2048X1536,  // QXGA | ||||
|   ESP32_CAMERA_SIZE_2560X1440,  // QHD | ||||
|   ESP32_CAMERA_SIZE_2560X1600,  // WQXGA | ||||
|   ESP32_CAMERA_SIZE_1080X1920,  // PFHD | ||||
|   ESP32_CAMERA_SIZE_2560X1920,  // QSXGA | ||||
| }; | ||||
|  | ||||
| enum ESP32AgcGainCeiling { | ||||
| @@ -95,7 +103,6 @@ class CameraImageReader { | ||||
| /* ---------------- ESP32Camera class ---------------- */ | ||||
| class ESP32Camera : public Component, public EntityBase { | ||||
|  public: | ||||
|   ESP32Camera(const std::string &name); | ||||
|   ESP32Camera(); | ||||
|  | ||||
|   /* setters */ | ||||
|   | ||||
| @@ -22,20 +22,12 @@ ESP32ImprovComponent = esp32_improv_ns.class_( | ||||
| ) | ||||
|  | ||||
|  | ||||
| def validate_none_(value): | ||||
|     if value in ("none", "None"): | ||||
|         return None | ||||
|     if cv.boolean(value) is False: | ||||
|         return None | ||||
|     raise cv.Invalid("Must be none") | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(ESP32ImprovComponent), | ||||
|         cv.GenerateID(CONF_BLE_SERVER_ID): cv.use_id(esp32_ble_server.BLEServer), | ||||
|         cv.Required(CONF_AUTHORIZER): cv.Any( | ||||
|             validate_none_, cv.use_id(binary_sensor.BinarySensor) | ||||
|             cv.none, cv.use_id(binary_sensor.BinarySensor) | ||||
|         ), | ||||
|         cv.Optional(CONF_STATUS_INDICATOR): cv.use_id(output.BinaryOutput), | ||||
|         cv.Optional( | ||||
|   | ||||
| @@ -34,6 +34,7 @@ ETHERNET_TYPES = { | ||||
|     "DP83848": EthernetType.ETHERNET_TYPE_DP83848, | ||||
|     "IP101": EthernetType.ETHERNET_TYPE_IP101, | ||||
|     "JL1101": EthernetType.ETHERNET_TYPE_JL1101, | ||||
|     "KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081, | ||||
| } | ||||
|  | ||||
| emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") | ||||
|   | ||||
| @@ -74,6 +74,10 @@ void EthernetComponent::setup() { | ||||
|       phy = esp_eth_phy_new_jl1101(&phy_config); | ||||
|       break; | ||||
|     } | ||||
|     case ETHERNET_TYPE_KSZ8081: { | ||||
|       phy = esp_eth_phy_new_ksz8081(&phy_config); | ||||
|       break; | ||||
|     } | ||||
|     default: { | ||||
|       this->mark_failed(); | ||||
|       return; | ||||
| @@ -140,7 +144,7 @@ void EthernetComponent::loop() { | ||||
| } | ||||
|  | ||||
| void EthernetComponent::dump_config() { | ||||
|   std::string eth_type; | ||||
|   const char *eth_type; | ||||
|   switch (this->type_) { | ||||
|     case ETHERNET_TYPE_LAN8720: | ||||
|       eth_type = "LAN8720"; | ||||
| @@ -158,6 +162,14 @@ void EthernetComponent::dump_config() { | ||||
|       eth_type = "IP101"; | ||||
|       break; | ||||
|  | ||||
|     case ETHERNET_TYPE_JL1101: | ||||
|       eth_type = "JL1101"; | ||||
|       break; | ||||
|  | ||||
|     case ETHERNET_TYPE_KSZ8081: | ||||
|       eth_type = "KSZ8081"; | ||||
|       break; | ||||
|  | ||||
|     default: | ||||
|       eth_type = "Unknown"; | ||||
|       break; | ||||
| @@ -170,7 +182,8 @@ void EthernetComponent::dump_config() { | ||||
|   } | ||||
|   ESP_LOGCONFIG(TAG, "  MDC Pin: %u", this->mdc_pin_); | ||||
|   ESP_LOGCONFIG(TAG, "  MDIO Pin: %u", this->mdio_pin_); | ||||
|   ESP_LOGCONFIG(TAG, "  Type: %s", eth_type.c_str()); | ||||
|   ESP_LOGCONFIG(TAG, "  Type: %s", eth_type); | ||||
|   ESP_LOGCONFIG(TAG, "  PHY addr: %u", this->phy_addr_); | ||||
| } | ||||
|  | ||||
| float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } | ||||
| @@ -255,14 +268,22 @@ void EthernetComponent::start_connect_() { | ||||
|   if (this->manual_ip_.has_value()) { | ||||
|     if (uint32_t(this->manual_ip_->dns1) != 0) { | ||||
|       ip_addr_t d; | ||||
| #if LWIP_IPV6 | ||||
|       d.type = IPADDR_TYPE_V4; | ||||
|       d.u_addr.ip4.addr = static_cast<uint32_t>(this->manual_ip_->dns1); | ||||
| #else | ||||
|       d.addr = static_cast<uint32_t>(this->manual_ip_->dns1); | ||||
| #endif | ||||
|       dns_setserver(0, &d); | ||||
|     } | ||||
|     if (uint32_t(this->manual_ip_->dns1) != 0) { | ||||
|       ip_addr_t d; | ||||
| #if LWIP_IPV6 | ||||
|       d.type = IPADDR_TYPE_V4; | ||||
|       d.u_addr.ip4.addr = static_cast<uint32_t>(this->manual_ip_->dns2); | ||||
| #else | ||||
|       d.addr = static_cast<uint32_t>(this->manual_ip_->dns2); | ||||
| #endif | ||||
|       dns_setserver(1, &d); | ||||
|     } | ||||
|   } else { | ||||
| @@ -289,8 +310,13 @@ void EthernetComponent::dump_connect_params_() { | ||||
|   const ip_addr_t *dns_ip1 = dns_getserver(0); | ||||
|   const ip_addr_t *dns_ip2 = dns_getserver(1); | ||||
|  | ||||
| #if LWIP_IPV6 | ||||
|   ESP_LOGCONFIG(TAG, "  DNS1: %s", network::IPAddress(dns_ip1->u_addr.ip4.addr).str().c_str()); | ||||
|   ESP_LOGCONFIG(TAG, "  DNS2: %s", network::IPAddress(dns_ip2->u_addr.ip4.addr).str().c_str()); | ||||
| #else | ||||
|   ESP_LOGCONFIG(TAG, "  DNS1: %s", network::IPAddress(dns_ip1->addr).str().c_str()); | ||||
|   ESP_LOGCONFIG(TAG, "  DNS2: %s", network::IPAddress(dns_ip2->addr).str().c_str()); | ||||
| #endif | ||||
|  | ||||
|   esp_err_t err; | ||||
|  | ||||
|   | ||||
| @@ -14,11 +14,13 @@ namespace esphome { | ||||
| namespace ethernet { | ||||
|  | ||||
| enum EthernetType { | ||||
|   ETHERNET_TYPE_LAN8720 = 0, | ||||
|   ETHERNET_TYPE_UNKNOWN = 0, | ||||
|   ETHERNET_TYPE_LAN8720, | ||||
|   ETHERNET_TYPE_RTL8201, | ||||
|   ETHERNET_TYPE_DP83848, | ||||
|   ETHERNET_TYPE_IP101, | ||||
|   ETHERNET_TYPE_JL1101, | ||||
|   ETHERNET_TYPE_KSZ8081, | ||||
| }; | ||||
|  | ||||
| struct ManualIP { | ||||
| @@ -69,7 +71,7 @@ class EthernetComponent : public Component { | ||||
|   int power_pin_{-1}; | ||||
|   uint8_t mdc_pin_{23}; | ||||
|   uint8_t mdio_pin_{18}; | ||||
|   EthernetType type_{ETHERNET_TYPE_LAN8720}; | ||||
|   EthernetType type_{ETHERNET_TYPE_UNKNOWN}; | ||||
|   emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; | ||||
|   emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO}; | ||||
|   optional<ManualIP> manual_ip_{}; | ||||
|   | ||||
| @@ -12,14 +12,14 @@ static const char *const TAG = "ezo.sensor"; | ||||
|  | ||||
| enum EzoCommandType : uint8_t { | ||||
|   EZO_READ = 0, | ||||
|   EZO_LED = 1, | ||||
|   EZO_DEVICE_INFORMATION = 2, | ||||
|   EZO_SLOPE = 3, | ||||
|   EZO_LED, | ||||
|   EZO_DEVICE_INFORMATION, | ||||
|   EZO_SLOPE, | ||||
|   EZO_CALIBRATION, | ||||
|   EZO_SLEEP = 4, | ||||
|   EZO_I2C = 5, | ||||
|   EZO_T = 6, | ||||
|   EZO_CUSTOM = 7 | ||||
|   EZO_SLEEP, | ||||
|   EZO_I2C, | ||||
|   EZO_T, | ||||
|   EZO_CUSTOM | ||||
| }; | ||||
|  | ||||
| enum EzoCalibrationType : uint8_t { EZO_CAL_LOW = 0, EZO_CAL_MID = 1, EZO_CAL_HIGH = 2 }; | ||||
|   | ||||
| @@ -63,7 +63,7 @@ FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.temp | ||||
| FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(Fan), | ||||
|         cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum( | ||||
|         cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum( | ||||
|             RESTORE_MODES, upper=True, space="_" | ||||
|         ), | ||||
|         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent), | ||||
|   | ||||
| @@ -80,9 +80,6 @@ void FanRestoreState::apply(Fan &fan) { | ||||
|   fan.publish_state(); | ||||
| } | ||||
|  | ||||
| Fan::Fan() : EntityBase("") {} | ||||
| Fan::Fan(const std::string &name) : EntityBase(name) {} | ||||
|  | ||||
| FanCall Fan::turn_on() { return this->make_call().set_state(true); } | ||||
| FanCall Fan::turn_off() { return this->make_call().set_state(false); } | ||||
| FanCall Fan::toggle() { return this->make_call().set_state(!this->state); } | ||||
|   | ||||
| @@ -99,10 +99,6 @@ struct FanRestoreState { | ||||
|  | ||||
| class Fan : public EntityBase { | ||||
|  public: | ||||
|   Fan(); | ||||
|   /// Construct the fan with name. | ||||
|   explicit Fan(const std::string &name); | ||||
|  | ||||
|   /// The current on/off state of the fan. | ||||
|   bool state{false}; | ||||
|   /// The current oscillation state of the fan. | ||||
|   | ||||
| @@ -15,7 +15,6 @@ enum ESPDEPRECATED("LegacyFanDirection members are deprecated, use FanDirection | ||||
| class ESPDEPRECATED("FanState is deprecated, use Fan instead.", "2022.2") FanState : public Fan, public Component { | ||||
|  public: | ||||
|   FanState() = default; | ||||
|   explicit FanState(const std::string &name) : Fan(name) {} | ||||
|  | ||||
|   /// Get the traits of this fan. | ||||
|   FanTraits get_traits() override { return this->traits_; } | ||||
|   | ||||
| @@ -122,11 +122,18 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo | ||||
|   } | ||||
|  | ||||
|   // Adjust limits to nice y_per_div boundaries | ||||
|   int yn = int(ymin / y_per_div); | ||||
|   int ym = int(ymax / y_per_div) + int(1 * (fmodf(ymax, y_per_div) != 0)); | ||||
|   int yn = 0; | ||||
|   int ym = 1; | ||||
|   if (!std::isnan(ymin) && !std::isnan(ymax)) { | ||||
|     yn = (int) floorf(ymin / y_per_div); | ||||
|     ym = (int) ceilf(ymax / y_per_div); | ||||
|     if (yn == ym) { | ||||
|       ym++; | ||||
|     } | ||||
|     ymin = yn * y_per_div; | ||||
|     ymax = ym * y_per_div; | ||||
|     yrange = ymax - ymin; | ||||
|   } | ||||
|  | ||||
|   /// Draw grid | ||||
|   if (!std::isnan(this->gridspacing_y_)) { | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include <cstring> | ||||
| #include <cinttypes> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace i2c { | ||||
| @@ -47,7 +48,7 @@ void IDFI2CBus::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "I2C Bus:"); | ||||
|   ESP_LOGCONFIG(TAG, "  SDA Pin: GPIO%u", this->sda_pin_); | ||||
|   ESP_LOGCONFIG(TAG, "  SCL Pin: GPIO%u", this->scl_pin_); | ||||
|   ESP_LOGCONFIG(TAG, "  Frequency: %u Hz", this->frequency_); | ||||
|   ESP_LOGCONFIG(TAG, "  Frequency: %" PRIu32 " Hz", this->frequency_); | ||||
|   switch (this->recovery_result_) { | ||||
|     case RECOVERY_COMPLETED: | ||||
|       ESP_LOGCONFIG(TAG, "  Recovery: bus successfully recovered"); | ||||
|   | ||||
| @@ -0,0 +1,70 @@ | ||||
| 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 | ||||
| from esphome.components.esp32 import get_esp32_variant | ||||
| from esphome.components.esp32.const import ( | ||||
|     VARIANT_ESP32, | ||||
|     VARIANT_ESP32S2, | ||||
|     VARIANT_ESP32S3, | ||||
|     VARIANT_ESP32C3, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@jesserockz"] | ||||
| DEPENDENCIES = ["esp32"] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| CONF_I2S_DOUT_PIN = "i2s_dout_pin" | ||||
| CONF_I2S_DIN_PIN = "i2s_din_pin" | ||||
| CONF_I2S_BCLK_PIN = "i2s_bclk_pin" | ||||
| CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin" | ||||
|  | ||||
| CONF_I2S_AUDIO = "i2s_audio" | ||||
| CONF_I2S_AUDIO_ID = "i2s_audio_id" | ||||
|  | ||||
| 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) | ||||
| ) | ||||
|  | ||||
| # https://github.com/espressif/esp-idf/blob/master/components/soc/{variant}/include/soc/soc_caps.h | ||||
| I2S_PORTS = { | ||||
|     VARIANT_ESP32: 2, | ||||
|     VARIANT_ESP32S2: 1, | ||||
|     VARIANT_ESP32S3: 2, | ||||
|     VARIANT_ESP32C3: 1, | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(I2SAudioComponent), | ||||
|         cv.Required(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number, | ||||
|         cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number, | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def _final_validate(_): | ||||
|     i2s_audio_configs = fv.full_config.get()[CONF_I2S_AUDIO] | ||||
|     variant = get_esp32_variant() | ||||
|     if variant not in I2S_PORTS: | ||||
|         raise cv.Invalid(f"Unsupported variant {variant}") | ||||
|     if len(i2s_audio_configs) > I2S_PORTS[variant]: | ||||
|         raise cv.Invalid( | ||||
|             f"Only {I2S_PORTS[variant]} I2S audio ports are supported on {variant}" | ||||
|         ) | ||||
|  | ||||
|  | ||||
| FINAL_VALIDATE_SCHEMA = _final_validate | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN])) | ||||
|     cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN])) | ||||
|   | ||||
							
								
								
									
										30
									
								
								esphome/components/i2s_audio/i2s_audio.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								esphome/components/i2s_audio/i2s_audio.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| #include "i2s_audio.h" | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace i2s_audio { | ||||
|  | ||||
| static const char *const TAG = "i2s_audio"; | ||||
|  | ||||
| void I2SAudioComponent::setup() { | ||||
|   static i2s_port_t next_port_num = I2S_NUM_0; | ||||
|  | ||||
|   if (next_port_num >= I2S_NUM_MAX) { | ||||
|     ESP_LOGE(TAG, "Too many I2S Audio components!"); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   this->port_ = next_port_num; | ||||
|   next_port_num = (i2s_port_t) (next_port_num + 1); | ||||
|  | ||||
|   ESP_LOGCONFIG(TAG, "Setting up I2S Audio..."); | ||||
| } | ||||
|  | ||||
| }  // namespace i2s_audio | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif  // USE_ESP32 | ||||
							
								
								
									
										64
									
								
								esphome/components/i2s_audio/i2s_audio.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								esphome/components/i2s_audio/i2s_audio.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| #pragma once | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include <driver/i2s.h> | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/helpers.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace i2s_audio { | ||||
|  | ||||
| class I2SAudioComponent; | ||||
|  | ||||
| class I2SAudioIn : public Parented<I2SAudioComponent> {}; | ||||
|  | ||||
| class I2SAudioOut : public Parented<I2SAudioComponent> {}; | ||||
|  | ||||
| class I2SAudioComponent : public Component { | ||||
|  public: | ||||
|   void setup() override; | ||||
|  | ||||
|   void register_audio_in(I2SAudioIn *in) { | ||||
|     this->audio_in_ = in; | ||||
|     in->set_parent(this); | ||||
|   } | ||||
|   void register_audio_out(I2SAudioOut *out) { | ||||
|     this->audio_out_ = out; | ||||
|     out->set_parent(this); | ||||
|   } | ||||
|  | ||||
|   i2s_pin_config_t get_pin_config() const { | ||||
|     return { | ||||
|         .mck_io_num = I2S_PIN_NO_CHANGE, | ||||
|         .bck_io_num = this->bclk_pin_, | ||||
|         .ws_io_num = this->lrclk_pin_, | ||||
|         .data_out_num = I2S_PIN_NO_CHANGE, | ||||
|         .data_in_num = I2S_PIN_NO_CHANGE, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   void set_bclk_pin(uint8_t pin) { this->bclk_pin_ = pin; } | ||||
|   void set_lrclk_pin(uint8_t pin) { this->lrclk_pin_ = pin; } | ||||
|  | ||||
|   void lock() { this->lock_.lock(); } | ||||
|   bool try_lock() { return this->lock_.try_lock(); } | ||||
|   void unlock() { this->lock_.unlock(); } | ||||
|  | ||||
|   i2s_port_t get_port() const { return this->port_; } | ||||
|  | ||||
|  protected: | ||||
|   Mutex lock_; | ||||
|  | ||||
|   I2SAudioIn *audio_in_{nullptr}; | ||||
|   I2SAudioOut *audio_out_{nullptr}; | ||||
|  | ||||
|   uint8_t bclk_pin_; | ||||
|   uint8_t lrclk_pin_; | ||||
|   i2s_port_t port_{}; | ||||
| }; | ||||
|  | ||||
| }  // namespace i2s_audio | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif  // USE_ESP32 | ||||
| @@ -5,22 +5,25 @@ import esphome.config_validation as cv | ||||
| from esphome import pins | ||||
| 
 | ||||
| from esphome.const import CONF_ID, CONF_MODE | ||||
| from esphome.core import CORE | ||||
| 
 | ||||
| from .. import ( | ||||
|     i2s_audio_ns, | ||||
|     I2SAudioComponent, | ||||
|     I2SAudioOut, | ||||
|     CONF_I2S_AUDIO_ID, | ||||
|     CONF_I2S_DOUT_PIN, | ||||
| ) | ||||
| 
 | ||||
| CODEOWNERS = ["@jesserockz"] | ||||
| DEPENDENCIES = ["esp32"] | ||||
| 
 | ||||
| i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio") | ||||
| DEPENDENCIES = ["i2s_audio"] | ||||
| 
 | ||||
| I2SAudioMediaPlayer = i2s_audio_ns.class_( | ||||
|     "I2SAudioMediaPlayer", cg.Component, media_player.MediaPlayer | ||||
|     "I2SAudioMediaPlayer", cg.Component, media_player.MediaPlayer, I2SAudioOut | ||||
| ) | ||||
| 
 | ||||
| i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t") | ||||
| 
 | ||||
| CONF_I2S_DOUT_PIN = "i2s_dout_pin" | ||||
| CONF_I2S_BCLK_PIN = "i2s_bclk_pin" | ||||
| CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin" | ||||
| 
 | ||||
| CONF_MUTE_PIN = "mute_pin" | ||||
| CONF_AUDIO_ID = "audio_id" | ||||
| CONF_DAC_TYPE = "dac_type" | ||||
| @@ -48,34 +51,26 @@ def validate_esp32_variant(config): | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.typed_schema( | ||||
|         { | ||||
|             "internal": cv.Schema( | ||||
|             "internal": media_player.MEDIA_PLAYER_SCHEMA.extend( | ||||
|                 { | ||||
|                     cv.GenerateID(): cv.declare_id(I2SAudioMediaPlayer), | ||||
|                     cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), | ||||
|                     cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True), | ||||
|                 } | ||||
|             ) | ||||
|             .extend(media_player.MEDIA_PLAYER_SCHEMA) | ||||
|             .extend(cv.COMPONENT_SCHEMA), | ||||
|             "external": cv.Schema( | ||||
|             ).extend(cv.COMPONENT_SCHEMA), | ||||
|             "external": media_player.MEDIA_PLAYER_SCHEMA.extend( | ||||
|                 { | ||||
|                     cv.GenerateID(): cv.declare_id(I2SAudioMediaPlayer), | ||||
|                     cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), | ||||
|                     cv.Required( | ||||
|                         CONF_I2S_DOUT_PIN | ||||
|                     ): pins.internal_gpio_output_pin_number, | ||||
|                     cv.Required( | ||||
|                         CONF_I2S_BCLK_PIN | ||||
|                     ): pins.internal_gpio_output_pin_number, | ||||
|                     cv.Required( | ||||
|                         CONF_I2S_LRCLK_PIN | ||||
|                     ): pins.internal_gpio_output_pin_number, | ||||
|                     cv.Optional(CONF_MUTE_PIN): pins.gpio_output_pin_schema, | ||||
|                     cv.Optional(CONF_MODE, default="mono"): cv.one_of( | ||||
|                         *EXTERNAL_DAC_OPTIONS, lower=True | ||||
|                     ), | ||||
|                 } | ||||
|             ) | ||||
|             .extend(media_player.MEDIA_PLAYER_SCHEMA) | ||||
|             .extend(cv.COMPONENT_SCHEMA), | ||||
|             ).extend(cv.COMPONENT_SCHEMA), | ||||
|         }, | ||||
|         key=CONF_DAC_TYPE, | ||||
|     ), | ||||
| @@ -89,18 +84,18 @@ async def to_code(config): | ||||
|     await cg.register_component(var, config) | ||||
|     await media_player.register_media_player(var, config) | ||||
| 
 | ||||
|     parent = await cg.get_variable(config[CONF_I2S_AUDIO_ID]) | ||||
|     cg.add(parent.register_audio_out(var)) | ||||
| 
 | ||||
|     if config[CONF_DAC_TYPE] == "internal": | ||||
|         cg.add(var.set_internal_dac_mode(config[CONF_MODE])) | ||||
|     else: | ||||
|         cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN])) | ||||
|         cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN])) | ||||
|         cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN])) | ||||
|         if CONF_MUTE_PIN in config: | ||||
|             pin = await cg.gpio_pin_expression(config[CONF_MUTE_PIN]) | ||||
|             cg.add(var.set_mute_pin(pin)) | ||||
|         cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1)) | ||||
| 
 | ||||
|     if CORE.is_esp32: | ||||
|     cg.add_library("WiFiClientSecure", None) | ||||
|     cg.add_library("HTTPClient", None) | ||||
|     cg.add_library("esphome/ESP32-audioI2S", "2.0.6") | ||||
| @@ -11,11 +11,19 @@ static const char *const TAG = "audio"; | ||||
| 
 | ||||
| void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) { | ||||
|   if (call.get_media_url().has_value()) { | ||||
|     if (this->audio_->isRunning()) | ||||
|     this->current_url_ = call.get_media_url(); | ||||
| 
 | ||||
|     if (this->state == media_player::MEDIA_PLAYER_STATE_PLAYING && this->audio_ != nullptr) { | ||||
|       if (this->audio_->isRunning()) { | ||||
|         this->audio_->stopSong(); | ||||
|     this->high_freq_.start(); | ||||
|     this->audio_->connecttohost(call.get_media_url().value().c_str()); | ||||
|     this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; | ||||
|       } | ||||
|       this->audio_->connecttohost(this->current_url_.value().c_str()); | ||||
|     } else { | ||||
|       this->start(); | ||||
|     } | ||||
|   } | ||||
|   if (this->i2s_state_ != I2S_STATE_RUNNING) { | ||||
|     return; | ||||
|   } | ||||
|   if (call.get_volume().has_value()) { | ||||
|     this->volume = call.get_volume().value(); | ||||
| @@ -35,7 +43,7 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) { | ||||
|         this->state = media_player::MEDIA_PLAYER_STATE_PAUSED; | ||||
|         break; | ||||
|       case media_player::MEDIA_PLAYER_COMMAND_STOP: | ||||
|         this->stop_(); | ||||
|         this->stop(); | ||||
|         break; | ||||
|       case media_player::MEDIA_PLAYER_COMMAND_MUTE: | ||||
|         this->mute_(); | ||||
| @@ -94,22 +102,51 @@ void I2SAudioMediaPlayer::set_volume_(float volume, bool publish) { | ||||
|     this->volume = volume; | ||||
| } | ||||
| 
 | ||||
| void I2SAudioMediaPlayer::stop_() { | ||||
|   if (this->audio_->isRunning()) | ||||
|     this->audio_->stopSong(); | ||||
|   this->high_freq_.stop(); | ||||
| void I2SAudioMediaPlayer::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up Audio..."); | ||||
|   this->state = media_player::MEDIA_PLAYER_STATE_IDLE; | ||||
| } | ||||
| 
 | ||||
| void I2SAudioMediaPlayer::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up Audio..."); | ||||
| void I2SAudioMediaPlayer::loop() { | ||||
|   switch (this->i2s_state_) { | ||||
|     case I2S_STATE_STARTING: | ||||
|       this->start_(); | ||||
|       break; | ||||
|     case I2S_STATE_RUNNING: | ||||
|       this->play_(); | ||||
|       break; | ||||
|     case I2S_STATE_STOPPING: | ||||
|       this->stop_(); | ||||
|       break; | ||||
|     case I2S_STATE_STOPPED: | ||||
|       break; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void I2SAudioMediaPlayer::play_() { | ||||
|   this->audio_->loop(); | ||||
|   if (this->state == media_player::MEDIA_PLAYER_STATE_PLAYING && !this->audio_->isRunning()) { | ||||
|     this->stop(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void I2SAudioMediaPlayer::start() { this->i2s_state_ = I2S_STATE_STARTING; } | ||||
| void I2SAudioMediaPlayer::start_() { | ||||
|   if (this->parent_->try_lock()) { | ||||
|     return;  // Waiting for another i2s to return lock
 | ||||
|   } | ||||
| 
 | ||||
| #if SOC_I2S_SUPPORTS_DAC | ||||
|   if (this->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) { | ||||
|     this->audio_ = make_unique<Audio>(true, this->internal_dac_mode_); | ||||
|     this->audio_ = make_unique<Audio>(true, this->internal_dac_mode_, this->parent_->get_port()); | ||||
|   } else { | ||||
| #endif | ||||
|     this->audio_ = make_unique<Audio>(false); | ||||
|     this->audio_->setPinout(this->bclk_pin_, this->lrclk_pin_, this->dout_pin_); | ||||
|     this->audio_ = make_unique<Audio>(false, I2S_DAC_CHANNEL_BOTH_EN, this->parent_->get_port()); | ||||
| 
 | ||||
|     i2s_pin_config_t pin_config = this->parent_->get_pin_config(); | ||||
|     pin_config.data_out_num = this->dout_pin_; | ||||
|     i2s_set_pin(this->parent_->get_port(), &pin_config); | ||||
| 
 | ||||
|     this->audio_->forceMono(this->external_dac_channels_ == 1); | ||||
|     if (this->mute_pin_ != nullptr) { | ||||
|       this->mute_pin_->setup(); | ||||
| @@ -118,16 +155,30 @@ void I2SAudioMediaPlayer::setup() { | ||||
| #if SOC_I2S_SUPPORTS_DAC | ||||
|   } | ||||
| #endif | ||||
|   this->state = media_player::MEDIA_PLAYER_STATE_IDLE; | ||||
| } | ||||
| 
 | ||||
| void I2SAudioMediaPlayer::loop() { | ||||
|   this->audio_->loop(); | ||||
|   if (this->state == media_player::MEDIA_PLAYER_STATE_PLAYING && !this->audio_->isRunning()) { | ||||
|     this->stop_(); | ||||
|   this->i2s_state_ = I2S_STATE_RUNNING; | ||||
|   this->high_freq_.start(); | ||||
|   if (this->current_url_.has_value()) { | ||||
|     this->audio_->connecttohost(this->current_url_.value().c_str()); | ||||
|     this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; | ||||
|     this->publish_state(); | ||||
|   } | ||||
| } | ||||
| void I2SAudioMediaPlayer::stop() { this->i2s_state_ = I2S_STATE_STOPPING; } | ||||
| void I2SAudioMediaPlayer::stop_() { | ||||
|   if (this->audio_->isRunning()) { | ||||
|     this->audio_->stopSong(); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   this->audio_ = nullptr; | ||||
|   this->current_url_ = {}; | ||||
|   this->parent_->unlock(); | ||||
|   this->i2s_state_ = I2S_STATE_STOPPED; | ||||
| 
 | ||||
|   this->high_freq_.stop(); | ||||
|   this->state = media_player::MEDIA_PLAYER_STATE_IDLE; | ||||
|   this->publish_state(); | ||||
| } | ||||
| 
 | ||||
| media_player::MediaPlayerTraits I2SAudioMediaPlayer::get_traits() { | ||||
|   auto traits = media_player::MediaPlayerTraits(); | ||||
| @@ -2,6 +2,10 @@ | ||||
| 
 | ||||
| #ifdef USE_ESP32_FRAMEWORK_ARDUINO | ||||
| 
 | ||||
| #include "../i2s_audio.h" | ||||
| 
 | ||||
| #include <driver/i2s.h> | ||||
| 
 | ||||
| #include "esphome/components/media_player/media_player.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/gpio.h" | ||||
| @@ -12,7 +16,14 @@ | ||||
| namespace esphome { | ||||
| namespace i2s_audio { | ||||
| 
 | ||||
| class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer { | ||||
| enum I2SState : uint8_t { | ||||
|   I2S_STATE_STOPPED = 0, | ||||
|   I2S_STATE_STARTING, | ||||
|   I2S_STATE_RUNNING, | ||||
|   I2S_STATE_STOPPING, | ||||
| }; | ||||
| 
 | ||||
| class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer, public I2SAudioOut { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   float get_setup_priority() const override { return esphome::setup_priority::LATE; } | ||||
| @@ -22,8 +33,6 @@ class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer { | ||||
|   void dump_config() override; | ||||
| 
 | ||||
|   void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; } | ||||
|   void set_bclk_pin(uint8_t pin) { this->bclk_pin_ = pin; } | ||||
|   void set_lrclk_pin(uint8_t pin) { this->lrclk_pin_ = pin; } | ||||
|   void set_mute_pin(GPIOPin *mute_pin) { this->mute_pin_ = mute_pin; } | ||||
| #if SOC_I2S_SUPPORTS_DAC | ||||
|   void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; } | ||||
| @@ -34,20 +43,24 @@ class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer { | ||||
| 
 | ||||
|   bool is_muted() const override { return this->muted_; } | ||||
| 
 | ||||
|   void start(); | ||||
|   void stop(); | ||||
| 
 | ||||
|  protected: | ||||
|   void control(const media_player::MediaPlayerCall &call) override; | ||||
| 
 | ||||
|   void mute_(); | ||||
|   void unmute_(); | ||||
|   void set_volume_(float volume, bool publish = true); | ||||
|   void stop_(); | ||||
| 
 | ||||
|   void start_(); | ||||
|   void stop_(); | ||||
|   void play_(); | ||||
| 
 | ||||
|   I2SState i2s_state_{I2S_STATE_STOPPED}; | ||||
|   std::unique_ptr<Audio> audio_; | ||||
| 
 | ||||
|   uint8_t dout_pin_{0}; | ||||
|   uint8_t din_pin_{0}; | ||||
|   uint8_t bclk_pin_; | ||||
|   uint8_t lrclk_pin_; | ||||
| 
 | ||||
|   GPIOPin *mute_pin_{nullptr}; | ||||
|   bool muted_{false}; | ||||
| @@ -59,6 +72,8 @@ class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer { | ||||
|   uint8_t external_dac_channels_; | ||||
| 
 | ||||
|   HighFrequencyLoopRequester high_freq_; | ||||
| 
 | ||||
|   optional<std::string> current_url_{}; | ||||
| }; | ||||
| 
 | ||||
| }  // namespace i2s_audio
 | ||||
							
								
								
									
										41
									
								
								esphome/components/i2s_audio/microphone/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								esphome/components/i2s_audio/microphone/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| import esphome.config_validation as cv | ||||
| import esphome.codegen as cg | ||||
|  | ||||
| from esphome import pins | ||||
| from esphome.const import CONF_ID | ||||
| from esphome.components import microphone | ||||
|  | ||||
| from .. import ( | ||||
|     i2s_audio_ns, | ||||
|     I2SAudioComponent, | ||||
|     I2SAudioIn, | ||||
|     CONF_I2S_AUDIO_ID, | ||||
|     CONF_I2S_DIN_PIN, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@jesserockz"] | ||||
| DEPENDENCIES = ["i2s_audio"] | ||||
|  | ||||
| I2SAudioMicrophone = i2s_audio_ns.class_( | ||||
|     "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = microphone.MICROPHONE_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(I2SAudioMicrophone), | ||||
|         cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), | ||||
|         cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number, | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     parent = await cg.get_variable(config[CONF_I2S_AUDIO_ID]) | ||||
|     cg.add(parent.register_audio_in(var)) | ||||
|  | ||||
|     cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN])) | ||||
|  | ||||
|     await microphone.register_microphone(var, config) | ||||
							
								
								
									
										101
									
								
								esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| #include "i2s_audio_microphone.h" | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include <driver/i2s.h> | ||||
|  | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace i2s_audio { | ||||
|  | ||||
| static const size_t BUFFER_SIZE = 512; | ||||
|  | ||||
| static const char *const TAG = "i2s_audio.microphone"; | ||||
|  | ||||
| void I2SAudioMicrophone::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone..."); | ||||
|   this->buffer_.resize(BUFFER_SIZE); | ||||
| } | ||||
|  | ||||
| void I2SAudioMicrophone::start() { this->state_ = microphone::STATE_STARTING; } | ||||
| void I2SAudioMicrophone::start_() { | ||||
|   if (!this->parent_->try_lock()) { | ||||
|     return;  // Waiting for another i2s to return lock | ||||
|   } | ||||
|   i2s_driver_config_t config = { | ||||
|       .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM), | ||||
|       .sample_rate = 16000, | ||||
|       .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, | ||||
|       .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, | ||||
|       .communication_format = I2S_COMM_FORMAT_STAND_I2S, | ||||
|       .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, | ||||
|       .dma_buf_count = 4, | ||||
|       .dma_buf_len = 256, | ||||
|       .use_apll = false, | ||||
|       .tx_desc_auto_clear = false, | ||||
|       .fixed_mclk = 0, | ||||
|       .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, | ||||
|       .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, | ||||
|   }; | ||||
|  | ||||
|   i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); | ||||
|  | ||||
|   i2s_pin_config_t pin_config = this->parent_->get_pin_config(); | ||||
|   pin_config.data_in_num = this->din_pin_; | ||||
|  | ||||
|   i2s_set_pin(this->parent_->get_port(), &pin_config); | ||||
|   this->state_ = microphone::STATE_RUNNING; | ||||
|   this->high_freq_.start(); | ||||
| } | ||||
|  | ||||
| void I2SAudioMicrophone::stop() { | ||||
|   if (this->state_ == microphone::STATE_STOPPED) | ||||
|     return; | ||||
|   this->state_ = microphone::STATE_STOPPING; | ||||
| } | ||||
|  | ||||
| void I2SAudioMicrophone::stop_() { | ||||
|   i2s_stop(this->parent_->get_port()); | ||||
|   i2s_driver_uninstall(this->parent_->get_port()); | ||||
|   this->parent_->unlock(); | ||||
|   this->state_ = microphone::STATE_STOPPED; | ||||
|   this->high_freq_.stop(); | ||||
| } | ||||
|  | ||||
| void I2SAudioMicrophone::read_() { | ||||
|   size_t bytes_read = 0; | ||||
|   esp_err_t err = | ||||
|       i2s_read(this->parent_->get_port(), this->buffer_.data(), BUFFER_SIZE, &bytes_read, (100 / portTICK_PERIOD_MS)); | ||||
|   if (err != ESP_OK) { | ||||
|     ESP_LOGW(TAG, "Error reading from I2S microphone: %s", esp_err_to_name(err)); | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   this->status_clear_warning(); | ||||
|  | ||||
|   this->data_callbacks_.call(this->buffer_); | ||||
| } | ||||
|  | ||||
| void I2SAudioMicrophone::loop() { | ||||
|   switch (this->state_) { | ||||
|     case microphone::STATE_STOPPED: | ||||
|       break; | ||||
|     case microphone::STATE_STARTING: | ||||
|       this->start_(); | ||||
|       break; | ||||
|     case microphone::STATE_RUNNING: | ||||
|       this->read_(); | ||||
|       break; | ||||
|     case microphone::STATE_STOPPING: | ||||
|       this->stop_(); | ||||
|       break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace i2s_audio | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif  // USE_ESP32 | ||||
| @@ -0,0 +1,37 @@ | ||||
| #pragma once | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include "../i2s_audio.h" | ||||
|  | ||||
| #include "esphome/components/microphone/microphone.h" | ||||
| #include "esphome/core/component.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace i2s_audio { | ||||
|  | ||||
| class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, public Component { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void start() override; | ||||
|   void stop() override; | ||||
|  | ||||
|   void loop() override; | ||||
|  | ||||
|   void set_din_pin(uint8_t pin) { this->din_pin_ = pin; } | ||||
|  | ||||
|  protected: | ||||
|   void start_(); | ||||
|   void stop_(); | ||||
|   void read_(); | ||||
|  | ||||
|   uint8_t din_pin_{0}; | ||||
|   std::vector<uint8_t> buffer_; | ||||
|  | ||||
|   HighFrequencyLoopRequester high_freq_; | ||||
| }; | ||||
|  | ||||
| }  // namespace i2s_audio | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif  // USE_ESP32 | ||||
| @@ -1,8 +1,8 @@ | ||||
| #include "ili9xxx_display.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ili9xxx { | ||||
|   | ||||
| @@ -21,7 +21,7 @@ std::string ImprovBase::get_formatted_next_url_() { | ||||
|   // Ip address | ||||
|   pos = this->next_url_.find("{{ip_address}}"); | ||||
|   if (pos != std::string::npos) { | ||||
|     std::string ip = network::IPAddress(network::get_ip_address()).str(); | ||||
|     std::string ip = network::get_ip_address().str(); | ||||
|     copy.replace(pos, 14, ip); | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -60,7 +60,7 @@ LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ex | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(LightState), | ||||
|         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTJSONLightComponent), | ||||
|         cv.Optional(CONF_RESTORE_MODE, default="restore_default_off"): cv.enum( | ||||
|         cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum( | ||||
|             RESTORE_MODES, upper=True, space="_" | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_TURN_ON): auto.validate_automation( | ||||
|   | ||||
| @@ -59,7 +59,8 @@ enum class ColorMode : uint8_t { | ||||
|   COLOR_TEMPERATURE = | ||||
|       (uint8_t) (ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::COLOR_TEMPERATURE), | ||||
|   /// Cold and warm white output with individually controllable brightness. | ||||
|   COLD_WARM_WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::COLD_WARM_WHITE), | ||||
|   COLD_WARM_WHITE = | ||||
|       (uint8_t) (ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::COLD_WARM_WHITE), | ||||
|   /// RGB color output. | ||||
|   RGB = (uint8_t) (ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::RGB), | ||||
|   /// RGB color output and a separate white output. | ||||
|   | ||||
| @@ -8,7 +8,6 @@ namespace light { | ||||
|  | ||||
| static const char *const TAG = "light"; | ||||
|  | ||||
| LightState::LightState(const std::string &name, LightOutput *output) : EntityBase(name), output_(output) {} | ||||
| LightState::LightState(LightOutput *output) : output_(output) {} | ||||
|  | ||||
| LightTraits LightState::get_traits() { return this->output_->get_traits(); } | ||||
|   | ||||
| @@ -33,9 +33,6 @@ enum LightRestoreMode { | ||||
|  */ | ||||
| class LightState : public EntityBase, public Component { | ||||
|  public: | ||||
|   /// Construct this LightState using the provided traits and name. | ||||
|   LightState(const std::string &name, LightOutput *output); | ||||
|  | ||||
|   LightState(LightOutput *output); | ||||
|  | ||||
|   LightTraits get_traits(); | ||||
|   | ||||
| @@ -24,8 +24,7 @@ const char *lock_state_to_string(LockState state) { | ||||
|   } | ||||
| } | ||||
|  | ||||
| Lock::Lock(const std::string &name) : EntityBase(name), state(LOCK_STATE_NONE) {} | ||||
| Lock::Lock() : Lock("") {} | ||||
| Lock::Lock() : state(LOCK_STATE_NONE) {} | ||||
| LockCall Lock::make_call() { return LockCall(this); } | ||||
|  | ||||
| void Lock::lock() { | ||||
|   | ||||
| @@ -103,7 +103,6 @@ class LockCall { | ||||
| class Lock : public EntityBase { | ||||
|  public: | ||||
|   explicit Lock(); | ||||
|   explicit Lock(const std::string &name); | ||||
|  | ||||
|   /** Make a lock device control call, this is used to control the lock device, see the LockCall description | ||||
|    * for more info. | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| #include "logger.h" | ||||
| #include <cinttypes> | ||||
|  | ||||
| #ifdef USE_ESP_IDF | ||||
| #include <driver/uart.h> | ||||
| @@ -292,7 +293,7 @@ const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"}; | ||||
| void Logger::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Logger:"); | ||||
|   ESP_LOGCONFIG(TAG, "  Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]); | ||||
|   ESP_LOGCONFIG(TAG, "  Log Baud Rate: %u", this->baud_rate_); | ||||
|   ESP_LOGCONFIG(TAG, "  Log Baud Rate: %" PRIu32, this->baud_rate_); | ||||
|   ESP_LOGCONFIG(TAG, "  Hardware UART: %s", UART_SELECTIONS[this->uart_]); | ||||
|   for (auto &it : this->log_levels_) { | ||||
|     ESP_LOGCONFIG(TAG, "  Level for '%s': %s", it.tag.c_str(), LOG_LEVELS[it.level]); | ||||
|   | ||||
| @@ -7,6 +7,7 @@ CODEOWNERS = ["@rspaargaren"] | ||||
| DEPENDENCIES = ["spi"] | ||||
|  | ||||
| CONF_ROTATE_CHIP = "rotate_chip" | ||||
| CONF_FLIP_X = "flip_x" | ||||
| CONF_SCROLL_SPEED = "scroll_speed" | ||||
| CONF_SCROLL_DWELL = "scroll_dwell" | ||||
| CONF_SCROLL_DELAY = "scroll_delay" | ||||
| @@ -67,6 +68,7 @@ CONFIG_SCHEMA = ( | ||||
|                 CONF_SCROLL_DWELL, default="1000ms" | ||||
|             ): cv.positive_time_period_milliseconds, | ||||
|             cv.Optional(CONF_REVERSE_ENABLE, default=False): cv.boolean, | ||||
|             cv.Optional(CONF_FLIP_X, default=False): cv.boolean, | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("500ms")) | ||||
| @@ -91,6 +93,7 @@ async def to_code(config): | ||||
|     cg.add(var.set_scroll(config[CONF_SCROLL_ENABLE])) | ||||
|     cg.add(var.set_scroll_mode(config[CONF_SCROLL_MODE])) | ||||
|     cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE])) | ||||
|     cg.add(var.set_flip_x([CONF_FLIP_X])) | ||||
|  | ||||
|     if CONF_LAMBDA in config: | ||||
|         lambda_ = await cg.process_lambda( | ||||
|   | ||||
| @@ -261,14 +261,22 @@ void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) { | ||||
|     if (this->orientation_ == 0) { | ||||
|       for (uint8_t i = 0; i < 8; i++) { | ||||
|         // run this loop 8 times for all the pixels[8] received | ||||
|         if (this->flip_x_) { | ||||
|           b |= ((pixels[i] >> col) & 1) << i;  // change the column bits into row bits | ||||
|         } else { | ||||
|           b |= ((pixels[i] >> col) & 1) << (7 - i);  // change the column bits into row bits | ||||
|         } | ||||
|       } | ||||
|     } else if (this->orientation_ == 1) { | ||||
|       b = pixels[col]; | ||||
|     } else if (this->orientation_ == 2) { | ||||
|       for (uint8_t i = 0; i < 8; i++) { | ||||
|         if (this->flip_x_) { | ||||
|           b |= ((pixels[i] >> (7 - col)) & 1) << (7 - i); | ||||
|         } else { | ||||
|           b |= ((pixels[i] >> (7 - col)) & 1) << i; | ||||
|         } | ||||
|       } | ||||
|     } else { | ||||
|       b = pixels[7 - col]; | ||||
|     } | ||||
|   | ||||
| @@ -67,6 +67,7 @@ class MAX7219Component : public PollingComponent, | ||||
|   void set_scroll(bool on_off) { this->scroll_ = on_off; }; | ||||
|   void set_scroll_mode(ScrollMode mode) { this->scroll_mode_ = mode; }; | ||||
|   void set_reverse(bool on_off) { this->reverse_ = on_off; }; | ||||
|   void set_flip_x(bool flip_x) { this->flip_x_ = flip_x; }; | ||||
|  | ||||
|   void send_char(uint8_t chip, uint8_t data); | ||||
|   void send64pixels(uint8_t chip, const uint8_t pixels[8]); | ||||
| @@ -108,6 +109,7 @@ class MAX7219Component : public PollingComponent, | ||||
|   ChipLinesStyle chip_lines_style_; | ||||
|   bool scroll_; | ||||
|   bool reverse_; | ||||
|   bool flip_x_; | ||||
|   bool update_{false}; | ||||
|   uint16_t scroll_speed_; | ||||
|   uint16_t scroll_delay_; | ||||
|   | ||||
							
								
								
									
										91
									
								
								esphome/components/microphone/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								esphome/components/microphone/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| from esphome import automation | ||||
| import esphome.config_validation as cv | ||||
| import esphome.codegen as cg | ||||
|  | ||||
| from esphome.automation import maybe_simple_id | ||||
| from esphome.const import CONF_ID, CONF_TRIGGER_ID | ||||
| from esphome.core import CORE | ||||
| from esphome.coroutine import coroutine_with_priority | ||||
|  | ||||
|  | ||||
| CODEOWNERS = ["@jesserockz"] | ||||
|  | ||||
| IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| CONF_ON_DATA = "on_data" | ||||
|  | ||||
| microphone_ns = cg.esphome_ns.namespace("microphone") | ||||
|  | ||||
| Microphone = microphone_ns.class_("Microphone") | ||||
|  | ||||
| CaptureAction = microphone_ns.class_( | ||||
|     "CaptureAction", automation.Action, cg.Parented.template(Microphone) | ||||
| ) | ||||
| StopCaptureAction = microphone_ns.class_( | ||||
|     "StopCaptureAction", automation.Action, cg.Parented.template(Microphone) | ||||
| ) | ||||
|  | ||||
|  | ||||
| DataTrigger = microphone_ns.class_( | ||||
|     "DataTrigger", | ||||
|     automation.Trigger.template(cg.std_vector.template(cg.int16).operator("ref")), | ||||
| ) | ||||
|  | ||||
| IsCapturingCondition = microphone_ns.class_( | ||||
|     "IsCapturingCondition", automation.Condition | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def setup_microphone_core_(var, config): | ||||
|     for conf in config.get(CONF_ON_DATA, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation( | ||||
|             trigger, | ||||
|             [(cg.std_vector.template(cg.uint8).operator("ref").operator("const"), "x")], | ||||
|             conf, | ||||
|         ) | ||||
|  | ||||
|  | ||||
| async def register_microphone(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     await setup_microphone_core_(var, config) | ||||
|  | ||||
|  | ||||
| MICROPHONE_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.Optional(CONF_ON_DATA): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DataTrigger), | ||||
|             } | ||||
|         ), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| MICROPHONE_ACTION_SCHEMA = maybe_simple_id({cv.GenerateID(): cv.use_id(Microphone)}) | ||||
|  | ||||
|  | ||||
| async def media_player_action(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     await cg.register_parented(var, config[CONF_ID]) | ||||
|     return var | ||||
|  | ||||
|  | ||||
| automation.register_action( | ||||
|     "microphone.capture", CaptureAction, MICROPHONE_ACTION_SCHEMA | ||||
| )(media_player_action) | ||||
|  | ||||
| automation.register_action( | ||||
|     "microphone.stop_capture", StopCaptureAction, MICROPHONE_ACTION_SCHEMA | ||||
| )(media_player_action) | ||||
|  | ||||
| automation.register_condition( | ||||
|     "microphone.is_capturing", IsCapturingCondition, MICROPHONE_ACTION_SCHEMA | ||||
| )(media_player_action) | ||||
|  | ||||
|  | ||||
| @coroutine_with_priority(100.0) | ||||
| async def to_code(config): | ||||
|     cg.add_global(microphone_ns.using) | ||||
|     cg.add_define("USE_MICROPHONE") | ||||
							
								
								
									
										32
									
								
								esphome/components/microphone/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								esphome/components/microphone/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/automation.h" | ||||
| #include "microphone.h" | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace microphone { | ||||
|  | ||||
| template<typename... Ts> class CaptureAction : public Action<Ts...>, public Parented<Microphone> { | ||||
|   void play(Ts... x) override { this->parent_->start(); } | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class StopCaptureAction : public Action<Ts...>, public Parented<Microphone> { | ||||
|   void play(Ts... x) override { this->parent_->stop(); } | ||||
| }; | ||||
|  | ||||
| class DataTrigger : public Trigger<const std::vector<uint8_t> &> { | ||||
|  public: | ||||
|   explicit DataTrigger(Microphone *mic) { | ||||
|     mic->add_data_callback([this](const std::vector<uint8_t> &data) { this->trigger(data); }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class IsCapturingActon : public Condition<Ts...>, public Parented<Microphone> { | ||||
|  public: | ||||
|   bool check(Ts... x) override { return this->parent_->is_running(); } | ||||
| }; | ||||
|  | ||||
| }  // namespace microphone | ||||
| }  // namespace esphome | ||||
							
								
								
									
										33
									
								
								esphome/components/microphone/microphone.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								esphome/components/microphone/microphone.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/entity_base.h" | ||||
| #include "esphome/core/helpers.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace microphone { | ||||
|  | ||||
| enum State : uint8_t { | ||||
|   STATE_STOPPED = 0, | ||||
|   STATE_STARTING, | ||||
|   STATE_RUNNING, | ||||
|   STATE_STOPPING, | ||||
| }; | ||||
|  | ||||
| class Microphone { | ||||
|  public: | ||||
|   virtual void start() = 0; | ||||
|   virtual void stop() = 0; | ||||
|   void add_data_callback(std::function<void(const std::vector<uint8_t> &)> &&data_callback) { | ||||
|     this->data_callbacks_.add(std::move(data_callback)); | ||||
|   } | ||||
|  | ||||
|   bool is_running() const { return this->state_ == STATE_RUNNING; } | ||||
|  | ||||
|  protected: | ||||
|   State state_{STATE_STOPPED}; | ||||
|  | ||||
|   CallbackManager<void(const std::vector<uint8_t> &)> data_callbacks_{}; | ||||
| }; | ||||
|  | ||||
| }  // namespace microphone | ||||
| }  // namespace esphome | ||||
							
								
								
									
										1
									
								
								esphome/components/mmc5603/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/mmc5603/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| CODEOWNERS = ["@benhoff"] | ||||
							
								
								
									
										162
									
								
								esphome/components/mmc5603/mmc5603.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								esphome/components/mmc5603/mmc5603.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | ||||
| #include "mmc5603.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace mmc5603 { | ||||
|  | ||||
| static const char *const TAG = "mmc5603"; | ||||
| static const uint8_t MMC5603_ADDRESS = 0x30; | ||||
| static const uint8_t MMC56X3_PRODUCT_ID = 0x39; | ||||
|  | ||||
| static const uint8_t MMC56X3_DEFAULT_ADDRESS = 0x30; | ||||
| static const uint8_t MMC56X3_CHIP_ID = 0x10; | ||||
|  | ||||
| static const uint8_t MMC56X3_ADDR_XOUT0 = 0x00; | ||||
| static const uint8_t MMC56X3_ADDR_XOUT1 = 0x01; | ||||
| static const uint8_t MMC56X3_ADDR_XOUT2 = 0x06; | ||||
|  | ||||
| static const uint8_t MMC56X3_ADDR_YOUT0 = 0x02; | ||||
| static const uint8_t MMC56X3_ADDR_YOUT1 = 0x03; | ||||
| static const uint8_t MMC56X3_ADDR_YOUT2 = 0x07; | ||||
|  | ||||
| static const uint8_t MMC56X3_ADDR_ZOUT0 = 0x04; | ||||
| static const uint8_t MMC56X3_ADDR_ZOUT1 = 0x05; | ||||
| static const uint8_t MMC56X3_ADDR_ZOUT2 = 0x08; | ||||
|  | ||||
| static const uint8_t MMC56X3_OUT_TEMP = 0x09; | ||||
| static const uint8_t MMC56X3_STATUS_REG = 0x18; | ||||
| static const uint8_t MMC56X3_CTRL0_REG = 0x1B; | ||||
| static const uint8_t MMC56X3_CTRL1_REG = 0x1C; | ||||
| static const uint8_t MMC56X3_CTRL2_REG = 0x1D; | ||||
| static const uint8_t MMC5603_ODR_REG = 0x1A; | ||||
|  | ||||
| void MMC5603Component::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up MMC5603..."); | ||||
|   uint8_t id = 0; | ||||
|   if (!this->read_byte(MMC56X3_PRODUCT_ID, &id)) { | ||||
|     this->error_code_ = COMMUNICATION_FAILED; | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (id != MMC56X3_CHIP_ID) { | ||||
|     ESP_LOGCONFIG(TAG, "Chip Wrong"); | ||||
|     this->error_code_ = ID_REGISTERS; | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!this->write_byte(MMC56X3_CTRL1_REG, 0x80)) {  // turn on set bit | ||||
|     ESP_LOGCONFIG(TAG, "Control 1 Failed for set bit"); | ||||
|     this->error_code_ = COMMUNICATION_FAILED; | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!this->write_byte(MMC56X3_CTRL0_REG, 0x08)) {  // turn on set bit | ||||
|     ESP_LOGCONFIG(TAG, "Control 0 Failed for set bit"); | ||||
|     this->error_code_ = COMMUNICATION_FAILED; | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!this->write_byte(MMC56X3_CTRL0_REG, 0x10)) { | ||||
|     this->error_code_ = COMMUNICATION_FAILED; | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   uint8_t ctrl_2 = 0; | ||||
|  | ||||
|   ctrl_2 &= ~0x10;  // turn off cmm_en bit | ||||
|   if (!this->write_byte(MMC56X3_CTRL2_REG, ctrl_2)) { | ||||
|     this->error_code_ = COMMUNICATION_FAILED; | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
| } | ||||
| void MMC5603Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "MMC5603:"); | ||||
|   LOG_I2C_DEVICE(this); | ||||
|   if (this->error_code_ == COMMUNICATION_FAILED) { | ||||
|     ESP_LOGE(TAG, "Communication with MMC5603 failed!"); | ||||
|   } else if (this->error_code_ == ID_REGISTERS) { | ||||
|     ESP_LOGE(TAG, "The ID registers don't match - Is this really an MMC5603?"); | ||||
|   } | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
|  | ||||
|   LOG_SENSOR("  ", "X Axis", this->x_sensor_); | ||||
|   LOG_SENSOR("  ", "Y Axis", this->y_sensor_); | ||||
|   LOG_SENSOR("  ", "Z Axis", this->z_sensor_); | ||||
|   LOG_SENSOR("  ", "Heading", this->heading_sensor_); | ||||
| } | ||||
|  | ||||
| float MMC5603Component::get_setup_priority() const { return setup_priority::DATA; } | ||||
|  | ||||
| void MMC5603Component::update() { | ||||
|   if (!this->write_byte(MMC56X3_CTRL0_REG, 0x01)) { | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
|   } | ||||
|   uint8_t status = 0; | ||||
|   if (!this->read_byte(MMC56X3_STATUS_REG, &status)) { | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   uint8_t buffer[9] = {0}; | ||||
|  | ||||
|   if (!this->read_byte(MMC56X3_ADDR_XOUT0, &buffer[0]) || !this->read_byte(MMC56X3_ADDR_XOUT1, &buffer[1]) || | ||||
|       !this->read_byte(MMC56X3_ADDR_XOUT2, &buffer[2])) { | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!this->read_byte(MMC56X3_ADDR_YOUT0, &buffer[3]) || !this->read_byte(MMC56X3_ADDR_YOUT1, &buffer[4]) || | ||||
|       !this->read_byte(MMC56X3_ADDR_YOUT2, &buffer[5])) { | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!this->read_byte(MMC56X3_ADDR_ZOUT0, &buffer[6]) || !this->read_byte(MMC56X3_ADDR_ZOUT1, &buffer[7]) || | ||||
|       !this->read_byte(MMC56X3_ADDR_ZOUT2, &buffer[8])) { | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   int32_t raw_x = 0; | ||||
|   raw_x |= buffer[0] << 12; | ||||
|   raw_x |= buffer[1] << 4; | ||||
|   raw_x |= buffer[2] << 0; | ||||
|  | ||||
|   const float x = 0.0625 * (raw_x - 524288); | ||||
|  | ||||
|   int32_t raw_y = 0; | ||||
|   raw_y |= buffer[3] << 12; | ||||
|   raw_y |= buffer[4] << 4; | ||||
|   raw_y |= buffer[5] << 0; | ||||
|  | ||||
|   const float y = 0.0625 * (raw_y - 524288); | ||||
|  | ||||
|   int32_t raw_z = 0; | ||||
|   raw_z |= buffer[6] << 12; | ||||
|   raw_z |= buffer[7] << 4; | ||||
|   raw_z |= buffer[8] << 0; | ||||
|  | ||||
|   const float z = 0.0625 * (raw_z - 524288); | ||||
|  | ||||
|   const float heading = atan2f(0.0f - x, y) * 180.0f / M_PI; | ||||
|   ESP_LOGD(TAG, "Got x=%0.02fµT y=%0.02fµT z=%0.02fµT heading=%0.01f°", x, y, z, heading); | ||||
|  | ||||
|   if (this->x_sensor_ != nullptr) | ||||
|     this->x_sensor_->publish_state(x); | ||||
|   if (this->y_sensor_ != nullptr) | ||||
|     this->y_sensor_->publish_state(y); | ||||
|   if (this->z_sensor_ != nullptr) | ||||
|     this->z_sensor_->publish_state(z); | ||||
|   if (this->heading_sensor_ != nullptr) | ||||
|     this->heading_sensor_->publish_state(heading); | ||||
| } | ||||
|  | ||||
| }  // namespace mmc5603 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										43
									
								
								esphome/components/mmc5603/mmc5603.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								esphome/components/mmc5603/mmc5603.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace mmc5603 { | ||||
|  | ||||
| enum MMC5603Datarate { | ||||
|   MMC5603_DATARATE_75_0_HZ, | ||||
|   MMC5603_DATARATE_150_0_HZ, | ||||
|   MMC5603_DATARATE_255_0_HZ, | ||||
| }; | ||||
|  | ||||
| class MMC5603Component : public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override; | ||||
|   void update() override; | ||||
|  | ||||
|   void set_datarate(MMC5603Datarate datarate) { datarate_ = datarate; } | ||||
|   void set_x_sensor(sensor::Sensor *x_sensor) { x_sensor_ = x_sensor; } | ||||
|   void set_y_sensor(sensor::Sensor *y_sensor) { y_sensor_ = y_sensor; } | ||||
|   void set_z_sensor(sensor::Sensor *z_sensor) { z_sensor_ = z_sensor; } | ||||
|   void set_heading_sensor(sensor::Sensor *heading_sensor) { heading_sensor_ = heading_sensor; } | ||||
|  | ||||
|  protected: | ||||
|   MMC5603Datarate datarate_; | ||||
|   sensor::Sensor *x_sensor_{nullptr}; | ||||
|   sensor::Sensor *y_sensor_{nullptr}; | ||||
|   sensor::Sensor *z_sensor_{nullptr}; | ||||
|   sensor::Sensor *heading_sensor_{nullptr}; | ||||
|   enum ErrorCode { | ||||
|     NONE = 0, | ||||
|     COMMUNICATION_FAILED, | ||||
|     ID_REGISTERS, | ||||
|   } error_code_; | ||||
| }; | ||||
|  | ||||
| }  // namespace mmc5603 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										91
									
								
								esphome/components/mmc5603/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								esphome/components/mmc5603/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import ( | ||||
|     CONF_ADDRESS, | ||||
|     CONF_ID, | ||||
|     ICON_MAGNET, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_MICROTESLA, | ||||
|     UNIT_DEGREES, | ||||
|     ICON_SCREEN_ROTATION, | ||||
|     CONF_UPDATE_INTERVAL, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| mmc5603_ns = cg.esphome_ns.namespace("mmc5603") | ||||
|  | ||||
| CONF_FIELD_STRENGTH_X = "field_strength_x" | ||||
| CONF_FIELD_STRENGTH_Y = "field_strength_y" | ||||
| CONF_FIELD_STRENGTH_Z = "field_strength_z" | ||||
| CONF_HEADING = "heading" | ||||
|  | ||||
| MMC5603Component = mmc5603_ns.class_( | ||||
|     "MMC5603Component", cg.PollingComponent, i2c.I2CDevice | ||||
| ) | ||||
|  | ||||
|  | ||||
| MMC5603Datarate = mmc5603_ns.enum("MMC5603Datarate") | ||||
| MMC5603Datarates = { | ||||
|     75: MMC5603Datarate.MMC5603_DATARATE_75_0_HZ, | ||||
|     150: MMC5603Datarate.MMC5603_DATARATE_150_0_HZ, | ||||
|     255: MMC5603Datarate.MMC5603_DATARATE_255_0_HZ, | ||||
| } | ||||
|  | ||||
|  | ||||
| field_strength_schema = sensor.sensor_schema( | ||||
|     unit_of_measurement=UNIT_MICROTESLA, | ||||
|     icon=ICON_MAGNET, | ||||
|     accuracy_decimals=1, | ||||
|     state_class=STATE_CLASS_MEASUREMENT, | ||||
| ) | ||||
| heading_schema = sensor.sensor_schema( | ||||
|     unit_of_measurement=UNIT_DEGREES, | ||||
|     icon=ICON_SCREEN_ROTATION, | ||||
|     accuracy_decimals=1, | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(MMC5603Component), | ||||
|             cv.Optional(CONF_ADDRESS): cv.i2c_address, | ||||
|             cv.Optional(CONF_FIELD_STRENGTH_X): field_strength_schema, | ||||
|             cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema, | ||||
|             cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema, | ||||
|             cv.Optional(CONF_HEADING): heading_schema, | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x1E)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def auto_data_rate(config): | ||||
|     interval_msec = config[CONF_UPDATE_INTERVAL].total_milliseconds | ||||
|     interval_hz = 1000.0 / interval_msec | ||||
|     for datarate in sorted(MMC5603Datarates.keys()): | ||||
|         if float(datarate) >= interval_hz: | ||||
|             return MMC5603Datarates[datarate] | ||||
|     return MMC5603Datarates[75] | ||||
|  | ||||
|  | ||||
| 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) | ||||
|  | ||||
|     cg.add(var.set_datarate(auto_data_rate(config))) | ||||
|     if CONF_FIELD_STRENGTH_X in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_FIELD_STRENGTH_X]) | ||||
|         cg.add(var.set_x_sensor(sens)) | ||||
|     if CONF_FIELD_STRENGTH_Y in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_FIELD_STRENGTH_Y]) | ||||
|         cg.add(var.set_y_sensor(sens)) | ||||
|     if CONF_FIELD_STRENGTH_Z in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_FIELD_STRENGTH_Z]) | ||||
|         cg.add(var.set_z_sensor(sens)) | ||||
|     if CONF_HEADING in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_HEADING]) | ||||
|         cg.add(var.set_heading_sensor(sens)) | ||||
| @@ -64,9 +64,10 @@ INTEGER_SENSOR_VALUE_TYPE = { | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     select.SELECT_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( | ||||
|     select.select_schema(ModbusSelect) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(ModbusSelect), | ||||
|             cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), | ||||
|             cv.Required(CONF_ADDRESS): cv.positive_int, | ||||
|             cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum( | ||||
|   | ||||
| @@ -123,7 +123,8 @@ uint32_t MopekaProCheck::parse_distance_(const std::vector<uint8_t> &message) { | ||||
|   double raw_level = raw & 0x3FFF; | ||||
|   double raw_t = (message[2] & 0x7F); | ||||
|  | ||||
|   return (uint32_t)(raw_level * (MOPEKA_LPG_COEF[0] + MOPEKA_LPG_COEF[1] * raw_t + MOPEKA_LPG_COEF[2] * raw_t * raw_t)); | ||||
|   return (uint32_t) (raw_level * | ||||
|                      (MOPEKA_LPG_COEF[0] + MOPEKA_LPG_COEF[1] * raw_t + MOPEKA_LPG_COEF[2] * raw_t * raw_t)); | ||||
| } | ||||
|  | ||||
| uint8_t MopekaProCheck::parse_temperature_(const std::vector<uint8_t> &message) { return (message[2] & 0x7F) - 40; } | ||||
|   | ||||
| @@ -2,16 +2,16 @@ | ||||
|  | ||||
| #ifdef USE_MQTT | ||||
|  | ||||
| #include <utility> | ||||
| #include "esphome/components/network/util.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/components/network/util.h" | ||||
| #include <utility> | ||||
| #ifdef USE_LOGGER | ||||
| #include "esphome/components/logger/logger.h" | ||||
| #endif | ||||
| #include "lwip/err.h" | ||||
| #include "lwip/dns.h" | ||||
| #include "lwip/err.h" | ||||
| #include "mqtt_component.h" | ||||
|  | ||||
| namespace esphome { | ||||
| @@ -104,7 +104,11 @@ void MQTTClientComponent::start_dnslookup_() { | ||||
|       // Got IP immediately | ||||
|       this->dns_resolved_ = true; | ||||
| #ifdef USE_ESP32 | ||||
| #if LWIP_IPV6 | ||||
|       this->ip_ = addr.u_addr.ip4.addr; | ||||
| #else | ||||
|       this->ip_ = addr.addr; | ||||
| #endif | ||||
| #endif | ||||
| #ifdef USE_ESP8266 | ||||
|       this->ip_ = addr.addr; | ||||
| @@ -160,8 +164,12 @@ void MQTTClientComponent::dns_found_callback(const char *name, const ip_addr_t * | ||||
|     a_this->dns_resolve_error_ = true; | ||||
|   } else { | ||||
| #ifdef USE_ESP32 | ||||
| #if LWIP_IPV6 | ||||
|     a_this->ip_ = ipaddr->u_addr.ip4.addr; | ||||
| #else | ||||
|     a_this->ip_ = ipaddr->addr; | ||||
| #endif | ||||
| #endif  // USE_ESP32 | ||||
| #ifdef USE_ESP8266 | ||||
|     a_this->ip_ = ipaddr->addr; | ||||
| #endif | ||||
|   | ||||
| @@ -22,6 +22,8 @@ CONFIG_SCHEMA = cv.Schema( | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     if CONF_ENABLE_IPV6 in config and config[CONF_ENABLE_IPV6]: | ||||
|         add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", True) | ||||
|         add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", True) | ||||
|     if CONF_ENABLE_IPV6 in config: | ||||
|         add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6]) | ||||
|         add_idf_sdkconfig_option( | ||||
|             "CONFIG_LWIP_IPV6_AUTOCONFIG", config[CONF_ENABLE_IPV6] | ||||
|         ) | ||||
|   | ||||
| @@ -44,6 +44,11 @@ NdefMessage::NdefMessage(std::vector<uint8_t> &data) { | ||||
|       index += id_length; | ||||
|     } | ||||
|  | ||||
|     if ((data.begin() + index > data.end()) || (data.begin() + index + payload_length > data.end())) { | ||||
|       ESP_LOGE(TAG, "Corrupt record encountered; NdefMessage constructor aborting"); | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     std::vector<uint8_t> payload_data(data.begin() + index, data.begin() + index + payload_length); | ||||
|  | ||||
|     std::unique_ptr<NdefRecord> record; | ||||
|   | ||||
| @@ -42,8 +42,8 @@ class NdefRecord { | ||||
|   virtual const std::string &get_payload() const { return this->payload_; }; | ||||
|  | ||||
|   virtual std::vector<uint8_t> get_encoded_payload() { | ||||
|     std::vector<uint8_t> empty_payload; | ||||
|     return empty_payload; | ||||
|     std::vector<uint8_t> payload(this->payload_.begin(), this->payload_.end()); | ||||
|     return payload; | ||||
|   }; | ||||
|  | ||||
|  protected: | ||||
|   | ||||
| @@ -89,18 +89,18 @@ uint32_t get_mifare_classic_buffer_size(uint32_t message_length) { | ||||
| } | ||||
|  | ||||
| bool mifare_classic_is_first_block(uint8_t block_num) { | ||||
|   if (block_num < 128) { | ||||
|     return (block_num % 4 == 0); | ||||
|   if (block_num < MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW * MIFARE_CLASSIC_16BLOCK_SECT_START) { | ||||
|     return (block_num % MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW == 0); | ||||
|   } else { | ||||
|     return (block_num % 16 == 0); | ||||
|     return (block_num % MIFARE_CLASSIC_BLOCKS_PER_SECT_HIGH == 0); | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool mifare_classic_is_trailer_block(uint8_t block_num) { | ||||
|   if (block_num < 128) { | ||||
|     return ((block_num + 1) % 4 == 0); | ||||
|   if (block_num < MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW * MIFARE_CLASSIC_16BLOCK_SECT_START) { | ||||
|     return ((block_num + 1) % MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW == 0); | ||||
|   } else { | ||||
|     return ((block_num + 1) % 16 == 0); | ||||
|     return ((block_num + 1) % MIFARE_CLASSIC_BLOCKS_PER_SECT_HIGH == 0); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -14,6 +14,9 @@ namespace nfc { | ||||
| static const uint8_t MIFARE_CLASSIC_BLOCK_SIZE = 16; | ||||
| static const uint8_t MIFARE_CLASSIC_LONG_TLV_SIZE = 4; | ||||
| static const uint8_t MIFARE_CLASSIC_SHORT_TLV_SIZE = 2; | ||||
| static const uint8_t MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW = 4; | ||||
| static const uint8_t MIFARE_CLASSIC_BLOCKS_PER_SECT_HIGH = 16; | ||||
| static const uint8_t MIFARE_CLASSIC_16BLOCK_SECT_START = 32; | ||||
|  | ||||
| static const uint8_t MIFARE_ULTRALIGHT_PAGE_SIZE = 4; | ||||
| static const uint8_t MIFARE_ULTRALIGHT_READ_SIZE = 4; | ||||
| @@ -30,10 +33,18 @@ static const uint8_t TAG_TYPE_UNKNOWN = 99; | ||||
| // Mifare Commands | ||||
| static const uint8_t MIFARE_CMD_AUTH_A = 0x60; | ||||
| static const uint8_t MIFARE_CMD_AUTH_B = 0x61; | ||||
| static const uint8_t MIFARE_CMD_HALT = 0x50; | ||||
| static const uint8_t MIFARE_CMD_READ = 0x30; | ||||
| static const uint8_t MIFARE_CMD_WRITE = 0xA0; | ||||
| static const uint8_t MIFARE_CMD_WRITE_ULTRALIGHT = 0xA2; | ||||
|  | ||||
| // Mifare Ack/Nak | ||||
| static const uint8_t MIFARE_CMD_ACK = 0x0A; | ||||
| static const uint8_t MIFARE_CMD_NAK_INVALID_XFER_BUFF_VALID = 0x00; | ||||
| static const uint8_t MIFARE_CMD_NAK_CRC_ERROR_XFER_BUFF_VALID = 0x01; | ||||
| static const uint8_t MIFARE_CMD_NAK_INVALID_XFER_BUFF_INVALID = 0x04; | ||||
| static const uint8_t MIFARE_CMD_NAK_CRC_ERROR_XFER_BUFF_INVALID = 0x05; | ||||
|  | ||||
| static const char *const MIFARE_CLASSIC = "Mifare Classic"; | ||||
| static const char *const NFC_FORUM_TYPE_2 = "NFC Forum Type 2"; | ||||
| static const char *const ERROR = "Error"; | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| from typing import Optional | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| @@ -31,6 +30,7 @@ from esphome.const import ( | ||||
|     DEVICE_CLASS_DISTANCE, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     DEVICE_CLASS_ENERGY, | ||||
|     DEVICE_CLASS_ENERGY_STORAGE, | ||||
|     DEVICE_CLASS_FREQUENCY, | ||||
|     DEVICE_CLASS_GAS, | ||||
|     DEVICE_CLASS_HUMIDITY, | ||||
| @@ -59,6 +59,7 @@ from esphome.const import ( | ||||
|     DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     DEVICE_CLASS_VOLUME, | ||||
|     DEVICE_CLASS_VOLUME_STORAGE, | ||||
|     DEVICE_CLASS_WATER, | ||||
|     DEVICE_CLASS_WEIGHT, | ||||
|     DEVICE_CLASS_WIND_SPEED, | ||||
| @@ -81,6 +82,7 @@ DEVICE_CLASSES = [ | ||||
|     DEVICE_CLASS_DISTANCE, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     DEVICE_CLASS_ENERGY, | ||||
|     DEVICE_CLASS_ENERGY_STORAGE, | ||||
|     DEVICE_CLASS_FREQUENCY, | ||||
|     DEVICE_CLASS_GAS, | ||||
|     DEVICE_CLASS_HUMIDITY, | ||||
| @@ -109,6 +111,7 @@ DEVICE_CLASSES = [ | ||||
|     DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     DEVICE_CLASS_VOLUME, | ||||
|     DEVICE_CLASS_VOLUME_STORAGE, | ||||
|     DEVICE_CLASS_WATER, | ||||
|     DEVICE_CLASS_WEIGHT, | ||||
|     DEVICE_CLASS_WIND_SPEED, | ||||
| @@ -204,13 +207,12 @@ def number_schema( | ||||
|  | ||||
|  | ||||
| async def setup_number_core_( | ||||
|     var, config, *, min_value: float, max_value: float, step: Optional[float] | ||||
|     var, config, *, min_value: float, max_value: float, step: float | ||||
| ): | ||||
|     await setup_entity(var, config) | ||||
|  | ||||
|     cg.add(var.traits.set_min_value(min_value)) | ||||
|     cg.add(var.traits.set_max_value(max_value)) | ||||
|     if step is not None: | ||||
|     cg.add(var.traits.set_step(step)) | ||||
|  | ||||
|     cg.add(var.traits.set_mode(config[CONF_MODE])) | ||||
| @@ -239,7 +241,7 @@ async def setup_number_core_( | ||||
|  | ||||
|  | ||||
| async def register_number( | ||||
|     var, config, *, min_value: float, max_value: float, step: Optional[float] = None | ||||
|     var, config, *, min_value: float, max_value: float, step: float | ||||
| ): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
| @@ -249,9 +251,7 @@ async def register_number( | ||||
|     ) | ||||
|  | ||||
|  | ||||
| async def new_number( | ||||
|     config, *, min_value: float, max_value: float, step: Optional[float] = None | ||||
| ): | ||||
| async def new_number(config, *, min_value: float, max_value: float, step: float): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await register_number( | ||||
|         var, config, min_value=min_value, max_value=max_value, step=step | ||||
|   | ||||
| @@ -65,7 +65,7 @@ void OTAComponent::setup() { | ||||
|  | ||||
|   struct sockaddr_storage server; | ||||
|  | ||||
|   socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_)); | ||||
|   socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_); | ||||
|   if (sl == 0) { | ||||
|     ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno); | ||||
|     this->mark_failed(); | ||||
|   | ||||
| @@ -102,7 +102,7 @@ async def to_code(config): | ||||
|                 conf[CONF_ADDRESSABLE_LIGHT_ID], | ||||
|                 await cg.get_variable(conf[CONF_SINGLE_LIGHT_ID]), | ||||
|             ) | ||||
|             light_state = cg.new_Pvariable(conf[CONF_LIGHT_ID], "", wrapper) | ||||
|             light_state = cg.new_Pvariable(conf[CONF_LIGHT_ID], wrapper) | ||||
|             await cg.register_component(light_state, conf) | ||||
|             segments.append(AddressableSegment(light_state, 0, 1, False)) | ||||
|  | ||||
|   | ||||
| @@ -264,13 +264,52 @@ void PMSX003Component::parse_data_() { | ||||
|       break; | ||||
|     } | ||||
|     case PMSX003_TYPE_5003T: { | ||||
|       uint16_t pm_1_0_std_concentration = this->get_16_bit_uint_(4); | ||||
|       uint16_t pm_2_5_std_concentration = this->get_16_bit_uint_(6); | ||||
|       uint16_t pm_10_0_std_concentration = this->get_16_bit_uint_(8); | ||||
|  | ||||
|       uint16_t pm_1_0_concentration = this->get_16_bit_uint_(10); | ||||
|       uint16_t pm_2_5_concentration = this->get_16_bit_uint_(12); | ||||
|       uint16_t pm_10_0_concentration = this->get_16_bit_uint_(14); | ||||
|  | ||||
|       uint16_t pm_particles_03um = this->get_16_bit_uint_(16); | ||||
|       uint16_t pm_particles_05um = this->get_16_bit_uint_(18); | ||||
|       uint16_t pm_particles_10um = this->get_16_bit_uint_(20); | ||||
|       uint16_t pm_particles_25um = this->get_16_bit_uint_(22); | ||||
|       // Note the pm particles 50um & 100um are not returned, | ||||
|       // as PMS5003T uses those data values for temperature and humidity. | ||||
|  | ||||
|       float temperature = this->get_16_bit_uint_(24) / 10.0f; | ||||
|       float humidity = this->get_16_bit_uint_(26) / 10.0f; | ||||
|       ESP_LOGD(TAG, "Got PM2.5 Concentration: %u µg/m^3, Temperature: %.1f°C, Humidity: %.1f%%", pm_2_5_concentration, | ||||
|                temperature, humidity); | ||||
|  | ||||
|       ESP_LOGD(TAG, | ||||
|                "Got PM1.0 Concentration: %u µg/m^3, PM2.5 Concentration %u µg/m^3, PM10.0 Concentration: %u µg/m^3, " | ||||
|                "Temperature: %.1f°C, Humidity: %.1f%%", | ||||
|                pm_1_0_concentration, pm_2_5_concentration, pm_10_0_concentration, temperature, humidity); | ||||
|  | ||||
|       if (this->pm_1_0_std_sensor_ != nullptr) | ||||
|         this->pm_1_0_std_sensor_->publish_state(pm_1_0_std_concentration); | ||||
|       if (this->pm_2_5_std_sensor_ != nullptr) | ||||
|         this->pm_2_5_std_sensor_->publish_state(pm_2_5_std_concentration); | ||||
|       if (this->pm_10_0_std_sensor_ != nullptr) | ||||
|         this->pm_10_0_std_sensor_->publish_state(pm_10_0_std_concentration); | ||||
|  | ||||
|       if (this->pm_1_0_sensor_ != nullptr) | ||||
|         this->pm_1_0_sensor_->publish_state(pm_1_0_concentration); | ||||
|       if (this->pm_2_5_sensor_ != nullptr) | ||||
|         this->pm_2_5_sensor_->publish_state(pm_2_5_concentration); | ||||
|       if (this->pm_10_0_sensor_ != nullptr) | ||||
|         this->pm_10_0_sensor_->publish_state(pm_10_0_concentration); | ||||
|  | ||||
|       if (this->pm_particles_03um_sensor_ != nullptr) | ||||
|         this->pm_particles_03um_sensor_->publish_state(pm_particles_03um); | ||||
|       if (this->pm_particles_05um_sensor_ != nullptr) | ||||
|         this->pm_particles_05um_sensor_->publish_state(pm_particles_05um); | ||||
|       if (this->pm_particles_10um_sensor_ != nullptr) | ||||
|         this->pm_particles_10um_sensor_->publish_state(pm_particles_10um); | ||||
|       if (this->pm_particles_25um_sensor_ != nullptr) | ||||
|         this->pm_particles_25um_sensor_->publish_state(pm_particles_25um); | ||||
|  | ||||
|       if (this->temperature_sensor_ != nullptr) | ||||
|         this->temperature_sensor_->publish_state(temperature); | ||||
|       if (this->humidity_sensor_ != nullptr) | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user