mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 16:41:50 +00:00 
			
		
		
		
	Compare commits
	
		
			94 Commits
		
	
	
		
			jesserockz
			...
			2024.6.0b2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					4e8a7986cd | ||
| 
						 | 
					3db71b98ae | ||
| 
						 | 
					73cb3ec852 | ||
| 
						 | 
					91e72fe121 | ||
| 
						 | 
					be486e0ca6 | ||
| 
						 | 
					fdefc825bb | ||
| 
						 | 
					8a83670f54 | ||
| 
						 | 
					f9f98fa6c6 | ||
| 
						 | 
					f25c296303 | ||
| 
						 | 
					bc408ad08c | ||
| 
						 | 
					e2c1af199c | ||
| 
						 | 
					7c843437a7 | ||
| 
						 | 
					4bf7c97088 | ||
| 
						 | 
					7b9fb57bb2 | ||
| 
						 | 
					699d00e218 | ||
| 
						 | 
					e2784d077d | ||
| 
						 | 
					13fabf1cd8 | ||
| 
						 | 
					7b60543afd | ||
| 
						 | 
					562700bd2c | ||
| 
						 | 
					a64106e48c | ||
| 
						 | 
					c723fd1f80 | ||
| 
						 | 
					3a97244b83 | ||
| 
						 | 
					1f8449ec0e | ||
| 
						 | 
					3cd2fb0843 | ||
| 
						 | 
					7dc07c5632 | ||
| 
						 | 
					95e45dc12c | ||
| 
						 | 
					51a8a7e875 | ||
| 
						 | 
					dceab6ce29 | ||
| 
						 | 
					6de79d6cfb | ||
| 
						 | 
					7b45498de6 | ||
| 
						 | 
					618102fe8c | ||
| 
						 | 
					38b7bed2fa | ||
| 
						 | 
					d77ea46157 | ||
| 
						 | 
					8718e15a6a | ||
| 
						 | 
					861a23d039 | ||
| 
						 | 
					276eea2b69 | ||
| 
						 | 
					ccab57fc58 | ||
| 
						 | 
					8ef4aaa70e | ||
| 
						 | 
					7143e9cd9e | ||
| 
						 | 
					cc217d8a83 | ||
| 
						 | 
					c52d5c0279 | ||
| 
						 | 
					f36a96c8e2 | ||
| 
						 | 
					594856899a | ||
| 
						 | 
					f7742cdf19 | ||
| 
						 | 
					5b062a222c | ||
| 
						 | 
					664ee56dc5 | ||
| 
						 | 
					388b2c2de0 | ||
| 
						 | 
					ce4a3d9950 | ||
| 
						 | 
					ac9f57600d | ||
| 
						 | 
					69d38f6137 | ||
| 
						 | 
					eb75778f84 | ||
| 
						 | 
					2d56d8d84f | ||
| 
						 | 
					cdf83c5d8c | ||
| 
						 | 
					78b48209aa | ||
| 
						 | 
					05491e756b | ||
| 
						 | 
					b8d2a6f574 | ||
| 
						 | 
					2353b2b5e1 | ||
| 
						 | 
					2beb1f0336 | ||
| 
						 | 
					41e13fa6f4 | ||
| 
						 | 
					1f301df51d | ||
| 
						 | 
					2894a138e7 | ||
| 
						 | 
					8dfe1d5220 | ||
| 
						 | 
					dd27881336 | ||
| 
						 | 
					8aba890e69 | ||
| 
						 | 
					63fc8ab10a | ||
| 
						 | 
					9de8eaff24 | ||
| 
						 | 
					c130ddbe9c | ||
| 
						 | 
					a7fc1a6298 | ||
| 
						 | 
					854d3f2e4a | ||
| 
						 | 
					5ae32e81c3 | ||
| 
						 | 
					439fd94718 | ||
| 
						 | 
					6d5d382f3d | ||
| 
						 | 
					60433c5e64 | ||
| 
						 | 
					bff24e2977 | ||
| 
						 | 
					ec3164f800 | ||
| 
						 | 
					2b691ad5ad | ||
| 
						 | 
					06996def72 | ||
| 
						 | 
					db6f6f0cb7 | ||
| 
						 | 
					497cf8742f | ||
| 
						 | 
					d2b35adcc8 | ||
| 
						 | 
					3fe2fc9b56 | ||
| 
						 | 
					4cd4b168b4 | ||
| 
						 | 
					f07479419c | ||
| 
						 | 
					54b51269ab | ||
| 
						 | 
					6e4fd428e7 | ||
| 
						 | 
					e285196709 | ||
| 
						 | 
					17c6bf57cd | ||
| 
						 | 
					4125b48b86 | ||
| 
						 | 
					6d341ce4e7 | ||
| 
						 | 
					964410bd64 | ||
| 
						 | 
					d72ab25d46 | ||
| 
						 | 
					af755380b7 | ||
| 
						 | 
					04db724295 | ||
| 
						 | 
					863bee28d9 | 
							
								
								
									
										4
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							@@ -46,7 +46,7 @@ runs:
 | 
			
		||||
 | 
			
		||||
    - name: Build and push to ghcr by digest
 | 
			
		||||
      id: build-ghcr
 | 
			
		||||
      uses: docker/build-push-action@v5.3.0
 | 
			
		||||
      uses: docker/build-push-action@v5.4.0
 | 
			
		||||
      with:
 | 
			
		||||
        context: .
 | 
			
		||||
        file: ./docker/Dockerfile
 | 
			
		||||
@@ -69,7 +69,7 @@ runs:
 | 
			
		||||
 | 
			
		||||
    - name: Build and push to dockerhub by digest
 | 
			
		||||
      id: build-dockerhub
 | 
			
		||||
      uses: docker/build-push-action@v5.3.0
 | 
			
		||||
      uses: docker/build-push-action@v5.4.0
 | 
			
		||||
      with:
 | 
			
		||||
        context: .
 | 
			
		||||
        file: ./docker/Dockerfile
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -454,8 +454,8 @@ jobs:
 | 
			
		||||
      matrix:
 | 
			
		||||
        file: ${{ fromJson(needs.list-components.outputs.components) }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Install libsodium
 | 
			
		||||
        run: sudo apt-get install libsodium-dev
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: sudo apt-get install libsodium-dev libsdl2-dev
 | 
			
		||||
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
@@ -508,8 +508,8 @@ jobs:
 | 
			
		||||
      - name: List components
 | 
			
		||||
        run: echo ${{ matrix.components }}
 | 
			
		||||
 | 
			
		||||
      - name: Install libsodium
 | 
			
		||||
        run: sudo apt-get install libsodium-dev
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: sudo apt-get install libsodium-dev libsdl2-dev
 | 
			
		||||
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.6
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -96,12 +96,12 @@ jobs:
 | 
			
		||||
        uses: docker/setup-qemu-action@v3.0.0
 | 
			
		||||
 | 
			
		||||
      - name: Log in to docker hub
 | 
			
		||||
        uses: docker/login-action@v3.1.0
 | 
			
		||||
        uses: docker/login-action@v3.2.0
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
      - name: Log in to the GitHub container registry
 | 
			
		||||
        uses: docker/login-action@v3.1.0
 | 
			
		||||
        uses: docker/login-action@v3.2.0
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
@@ -188,13 +188,13 @@ jobs:
 | 
			
		||||
 | 
			
		||||
      - name: Log in to docker hub
 | 
			
		||||
        if: matrix.registry == 'dockerhub'
 | 
			
		||||
        uses: docker/login-action@v3.1.0
 | 
			
		||||
        uses: docker/login-action@v3.2.0
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
      - name: Log in to the GitHub container registry
 | 
			
		||||
        if: matrix.registry == 'ghcr'
 | 
			
		||||
        uses: docker/login-action@v3.1.0
 | 
			
		||||
        uses: docker/login-action@v3.2.0
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							@@ -36,7 +36,7 @@ jobs:
 | 
			
		||||
          python ./script/sync-device_class.py
 | 
			
		||||
 | 
			
		||||
      - name: Commit changes
 | 
			
		||||
        uses: peter-evans/create-pull-request@v6.0.4
 | 
			
		||||
        uses: peter-evans/create-pull-request@v6.0.5
 | 
			
		||||
        with:
 | 
			
		||||
          commit-message: "Synchronise Device Classes from Home Assistant"
 | 
			
		||||
          committer: esphomebot <esphome@nabucasa.com>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
			
		||||
repos:
 | 
			
		||||
  - repo: https://github.com/psf/black-pre-commit-mirror
 | 
			
		||||
    rev: 24.2.0
 | 
			
		||||
    rev: 24.4.2
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: black
 | 
			
		||||
        args:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								CODEOWNERS
									
									
									
									
									
								
							@@ -94,6 +94,7 @@ esphome/components/current_based/* @djwmarcx
 | 
			
		||||
esphome/components/dac7678/* @NickB1
 | 
			
		||||
esphome/components/daikin_arc/* @MagicBear
 | 
			
		||||
esphome/components/daikin_brc/* @hagak
 | 
			
		||||
esphome/components/dallas_temp/* @ssieb
 | 
			
		||||
esphome/components/daly_bms/* @s1lvi0
 | 
			
		||||
esphome/components/dashboard_import/* @esphome/core
 | 
			
		||||
esphome/components/datetime/* @jesserockz @rfdarter
 | 
			
		||||
@@ -144,6 +145,7 @@ esphome/components/gdk101/* @Szewcson
 | 
			
		||||
esphome/components/globals/* @esphome/core
 | 
			
		||||
esphome/components/gp8403/* @jesserockz
 | 
			
		||||
esphome/components/gpio/* @esphome/core
 | 
			
		||||
esphome/components/gpio/one_wire/* @ssieb
 | 
			
		||||
esphome/components/gps/* @coogle
 | 
			
		||||
esphome/components/graph/* @synco
 | 
			
		||||
esphome/components/graphical_display_menu/* @MrMDavidson
 | 
			
		||||
@@ -167,9 +169,12 @@ esphome/components/homeassistant/* @OttoWinter
 | 
			
		||||
esphome/components/honeywell_hih_i2c/* @Benichou34
 | 
			
		||||
esphome/components/honeywellabp/* @RubyBailey
 | 
			
		||||
esphome/components/honeywellabp2_i2c/* @jpfaff
 | 
			
		||||
esphome/components/host/* @esphome/core
 | 
			
		||||
esphome/components/host/* @clydebarrow @esphome/core
 | 
			
		||||
esphome/components/host/time/* @clydebarrow
 | 
			
		||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
 | 
			
		||||
esphome/components/hte501/* @Stock-M
 | 
			
		||||
esphome/components/http_request/ota/* @oarcher
 | 
			
		||||
esphome/components/http_request/update/* @jesserockz
 | 
			
		||||
esphome/components/htu31d/* @betterengineering
 | 
			
		||||
esphome/components/hydreon_rgxx/* @functionpointer
 | 
			
		||||
esphome/components/hyt271/* @Philippe12
 | 
			
		||||
@@ -210,6 +215,7 @@ esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
 | 
			
		||||
esphome/components/lock/* @esphome/core
 | 
			
		||||
esphome/components/logger/* @esphome/core
 | 
			
		||||
esphome/components/ltr390/* @sjtrny
 | 
			
		||||
esphome/components/ltr_als_ps/* @latonita
 | 
			
		||||
esphome/components/matrix_keypad/* @ssieb
 | 
			
		||||
esphome/components/max31865/* @DAVe3283
 | 
			
		||||
esphome/components/max44009/* @berfenger
 | 
			
		||||
@@ -266,6 +272,7 @@ esphome/components/nextion/text_sensor/* @senexcrenshaw
 | 
			
		||||
esphome/components/nfc/* @jesserockz @kbx81
 | 
			
		||||
esphome/components/noblex/* @AGalfra
 | 
			
		||||
esphome/components/number/* @esphome/core
 | 
			
		||||
esphome/components/one_wire/* @ssieb
 | 
			
		||||
esphome/components/ota/* @esphome/core
 | 
			
		||||
esphome/components/output/* @esphome/core
 | 
			
		||||
esphome/components/pca6416a/* @Mat931
 | 
			
		||||
@@ -313,6 +320,7 @@ esphome/components/rtttl/* @glmnet
 | 
			
		||||
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
 | 
			
		||||
esphome/components/scd4x/* @martgras @sjtrny
 | 
			
		||||
esphome/components/script/* @esphome/core
 | 
			
		||||
esphome/components/sdl/* @clydebarrow
 | 
			
		||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
 | 
			
		||||
esphome/components/sdp3x/* @Azimath
 | 
			
		||||
esphome/components/seeed_mr24hpc1/* @limengdu
 | 
			
		||||
@@ -407,6 +415,7 @@ esphome/components/uart/button/* @ssieb
 | 
			
		||||
esphome/components/ufire_ec/* @pvizeli
 | 
			
		||||
esphome/components/ufire_ise/* @pvizeli
 | 
			
		||||
esphome/components/ultrasonic/* @OttoWinter
 | 
			
		||||
esphome/components/update/* @jesserockz
 | 
			
		||||
esphome/components/uponor_smatrix/* @kroimon
 | 
			
		||||
esphome/components/valve/* @esphome/core
 | 
			
		||||
esphome/components/vbus/* @ssieb
 | 
			
		||||
@@ -414,7 +423,7 @@ esphome/components/veml3235/* @kbx81
 | 
			
		||||
esphome/components/veml7700/* @latonita
 | 
			
		||||
esphome/components/version/* @esphome/core
 | 
			
		||||
esphome/components/voice_assistant/* @jesserockz
 | 
			
		||||
esphome/components/wake_on_lan/* @willwill2will54
 | 
			
		||||
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
 | 
			
		||||
esphome/components/waveshare_epaper/* @clydebarrow
 | 
			
		||||
esphome/components/web_server_base/* @OttoWinter
 | 
			
		||||
esphome/components/web_server_idf/* @dentra
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,8 @@ RUN \
 | 
			
		||||
    fi; \
 | 
			
		||||
    pip3 install \
 | 
			
		||||
    --break-system-packages --no-cache-dir \
 | 
			
		||||
    platformio==6.1.13 \
 | 
			
		||||
    # Keep platformio version in sync with requirements.txt
 | 
			
		||||
    platformio==6.1.15 \
 | 
			
		||||
    # Change some platformio settings
 | 
			
		||||
    && platformio settings set enable_telemetry No \
 | 
			
		||||
    && platformio settings set check_platformio_interval 1000000 \
 | 
			
		||||
@@ -100,6 +101,9 @@ RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "a
 | 
			
		||||
    --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
 | 
			
		||||
    && /platformio_install_deps.py /platformio.ini --libraries
 | 
			
		||||
 | 
			
		||||
# Avoid unsafe git error when container user and file config volume permissions don't match
 | 
			
		||||
RUN git config --system --add safe.directory '*'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ======================= docker-type image =======================
 | 
			
		||||
FROM base AS docker
 | 
			
		||||
 
 | 
			
		||||
@@ -488,6 +488,15 @@ def command_run(args, config):
 | 
			
		||||
    if exit_code != 0:
 | 
			
		||||
        return exit_code
 | 
			
		||||
    _LOGGER.info("Successfully compiled program.")
 | 
			
		||||
    if CORE.is_host:
 | 
			
		||||
        from esphome.platformio_api import get_idedata
 | 
			
		||||
 | 
			
		||||
        idedata = get_idedata(config)
 | 
			
		||||
        if idedata is None:
 | 
			
		||||
            return 1
 | 
			
		||||
        program_path = idedata.raw["prog_path"]
 | 
			
		||||
        return run_external_process(program_path)
 | 
			
		||||
 | 
			
		||||
    port = choose_upload_log_host(
 | 
			
		||||
        default=args.device,
 | 
			
		||||
        check_default=None,
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,7 @@ from esphome.cpp_types import (  # noqa
 | 
			
		||||
    bool_,
 | 
			
		||||
    int_,
 | 
			
		||||
    std_ns,
 | 
			
		||||
    std_shared_ptr,
 | 
			
		||||
    std_string,
 | 
			
		||||
    std_vector,
 | 
			
		||||
    uint8,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,11 @@ from esphome.const import (
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    ICON_ARROW_EXPAND_VERTICAL,
 | 
			
		||||
    DEVICE_CLASS_DISTANCE,
 | 
			
		||||
    UNIT_MILLIMETER,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@TH-Braemer"]
 | 
			
		||||
DEPENDENCIES = ["uart"]
 | 
			
		||||
UNIT_MILLIMETERS = "mm"
 | 
			
		||||
 | 
			
		||||
a02yyuw_ns = cg.esphome_ns.namespace("a02yyuw")
 | 
			
		||||
A02yyuwComponent = a02yyuw_ns.class_(
 | 
			
		||||
@@ -17,7 +17,7 @@ A02yyuwComponent = a02yyuw_ns.class_(
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = sensor.sensor_schema(
 | 
			
		||||
    A02yyuwComponent,
 | 
			
		||||
    unit_of_measurement=UNIT_MILLIMETERS,
 | 
			
		||||
    unit_of_measurement=UNIT_MILLIMETER,
 | 
			
		||||
    icon=ICON_ARROW_EXPAND_VERTICAL,
 | 
			
		||||
    accuracy_decimals=0,
 | 
			
		||||
    state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,8 @@
 | 
			
		||||
#include "ade7880_registers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ade7880 {
 | 
			
		||||
 | 
			
		||||
@@ -156,7 +158,7 @@ void ADE7880::update() {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "update took %u ms", millis() - start);
 | 
			
		||||
  ESP_LOGD(TAG, "update took %" PRIu32 " ms", millis() - start);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADE7880::dump_config() {
 | 
			
		||||
@@ -176,9 +178,9 @@ void ADE7880::dump_config() {
 | 
			
		||||
    LOG_SENSOR("    ", "Forward Active Energy", this->channel_a_->forward_active_energy);
 | 
			
		||||
    LOG_SENSOR("    ", "Reverse Active Energy", this->channel_a_->reverse_active_energy);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Calibration:");
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Current: %u", this->channel_a_->current_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Voltage: %d", this->channel_a_->voltage_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Power: %d", this->channel_a_->power_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Current: %" PRId32, this->channel_a_->current_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Voltage: %" PRId32, this->channel_a_->voltage_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Power: %" PRId32, this->channel_a_->power_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Phase Angle: %u", this->channel_a_->phase_angle_calibration);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -192,9 +194,9 @@ void ADE7880::dump_config() {
 | 
			
		||||
    LOG_SENSOR("    ", "Forward Active Energy", this->channel_b_->forward_active_energy);
 | 
			
		||||
    LOG_SENSOR("    ", "Reverse Active Energy", this->channel_b_->reverse_active_energy);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Calibration:");
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Current: %u", this->channel_b_->current_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Voltage: %d", this->channel_b_->voltage_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Power: %d", this->channel_b_->power_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Current: %" PRId32, this->channel_b_->current_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Voltage: %" PRId32, this->channel_b_->voltage_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Power: %" PRId32, this->channel_b_->power_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Phase Angle: %u", this->channel_b_->phase_angle_calibration);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -208,9 +210,9 @@ void ADE7880::dump_config() {
 | 
			
		||||
    LOG_SENSOR("    ", "Forward Active Energy", this->channel_c_->forward_active_energy);
 | 
			
		||||
    LOG_SENSOR("    ", "Reverse Active Energy", this->channel_c_->reverse_active_energy);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Calibration:");
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Current: %u", this->channel_c_->current_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Voltage: %d", this->channel_c_->voltage_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Power: %d", this->channel_c_->power_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Current: %" PRId32, this->channel_c_->current_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Voltage: %" PRId32, this->channel_c_->voltage_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Power: %" PRId32, this->channel_c_->power_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Phase Angle: %u", this->channel_c_->phase_angle_calibration);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -218,7 +220,7 @@ void ADE7880::dump_config() {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Neutral:");
 | 
			
		||||
    LOG_SENSOR("    ", "Current", this->channel_n_->current);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Calibration:");
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Current: %u", this->channel_n_->current_gain_calibration);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "     Current: %" PRId32, this->channel_n_->current_gain_calibration);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
#include "ade7953_base.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ade7953_base {
 | 
			
		||||
 | 
			
		||||
@@ -105,7 +107,7 @@ void ADE7953::update() {
 | 
			
		||||
    this->last_update_ = now;
 | 
			
		||||
    // prevent DIV/0
 | 
			
		||||
    pf = ADE_WATTSEC_POWER_FACTOR * (diff < 10 ? 10 : diff) / 1000;
 | 
			
		||||
    ESP_LOGVV(TAG, "ADE7953::update() diff=%d pf=%f", diff, pf);
 | 
			
		||||
    ESP_LOGVV(TAG, "ADE7953::update() diff=%" PRIu32 " pf=%f", diff, pf);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Apparent power
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
#include "ags10.h"
 | 
			
		||||
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ags10 {
 | 
			
		||||
static const char *const TAG = "ags10";
 | 
			
		||||
@@ -35,7 +37,7 @@ void AGS10Component::setup() {
 | 
			
		||||
 | 
			
		||||
  auto resistance = this->read_resistance_();
 | 
			
		||||
  if (resistance) {
 | 
			
		||||
    ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08X", *resistance);
 | 
			
		||||
    ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08" PRIX32, *resistance);
 | 
			
		||||
    if (this->resistance_ != nullptr) {
 | 
			
		||||
      this->resistance_->publish_state(*resistance);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import web_server
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.automation import maybe_simple_id
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
@@ -8,6 +9,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_ON_STATE,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_CODE,
 | 
			
		||||
    CONF_WEB_SERVER_ID,
 | 
			
		||||
)
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
 | 
			
		||||
@@ -76,6 +78,8 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
 | 
			
		||||
    web_server.WEBSERVER_SORTING_SCHEMA
 | 
			
		||||
).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(AlarmControlPanel),
 | 
			
		||||
        cv.Optional(CONF_ON_STATE): automation.validate_automation(
 | 
			
		||||
@@ -185,6 +189,9 @@ async def setup_alarm_control_panel_core_(var, config):
 | 
			
		||||
    for conf in config.get(CONF_ON_READY, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
    if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
 | 
			
		||||
        web_server_ = await cg.get_variable(webserver_id)
 | 
			
		||||
        web_server.add_entity_to_sorting_list(web_server_, var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def register_alarm_control_panel(var, config):
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,13 @@ import logging
 | 
			
		||||
from esphome import automation, core
 | 
			
		||||
from esphome.components import font
 | 
			
		||||
import esphome.components.image as espImage
 | 
			
		||||
from esphome.components.image import CONF_USE_TRANSPARENCY
 | 
			
		||||
from esphome.components.image import (
 | 
			
		||||
    CONF_USE_TRANSPARENCY,
 | 
			
		||||
    LOCAL_SCHEMA,
 | 
			
		||||
    WEB_SCHEMA,
 | 
			
		||||
    SOURCE_WEB,
 | 
			
		||||
    SOURCE_LOCAL,
 | 
			
		||||
)
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
@@ -13,6 +19,9 @@ from esphome.const import (
 | 
			
		||||
    CONF_REPEAT,
 | 
			
		||||
    CONF_RESIZE,
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
    CONF_SOURCE,
 | 
			
		||||
    CONF_PATH,
 | 
			
		||||
    CONF_URL,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, HexInt
 | 
			
		||||
 | 
			
		||||
@@ -43,6 +52,40 @@ SetFrameAction = animation_ns.class_(
 | 
			
		||||
    "AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
TYPED_FILE_SCHEMA = cv.typed_schema(
 | 
			
		||||
    {
 | 
			
		||||
        SOURCE_LOCAL: LOCAL_SCHEMA,
 | 
			
		||||
        SOURCE_WEB: WEB_SCHEMA,
 | 
			
		||||
    },
 | 
			
		||||
    key=CONF_SOURCE,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _file_schema(value):
 | 
			
		||||
    if isinstance(value, str):
 | 
			
		||||
        return validate_file_shorthand(value)
 | 
			
		||||
    return TYPED_FILE_SCHEMA(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FILE_SCHEMA = cv.Schema(_file_schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_file_shorthand(value):
 | 
			
		||||
    value = cv.string_strict(value)
 | 
			
		||||
    if value.startswith("http://") or value.startswith("https://"):
 | 
			
		||||
        return FILE_SCHEMA(
 | 
			
		||||
            {
 | 
			
		||||
                CONF_SOURCE: SOURCE_WEB,
 | 
			
		||||
                CONF_URL: value,
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    return FILE_SCHEMA(
 | 
			
		||||
        {
 | 
			
		||||
            CONF_SOURCE: SOURCE_LOCAL,
 | 
			
		||||
            CONF_PATH: value,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_cross_dependencies(config):
 | 
			
		||||
    """
 | 
			
		||||
@@ -67,7 +110,7 @@ ANIMATION_SCHEMA = cv.Schema(
 | 
			
		||||
    cv.All(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_ID): cv.declare_id(Animation_),
 | 
			
		||||
            cv.Required(CONF_FILE): cv.file_,
 | 
			
		||||
            cv.Required(CONF_FILE): FILE_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_RESIZE): cv.dimensions,
 | 
			
		||||
            cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
 | 
			
		||||
                espImage.IMAGE_TYPE, upper=True
 | 
			
		||||
@@ -124,7 +167,11 @@ async def animation_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    from PIL import Image
 | 
			
		||||
 | 
			
		||||
    path = CORE.relative_config_path(config[CONF_FILE])
 | 
			
		||||
    conf_file = config[CONF_FILE]
 | 
			
		||||
    if conf_file[CONF_SOURCE] == SOURCE_LOCAL:
 | 
			
		||||
        path = CORE.relative_config_path(conf_file[CONF_PATH])
 | 
			
		||||
    elif conf_file[CONF_SOURCE] == SOURCE_WEB:
 | 
			
		||||
        path = espImage.compute_local_image_path(conf_file).as_posix()
 | 
			
		||||
    try:
 | 
			
		||||
        image = Image.open(path)
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
 
 | 
			
		||||
@@ -48,6 +48,7 @@ service APIConnection {
 | 
			
		||||
  rpc date_command (DateCommandRequest) returns (void) {}
 | 
			
		||||
  rpc time_command (TimeCommandRequest) returns (void) {}
 | 
			
		||||
  rpc datetime_command (DateTimeCommandRequest) returns (void) {}
 | 
			
		||||
  rpc update_command (UpdateCommandRequest) returns (void) {}
 | 
			
		||||
 | 
			
		||||
  rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
 | 
			
		||||
@@ -1517,6 +1518,25 @@ message VoiceAssistantAudio {
 | 
			
		||||
  bool end = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum VoiceAssistantTimerEvent {
 | 
			
		||||
  VOICE_ASSISTANT_TIMER_STARTED = 0;
 | 
			
		||||
  VOICE_ASSISTANT_TIMER_UPDATED = 1;
 | 
			
		||||
  VOICE_ASSISTANT_TIMER_CANCELLED = 2;
 | 
			
		||||
  VOICE_ASSISTANT_TIMER_FINISHED = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message VoiceAssistantTimerEventResponse {
 | 
			
		||||
  option (id) = 115;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_VOICE_ASSISTANT";
 | 
			
		||||
 | 
			
		||||
  VoiceAssistantTimerEvent event_type = 1;
 | 
			
		||||
  string timer_id = 2;
 | 
			
		||||
  string name = 3;
 | 
			
		||||
  uint32 total_seconds = 4;
 | 
			
		||||
  uint32 seconds_left = 5;
 | 
			
		||||
  bool is_active = 6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== ALARM CONTROL PANEL ====================
 | 
			
		||||
enum AlarmControlPanelState {
 | 
			
		||||
@@ -1818,3 +1838,46 @@ message DateTimeCommandRequest {
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  fixed32 epoch_seconds = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== UPDATE ====================
 | 
			
		||||
message ListEntitiesUpdateResponse {
 | 
			
		||||
  option (id) = 116;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_UPDATE";
 | 
			
		||||
 | 
			
		||||
  string object_id = 1;
 | 
			
		||||
  fixed32 key = 2;
 | 
			
		||||
  string name = 3;
 | 
			
		||||
  string unique_id = 4;
 | 
			
		||||
 | 
			
		||||
  string icon = 5;
 | 
			
		||||
  bool disabled_by_default = 6;
 | 
			
		||||
  EntityCategory entity_category = 7;
 | 
			
		||||
  string device_class = 8;
 | 
			
		||||
}
 | 
			
		||||
message UpdateStateResponse {
 | 
			
		||||
  option (id) = 117;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_UPDATE";
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  bool missing_state = 2;
 | 
			
		||||
  bool in_progress = 3;
 | 
			
		||||
  bool has_progress = 4;
 | 
			
		||||
  float progress = 5;
 | 
			
		||||
  string current_version = 6;
 | 
			
		||||
  string latest_version = 7;
 | 
			
		||||
  string title = 8;
 | 
			
		||||
  string release_summary = 9;
 | 
			
		||||
  string release_url = 10;
 | 
			
		||||
}
 | 
			
		||||
message UpdateCommandRequest {
 | 
			
		||||
  option (id) = 118;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_UPDATE";
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  bool install = 2;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1193,6 +1193,15 @@ void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) {
 | 
			
		||||
    voice_assistant::global_voice_assistant->on_audio(msg);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) {
 | 
			
		||||
  if (voice_assistant::global_voice_assistant != nullptr) {
 | 
			
		||||
    if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    voice_assistant::global_voice_assistant->on_timer_event(msg);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@@ -1278,6 +1287,51 @@ bool APIConnection::send_event_info(event::Event *event) {
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
bool APIConnection::send_update_state(update::UpdateEntity *update) {
 | 
			
		||||
  if (!this->state_subscription_)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  UpdateStateResponse resp{};
 | 
			
		||||
  resp.key = update->get_object_id_hash();
 | 
			
		||||
  resp.missing_state = !update->has_state();
 | 
			
		||||
  if (update->has_state()) {
 | 
			
		||||
    resp.in_progress = update->state == update::UpdateState::UPDATE_STATE_INSTALLING;
 | 
			
		||||
    if (update->update_info.has_progress) {
 | 
			
		||||
      resp.has_progress = true;
 | 
			
		||||
      resp.progress = update->update_info.progress;
 | 
			
		||||
    }
 | 
			
		||||
    resp.current_version = update->update_info.current_version;
 | 
			
		||||
    resp.latest_version = update->update_info.latest_version;
 | 
			
		||||
    resp.title = update->update_info.title;
 | 
			
		||||
    resp.release_summary = update->update_info.summary;
 | 
			
		||||
    resp.release_url = update->update_info.release_url;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return this->send_update_state_response(resp);
 | 
			
		||||
}
 | 
			
		||||
bool APIConnection::send_update_info(update::UpdateEntity *update) {
 | 
			
		||||
  ListEntitiesUpdateResponse msg;
 | 
			
		||||
  msg.key = update->get_object_id_hash();
 | 
			
		||||
  msg.object_id = update->get_object_id();
 | 
			
		||||
  if (update->has_own_name())
 | 
			
		||||
    msg.name = update->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("update", update);
 | 
			
		||||
  msg.icon = update->get_icon();
 | 
			
		||||
  msg.disabled_by_default = update->is_disabled_by_default();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(update->get_entity_category());
 | 
			
		||||
  msg.device_class = update->get_device_class();
 | 
			
		||||
  return this->send_list_entities_update_response(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::update_command(const UpdateCommandRequest &msg) {
 | 
			
		||||
  update::UpdateEntity *update = App.get_update_by_key(msg.key);
 | 
			
		||||
  if (update == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  update->perform();
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
 | 
			
		||||
  if (this->log_subscription_ < level)
 | 
			
		||||
    return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -150,6 +150,7 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
 | 
			
		||||
  void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
 | 
			
		||||
  void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override;
 | 
			
		||||
  void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
@@ -163,6 +164,12 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  bool send_event_info(event::Event *event);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  bool send_update_state(update::UpdateEntity *update);
 | 
			
		||||
  bool send_update_info(update::UpdateEntity *update);
 | 
			
		||||
  void update_command(const UpdateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  void on_disconnect_response(const DisconnectResponse &value) override;
 | 
			
		||||
  void on_ping_response(const PingResponse &value) override {
 | 
			
		||||
    // we initiated ping
 | 
			
		||||
 
 | 
			
		||||
@@ -475,6 +475,22 @@ template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::V
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::VoiceAssistantTimerEvent>(enums::VoiceAssistantTimerEvent value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::VOICE_ASSISTANT_TIMER_STARTED:
 | 
			
		||||
      return "VOICE_ASSISTANT_TIMER_STARTED";
 | 
			
		||||
    case enums::VOICE_ASSISTANT_TIMER_UPDATED:
 | 
			
		||||
      return "VOICE_ASSISTANT_TIMER_UPDATED";
 | 
			
		||||
    case enums::VOICE_ASSISTANT_TIMER_CANCELLED:
 | 
			
		||||
      return "VOICE_ASSISTANT_TIMER_CANCELLED";
 | 
			
		||||
    case enums::VOICE_ASSISTANT_TIMER_FINISHED:
 | 
			
		||||
      return "VOICE_ASSISTANT_TIMER_FINISHED";
 | 
			
		||||
    default:
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::AlarmControlPanelState>(enums::AlarmControlPanelState value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::ALARM_STATE_DISARMED:
 | 
			
		||||
@@ -6857,6 +6873,82 @@ void VoiceAssistantAudio::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->event_type = value.as_enum<enums::VoiceAssistantTimerEvent>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->total_seconds = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 5: {
 | 
			
		||||
      this->seconds_left = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 6: {
 | 
			
		||||
      this->is_active = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool VoiceAssistantTimerEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->timer_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->name = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void VoiceAssistantTimerEventResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_enum<enums::VoiceAssistantTimerEvent>(1, this->event_type);
 | 
			
		||||
  buffer.encode_string(2, this->timer_id);
 | 
			
		||||
  buffer.encode_string(3, this->name);
 | 
			
		||||
  buffer.encode_uint32(4, this->total_seconds);
 | 
			
		||||
  buffer.encode_uint32(5, this->seconds_left);
 | 
			
		||||
  buffer.encode_bool(6, this->is_active);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("VoiceAssistantTimerEventResponse {\n");
 | 
			
		||||
  out.append("  event_type: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::VoiceAssistantTimerEvent>(this->event_type));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  timer_id: ");
 | 
			
		||||
  out.append("'").append(this->timer_id).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  name: ");
 | 
			
		||||
  out.append("'").append(this->name).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  total_seconds: ");
 | 
			
		||||
  sprintf(buffer, "%" PRIu32, this->total_seconds);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  seconds_left: ");
 | 
			
		||||
  sprintf(buffer, "%" PRIu32, this->seconds_left);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  is_active: ");
 | 
			
		||||
  out.append(YESNO(this->is_active));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 6: {
 | 
			
		||||
@@ -8284,6 +8376,262 @@ void DateTimeCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 6: {
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool ListEntitiesUpdateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->object_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->name = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->unique_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 5: {
 | 
			
		||||
      this->icon = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 8: {
 | 
			
		||||
      this->device_class = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool ListEntitiesUpdateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(1, this->object_id);
 | 
			
		||||
  buffer.encode_fixed32(2, this->key);
 | 
			
		||||
  buffer.encode_string(3, this->name);
 | 
			
		||||
  buffer.encode_string(4, this->unique_id);
 | 
			
		||||
  buffer.encode_string(5, this->icon);
 | 
			
		||||
  buffer.encode_bool(6, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
 | 
			
		||||
  buffer.encode_string(8, this->device_class);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesUpdateResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%" PRIu32, this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  name: ");
 | 
			
		||||
  out.append("'").append(this->name).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  unique_id: ");
 | 
			
		||||
  out.append("'").append(this->unique_id).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  icon: ");
 | 
			
		||||
  out.append("'").append(this->icon).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  disabled_by_default: ");
 | 
			
		||||
  out.append(YESNO(this->disabled_by_default));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  device_class: ");
 | 
			
		||||
  out.append("'").append(this->device_class).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool UpdateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->missing_state = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->in_progress = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->has_progress = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool UpdateStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 6: {
 | 
			
		||||
      this->current_version = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->latest_version = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 8: {
 | 
			
		||||
      this->title = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 9: {
 | 
			
		||||
      this->release_summary = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 10: {
 | 
			
		||||
      this->release_url = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool UpdateStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 5: {
 | 
			
		||||
      this->progress = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void UpdateStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_fixed32(1, this->key);
 | 
			
		||||
  buffer.encode_bool(2, this->missing_state);
 | 
			
		||||
  buffer.encode_bool(3, this->in_progress);
 | 
			
		||||
  buffer.encode_bool(4, this->has_progress);
 | 
			
		||||
  buffer.encode_float(5, this->progress);
 | 
			
		||||
  buffer.encode_string(6, this->current_version);
 | 
			
		||||
  buffer.encode_string(7, this->latest_version);
 | 
			
		||||
  buffer.encode_string(8, this->title);
 | 
			
		||||
  buffer.encode_string(9, this->release_summary);
 | 
			
		||||
  buffer.encode_string(10, this->release_url);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void UpdateStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("UpdateStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%" PRIu32, this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  missing_state: ");
 | 
			
		||||
  out.append(YESNO(this->missing_state));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  in_progress: ");
 | 
			
		||||
  out.append(YESNO(this->in_progress));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_progress: ");
 | 
			
		||||
  out.append(YESNO(this->has_progress));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  progress: ");
 | 
			
		||||
  sprintf(buffer, "%g", this->progress);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  current_version: ");
 | 
			
		||||
  out.append("'").append(this->current_version).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  latest_version: ");
 | 
			
		||||
  out.append("'").append(this->latest_version).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  title: ");
 | 
			
		||||
  out.append("'").append(this->title).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  release_summary: ");
 | 
			
		||||
  out.append("'").append(this->release_summary).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  release_url: ");
 | 
			
		||||
  out.append("'").append(this->release_url).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->install = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void UpdateCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_fixed32(1, this->key);
 | 
			
		||||
  buffer.encode_bool(2, this->install);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void UpdateCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("UpdateCommandRequest {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%" PRIu32, this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  install: ");
 | 
			
		||||
  out.append(YESNO(this->install));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -191,6 +191,12 @@ enum VoiceAssistantEvent : uint32_t {
 | 
			
		||||
  VOICE_ASSISTANT_TTS_STREAM_START = 98,
 | 
			
		||||
  VOICE_ASSISTANT_TTS_STREAM_END = 99,
 | 
			
		||||
};
 | 
			
		||||
enum VoiceAssistantTimerEvent : uint32_t {
 | 
			
		||||
  VOICE_ASSISTANT_TIMER_STARTED = 0,
 | 
			
		||||
  VOICE_ASSISTANT_TIMER_UPDATED = 1,
 | 
			
		||||
  VOICE_ASSISTANT_TIMER_CANCELLED = 2,
 | 
			
		||||
  VOICE_ASSISTANT_TIMER_FINISHED = 3,
 | 
			
		||||
};
 | 
			
		||||
enum AlarmControlPanelState : uint32_t {
 | 
			
		||||
  ALARM_STATE_DISARMED = 0,
 | 
			
		||||
  ALARM_STATE_ARMED_HOME = 1,
 | 
			
		||||
@@ -1775,6 +1781,23 @@ class VoiceAssistantAudio : public ProtoMessage {
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class VoiceAssistantTimerEventResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  enums::VoiceAssistantTimerEvent event_type{};
 | 
			
		||||
  std::string timer_id{};
 | 
			
		||||
  std::string name{};
 | 
			
		||||
  uint32_t total_seconds{0};
 | 
			
		||||
  uint32_t seconds_left{0};
 | 
			
		||||
  bool is_active{false};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class ListEntitiesAlarmControlPanelResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::string object_id{};
 | 
			
		||||
@@ -2107,6 +2130,61 @@ class DateTimeCommandRequest : public ProtoMessage {
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
};
 | 
			
		||||
class ListEntitiesUpdateResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::string object_id{};
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  std::string name{};
 | 
			
		||||
  std::string unique_id{};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  std::string device_class{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class UpdateStateResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  bool missing_state{false};
 | 
			
		||||
  bool in_progress{false};
 | 
			
		||||
  bool has_progress{false};
 | 
			
		||||
  float progress{0.0f};
 | 
			
		||||
  std::string current_version{};
 | 
			
		||||
  std::string latest_version{};
 | 
			
		||||
  std::string title{};
 | 
			
		||||
  std::string release_summary{};
 | 
			
		||||
  std::string release_url{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class UpdateCommandRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  bool install{false};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -484,6 +484,8 @@ bool APIServerConnectionBase::send_voice_assistant_audio(const VoiceAssistantAud
 | 
			
		||||
  return this->send_message_<VoiceAssistantAudio>(msg, 106);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response(
 | 
			
		||||
    const ListEntitiesAlarmControlPanelResponse &msg) {
 | 
			
		||||
@@ -609,6 +611,24 @@ bool APIServerConnectionBase::send_date_time_state_response(const DateTimeStateR
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
bool APIServerConnectionBase::send_list_entities_update_response(const ListEntitiesUpdateResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_list_entities_update_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<ListEntitiesUpdateResponse>(msg, 116);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
bool APIServerConnectionBase::send_update_state_response(const UpdateStateResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_update_state_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<UpdateStateResponse>(msg, 117);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
#endif
 | 
			
		||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
 | 
			
		||||
  switch (msg_type) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
@@ -1093,6 +1113,28 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
			
		||||
      ESP_LOGVV(TAG, "on_date_time_command_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_date_time_command_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 115: {
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
      VoiceAssistantTimerEventResponse msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_voice_assistant_timer_event_response(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 118: {
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
      UpdateCommandRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_update_command_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -1421,6 +1463,19 @@ void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequ
 | 
			
		||||
  this->datetime_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->update_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
    const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
			
		||||
 
 | 
			
		||||
@@ -244,6 +244,9 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
  bool send_voice_assistant_audio(const VoiceAssistantAudio &msg);
 | 
			
		||||
  virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
  bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
@@ -303,6 +306,15 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
  virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  bool send_list_entities_update_response(const ListEntitiesUpdateResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  bool send_update_state_response(const UpdateStateResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  virtual void on_update_command_request(const UpdateCommandRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
 | 
			
		||||
@@ -370,6 +382,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
  virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  virtual void update_command(const UpdateCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -468,6 +483,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
  void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  void on_update_command_request(const UpdateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -334,6 +334,13 @@ void APIServer::on_event(event::Event *obj, const std::string &event_type) {
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
void APIServer::on_update(update::UpdateEntity *obj) {
 | 
			
		||||
  for (auto &c : this->clients_)
 | 
			
		||||
    c->send_update_state(obj);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
 | 
			
		||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
 | 
			
		||||
APIServer *global_api_server = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 
 | 
			
		||||
@@ -102,6 +102,9 @@ class APIServer : public Component, public Controller {
 | 
			
		||||
#ifdef USE_EVENT
 | 
			
		||||
  void on_event(event::Event *obj, const std::string &event_type) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  void on_update(update::UpdateEntity *obj) override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  bool is_connected() const;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -105,7 +105,7 @@ class CustomAPIDevice {
 | 
			
		||||
  /** Subscribe to the state (or attribute state) of an entity from Home Assistant.
 | 
			
		||||
   *
 | 
			
		||||
   * Usage:
 | 
			
		||||
   *å
 | 
			
		||||
   *
 | 
			
		||||
   * ```cpp
 | 
			
		||||
   * void setup() override {
 | 
			
		||||
   *   subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
 | 
			
		||||
 
 | 
			
		||||
@@ -98,6 +98,9 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
 | 
			
		||||
#ifdef USE_EVENT
 | 
			
		||||
bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); }
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_info(update); }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -75,6 +75,9 @@ class ListEntitiesIterator : public ComponentIterator {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_EVENT
 | 
			
		||||
  bool on_event(event::Event *event) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  bool on_update(update::UpdateEntity *update) override;
 | 
			
		||||
#endif
 | 
			
		||||
  bool on_end() override;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -77,6 +77,9 @@ bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
 | 
			
		||||
  return this->client_->send_alarm_control_panel_state(a_alarm_control_panel);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
bool InitialStateIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_state(update); }
 | 
			
		||||
#endif
 | 
			
		||||
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
 
 | 
			
		||||
@@ -72,6 +72,9 @@ class InitialStateIterator : public ComponentIterator {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_EVENT
 | 
			
		||||
  bool on_event(event::Event *event) override { return true; };
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  bool on_update(update::UpdateEntity *update) override;
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  APIConnection *client_;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ from esphome.cpp_generator import MockObjClass
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
from esphome import automation, core
 | 
			
		||||
from esphome.automation import Condition, maybe_simple_id
 | 
			
		||||
from esphome.components import mqtt
 | 
			
		||||
from esphome.components import mqtt, web_server
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DELAY,
 | 
			
		||||
    CONF_DEVICE_CLASS,
 | 
			
		||||
@@ -27,6 +27,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_TIMING,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_WEB_SERVER_ID,
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_BATTERY_CHARGING,
 | 
			
		||||
    DEVICE_CLASS_CARBON_MONOXIDE,
 | 
			
		||||
@@ -385,70 +386,76 @@ def validate_click_timing(value):
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(BinarySensor),
 | 
			
		||||
        cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
 | 
			
		||||
            mqtt.MQTTBinarySensorComponent
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
 | 
			
		||||
        cv.Optional(CONF_FILTERS): validate_filters,
 | 
			
		||||
        cv.Optional(CONF_ON_PRESS): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_CLICK): cv.All(
 | 
			
		||||
            automation.validate_automation(
 | 
			
		||||
BINARY_SENSOR_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BinarySensor),
 | 
			
		||||
            cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
 | 
			
		||||
                mqtt.MQTTBinarySensorComponent
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
 | 
			
		||||
            cv.Optional(CONF_FILTERS): validate_filters,
 | 
			
		||||
            cv.Optional(CONF_ON_PRESS): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
 | 
			
		||||
                    cv.Optional(
 | 
			
		||||
                        CONF_MIN_LENGTH, default="50ms"
 | 
			
		||||
                    ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                    cv.Optional(
 | 
			
		||||
                        CONF_MAX_LENGTH, default="350ms"
 | 
			
		||||
                    ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            validate_click_timing,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
 | 
			
		||||
            automation.validate_automation(
 | 
			
		||||
            cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
 | 
			
		||||
                    cv.Optional(
 | 
			
		||||
                        CONF_MIN_LENGTH, default="50ms"
 | 
			
		||||
                    ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                    cv.Optional(
 | 
			
		||||
                        CONF_MAX_LENGTH, default="350ms"
 | 
			
		||||
                    ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            validate_click_timing,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
 | 
			
		||||
                cv.Required(CONF_TIMING): cv.All(
 | 
			
		||||
                    [parse_multi_click_timing_str], validate_multi_click_timing
 | 
			
		||||
            cv.Optional(CONF_ON_CLICK): cv.All(
 | 
			
		||||
                automation.validate_automation(
 | 
			
		||||
                    {
 | 
			
		||||
                        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
 | 
			
		||||
                        cv.Optional(
 | 
			
		||||
                            CONF_MIN_LENGTH, default="50ms"
 | 
			
		||||
                        ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                        cv.Optional(
 | 
			
		||||
                            CONF_MAX_LENGTH, default="350ms"
 | 
			
		||||
                        ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_INVALID_COOLDOWN, default="1s"
 | 
			
		||||
                ): cv.positive_time_period_milliseconds,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_STATE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
                validate_click_timing,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
 | 
			
		||||
                automation.validate_automation(
 | 
			
		||||
                    {
 | 
			
		||||
                        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                            DoubleClickTrigger
 | 
			
		||||
                        ),
 | 
			
		||||
                        cv.Optional(
 | 
			
		||||
                            CONF_MIN_LENGTH, default="50ms"
 | 
			
		||||
                        ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                        cv.Optional(
 | 
			
		||||
                            CONF_MAX_LENGTH, default="350ms"
 | 
			
		||||
                        ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
                validate_click_timing,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
 | 
			
		||||
                    cv.Required(CONF_TIMING): cv.All(
 | 
			
		||||
                        [parse_multi_click_timing_str], validate_multi_click_timing
 | 
			
		||||
                    ),
 | 
			
		||||
                    cv.Optional(
 | 
			
		||||
                        CONF_INVALID_COOLDOWN, default="1s"
 | 
			
		||||
                    ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_STATE): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
_UNDEF = object()
 | 
			
		||||
@@ -536,6 +543,10 @@ async def setup_binary_sensor_core_(var, config):
 | 
			
		||||
        mqtt_ = cg.new_Pvariable(mqtt_id, var)
 | 
			
		||||
        await mqtt.register_mqtt_component(mqtt_, config)
 | 
			
		||||
 | 
			
		||||
    if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
 | 
			
		||||
        web_server_ = await cg.get_variable(webserver_id)
 | 
			
		||||
        web_server.add_entity_to_sorting_list(web_server_, var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def register_binary_sensor(var, config):
 | 
			
		||||
    if not CORE.has_id(config[CONF_ID]):
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.automation import maybe_simple_id
 | 
			
		||||
from esphome.components import mqtt
 | 
			
		||||
from esphome.components import mqtt, web_server
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DEVICE_CLASS,
 | 
			
		||||
    CONF_ENTITY_CATEGORY,
 | 
			
		||||
@@ -11,6 +11,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_ON_PRESS,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_WEB_SERVER_ID,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_IDENTIFY,
 | 
			
		||||
    DEVICE_CLASS_RESTART,
 | 
			
		||||
@@ -43,16 +44,20 @@ ButtonPressTrigger = button_ns.class_(
 | 
			
		||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
 | 
			
		||||
        cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
 | 
			
		||||
        cv.Optional(CONF_ON_PRESS): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
BUTTON_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
 | 
			
		||||
            cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
 | 
			
		||||
            cv.Optional(CONF_ON_PRESS): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
_UNDEF = object()
 | 
			
		||||
@@ -92,6 +97,10 @@ async def setup_button_core_(var, config):
 | 
			
		||||
        mqtt_ = cg.new_Pvariable(mqtt_id, var)
 | 
			
		||||
        await mqtt.register_mqtt_component(mqtt_, config)
 | 
			
		||||
 | 
			
		||||
    if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
 | 
			
		||||
        web_server_ = await cg.get_variable(webserver_id)
 | 
			
		||||
        web_server.add_entity_to_sorting_list(web_server_, var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def register_button(var, config):
 | 
			
		||||
    if not CORE.has_id(config[CONF_ID]):
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.components import mqtt
 | 
			
		||||
from esphome.components import mqtt, web_server
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ACTION_STATE_TOPIC,
 | 
			
		||||
    CONF_AWAY,
 | 
			
		||||
@@ -44,6 +44,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_VISUAL,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_WEB_SERVER_ID,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
 | 
			
		||||
@@ -150,93 +151,97 @@ VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any(
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(Climate),
 | 
			
		||||
        cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
 | 
			
		||||
        cv.Optional(CONF_VISUAL, default={}): cv.Schema(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
 | 
			
		||||
                cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
 | 
			
		||||
                cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA,
 | 
			
		||||
                cv.Optional(CONF_MIN_HUMIDITY): cv.percentage_int,
 | 
			
		||||
                cv.Optional(CONF_MAX_HUMIDITY): cv.percentage_int,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_CURRENT_HUMIDITY_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_TARGET_HUMIDITY_COMMAND_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_CONTROL): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_STATE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
CLIMATE_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(Climate),
 | 
			
		||||
            cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
 | 
			
		||||
            cv.Optional(CONF_VISUAL, default={}): cv.Schema(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
 | 
			
		||||
                    cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
 | 
			
		||||
                    cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA,
 | 
			
		||||
                    cv.Optional(CONF_MIN_HUMIDITY): cv.percentage_int,
 | 
			
		||||
                    cv.Optional(CONF_MAX_HUMIDITY): cv.percentage_int,
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CURRENT_HUMIDITY_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TARGET_HUMIDITY_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_CONTROL): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_STATE): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -403,6 +408,10 @@ async def setup_climate_core_(var, config):
 | 
			
		||||
            trigger, [(ClimateCall.operator("ref"), "x")], conf
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
 | 
			
		||||
        web_server_ = await cg.get_variable(webserver_id)
 | 
			
		||||
        web_server.add_entity_to_sorting_list(web_server_, var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def register_climate(var, config):
 | 
			
		||||
    if not CORE.has_id(config[CONF_ID]):
 | 
			
		||||
 
 | 
			
		||||
@@ -6,18 +6,24 @@ namespace climate_ir_lg {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "climate.climate_ir_lg";
 | 
			
		||||
 | 
			
		||||
const uint32_t COMMAND_ON = 0x00000;
 | 
			
		||||
const uint32_t COMMAND_ON_AI = 0x03000;
 | 
			
		||||
const uint32_t COMMAND_COOL = 0x08000;
 | 
			
		||||
const uint32_t COMMAND_HEAT = 0x0C000;
 | 
			
		||||
// Commands
 | 
			
		||||
const uint32_t COMMAND_MASK = 0xFF000;
 | 
			
		||||
const uint32_t COMMAND_OFF = 0xC0000;
 | 
			
		||||
const uint32_t COMMAND_SWING = 0x10000;
 | 
			
		||||
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
 | 
			
		||||
const uint32_t COMMAND_AUTO = 0x0B000;
 | 
			
		||||
const uint32_t COMMAND_DRY_FAN = 0x09000;
 | 
			
		||||
 | 
			
		||||
const uint32_t COMMAND_MASK = 0xFF000;
 | 
			
		||||
const uint32_t COMMAND_ON_COOL = 0x00000;
 | 
			
		||||
const uint32_t COMMAND_ON_DRY = 0x01000;
 | 
			
		||||
const uint32_t COMMAND_ON_FAN_ONLY = 0x02000;
 | 
			
		||||
const uint32_t COMMAND_ON_AI = 0x03000;
 | 
			
		||||
const uint32_t COMMAND_ON_HEAT = 0x04000;
 | 
			
		||||
 | 
			
		||||
const uint32_t COMMAND_COOL = 0x08000;
 | 
			
		||||
const uint32_t COMMAND_DRY = 0x09000;
 | 
			
		||||
const uint32_t COMMAND_FAN_ONLY = 0x0A000;
 | 
			
		||||
const uint32_t COMMAND_AI = 0x0B000;
 | 
			
		||||
const uint32_t COMMAND_HEAT = 0x0C000;
 | 
			
		||||
 | 
			
		||||
// Fan speed
 | 
			
		||||
const uint32_t FAN_MASK = 0xF0;
 | 
			
		||||
const uint32_t FAN_AUTO = 0x50;
 | 
			
		||||
const uint32_t FAN_MIN = 0x00;
 | 
			
		||||
@@ -35,69 +41,67 @@ void LgIrClimate::transmit_state() {
 | 
			
		||||
  uint32_t remote_state = 0x8800000;
 | 
			
		||||
 | 
			
		||||
  // ESP_LOGD(TAG, "climate_lg_ir mode_before_ code: 0x%02X", modeBefore_);
 | 
			
		||||
 | 
			
		||||
  // Set command
 | 
			
		||||
  if (send_swing_cmd_) {
 | 
			
		||||
    send_swing_cmd_ = false;
 | 
			
		||||
    remote_state |= COMMAND_SWING;
 | 
			
		||||
  } else {
 | 
			
		||||
    if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
 | 
			
		||||
      remote_state |= COMMAND_ON_AI;
 | 
			
		||||
    } else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) {
 | 
			
		||||
      remote_state |= COMMAND_ON;
 | 
			
		||||
      this->mode = climate::CLIMATE_MODE_COOL;
 | 
			
		||||
    } else {
 | 
			
		||||
      switch (this->mode) {
 | 
			
		||||
        case climate::CLIMATE_MODE_COOL:
 | 
			
		||||
          remote_state |= COMMAND_COOL;
 | 
			
		||||
          break;
 | 
			
		||||
        case climate::CLIMATE_MODE_HEAT:
 | 
			
		||||
          remote_state |= COMMAND_HEAT;
 | 
			
		||||
          break;
 | 
			
		||||
        case climate::CLIMATE_MODE_HEAT_COOL:
 | 
			
		||||
          remote_state |= COMMAND_AUTO;
 | 
			
		||||
          break;
 | 
			
		||||
        case climate::CLIMATE_MODE_DRY:
 | 
			
		||||
          remote_state |= COMMAND_DRY_FAN;
 | 
			
		||||
          break;
 | 
			
		||||
        case climate::CLIMATE_MODE_OFF:
 | 
			
		||||
        default:
 | 
			
		||||
          remote_state |= COMMAND_OFF;
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    mode_before_ = this->mode;
 | 
			
		||||
 | 
			
		||||
    ESP_LOGD(TAG, "climate_lg_ir mode code: 0x%02X", this->mode);
 | 
			
		||||
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_OFF) {
 | 
			
		||||
      remote_state |= FAN_AUTO;
 | 
			
		||||
    } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY ||
 | 
			
		||||
               this->mode == climate::CLIMATE_MODE_HEAT) {
 | 
			
		||||
      switch (this->fan_mode.value()) {
 | 
			
		||||
        case climate::CLIMATE_FAN_HIGH:
 | 
			
		||||
          remote_state |= FAN_MAX;
 | 
			
		||||
          break;
 | 
			
		||||
        case climate::CLIMATE_FAN_MEDIUM:
 | 
			
		||||
          remote_state |= FAN_MED;
 | 
			
		||||
          break;
 | 
			
		||||
        case climate::CLIMATE_FAN_LOW:
 | 
			
		||||
          remote_state |= FAN_MIN;
 | 
			
		||||
          break;
 | 
			
		||||
        case climate::CLIMATE_FAN_AUTO:
 | 
			
		||||
        default:
 | 
			
		||||
          remote_state |= FAN_AUTO;
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_AUTO;
 | 
			
		||||
      // remote_state |= FAN_MODE_AUTO_DRY;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
 | 
			
		||||
      auto temp = (uint8_t) roundf(clamp<float>(this->target_temperature, TEMP_MIN, TEMP_MAX));
 | 
			
		||||
      remote_state |= ((temp - 15) << TEMP_SHIFT);
 | 
			
		||||
    bool climate_is_off = (mode_before_ == climate::CLIMATE_MODE_OFF);
 | 
			
		||||
    switch (this->mode) {
 | 
			
		||||
      case climate::CLIMATE_MODE_COOL:
 | 
			
		||||
        remote_state |= climate_is_off ? COMMAND_ON_COOL : COMMAND_COOL;
 | 
			
		||||
        break;
 | 
			
		||||
      case climate::CLIMATE_MODE_DRY:
 | 
			
		||||
        remote_state |= climate_is_off ? COMMAND_ON_DRY : COMMAND_DRY;
 | 
			
		||||
        break;
 | 
			
		||||
      case climate::CLIMATE_MODE_FAN_ONLY:
 | 
			
		||||
        remote_state |= climate_is_off ? COMMAND_ON_FAN_ONLY : COMMAND_FAN_ONLY;
 | 
			
		||||
        break;
 | 
			
		||||
      case climate::CLIMATE_MODE_HEAT_COOL:
 | 
			
		||||
        remote_state |= climate_is_off ? COMMAND_ON_AI : COMMAND_AI;
 | 
			
		||||
        break;
 | 
			
		||||
      case climate::CLIMATE_MODE_HEAT:
 | 
			
		||||
        remote_state |= climate_is_off ? COMMAND_ON_HEAT : COMMAND_HEAT;
 | 
			
		||||
        break;
 | 
			
		||||
      case climate::CLIMATE_MODE_OFF:
 | 
			
		||||
      default:
 | 
			
		||||
        remote_state |= COMMAND_OFF;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mode_before_ = this->mode;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "climate_lg_ir mode code: 0x%02X", this->mode);
 | 
			
		||||
 | 
			
		||||
  // Set fan speed
 | 
			
		||||
  if (this->mode == climate::CLIMATE_MODE_OFF) {
 | 
			
		||||
    remote_state |= FAN_AUTO;
 | 
			
		||||
  } else {
 | 
			
		||||
    switch (this->fan_mode.value()) {
 | 
			
		||||
      case climate::CLIMATE_FAN_HIGH:
 | 
			
		||||
        remote_state |= FAN_MAX;
 | 
			
		||||
        break;
 | 
			
		||||
      case climate::CLIMATE_FAN_MEDIUM:
 | 
			
		||||
        remote_state |= FAN_MED;
 | 
			
		||||
        break;
 | 
			
		||||
      case climate::CLIMATE_FAN_LOW:
 | 
			
		||||
        remote_state |= FAN_MIN;
 | 
			
		||||
        break;
 | 
			
		||||
      case climate::CLIMATE_FAN_AUTO:
 | 
			
		||||
      default:
 | 
			
		||||
        remote_state |= FAN_AUTO;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Set temperature
 | 
			
		||||
  if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
 | 
			
		||||
    auto temp = (uint8_t) roundf(clamp<float>(this->target_temperature, TEMP_MIN, TEMP_MAX));
 | 
			
		||||
    remote_state |= ((temp - 15) << TEMP_SHIFT);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  transmit_(remote_state);
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
}
 | 
			
		||||
@@ -125,37 +129,42 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
 | 
			
		||||
  if ((remote_state & 0xFF00000) != 0x8800000)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  if ((remote_state & COMMAND_MASK) == COMMAND_ON) {
 | 
			
		||||
    this->mode = climate::CLIMATE_MODE_COOL;
 | 
			
		||||
  } else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) {
 | 
			
		||||
    this->mode = climate::CLIMATE_MODE_HEAT_COOL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Get command
 | 
			
		||||
  if ((remote_state & COMMAND_MASK) == COMMAND_OFF) {
 | 
			
		||||
    this->mode = climate::CLIMATE_MODE_OFF;
 | 
			
		||||
  } else if ((remote_state & COMMAND_MASK) == COMMAND_SWING) {
 | 
			
		||||
    this->swing_mode =
 | 
			
		||||
        this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
 | 
			
		||||
  } else {
 | 
			
		||||
    if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) {
 | 
			
		||||
      this->mode = climate::CLIMATE_MODE_HEAT_COOL;
 | 
			
		||||
    } else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) {
 | 
			
		||||
      this->mode = climate::CLIMATE_MODE_DRY;
 | 
			
		||||
    } else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) {
 | 
			
		||||
      this->mode = climate::CLIMATE_MODE_HEAT;
 | 
			
		||||
    } else {
 | 
			
		||||
      this->mode = climate::CLIMATE_MODE_COOL;
 | 
			
		||||
    switch (remote_state & COMMAND_MASK) {
 | 
			
		||||
      case COMMAND_DRY:
 | 
			
		||||
      case COMMAND_ON_DRY:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_DRY;
 | 
			
		||||
        break;
 | 
			
		||||
      case COMMAND_FAN_ONLY:
 | 
			
		||||
      case COMMAND_ON_FAN_ONLY:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_FAN_ONLY;
 | 
			
		||||
        break;
 | 
			
		||||
      case COMMAND_AI:
 | 
			
		||||
      case COMMAND_ON_AI:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_HEAT_COOL;
 | 
			
		||||
        break;
 | 
			
		||||
      case COMMAND_HEAT:
 | 
			
		||||
      case COMMAND_ON_HEAT:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_HEAT;
 | 
			
		||||
        break;
 | 
			
		||||
      case COMMAND_COOL:
 | 
			
		||||
      case COMMAND_ON_COOL:
 | 
			
		||||
      default:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_COOL;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Temperature
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT)
 | 
			
		||||
      this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
 | 
			
		||||
 | 
			
		||||
    // Fan Speed
 | 
			
		||||
    // Get fan speed
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_AUTO;
 | 
			
		||||
    } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT ||
 | 
			
		||||
               this->mode == climate::CLIMATE_MODE_DRY) {
 | 
			
		||||
    } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY ||
 | 
			
		||||
               this->mode == climate::CLIMATE_MODE_FAN_ONLY || this->mode == climate::CLIMATE_MODE_HEAT) {
 | 
			
		||||
      if ((remote_state & FAN_MASK) == FAN_AUTO) {
 | 
			
		||||
        this->fan_mode = climate::CLIMATE_FAN_AUTO;
 | 
			
		||||
      } else if ((remote_state & FAN_MASK) == FAN_MIN) {
 | 
			
		||||
@@ -166,11 +175,17 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
 | 
			
		||||
        this->fan_mode = climate::CLIMATE_FAN_HIGH;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get temperature
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
 | 
			
		||||
      this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LgIrClimate::transmit_(uint32_t value) {
 | 
			
		||||
  calc_checksum_(value);
 | 
			
		||||
  ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02" PRIX32, value);
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ const uint8_t TEMP_MAX = 30;  // Celsius
 | 
			
		||||
class LgIrClimate : public climate_ir::ClimateIR {
 | 
			
		||||
 public:
 | 
			
		||||
  LgIrClimate()
 | 
			
		||||
      : climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, false,
 | 
			
		||||
      : climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, true,
 | 
			
		||||
                              {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
 | 
			
		||||
                               climate::CLIMATE_FAN_HIGH},
 | 
			
		||||
                              {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.automation import maybe_simple_id, Condition
 | 
			
		||||
from esphome.components import mqtt
 | 
			
		||||
from esphome.components import mqtt, web_server
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_DEVICE_CLASS,
 | 
			
		||||
@@ -16,6 +16,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_TILT_STATE_TOPIC,
 | 
			
		||||
    CONF_STOP,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_WEB_SERVER_ID,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    DEVICE_CLASS_AWNING,
 | 
			
		||||
    DEVICE_CLASS_BLIND,
 | 
			
		||||
@@ -88,34 +89,38 @@ CoverClosedTrigger = cover_ns.class_(
 | 
			
		||||
 | 
			
		||||
CONF_ON_CLOSED = "on_closed"
 | 
			
		||||
 | 
			
		||||
COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(Cover),
 | 
			
		||||
        cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
 | 
			
		||||
        cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
 | 
			
		||||
        cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_TILT_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_OPEN): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
COVER_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(Cover),
 | 
			
		||||
            cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
 | 
			
		||||
            cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
 | 
			
		||||
            cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TILT_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_OPEN): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -132,6 +137,10 @@ async def setup_cover_core_(var, config):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
 | 
			
		||||
    if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
 | 
			
		||||
        web_server_ = await cg.get_variable(webserver_id)
 | 
			
		||||
        web_server.add_entity_to_sorting_list(web_server_, var, config)
 | 
			
		||||
 | 
			
		||||
    if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
 | 
			
		||||
        mqtt_ = cg.new_Pvariable(mqtt_id, var)
 | 
			
		||||
        await mqtt.register_mqtt_component(mqtt_, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
#include "ct_clamp_sensor.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include <cmath>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -37,8 +38,8 @@ void CTClampSensor::update() {
 | 
			
		||||
    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_);
 | 
			
		||||
    ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %" PRIu32 " different samples (%" PRIu32 " SPS)",
 | 
			
		||||
             this->name_.c_str(), rms_ac, this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_);
 | 
			
		||||
    this->publish_state(rms_ac);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import CONF_ID, CONF_PIN
 | 
			
		||||
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
AUTO_LOAD = ["sensor"]
 | 
			
		||||
 | 
			
		||||
dallas_ns = cg.esphome_ns.namespace("dallas")
 | 
			
		||||
DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(DallasComponent),
 | 
			
		||||
        cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.polling_component_schema("60s"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    pin = await cg.gpio_pin_expression(config[CONF_PIN])
 | 
			
		||||
    cg.add(var.set_pin(pin))
 | 
			
		||||
CONFIG_SCHEMA = cv.invalid(
 | 
			
		||||
    'The "dallas" component has been replaced by the "one_wire" component.\nhttps://esphome.io/components/one_wire'
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,287 +0,0 @@
 | 
			
		||||
#include "dallas_component.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dallas {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "dallas.sensor";
 | 
			
		||||
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS1822 = 0x22;
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS18B20 = 0x28;
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS1825 = 0x3B;
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS28EA00 = 0x42;
 | 
			
		||||
static const uint8_t DALLAS_COMMAND_START_CONVERSION = 0x44;
 | 
			
		||||
static const uint8_t DALLAS_COMMAND_READ_SCRATCH_PAD = 0xBE;
 | 
			
		||||
static const uint8_t DALLAS_COMMAND_WRITE_SCRATCH_PAD = 0x4E;
 | 
			
		||||
 | 
			
		||||
uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion() const {
 | 
			
		||||
  switch (this->resolution_) {
 | 
			
		||||
    case 9:
 | 
			
		||||
      return 94;
 | 
			
		||||
    case 10:
 | 
			
		||||
      return 188;
 | 
			
		||||
    case 11:
 | 
			
		||||
      return 375;
 | 
			
		||||
    default:
 | 
			
		||||
      return 750;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DallasComponent::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up DallasComponent...");
 | 
			
		||||
 | 
			
		||||
  pin_->setup();
 | 
			
		||||
 | 
			
		||||
  // clear bus with 480µs high, otherwise initial reset in search_vec() fails
 | 
			
		||||
  pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  delayMicroseconds(480);
 | 
			
		||||
 | 
			
		||||
  one_wire_ = new ESPOneWire(pin_);  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
 | 
			
		||||
  std::vector<uint64_t> raw_sensors;
 | 
			
		||||
  raw_sensors = this->one_wire_->search_vec();
 | 
			
		||||
 | 
			
		||||
  for (auto &address : raw_sensors) {
 | 
			
		||||
    auto *address8 = reinterpret_cast<uint8_t *>(&address);
 | 
			
		||||
    if (crc8(address8, 7) != address8[7]) {
 | 
			
		||||
      ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    if (address8[0] != DALLAS_MODEL_DS18S20 && address8[0] != DALLAS_MODEL_DS1822 &&
 | 
			
		||||
        address8[0] != DALLAS_MODEL_DS18B20 && address8[0] != DALLAS_MODEL_DS1825 &&
 | 
			
		||||
        address8[0] != DALLAS_MODEL_DS28EA00) {
 | 
			
		||||
      ESP_LOGW(TAG, "Unknown device type 0x%02X.", address8[0]);
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    this->found_sensors_.push_back(address);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (auto *sensor : this->sensors_) {
 | 
			
		||||
    if (sensor->get_index().has_value()) {
 | 
			
		||||
      if (*sensor->get_index() >= this->found_sensors_.size()) {
 | 
			
		||||
        this->status_set_error("Sensor configured by index but not found");
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      sensor->set_address(this->found_sensors_[*sensor->get_index()]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!sensor->setup_sensor()) {
 | 
			
		||||
      this->status_set_error();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void DallasComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "DallasComponent:");
 | 
			
		||||
  LOG_PIN("  Pin: ", this->pin_);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
 | 
			
		||||
  if (this->found_sensors_.empty()) {
 | 
			
		||||
    ESP_LOGW(TAG, "  Found no sensors!");
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGD(TAG, "  Found sensors:");
 | 
			
		||||
    for (auto &address : this->found_sensors_) {
 | 
			
		||||
      ESP_LOGD(TAG, "    0x%s", format_hex(address).c_str());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (auto *sensor : this->sensors_) {
 | 
			
		||||
    LOG_SENSOR("  ", "Device", sensor);
 | 
			
		||||
    if (sensor->get_index().has_value()) {
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "    Index %u", *sensor->get_index());
 | 
			
		||||
      if (*sensor->get_index() >= this->found_sensors_.size()) {
 | 
			
		||||
        ESP_LOGE(TAG, "Couldn't find sensor by index - not connected. Proceeding without it.");
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Address: %s", sensor->get_address_name().c_str());
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Resolution: %u", sensor->get_resolution());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DallasComponent::register_sensor(DallasTemperatureSensor *sensor) { this->sensors_.push_back(sensor); }
 | 
			
		||||
void DallasComponent::update() {
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
 | 
			
		||||
  bool result;
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    result = this->one_wire_->reset();
 | 
			
		||||
  }
 | 
			
		||||
  if (!result) {
 | 
			
		||||
    if (!this->found_sensors_.empty()) {
 | 
			
		||||
      // Only log error if at the start sensors were found (and thus are disconnected during uptime)
 | 
			
		||||
      ESP_LOGE(TAG, "Requesting conversion failed");
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (auto *sensor : this->sensors_) {
 | 
			
		||||
      sensor->publish_state(NAN);
 | 
			
		||||
    }
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    this->one_wire_->skip();
 | 
			
		||||
    this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (auto *sensor : this->sensors_) {
 | 
			
		||||
    if (sensor->get_address() == 0) {
 | 
			
		||||
      ESP_LOGV(TAG, "'%s' - Indexed sensor not found at startup, skipping update", sensor->get_name().c_str());
 | 
			
		||||
      sensor->publish_state(NAN);
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] {
 | 
			
		||||
      bool res = sensor->read_scratch_pad();
 | 
			
		||||
 | 
			
		||||
      if (!res) {
 | 
			
		||||
        ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str());
 | 
			
		||||
        sensor->publish_state(NAN);
 | 
			
		||||
        this->status_set_warning();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (!sensor->check_scratch_pad()) {
 | 
			
		||||
        sensor->publish_state(NAN);
 | 
			
		||||
        this->status_set_warning();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      float tempc = sensor->get_temp_c();
 | 
			
		||||
      ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", sensor->get_name().c_str(), tempc);
 | 
			
		||||
      sensor->publish_state(tempc);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
 | 
			
		||||
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
 | 
			
		||||
void DallasTemperatureSensor::set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
 | 
			
		||||
optional<uint8_t> DallasTemperatureSensor::get_index() const { return this->index_; }
 | 
			
		||||
void DallasTemperatureSensor::set_index(uint8_t index) { this->index_ = index; }
 | 
			
		||||
uint8_t *DallasTemperatureSensor::get_address8() { return reinterpret_cast<uint8_t *>(&this->address_); }
 | 
			
		||||
uint64_t DallasTemperatureSensor::get_address() { return this->address_; }
 | 
			
		||||
 | 
			
		||||
const std::string &DallasTemperatureSensor::get_address_name() {
 | 
			
		||||
  if (this->address_name_.empty()) {
 | 
			
		||||
    this->address_name_ = std::string("0x") + format_hex(this->address_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return this->address_name_;
 | 
			
		||||
}
 | 
			
		||||
bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
 | 
			
		||||
  auto *wire = this->parent_->one_wire_;
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
 | 
			
		||||
    if (!wire->reset()) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    wire->select(this->address_);
 | 
			
		||||
    wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD);
 | 
			
		||||
 | 
			
		||||
    for (unsigned char &i : this->scratch_pad_) {
 | 
			
		||||
      i = wire->read8();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
bool DallasTemperatureSensor::setup_sensor() {
 | 
			
		||||
  bool r = this->read_scratch_pad();
 | 
			
		||||
 | 
			
		||||
  if (!r) {
 | 
			
		||||
    ESP_LOGE(TAG, "Reading scratchpad failed: reset");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->check_scratch_pad())
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  if (this->scratch_pad_[4] == this->resolution_)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  if (this->get_address8()[0] == DALLAS_MODEL_DS18S20) {
 | 
			
		||||
    // DS18S20 doesn't support resolution.
 | 
			
		||||
    ESP_LOGW(TAG, "DS18S20 doesn't support setting resolution.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (this->resolution_) {
 | 
			
		||||
    case 12:
 | 
			
		||||
      this->scratch_pad_[4] = 0x7F;
 | 
			
		||||
      break;
 | 
			
		||||
    case 11:
 | 
			
		||||
      this->scratch_pad_[4] = 0x5F;
 | 
			
		||||
      break;
 | 
			
		||||
    case 10:
 | 
			
		||||
      this->scratch_pad_[4] = 0x3F;
 | 
			
		||||
      break;
 | 
			
		||||
    case 9:
 | 
			
		||||
    default:
 | 
			
		||||
      this->scratch_pad_[4] = 0x1F;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto *wire = this->parent_->one_wire_;
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    if (wire->reset()) {
 | 
			
		||||
      wire->select(this->address_);
 | 
			
		||||
      wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
 | 
			
		||||
      wire->write8(this->scratch_pad_[2]);  // high alarm temp
 | 
			
		||||
      wire->write8(this->scratch_pad_[3]);  // low alarm temp
 | 
			
		||||
      wire->write8(this->scratch_pad_[4]);  // resolution
 | 
			
		||||
      wire->reset();
 | 
			
		||||
 | 
			
		||||
      // write value to EEPROM
 | 
			
		||||
      wire->select(this->address_);
 | 
			
		||||
      wire->write8(0x48);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  delay(20);  // allow it to finish operation
 | 
			
		||||
  wire->reset();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
bool DallasTemperatureSensor::check_scratch_pad() {
 | 
			
		||||
  bool chksum_validity = (crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]);
 | 
			
		||||
  bool config_validity = false;
 | 
			
		||||
 | 
			
		||||
  switch (this->get_address8()[0]) {
 | 
			
		||||
    case DALLAS_MODEL_DS18B20:
 | 
			
		||||
      config_validity = ((this->scratch_pad_[4] & 0x9F) == 0x1F);
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      config_validity = ((this->scratch_pad_[4] & 0x10) == 0x10);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#ifdef ESPHOME_LOG_LEVEL_VERY_VERBOSE
 | 
			
		||||
  ESP_LOGVV(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
 | 
			
		||||
            this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
 | 
			
		||||
            this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
 | 
			
		||||
            crc8(this->scratch_pad_, 8));
 | 
			
		||||
#endif
 | 
			
		||||
  if (!chksum_validity) {
 | 
			
		||||
    ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
 | 
			
		||||
  } else if (!config_validity) {
 | 
			
		||||
    ESP_LOGW(TAG, "'%s' - Scratch pad config register invalid!", this->get_name().c_str());
 | 
			
		||||
  }
 | 
			
		||||
  return chksum_validity && config_validity;
 | 
			
		||||
}
 | 
			
		||||
float DallasTemperatureSensor::get_temp_c() {
 | 
			
		||||
  int16_t temp = (int16_t(this->scratch_pad_[1]) << 11) | (int16_t(this->scratch_pad_[0]) << 3);
 | 
			
		||||
  if (this->get_address8()[0] == DALLAS_MODEL_DS18S20) {
 | 
			
		||||
    int diff = (this->scratch_pad_[7] - this->scratch_pad_[6]) << 7;
 | 
			
		||||
    temp = ((temp & 0xFFF0) << 3) - 16 + (diff / this->scratch_pad_[7]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return temp / 128.0f;
 | 
			
		||||
}
 | 
			
		||||
std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); }
 | 
			
		||||
 | 
			
		||||
}  // namespace dallas
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,79 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esp_one_wire.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dallas {
 | 
			
		||||
 | 
			
		||||
class DallasTemperatureSensor;
 | 
			
		||||
 | 
			
		||||
class DallasComponent : public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
 | 
			
		||||
  void register_sensor(DallasTemperatureSensor *sensor);
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  friend DallasTemperatureSensor;
 | 
			
		||||
 | 
			
		||||
  InternalGPIOPin *pin_;
 | 
			
		||||
  ESPOneWire *one_wire_;
 | 
			
		||||
  std::vector<DallasTemperatureSensor *> sensors_;
 | 
			
		||||
  std::vector<uint64_t> found_sensors_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Internal class that helps us create multiple sensors for one Dallas hub.
 | 
			
		||||
class DallasTemperatureSensor : public sensor::Sensor {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_parent(DallasComponent *parent) { parent_ = parent; }
 | 
			
		||||
  /// Helper to get a pointer to the address as uint8_t.
 | 
			
		||||
  uint8_t *get_address8();
 | 
			
		||||
  uint64_t get_address();
 | 
			
		||||
  /// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
 | 
			
		||||
  const std::string &get_address_name();
 | 
			
		||||
 | 
			
		||||
  /// Set the 64-bit unsigned address for this sensor.
 | 
			
		||||
  void set_address(uint64_t address);
 | 
			
		||||
  /// Get the index of this sensor. (0 if using address.)
 | 
			
		||||
  optional<uint8_t> get_index() const;
 | 
			
		||||
  /// Set the index of this sensor. If using index, address will be set after setup.
 | 
			
		||||
  void set_index(uint8_t index);
 | 
			
		||||
  /// Get the set resolution for this sensor.
 | 
			
		||||
  uint8_t get_resolution() const;
 | 
			
		||||
  /// Set the resolution for this sensor.
 | 
			
		||||
  void set_resolution(uint8_t resolution);
 | 
			
		||||
  /// Get the number of milliseconds we have to wait for the conversion phase.
 | 
			
		||||
  uint16_t millis_to_wait_for_conversion() const;
 | 
			
		||||
 | 
			
		||||
  bool setup_sensor();
 | 
			
		||||
  bool read_scratch_pad();
 | 
			
		||||
 | 
			
		||||
  bool check_scratch_pad();
 | 
			
		||||
 | 
			
		||||
  float get_temp_c();
 | 
			
		||||
 | 
			
		||||
  std::string unique_id() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DallasComponent *parent_;
 | 
			
		||||
  uint64_t address_;
 | 
			
		||||
  optional<uint8_t> index_;
 | 
			
		||||
 | 
			
		||||
  uint8_t resolution_;
 | 
			
		||||
  std::string address_name_;
 | 
			
		||||
  uint8_t scratch_pad_[9] = {
 | 
			
		||||
      0,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace dallas
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,252 +0,0 @@
 | 
			
		||||
#include "esp_one_wire.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dallas {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "dallas.one_wire";
 | 
			
		||||
 | 
			
		||||
const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
 | 
			
		||||
const int ONE_WIRE_ROM_SEARCH = 0xF0;
 | 
			
		||||
 | 
			
		||||
ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); }
 | 
			
		||||
 | 
			
		||||
bool HOT IRAM_ATTR ESPOneWire::reset() {
 | 
			
		||||
  // See reset here:
 | 
			
		||||
  // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
 | 
			
		||||
  // Wait for communication to clear (delay G)
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  uint8_t retries = 125;
 | 
			
		||||
  do {
 | 
			
		||||
    if (--retries == 0)
 | 
			
		||||
      return false;
 | 
			
		||||
    delayMicroseconds(2);
 | 
			
		||||
  } while (!pin_.digital_read());
 | 
			
		||||
 | 
			
		||||
  // Send 480µs LOW TX reset pulse (drive bus low, delay H)
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
  delayMicroseconds(480);
 | 
			
		||||
 | 
			
		||||
  // Release the bus, delay I
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  delayMicroseconds(70);
 | 
			
		||||
 | 
			
		||||
  // sample bus, 0=device(s) present, 1=no device present
 | 
			
		||||
  bool r = !pin_.digital_read();
 | 
			
		||||
  // delay J
 | 
			
		||||
  delayMicroseconds(410);
 | 
			
		||||
  return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) {
 | 
			
		||||
  // drive bus low
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
 | 
			
		||||
  // from datasheet:
 | 
			
		||||
  // write 0 low time: t_low0: min=60µs, max=120µs
 | 
			
		||||
  // write 1 low time: t_low1: min=1µs, max=15µs
 | 
			
		||||
  // time slot: t_slot: min=60µs, max=120µs
 | 
			
		||||
  // recovery time: t_rec: min=1µs
 | 
			
		||||
  // ds18b20 appears to read the bus after roughly 14µs
 | 
			
		||||
  uint32_t delay0 = bit ? 6 : 60;
 | 
			
		||||
  uint32_t delay1 = bit ? 54 : 5;
 | 
			
		||||
 | 
			
		||||
  // delay A/C
 | 
			
		||||
  delayMicroseconds(delay0);
 | 
			
		||||
  // release bus
 | 
			
		||||
  pin_.digital_write(true);
 | 
			
		||||
  // delay B/D
 | 
			
		||||
  delayMicroseconds(delay1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HOT IRAM_ATTR ESPOneWire::read_bit() {
 | 
			
		||||
  // drive bus low
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
 | 
			
		||||
  // note: for reading we'll need very accurate timing, as the
 | 
			
		||||
  // timing for the digital_read() is tight; according to the datasheet,
 | 
			
		||||
  // we should read at the end of 16µs starting from the bus low
 | 
			
		||||
  // typically, the ds18b20 pulls the line high after 11µs for a logical 1
 | 
			
		||||
  // and 29µs for a logical 0
 | 
			
		||||
 | 
			
		||||
  uint32_t start = micros();
 | 
			
		||||
  // datasheet says >1µs
 | 
			
		||||
  delayMicroseconds(3);
 | 
			
		||||
 | 
			
		||||
  // release bus, delay E
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
 | 
			
		||||
  // Unfortunately some frameworks have different characteristics than others
 | 
			
		||||
  // esp32 arduino appears to pull the bus low only after the digital_write(false),
 | 
			
		||||
  // whereas on esp-idf it already happens during the pin_mode(OUTPUT)
 | 
			
		||||
  // manually correct for this with these constants.
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  uint32_t timing_constant = 12;
 | 
			
		||||
#else
 | 
			
		||||
  uint32_t timing_constant = 14;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  // measure from start value directly, to get best accurate timing no matter
 | 
			
		||||
  // how long pin_mode/delayMicroseconds took
 | 
			
		||||
  while (micros() - start < timing_constant)
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
  // sample bus to read bit from peer
 | 
			
		||||
  bool r = pin_.digital_read();
 | 
			
		||||
 | 
			
		||||
  // read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
 | 
			
		||||
  uint32_t now = micros();
 | 
			
		||||
  if (now - start < 60)
 | 
			
		||||
    delayMicroseconds(60 - (now - start));
 | 
			
		||||
 | 
			
		||||
  return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR ESPOneWire::write8(uint8_t val) {
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    this->write_bit(bool((1u << i) & val));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR ESPOneWire::write64(uint64_t val) {
 | 
			
		||||
  for (uint8_t i = 0; i < 64; i++) {
 | 
			
		||||
    this->write_bit(bool((1ULL << i) & val));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t IRAM_ATTR ESPOneWire::read8() {
 | 
			
		||||
  uint8_t ret = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    ret |= (uint8_t(this->read_bit()) << i);
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
uint64_t IRAM_ATTR ESPOneWire::read64() {
 | 
			
		||||
  uint64_t ret = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    ret |= (uint64_t(this->read_bit()) << i);
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ESPOneWire::select(uint64_t address) {
 | 
			
		||||
  this->write8(ONE_WIRE_ROM_SELECT);
 | 
			
		||||
  this->write64(address);
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ESPOneWire::reset_search() {
 | 
			
		||||
  this->last_discrepancy_ = 0;
 | 
			
		||||
  this->last_device_flag_ = false;
 | 
			
		||||
  this->rom_number_ = 0;
 | 
			
		||||
}
 | 
			
		||||
uint64_t IRAM_ATTR ESPOneWire::search() {
 | 
			
		||||
  if (this->last_device_flag_) {
 | 
			
		||||
    return 0u;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    if (!this->reset()) {
 | 
			
		||||
      // Reset failed or no devices present
 | 
			
		||||
      this->reset_search();
 | 
			
		||||
      return 0u;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t id_bit_number = 1;
 | 
			
		||||
  uint8_t last_zero = 0;
 | 
			
		||||
  uint8_t rom_byte_number = 0;
 | 
			
		||||
  bool search_result = false;
 | 
			
		||||
  uint8_t rom_byte_mask = 1;
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    // Initiate search
 | 
			
		||||
    this->write8(ONE_WIRE_ROM_SEARCH);
 | 
			
		||||
    do {
 | 
			
		||||
      // read bit
 | 
			
		||||
      bool id_bit = this->read_bit();
 | 
			
		||||
      // read its complement
 | 
			
		||||
      bool cmp_id_bit = this->read_bit();
 | 
			
		||||
 | 
			
		||||
      if (id_bit && cmp_id_bit) {
 | 
			
		||||
        // No devices participating in search
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      bool branch;
 | 
			
		||||
 | 
			
		||||
      if (id_bit != cmp_id_bit) {
 | 
			
		||||
        // only chose one branch, the other one doesn't have any devices.
 | 
			
		||||
        branch = id_bit;
 | 
			
		||||
      } else {
 | 
			
		||||
        // there are devices with both 0s and 1s at this bit
 | 
			
		||||
        if (id_bit_number < this->last_discrepancy_) {
 | 
			
		||||
          branch = (this->rom_number8_()[rom_byte_number] & rom_byte_mask) > 0;
 | 
			
		||||
        } else {
 | 
			
		||||
          branch = id_bit_number == this->last_discrepancy_;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!branch) {
 | 
			
		||||
          last_zero = id_bit_number;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (branch) {
 | 
			
		||||
        // set bit
 | 
			
		||||
        this->rom_number8_()[rom_byte_number] |= rom_byte_mask;
 | 
			
		||||
      } else {
 | 
			
		||||
        // clear bit
 | 
			
		||||
        this->rom_number8_()[rom_byte_number] &= ~rom_byte_mask;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // choose/announce branch
 | 
			
		||||
      this->write_bit(branch);
 | 
			
		||||
      id_bit_number++;
 | 
			
		||||
      rom_byte_mask <<= 1;
 | 
			
		||||
      if (rom_byte_mask == 0u) {
 | 
			
		||||
        // go to next byte
 | 
			
		||||
        rom_byte_number++;
 | 
			
		||||
        rom_byte_mask = 1;
 | 
			
		||||
      }
 | 
			
		||||
    } while (rom_byte_number < 8);  // loop through all bytes
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (id_bit_number >= 65) {
 | 
			
		||||
    this->last_discrepancy_ = last_zero;
 | 
			
		||||
    if (this->last_discrepancy_ == 0) {
 | 
			
		||||
      // we're at root and have no choices left, so this was the last one.
 | 
			
		||||
      this->last_device_flag_ = true;
 | 
			
		||||
    }
 | 
			
		||||
    search_result = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  search_result = search_result && (this->rom_number8_()[0] != 0);
 | 
			
		||||
  if (!search_result) {
 | 
			
		||||
    this->reset_search();
 | 
			
		||||
    return 0u;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return this->rom_number_;
 | 
			
		||||
}
 | 
			
		||||
std::vector<uint64_t> ESPOneWire::search_vec() {
 | 
			
		||||
  std::vector<uint64_t> res;
 | 
			
		||||
 | 
			
		||||
  this->reset_search();
 | 
			
		||||
  uint64_t address;
 | 
			
		||||
  while ((address = this->search()) != 0u)
 | 
			
		||||
    res.push_back(address);
 | 
			
		||||
 | 
			
		||||
  return res;
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ESPOneWire::skip() {
 | 
			
		||||
  this->write8(0xCC);  // skip ROM
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast<uint8_t *>(&this->rom_number_); }
 | 
			
		||||
 | 
			
		||||
}  // namespace dallas
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,68 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dallas {
 | 
			
		||||
 | 
			
		||||
extern const uint8_t ONE_WIRE_ROM_SELECT;
 | 
			
		||||
extern const int ONE_WIRE_ROM_SEARCH;
 | 
			
		||||
 | 
			
		||||
class ESPOneWire {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit ESPOneWire(InternalGPIOPin *pin);
 | 
			
		||||
 | 
			
		||||
  /** Reset the bus, should be done before all write operations.
 | 
			
		||||
   *
 | 
			
		||||
   * Takes approximately 1ms.
 | 
			
		||||
   *
 | 
			
		||||
   * @return Whether the operation was successful.
 | 
			
		||||
   */
 | 
			
		||||
  bool reset();
 | 
			
		||||
 | 
			
		||||
  /// Write a single bit to the bus, takes about 70µs.
 | 
			
		||||
  void write_bit(bool bit);
 | 
			
		||||
 | 
			
		||||
  /// Read a single bit from the bus, takes about 70µs
 | 
			
		||||
  bool read_bit();
 | 
			
		||||
 | 
			
		||||
  /// Write a word to the bus. LSB first.
 | 
			
		||||
  void write8(uint8_t val);
 | 
			
		||||
 | 
			
		||||
  /// Write a 64 bit unsigned integer to the bus. LSB first.
 | 
			
		||||
  void write64(uint64_t val);
 | 
			
		||||
 | 
			
		||||
  /// Write a command to the bus that addresses all devices by skipping the ROM.
 | 
			
		||||
  void skip();
 | 
			
		||||
 | 
			
		||||
  /// Read an 8 bit word from the bus.
 | 
			
		||||
  uint8_t read8();
 | 
			
		||||
 | 
			
		||||
  /// Read an 64-bit unsigned integer from the bus.
 | 
			
		||||
  uint64_t read64();
 | 
			
		||||
 | 
			
		||||
  /// Select a specific address on the bus for the following command.
 | 
			
		||||
  void select(uint64_t address);
 | 
			
		||||
 | 
			
		||||
  /// Reset the device search.
 | 
			
		||||
  void reset_search();
 | 
			
		||||
 | 
			
		||||
  /// Search for a 1-Wire device on the bus. Returns 0 if all devices have been found.
 | 
			
		||||
  uint64_t search();
 | 
			
		||||
 | 
			
		||||
  /// Helper that wraps search in a std::vector.
 | 
			
		||||
  std::vector<uint64_t> search_vec();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  /// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer.
 | 
			
		||||
  inline uint8_t *rom_number8_();
 | 
			
		||||
 | 
			
		||||
  ISRInternalGPIOPin pin_;
 | 
			
		||||
  uint8_t last_discrepancy_{0};
 | 
			
		||||
  bool last_device_flag_{false};
 | 
			
		||||
  uint64_t rom_number_{0};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace dallas
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,50 +1,5 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ADDRESS,
 | 
			
		||||
    CONF_DALLAS_ID,
 | 
			
		||||
    CONF_INDEX,
 | 
			
		||||
    CONF_RESOLUTION,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.invalid(
 | 
			
		||||
    'The "dallas" sensor is now "dallas_temp"\nhttps://esphome.io/components/sensor/dallas_temp'
 | 
			
		||||
)
 | 
			
		||||
from . import DallasComponent, dallas_ns
 | 
			
		||||
 | 
			
		||||
DallasTemperatureSensor = dallas_ns.class_("DallasTemperatureSensor", sensor.Sensor)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    sensor.sensor_schema(
 | 
			
		||||
        DallasTemperatureSensor,
 | 
			
		||||
        unit_of_measurement=UNIT_CELSIUS,
 | 
			
		||||
        accuracy_decimals=1,
 | 
			
		||||
        device_class=DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
        state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    ).extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(CONF_DALLAS_ID): cv.use_id(DallasComponent),
 | 
			
		||||
            cv.Optional(CONF_ADDRESS): cv.hex_uint64_t,
 | 
			
		||||
            cv.Optional(CONF_INDEX): cv.positive_int,
 | 
			
		||||
            cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    hub = await cg.get_variable(config[CONF_DALLAS_ID])
 | 
			
		||||
    var = await sensor.new_sensor(config)
 | 
			
		||||
 | 
			
		||||
    if CONF_ADDRESS in config:
 | 
			
		||||
        cg.add(var.set_address(config[CONF_ADDRESS]))
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_index(config[CONF_INDEX]))
 | 
			
		||||
 | 
			
		||||
    if CONF_RESOLUTION in config:
 | 
			
		||||
        cg.add(var.set_resolution(config[CONF_RESOLUTION]))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_parent(hub))
 | 
			
		||||
 | 
			
		||||
    cg.add(hub.register_sensor(var))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								esphome/components/dallas_temp/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/dallas_temp/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
CODEOWNERS = ["@ssieb"]
 | 
			
		||||
							
								
								
									
										172
									
								
								esphome/components/dallas_temp/dallas_temp.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								esphome/components/dallas_temp/dallas_temp.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,172 @@
 | 
			
		||||
#include "dallas_temp.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dallas_temp {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "dallas.temp.sensor";
 | 
			
		||||
 | 
			
		||||
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
 | 
			
		||||
static const uint8_t DALLAS_COMMAND_START_CONVERSION = 0x44;
 | 
			
		||||
static const uint8_t DALLAS_COMMAND_READ_SCRATCH_PAD = 0xBE;
 | 
			
		||||
static const uint8_t DALLAS_COMMAND_WRITE_SCRATCH_PAD = 0x4E;
 | 
			
		||||
static const uint8_t DALLAS_COMMAND_COPY_SCRATCH_PAD = 0x48;
 | 
			
		||||
 | 
			
		||||
uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion_() const {
 | 
			
		||||
  switch (this->resolution_) {
 | 
			
		||||
    case 9:
 | 
			
		||||
      return 94;
 | 
			
		||||
    case 10:
 | 
			
		||||
      return 188;
 | 
			
		||||
    case 11:
 | 
			
		||||
      return 375;
 | 
			
		||||
    default:
 | 
			
		||||
      return 750;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DallasTemperatureSensor::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Dallas Temperature Sensor:");
 | 
			
		||||
  if (this->address_ == 0) {
 | 
			
		||||
    ESP_LOGW(TAG, "  Unable to select an address");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  LOG_ONE_WIRE_DEVICE(this);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Resolution: %u bits", this->resolution_);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DallasTemperatureSensor::update() {
 | 
			
		||||
  if (this->address_ == 0)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
 | 
			
		||||
  this->send_command_(DALLAS_COMMAND_START_CONVERSION);
 | 
			
		||||
 | 
			
		||||
  this->set_timeout(this->get_address_name(), this->millis_to_wait_for_conversion_(), [this] {
 | 
			
		||||
    if (!this->read_scratch_pad_() || !this->check_scratch_pad_()) {
 | 
			
		||||
      this->publish_state(NAN);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    float tempc = this->get_temp_c_();
 | 
			
		||||
    ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", this->get_name().c_str(), tempc);
 | 
			
		||||
    this->publish_state(tempc);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR DallasTemperatureSensor::read_scratch_pad_int_() {
 | 
			
		||||
  for (uint8_t &i : this->scratch_pad_) {
 | 
			
		||||
    i = this->bus_->read8();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DallasTemperatureSensor::read_scratch_pad_() {
 | 
			
		||||
  bool success;
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    success = this->send_command_(DALLAS_COMMAND_READ_SCRATCH_PAD);
 | 
			
		||||
    if (success)
 | 
			
		||||
      this->read_scratch_pad_int_();
 | 
			
		||||
  }
 | 
			
		||||
  if (!success) {
 | 
			
		||||
    ESP_LOGW(TAG, "'%s' - reading scratch pad failed bus reset", this->get_name().c_str());
 | 
			
		||||
    this->status_set_warning("bus reset failed");
 | 
			
		||||
  }
 | 
			
		||||
  return success;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DallasTemperatureSensor::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "setting up Dallas temperature sensor...");
 | 
			
		||||
  if (!this->check_address_())
 | 
			
		||||
    return;
 | 
			
		||||
  if (!this->read_scratch_pad_())
 | 
			
		||||
    return;
 | 
			
		||||
  if (!this->check_scratch_pad_())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
 | 
			
		||||
    // DS18S20 doesn't support resolution.
 | 
			
		||||
    ESP_LOGW(TAG, "DS18S20 doesn't support setting resolution.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t res;
 | 
			
		||||
  switch (this->resolution_) {
 | 
			
		||||
    case 12:
 | 
			
		||||
      res = 0x7F;
 | 
			
		||||
      break;
 | 
			
		||||
    case 11:
 | 
			
		||||
      res = 0x5F;
 | 
			
		||||
      break;
 | 
			
		||||
    case 10:
 | 
			
		||||
      res = 0x3F;
 | 
			
		||||
      break;
 | 
			
		||||
    case 9:
 | 
			
		||||
    default:
 | 
			
		||||
      res = 0x1F;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->scratch_pad_[4] == res)
 | 
			
		||||
    return;
 | 
			
		||||
  this->scratch_pad_[4] = res;
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    if (this->send_command_(DALLAS_COMMAND_WRITE_SCRATCH_PAD)) {
 | 
			
		||||
      this->bus_->write8(this->scratch_pad_[2]);  // high alarm temp
 | 
			
		||||
      this->bus_->write8(this->scratch_pad_[3]);  // low alarm temp
 | 
			
		||||
      this->bus_->write8(this->scratch_pad_[4]);  // resolution
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // write value to EEPROM
 | 
			
		||||
    this->send_command_(DALLAS_COMMAND_COPY_SCRATCH_PAD);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DallasTemperatureSensor::check_scratch_pad_() {
 | 
			
		||||
  bool chksum_validity = (crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]);
 | 
			
		||||
 | 
			
		||||
#ifdef ESPHOME_LOG_LEVEL_VERY_VERBOSE
 | 
			
		||||
  ESP_LOGVV(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
 | 
			
		||||
            this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
 | 
			
		||||
            this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
 | 
			
		||||
            crc8(this->scratch_pad_, 8));
 | 
			
		||||
#endif
 | 
			
		||||
  if (!chksum_validity) {
 | 
			
		||||
    ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
 | 
			
		||||
    this->status_set_warning("scratch pad checksum invalid");
 | 
			
		||||
  }
 | 
			
		||||
  return chksum_validity;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float DallasTemperatureSensor::get_temp_c_() {
 | 
			
		||||
  int16_t temp = (this->scratch_pad_[1] << 8) | this->scratch_pad_[0];
 | 
			
		||||
  if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
 | 
			
		||||
    if (this->scratch_pad_[7] != 0x10)
 | 
			
		||||
      ESP_LOGE(TAG, "unexpected COUNT_PER_C value: %u", this->scratch_pad_[7]);
 | 
			
		||||
    temp = ((temp & 0xfff7) << 3) + (0x10 - this->scratch_pad_[6]) - 4;
 | 
			
		||||
  } else {
 | 
			
		||||
    switch (this->resolution_) {
 | 
			
		||||
      case 9:
 | 
			
		||||
        temp &= 0xfff8;
 | 
			
		||||
        break;
 | 
			
		||||
      case 10:
 | 
			
		||||
        temp &= 0xfffc;
 | 
			
		||||
        break;
 | 
			
		||||
      case 11:
 | 
			
		||||
        temp &= 0xfffe;
 | 
			
		||||
        break;
 | 
			
		||||
      case 12:
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return temp / 16.0f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace dallas_temp
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										32
									
								
								esphome/components/dallas_temp/dallas_temp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								esphome/components/dallas_temp/dallas_temp.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/one_wire/one_wire.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dallas_temp {
 | 
			
		||||
 | 
			
		||||
class DallasTemperatureSensor : public PollingComponent, public sensor::Sensor, public one_wire::OneWireDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  /// Set the resolution for this sensor.
 | 
			
		||||
  void set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t resolution_;
 | 
			
		||||
  uint8_t scratch_pad_[9] = {0};
 | 
			
		||||
 | 
			
		||||
  /// Get the number of milliseconds we have to wait for the conversion phase.
 | 
			
		||||
  uint16_t millis_to_wait_for_conversion_() const;
 | 
			
		||||
  bool read_scratch_pad_();
 | 
			
		||||
  void read_scratch_pad_int_();
 | 
			
		||||
  bool check_scratch_pad_();
 | 
			
		||||
  float get_temp_c_();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace dallas_temp
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										43
									
								
								esphome/components/dallas_temp/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								esphome/components/dallas_temp/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import one_wire, sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_RESOLUTION,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
dallas_temp_ns = cg.esphome_ns.namespace("dallas_temp")
 | 
			
		||||
 | 
			
		||||
DallasTemperatureSensor = dallas_temp_ns.class_(
 | 
			
		||||
    "DallasTemperatureSensor",
 | 
			
		||||
    cg.PollingComponent,
 | 
			
		||||
    sensor.Sensor,
 | 
			
		||||
    one_wire.OneWireDevice,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    sensor.sensor_schema(
 | 
			
		||||
        DallasTemperatureSensor,
 | 
			
		||||
        unit_of_measurement=UNIT_CELSIUS,
 | 
			
		||||
        accuracy_decimals=1,
 | 
			
		||||
        device_class=DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
        state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    )
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(one_wire.one_wire_device_schema())
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await sensor.new_sensor(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await one_wire.register_one_wire_device(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_resolution(config[CONF_RESOLUTION]))
 | 
			
		||||
@@ -2,7 +2,7 @@ import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.components import mqtt, time
 | 
			
		||||
from esphome.components import mqtt, web_server, time
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_ON_TIME,
 | 
			
		||||
@@ -11,6 +11,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_WEB_SERVER_ID,
 | 
			
		||||
    CONF_DATE,
 | 
			
		||||
    CONF_DATETIME,
 | 
			
		||||
    CONF_TIME,
 | 
			
		||||
@@ -63,16 +64,20 @@ DATETIME_MODES = [
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_DATETIME_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_ON_VALUE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA))
 | 
			
		||||
_DATETIME_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_ON_VALUE): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def date_schema(class_: MockObjClass) -> cv.Schema:
 | 
			
		||||
@@ -128,6 +133,9 @@ async def setup_datetime_core_(var, config):
 | 
			
		||||
    if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
 | 
			
		||||
        mqtt_ = cg.new_Pvariable(mqtt_id, var)
 | 
			
		||||
        await mqtt.register_mqtt_component(mqtt_, config)
 | 
			
		||||
    if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
 | 
			
		||||
        web_server_ = await cg.get_variable(webserver_id)
 | 
			
		||||
        web_server.add_entity_to_sorting_list(web_server_, var, config)
 | 
			
		||||
    for conf in config.get(CONF_ON_VALUE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)
 | 
			
		||||
 
 | 
			
		||||
@@ -80,6 +80,17 @@ void DateCall::validate_() {
 | 
			
		||||
 | 
			
		||||
void DateCall::perform() {
 | 
			
		||||
  this->validate_();
 | 
			
		||||
  ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
 | 
			
		||||
 | 
			
		||||
  if (this->year_.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, " Year: %d", *this->year_);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->month_.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, " Month: %d", *this->month_);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->day_.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, " Day: %d", *this->day_);
 | 
			
		||||
  }
 | 
			
		||||
  this->parent_->control(*this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ std::string DebugComponent::get_reset_reason_() { return lt_get_reboot_reason_na
 | 
			
		||||
uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); }
 | 
			
		||||
 | 
			
		||||
void DebugComponent::get_device_info_(std::string &device_info) {
 | 
			
		||||
  reset_reason = get_reset_reason_();
 | 
			
		||||
  str::string reset_reason = get_reset_reason_();
 | 
			
		||||
  ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version());
 | 
			
		||||
  ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz());
 | 
			
		||||
  ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id());
 | 
			
		||||
 
 | 
			
		||||
@@ -34,10 +34,12 @@ enum WakeupPinMode {
 | 
			
		||||
  WAKEUP_PIN_MODE_INVERT_WAKEUP,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
 | 
			
		||||
struct Ext1Wakeup {
 | 
			
		||||
  uint64_t mask;
 | 
			
		||||
  esp_sleep_ext1_wakeup_mode_t wakeup_mode;
 | 
			
		||||
};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
struct WakeupCauseToRunDuration {
 | 
			
		||||
  // Run duration if woken up by timer or any other reason besides those below.
 | 
			
		||||
@@ -114,7 +116,11 @@ class DeepSleepComponent : public Component {
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  InternalGPIOPin *wakeup_pin_;
 | 
			
		||||
  WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
 | 
			
		||||
 | 
			
		||||
#if !defined(USE_ESP32_VARIANT_ESP32C3)
 | 
			
		||||
  optional<Ext1Wakeup> ext1_wakeup_;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  optional<bool> touch_wakeup_;
 | 
			
		||||
  optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -86,9 +86,14 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
 | 
			
		||||
  if (this->model_ == DHT_MODEL_DHT11) {
 | 
			
		||||
    delayMicroseconds(18000);
 | 
			
		||||
  } else if (this->model_ == DHT_MODEL_SI7021) {
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
    delayMicroseconds(500);
 | 
			
		||||
    this->pin_->digital_write(true);
 | 
			
		||||
    delayMicroseconds(40);
 | 
			
		||||
#else
 | 
			
		||||
    delayMicroseconds(400);
 | 
			
		||||
    this->pin_->digital_write(true);
 | 
			
		||||
#endif
 | 
			
		||||
  } else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
 | 
			
		||||
    delayMicroseconds(2000);
 | 
			
		||||
  } else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,9 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -31,15 +31,14 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) {
 | 
			
		||||
  memcpy(ret.uuid_.uuid.uuid128, data, ESP_UUID_LEN_128);
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { return ESPBTUUID::from_raw(data.data(), data.length()); }
 | 
			
		||||
ESPBTUUID ESPBTUUID::from_raw(const char *data, size_t len) {
 | 
			
		||||
ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
 | 
			
		||||
  ESPBTUUID ret;
 | 
			
		||||
  if (len == 4) {
 | 
			
		||||
  if (data.length() == 4) {
 | 
			
		||||
    ret.uuid_.len = ESP_UUID_LEN_16;
 | 
			
		||||
    ret.uuid_.uuid.uuid16 = 0;
 | 
			
		||||
    for (int i = 0; i < len;) {
 | 
			
		||||
      uint8_t msb = data[i];
 | 
			
		||||
      uint8_t lsb = data[i + 1];
 | 
			
		||||
    for (int i = 0; i < data.length();) {
 | 
			
		||||
      uint8_t msb = data.c_str()[i];
 | 
			
		||||
      uint8_t lsb = data.c_str()[i + 1];
 | 
			
		||||
 | 
			
		||||
      if (msb > '9')
 | 
			
		||||
        msb -= 7;
 | 
			
		||||
@@ -48,12 +47,12 @@ ESPBTUUID ESPBTUUID::from_raw(const char *data, size_t len) {
 | 
			
		||||
      ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (2 - i) * 4;
 | 
			
		||||
      i += 2;
 | 
			
		||||
    }
 | 
			
		||||
  } else if (len == 8) {
 | 
			
		||||
  } else if (data.length() == 8) {
 | 
			
		||||
    ret.uuid_.len = ESP_UUID_LEN_32;
 | 
			
		||||
    ret.uuid_.uuid.uuid32 = 0;
 | 
			
		||||
    for (int i = 0; i < len;) {
 | 
			
		||||
      uint8_t msb = data[i];
 | 
			
		||||
      uint8_t lsb = data[i + 1];
 | 
			
		||||
    for (int i = 0; i < data.length();) {
 | 
			
		||||
      uint8_t msb = data.c_str()[i];
 | 
			
		||||
      uint8_t lsb = data.c_str()[i + 1];
 | 
			
		||||
 | 
			
		||||
      if (msb > '9')
 | 
			
		||||
        msb -= 7;
 | 
			
		||||
@@ -62,20 +61,20 @@ ESPBTUUID ESPBTUUID::from_raw(const char *data, size_t len) {
 | 
			
		||||
      ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (6 - i) * 4;
 | 
			
		||||
      i += 2;
 | 
			
		||||
    }
 | 
			
		||||
  } else if (len == 16) {  // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be
 | 
			
		||||
                           // investigated (lack of time)
 | 
			
		||||
  } else if (data.length() == 16) {  // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be
 | 
			
		||||
                                     // investigated (lack of time)
 | 
			
		||||
    ret.uuid_.len = ESP_UUID_LEN_128;
 | 
			
		||||
    memcpy(ret.uuid_.uuid.uuid128, (uint8_t *) data, 16);
 | 
			
		||||
  } else if (len == 36) {
 | 
			
		||||
    memcpy(ret.uuid_.uuid.uuid128, (uint8_t *) data.data(), 16);
 | 
			
		||||
  } else if (data.length() == 36) {
 | 
			
		||||
    // If the length of the string is 36 bytes then we will assume it is a long hex string in
 | 
			
		||||
    // UUID format.
 | 
			
		||||
    ret.uuid_.len = ESP_UUID_LEN_128;
 | 
			
		||||
    int n = 0;
 | 
			
		||||
    for (int i = 0; i < len;) {
 | 
			
		||||
      if (data[i] == '-')
 | 
			
		||||
    for (int i = 0; i < data.length();) {
 | 
			
		||||
      if (data.c_str()[i] == '-')
 | 
			
		||||
        i++;
 | 
			
		||||
      uint8_t msb = data[i];
 | 
			
		||||
      uint8_t lsb = data[i + 1];
 | 
			
		||||
      uint8_t msb = data.c_str()[i];
 | 
			
		||||
      uint8_t lsb = data.c_str()[i + 1];
 | 
			
		||||
 | 
			
		||||
      if (msb > '9')
 | 
			
		||||
        msb -= 7;
 | 
			
		||||
@@ -85,7 +84,7 @@ ESPBTUUID ESPBTUUID::from_raw(const char *data, size_t len) {
 | 
			
		||||
      i += 2;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data);
 | 
			
		||||
    ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data.c_str());
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
@@ -163,19 +162,14 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
 | 
			
		||||
std::string ESPBTUUID::to_string() {
 | 
			
		||||
  if (!this->string_.empty())
 | 
			
		||||
    return this->string_;
 | 
			
		||||
 | 
			
		||||
std::string ESPBTUUID::to_string() const {
 | 
			
		||||
  switch (this->uuid_.len) {
 | 
			
		||||
    case ESP_UUID_LEN_16:
 | 
			
		||||
      this->string_ = str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
 | 
			
		||||
      return this->string_;
 | 
			
		||||
      return str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
 | 
			
		||||
    case ESP_UUID_LEN_32:
 | 
			
		||||
      this->string_ = str_snprintf("0x%02" PRIX32 "%02" PRIX32 "%02" PRIX32 "%02" PRIX32, 10,
 | 
			
		||||
                                   (this->uuid_.uuid.uuid32 >> 24), (this->uuid_.uuid.uuid32 >> 16 & 0xff),
 | 
			
		||||
                                   (this->uuid_.uuid.uuid32 >> 8 & 0xff), this->uuid_.uuid.uuid32 & 0xff);
 | 
			
		||||
      return this->string_;
 | 
			
		||||
      return str_snprintf("0x%02" PRIX32 "%02" PRIX32 "%02" PRIX32 "%02" PRIX32, 10, (this->uuid_.uuid.uuid32 >> 24),
 | 
			
		||||
                          (this->uuid_.uuid.uuid32 >> 16 & 0xff), (this->uuid_.uuid.uuid32 >> 8 & 0xff),
 | 
			
		||||
                          this->uuid_.uuid.uuid32 & 0xff);
 | 
			
		||||
    default:
 | 
			
		||||
    case ESP_UUID_LEN_128:
 | 
			
		||||
      std::string buf;
 | 
			
		||||
@@ -184,7 +178,6 @@ std::string ESPBTUUID::to_string() {
 | 
			
		||||
        if (i == 6 || i == 8 || i == 10 || i == 12)
 | 
			
		||||
          buf += "-";
 | 
			
		||||
      }
 | 
			
		||||
      this->string_ = buf;
 | 
			
		||||
      return buf;
 | 
			
		||||
  }
 | 
			
		||||
  return "";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <esp_bt_defs.h>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <esp_bt_defs.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_ble {
 | 
			
		||||
@@ -23,8 +23,6 @@ class ESPBTUUID {
 | 
			
		||||
 | 
			
		||||
  static ESPBTUUID from_raw(const std::string &data);
 | 
			
		||||
 | 
			
		||||
  static ESPBTUUID from_raw(const char *uuid, size_t len);
 | 
			
		||||
 | 
			
		||||
  static ESPBTUUID from_uuid(esp_bt_uuid_t uuid);
 | 
			
		||||
 | 
			
		||||
  ESPBTUUID as_128bit() const;
 | 
			
		||||
@@ -36,11 +34,10 @@ class ESPBTUUID {
 | 
			
		||||
 | 
			
		||||
  esp_bt_uuid_t get_uuid() const;
 | 
			
		||||
 | 
			
		||||
  std::string to_string();
 | 
			
		||||
  std::string to_string() const;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  esp_bt_uuid_t uuid_;
 | 
			
		||||
  std::string string_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32_ble
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,6 @@ namespace esp32_ble_server {
 | 
			
		||||
 | 
			
		||||
BLE2901::BLE2901(const std::string &value) : BLE2901((uint8_t *) value.data(), value.length()) {}
 | 
			
		||||
BLE2901::BLE2901(const uint8_t *data, size_t length) : BLEDescriptor(esp32_ble::ESPBTUUID::from_uint16(0x2901)) {
 | 
			
		||||
  if (this->state_ == FAILED) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->set_value(data, length);
 | 
			
		||||
  this->permissions_ = ESP_GATT_PERM_READ;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,6 @@
 | 
			
		||||
#include "ble_2902.h"
 | 
			
		||||
#include "esphome/components/esp32_ble/ble_uuid.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
@@ -11,9 +9,6 @@ namespace esphome {
 | 
			
		||||
namespace esp32_ble_server {
 | 
			
		||||
 | 
			
		||||
BLE2902::BLE2902() : BLEDescriptor(esp32_ble::ESPBTUUID::from_uint16(0x2902)) {
 | 
			
		||||
  if (this->state_ == FAILED) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->value_.attr_len = 2;
 | 
			
		||||
  uint8_t data[2] = {0, 0};
 | 
			
		||||
  memcpy(this->value_.attr_value, data, 2);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,4 @@
 | 
			
		||||
#include "ble_characteristic.h"
 | 
			
		||||
#include "ble_2901.h"
 | 
			
		||||
#include "ble_2902.h"
 | 
			
		||||
#include "ble_server.h"
 | 
			
		||||
#include "ble_service.h"
 | 
			
		||||
 | 
			
		||||
@@ -14,17 +12,14 @@ namespace esp32_ble_server {
 | 
			
		||||
static const char *const TAG = "esp32_ble_server.characteristic";
 | 
			
		||||
 | 
			
		||||
BLECharacteristic::~BLECharacteristic() {
 | 
			
		||||
  this->descriptors_.clear();
 | 
			
		||||
  for (auto *descriptor : this->descriptors_) {
 | 
			
		||||
    delete descriptor;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  }
 | 
			
		||||
  vSemaphoreDelete(this->set_value_lock_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties) : uuid_(uuid) {
 | 
			
		||||
  this->set_value_lock_ = xSemaphoreCreateBinary();
 | 
			
		||||
  if (this->set_value_lock_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to create set_value_lock_ semaphore");
 | 
			
		||||
    this->state_ = FAILED;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  xSemaphoreGive(this->set_value_lock_);
 | 
			
		||||
 | 
			
		||||
  this->properties_ = (esp_gatt_char_prop_t) 0;
 | 
			
		||||
@@ -108,36 +103,14 @@ void BLECharacteristic::notify(bool notification) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLECharacteristic::add_descriptor(std::shared_ptr<BLEDescriptor> descriptor) {
 | 
			
		||||
  this->descriptors_.push_back(descriptor);
 | 
			
		||||
}
 | 
			
		||||
void BLECharacteristic::add_descriptor(BLEDescriptor *descriptor) { this->descriptors_.push_back(descriptor); }
 | 
			
		||||
 | 
			
		||||
void BLECharacteristic::remove_descriptor(std::shared_ptr<BLEDescriptor> descriptor) {
 | 
			
		||||
void BLECharacteristic::remove_descriptor(BLEDescriptor *descriptor) {
 | 
			
		||||
  this->descriptors_.erase(std::remove(this->descriptors_.begin(), this->descriptors_.end(), descriptor),
 | 
			
		||||
                           this->descriptors_.end());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<BLE2901> BLECharacteristic::make_2901_descriptor(const std::string &value) {
 | 
			
		||||
  auto descriptor = std::make_shared<BLE2901>(value);
 | 
			
		||||
  if (descriptor == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to allocate BLE2901 descriptor");
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
  this->add_descriptor(descriptor);
 | 
			
		||||
  return descriptor;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<BLE2902> BLECharacteristic::make_2902_descriptor() {
 | 
			
		||||
  auto descriptor = std::make_shared<BLE2902>();
 | 
			
		||||
  if (descriptor == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to allocate BLE2902 descriptor");
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
  this->add_descriptor(descriptor);
 | 
			
		||||
  return descriptor;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLECharacteristic::do_create(std::shared_ptr<BLEService> service) {
 | 
			
		||||
void BLECharacteristic::do_create(BLEService *service) {
 | 
			
		||||
  this->service_ = service;
 | 
			
		||||
  esp_attr_control_t control;
 | 
			
		||||
  control.auto_rsp = ESP_GATT_RSP_BY_APP;
 | 
			
		||||
@@ -164,7 +137,7 @@ bool BLECharacteristic::is_created() {
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  bool created = true;
 | 
			
		||||
  for (auto descriptor : this->descriptors_) {
 | 
			
		||||
  for (auto *descriptor : this->descriptors_) {
 | 
			
		||||
    created &= descriptor->is_created();
 | 
			
		||||
  }
 | 
			
		||||
  if (created)
 | 
			
		||||
@@ -177,7 +150,7 @@ bool BLECharacteristic::is_failed() {
 | 
			
		||||
    return true;
 | 
			
		||||
 | 
			
		||||
  bool failed = false;
 | 
			
		||||
  for (auto descriptor : this->descriptors_) {
 | 
			
		||||
  for (auto *descriptor : this->descriptors_) {
 | 
			
		||||
    failed |= descriptor->is_failed();
 | 
			
		||||
  }
 | 
			
		||||
  if (failed)
 | 
			
		||||
@@ -235,8 +208,8 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt
 | 
			
		||||
      if (this->uuid_ == ESPBTUUID::from_uuid(param->add_char.char_uuid)) {
 | 
			
		||||
        this->handle_ = param->add_char.attr_handle;
 | 
			
		||||
 | 
			
		||||
        for (auto descriptor : this->descriptors_) {
 | 
			
		||||
          descriptor->do_create(shared_from_this());
 | 
			
		||||
        for (auto *descriptor : this->descriptors_) {
 | 
			
		||||
          descriptor->do_create(this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this->state_ = CREATING_DEPENDENTS;
 | 
			
		||||
@@ -340,7 +313,7 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (auto descriptor : this->descriptors_) {
 | 
			
		||||
  for (auto *descriptor : this->descriptors_) {
 | 
			
		||||
    descriptor->gatts_event_handler(event, gatts_if, param);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,17 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "ble_2901.h"
 | 
			
		||||
#include "ble_2902.h"
 | 
			
		||||
#include "ble_descriptor.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/esp32_ble/ble_uuid.h"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <esp_bt_defs.h>
 | 
			
		||||
#include <esp_gap_ble_api.h>
 | 
			
		||||
#include <esp_gatt_defs.h>
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
#include <esp_gatts_api.h>
 | 
			
		||||
#include <esp_bt_defs.h>
 | 
			
		||||
#include <freertos/FreeRTOS.h>
 | 
			
		||||
#include <freertos/semphr.h>
 | 
			
		||||
 | 
			
		||||
@@ -26,7 +22,7 @@ using namespace esp32_ble;
 | 
			
		||||
 | 
			
		||||
class BLEService;
 | 
			
		||||
 | 
			
		||||
class BLECharacteristic : public std::enable_shared_from_this<BLECharacteristic> {
 | 
			
		||||
class BLECharacteristic {
 | 
			
		||||
 public:
 | 
			
		||||
  BLECharacteristic(ESPBTUUID uuid, uint32_t properties);
 | 
			
		||||
  ~BLECharacteristic();
 | 
			
		||||
@@ -51,18 +47,15 @@ class BLECharacteristic : public std::enable_shared_from_this<BLECharacteristic>
 | 
			
		||||
 | 
			
		||||
  void notify(bool notification = true);
 | 
			
		||||
 | 
			
		||||
  void do_create(std::shared_ptr<BLEService> service);
 | 
			
		||||
  void do_create(BLEService *service);
 | 
			
		||||
  void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
 | 
			
		||||
 | 
			
		||||
  void on_write(const std::function<void(const std::vector<uint8_t> &)> &&func) { this->on_write_ = func; }
 | 
			
		||||
 | 
			
		||||
  void add_descriptor(std::shared_ptr<BLEDescriptor> descriptor);
 | 
			
		||||
  void remove_descriptor(std::shared_ptr<BLEDescriptor> descriptor);
 | 
			
		||||
  void add_descriptor(BLEDescriptor *descriptor);
 | 
			
		||||
  void remove_descriptor(BLEDescriptor *descriptor);
 | 
			
		||||
 | 
			
		||||
  std::shared_ptr<BLE2901> make_2901_descriptor(const std::string &value);
 | 
			
		||||
  std::shared_ptr<BLE2902> make_2902_descriptor();
 | 
			
		||||
 | 
			
		||||
  std::shared_ptr<BLEService> get_service() { return this->service_; }
 | 
			
		||||
  BLEService *get_service() { return this->service_; }
 | 
			
		||||
  ESPBTUUID get_uuid() { return this->uuid_; }
 | 
			
		||||
  std::vector<uint8_t> &get_value() { return this->value_; }
 | 
			
		||||
 | 
			
		||||
@@ -78,7 +71,7 @@ class BLECharacteristic : public std::enable_shared_from_this<BLECharacteristic>
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool write_event_{false};
 | 
			
		||||
  std::shared_ptr<BLEService> service_;
 | 
			
		||||
  BLEService *service_;
 | 
			
		||||
  ESPBTUUID uuid_;
 | 
			
		||||
  esp_gatt_char_prop_t properties_;
 | 
			
		||||
  uint16_t handle_{0xFFFF};
 | 
			
		||||
@@ -87,7 +80,7 @@ class BLECharacteristic : public std::enable_shared_from_this<BLECharacteristic>
 | 
			
		||||
  std::vector<uint8_t> value_;
 | 
			
		||||
  SemaphoreHandle_t set_value_lock_;
 | 
			
		||||
 | 
			
		||||
  std::vector<std::shared_ptr<BLEDescriptor>> descriptors_;
 | 
			
		||||
  std::vector<BLEDescriptor *> descriptors_;
 | 
			
		||||
 | 
			
		||||
  std::function<void(const std::vector<uint8_t> &)> on_write_;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,8 @@
 | 
			
		||||
#include "ble_descriptor.h"
 | 
			
		||||
#include "ble_characteristic.h"
 | 
			
		||||
#include "ble_service.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
@@ -20,23 +16,12 @@ BLEDescriptor::BLEDescriptor(ESPBTUUID uuid, uint16_t max_len) {
 | 
			
		||||
  this->uuid_ = uuid;
 | 
			
		||||
  this->value_.attr_len = 0;
 | 
			
		||||
  this->value_.attr_max_len = max_len;
 | 
			
		||||
 | 
			
		||||
  ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
 | 
			
		||||
  this->value_.attr_value = allocator.allocate(max_len);
 | 
			
		||||
  if (this->value_.attr_value == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to allocate %d bytes for value", max_len);
 | 
			
		||||
    this->state_ = FAILED;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->value_.attr_value = (uint8_t *) malloc(max_len);  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEDescriptor::~BLEDescriptor() {
 | 
			
		||||
  ExternalRAMAllocator<uint8_t> deallocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
 | 
			
		||||
  deallocator.deallocate(this->value_.attr_value, this->value_.attr_max_len);
 | 
			
		||||
  this->value_.attr_value = nullptr;
 | 
			
		||||
}
 | 
			
		||||
BLEDescriptor::~BLEDescriptor() { free(this->value_.attr_value); }  // NOLINT
 | 
			
		||||
 | 
			
		||||
void BLEDescriptor::do_create(std::shared_ptr<BLECharacteristic> characteristic) {
 | 
			
		||||
void BLEDescriptor::do_create(BLECharacteristic *characteristic) {
 | 
			
		||||
  this->characteristic_ = characteristic;
 | 
			
		||||
  esp_attr_control_t control;
 | 
			
		||||
  control.auto_rsp = ESP_GATT_AUTO_RSP;
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ class BLEDescriptor {
 | 
			
		||||
 public:
 | 
			
		||||
  BLEDescriptor(ESPBTUUID uuid, uint16_t max_len = 100);
 | 
			
		||||
  virtual ~BLEDescriptor();
 | 
			
		||||
  void do_create(std::shared_ptr<BLECharacteristic> characteristic);
 | 
			
		||||
  void do_create(BLECharacteristic *characteristic);
 | 
			
		||||
 | 
			
		||||
  void set_value(const std::string &value);
 | 
			
		||||
  void set_value(const uint8_t *data, size_t length);
 | 
			
		||||
@@ -29,7 +29,7 @@ class BLEDescriptor {
 | 
			
		||||
  bool is_failed() { return this->state_ == FAILED; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> characteristic_{nullptr};
 | 
			
		||||
  BLECharacteristic *characteristic_{nullptr};
 | 
			
		||||
  ESPBTUUID uuid_;
 | 
			
		||||
  uint16_t handle_{0xFFFF};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,18 @@
 | 
			
		||||
#include "ble_server.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/esp32_ble/ble.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/version.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <esp_bt.h>
 | 
			
		||||
#include <esp_bt_main.h>
 | 
			
		||||
#include <esp_gap_ble_api.h>
 | 
			
		||||
#include <freertos/FreeRTOSConfig.h>
 | 
			
		||||
#include <freertos/task.h>
 | 
			
		||||
#include <nvs_flash.h>
 | 
			
		||||
#include <freertos/FreeRTOSConfig.h>
 | 
			
		||||
#include <esp_bt_main.h>
 | 
			
		||||
#include <esp_bt.h>
 | 
			
		||||
#include <freertos/task.h>
 | 
			
		||||
#include <esp_gap_ble_api.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_ble_server {
 | 
			
		||||
@@ -58,8 +58,9 @@ void BLEServer::loop() {
 | 
			
		||||
          pair.second->do_create(this);
 | 
			
		||||
        }
 | 
			
		||||
        if (this->device_information_service_ == nullptr) {
 | 
			
		||||
          this->create_service(ESPBTUUID::from_uint16(DEVICE_INFORMATION_SERVICE_UUID));
 | 
			
		||||
          this->device_information_service_ =
 | 
			
		||||
              this->create_service(ESPBTUUID::from_uint16(DEVICE_INFORMATION_SERVICE_UUID));
 | 
			
		||||
              this->get_service(ESPBTUUID::from_uint16(DEVICE_INFORMATION_SERVICE_UUID));
 | 
			
		||||
          this->create_device_characteristics_();
 | 
			
		||||
        }
 | 
			
		||||
        this->state_ = STARTING_SERVICE;
 | 
			
		||||
@@ -93,58 +94,56 @@ void BLEServer::restart_advertising_() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BLEServer::create_device_characteristics_() {
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> model =
 | 
			
		||||
      this->device_information_service_->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ);
 | 
			
		||||
  if (this->model_.has_value()) {
 | 
			
		||||
    BLECharacteristic *model =
 | 
			
		||||
        this->device_information_service_->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ);
 | 
			
		||||
    model->set_value(this->model_.value());
 | 
			
		||||
  } else {
 | 
			
		||||
    BLECharacteristic *model =
 | 
			
		||||
        this->device_information_service_->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ);
 | 
			
		||||
    model->set_value(ESPHOME_BOARD);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> version =
 | 
			
		||||
  BLECharacteristic *version =
 | 
			
		||||
      this->device_information_service_->create_characteristic(VERSION_UUID, BLECharacteristic::PROPERTY_READ);
 | 
			
		||||
  version->set_value("ESPHome " ESPHOME_VERSION);
 | 
			
		||||
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> manufacturer =
 | 
			
		||||
  BLECharacteristic *manufacturer =
 | 
			
		||||
      this->device_information_service_->create_characteristic(MANUFACTURER_UUID, BLECharacteristic::PROPERTY_READ);
 | 
			
		||||
  manufacturer->set_value(this->manufacturer_);
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<BLEService> BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles,
 | 
			
		||||
                                                      uint8_t inst_id) {
 | 
			
		||||
  std::string uuid_str = uuid.to_string();
 | 
			
		||||
void BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles, uint8_t inst_id) {
 | 
			
		||||
  ESP_LOGV(TAG, "Creating BLE service - %s", uuid.to_string().c_str());
 | 
			
		||||
  // If the service already exists, do nothing
 | 
			
		||||
  std::shared_ptr<BLEService> service = this->get_service(uuid);
 | 
			
		||||
  BLEService *service = this->get_service(uuid);
 | 
			
		||||
  if (service != nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "BLE service %s already exists", uuid_str.c_str());
 | 
			
		||||
    return service;
 | 
			
		||||
    ESP_LOGW(TAG, "BLE service %s already exists", uuid.to_string().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "Creating BLE service - %s", uuid_str.c_str());
 | 
			
		||||
  service = std::make_shared<BLEService>(uuid, num_handles, inst_id, advertise);
 | 
			
		||||
  this->services_.emplace(uuid_str, service);
 | 
			
		||||
  service = new BLEService(uuid, num_handles, inst_id, advertise);  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  this->services_.emplace(uuid.to_string(), service);
 | 
			
		||||
  service->do_create(this);
 | 
			
		||||
  return service;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEServer::remove_service(ESPBTUUID uuid) {
 | 
			
		||||
  std::string uuid_str = uuid.to_string();
 | 
			
		||||
  std::shared_ptr<BLEService> service = this->get_service(uuid);
 | 
			
		||||
  ESP_LOGV(TAG, "Removing BLE service - %s", uuid.to_string().c_str());
 | 
			
		||||
  BLEService *service = this->get_service(uuid);
 | 
			
		||||
  if (service == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "BLE service %s not found", uuid_str.c_str());
 | 
			
		||||
    ESP_LOGW(TAG, "BLE service %s not found", uuid.to_string().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "Removing BLE service - %s", uuid_str.c_str());
 | 
			
		||||
  service->do_delete();
 | 
			
		||||
  this->services_.erase(uuid_str);
 | 
			
		||||
  delete service;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  this->services_.erase(uuid.to_string());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<BLEService> BLEServer::get_service(ESPBTUUID uuid) {
 | 
			
		||||
  std::string uuid_str = uuid.to_string();
 | 
			
		||||
  std::shared_ptr<BLEService> service = nullptr;
 | 
			
		||||
  if (this->services_.count(uuid_str) > 0) {
 | 
			
		||||
    service = this->services_.at(uuid_str);
 | 
			
		||||
BLEService *BLEServer::get_service(ESPBTUUID uuid) {
 | 
			
		||||
  BLEService *service = nullptr;
 | 
			
		||||
  if (this->services_.count(uuid.to_string()) > 0) {
 | 
			
		||||
    service = this->services_.at(uuid.to_string());
 | 
			
		||||
  }
 | 
			
		||||
  return service;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "ble_characteristic.h"
 | 
			
		||||
#include "ble_service.h"
 | 
			
		||||
#include "ble_characteristic.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/esp32_ble/ble.h"
 | 
			
		||||
#include "esphome/components/esp32_ble/ble_advertising.h"
 | 
			
		||||
@@ -12,8 +12,8 @@
 | 
			
		||||
#include "esphome/core/preferences.h"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
@@ -51,10 +51,9 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv
 | 
			
		||||
    this->restart_advertising_();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::shared_ptr<BLEService> create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15,
 | 
			
		||||
                                             uint8_t inst_id = 0);
 | 
			
		||||
  void create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15, uint8_t inst_id = 0);
 | 
			
		||||
  void remove_service(ESPBTUUID uuid);
 | 
			
		||||
  std::shared_ptr<BLEService> get_service(ESPBTUUID uuid);
 | 
			
		||||
  BLEService *get_service(ESPBTUUID uuid);
 | 
			
		||||
 | 
			
		||||
  esp_gatt_if_t get_gatts_if() { return this->gatts_if_; }
 | 
			
		||||
  uint32_t get_connected_client_count() { return this->connected_clients_; }
 | 
			
		||||
@@ -82,8 +81,8 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv
 | 
			
		||||
 | 
			
		||||
  uint32_t connected_clients_{0};
 | 
			
		||||
  std::unordered_map<uint16_t, void *> clients_;
 | 
			
		||||
  std::unordered_map<std::string, std::shared_ptr<BLEService>> services_;
 | 
			
		||||
  std::shared_ptr<BLEService> device_information_service_;
 | 
			
		||||
  std::unordered_map<std::string, BLEService *> services_;
 | 
			
		||||
  BLEService *device_information_service_;
 | 
			
		||||
 | 
			
		||||
  std::vector<BLEServiceComponent *> service_components_;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,33 +12,31 @@ static const char *const TAG = "esp32_ble_server.service";
 | 
			
		||||
BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id, bool advertise)
 | 
			
		||||
    : uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id), advertise_(advertise) {}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<BLECharacteristic> BLEService::get_characteristic(ESPBTUUID uuid) {
 | 
			
		||||
  for (auto chr : this->characteristics_) {
 | 
			
		||||
BLEService::~BLEService() {
 | 
			
		||||
  for (auto &chr : this->characteristics_)
 | 
			
		||||
    delete chr;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLECharacteristic *BLEService::get_characteristic(ESPBTUUID uuid) {
 | 
			
		||||
  for (auto *chr : this->characteristics_) {
 | 
			
		||||
    if (chr->get_uuid() == uuid)
 | 
			
		||||
      return chr;
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<BLECharacteristic> BLEService::get_characteristic(uint16_t uuid) {
 | 
			
		||||
BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) {
 | 
			
		||||
  return this->get_characteristic(ESPBTUUID::from_uint16(uuid));
 | 
			
		||||
}
 | 
			
		||||
std::shared_ptr<BLECharacteristic> BLEService::create_characteristic(uint16_t uuid, esp_gatt_char_prop_t properties) {
 | 
			
		||||
BLECharacteristic *BLEService::create_characteristic(uint16_t uuid, esp_gatt_char_prop_t properties) {
 | 
			
		||||
  return create_characteristic(ESPBTUUID::from_uint16(uuid), properties);
 | 
			
		||||
}
 | 
			
		||||
std::shared_ptr<BLECharacteristic> BLEService::create_characteristic(const std::string &uuid,
 | 
			
		||||
                                                                     esp_gatt_char_prop_t properties) {
 | 
			
		||||
BLECharacteristic *BLEService::create_characteristic(const std::string &uuid, esp_gatt_char_prop_t properties) {
 | 
			
		||||
  return create_characteristic(ESPBTUUID::from_raw(uuid), properties);
 | 
			
		||||
}
 | 
			
		||||
std::shared_ptr<BLECharacteristic> BLEService::create_characteristic(const char *uuid, size_t len,
 | 
			
		||||
                                                                     esp_gatt_char_prop_t properties) {
 | 
			
		||||
  return create_characteristic(ESPBTUUID::from_raw(uuid, len), properties);
 | 
			
		||||
}
 | 
			
		||||
std::shared_ptr<BLECharacteristic> BLEService::create_characteristic(ESPBTUUID uuid, esp_gatt_char_prop_t properties) {
 | 
			
		||||
  auto characteristic = std::make_shared<BLECharacteristic>(uuid, properties);
 | 
			
		||||
  if (characteristic == nullptr) {
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
BLECharacteristic *BLEService::create_characteristic(ESPBTUUID uuid, esp_gatt_char_prop_t properties) {
 | 
			
		||||
  // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
 | 
			
		||||
  BLECharacteristic *characteristic = new BLECharacteristic(uuid, properties);
 | 
			
		||||
  this->characteristics_.push_back(characteristic);
 | 
			
		||||
  return characteristic;
 | 
			
		||||
}
 | 
			
		||||
@@ -82,9 +80,9 @@ bool BLEService::do_create_characteristics_() {
 | 
			
		||||
  if (this->last_created_characteristic_ != nullptr && !this->last_created_characteristic_->is_created())
 | 
			
		||||
    return true;  // Signifies that the previous characteristic is still being created.
 | 
			
		||||
 | 
			
		||||
  auto characteristic = this->characteristics_[this->created_characteristic_count_++];
 | 
			
		||||
  auto *characteristic = this->characteristics_[this->created_characteristic_count_++];
 | 
			
		||||
  this->last_created_characteristic_ = characteristic;
 | 
			
		||||
  characteristic->do_create(shared_from_this());
 | 
			
		||||
  characteristic->do_create(this);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -126,7 +124,7 @@ bool BLEService::is_failed() {
 | 
			
		||||
  if (this->init_state_ == FAILED)
 | 
			
		||||
    return true;
 | 
			
		||||
  bool failed = false;
 | 
			
		||||
  for (auto characteristic : this->characteristics_)
 | 
			
		||||
  for (auto *characteristic : this->characteristics_)
 | 
			
		||||
    failed |= characteristic->is_failed();
 | 
			
		||||
 | 
			
		||||
  if (failed)
 | 
			
		||||
@@ -168,7 +166,7 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (auto characteristic : this->characteristics_) {
 | 
			
		||||
  for (auto *characteristic : this->characteristics_) {
 | 
			
		||||
    characteristic->gatts_event_handler(event, gatts_if, param);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@
 | 
			
		||||
#include "ble_characteristic.h"
 | 
			
		||||
#include "esphome/components/esp32_ble/ble_uuid.h"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
@@ -21,21 +20,19 @@ class BLEServer;
 | 
			
		||||
 | 
			
		||||
using namespace esp32_ble;
 | 
			
		||||
 | 
			
		||||
class BLEService : public std::enable_shared_from_this<BLEService> {
 | 
			
		||||
class BLEService {
 | 
			
		||||
 public:
 | 
			
		||||
  BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id, bool advertise);
 | 
			
		||||
  ~BLEService();
 | 
			
		||||
  BLECharacteristic *get_characteristic(ESPBTUUID uuid);
 | 
			
		||||
  BLECharacteristic *get_characteristic(uint16_t uuid);
 | 
			
		||||
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> get_characteristic(ESPBTUUID uuid);
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> get_characteristic(uint16_t uuid);
 | 
			
		||||
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> create_characteristic(const char *uuid, size_t len,
 | 
			
		||||
                                                           esp_gatt_char_prop_t properties);
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> create_characteristic(const std::string &uuid, esp_gatt_char_prop_t properties);
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> create_characteristic(uint16_t uuid, esp_gatt_char_prop_t properties);
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> create_characteristic(ESPBTUUID uuid, esp_gatt_char_prop_t properties);
 | 
			
		||||
  BLECharacteristic *create_characteristic(const std::string &uuid, esp_gatt_char_prop_t properties);
 | 
			
		||||
  BLECharacteristic *create_characteristic(uint16_t uuid, esp_gatt_char_prop_t properties);
 | 
			
		||||
  BLECharacteristic *create_characteristic(ESPBTUUID uuid, esp_gatt_char_prop_t properties);
 | 
			
		||||
 | 
			
		||||
  ESPBTUUID get_uuid() { return this->uuid_; }
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> get_last_created_characteristic() { return this->last_created_characteristic_; }
 | 
			
		||||
  BLECharacteristic *get_last_created_characteristic() { return this->last_created_characteristic_; }
 | 
			
		||||
  uint16_t get_handle() { return this->handle_; }
 | 
			
		||||
 | 
			
		||||
  BLEServer *get_server() { return this->server_; }
 | 
			
		||||
@@ -55,8 +52,8 @@ class BLEService : public std::enable_shared_from_this<BLEService> {
 | 
			
		||||
  bool is_deleted() { return this->init_state_ == DELETED; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::vector<std::shared_ptr<BLECharacteristic>> characteristics_;
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> last_created_characteristic_{nullptr};
 | 
			
		||||
  std::vector<BLECharacteristic *> characteristics_;
 | 
			
		||||
  BLECharacteristic *last_created_characteristic_{nullptr};
 | 
			
		||||
  uint32_t created_characteristic_count_{0};
 | 
			
		||||
  BLEServer *server_;
 | 
			
		||||
  ESPBTUUID uuid_;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,12 @@
 | 
			
		||||
#include "esp32_improv_component.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/esp32_ble/ble.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_server/ble_2902.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
static constexpr size_t MAX_UUID_LENGTH = 37;
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_improv {
 | 
			
		||||
 | 
			
		||||
@@ -29,72 +28,35 @@ void ESP32ImprovComponent::setup() {
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ESP32ImprovComponent::setup_characteristics() {
 | 
			
		||||
  this->status_ =
 | 
			
		||||
      this->service_->create_characteristic(improv::STATUS_UUID, strnlen(improv::STATUS_UUID, MAX_UUID_LENGTH),
 | 
			
		||||
                                            BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
 | 
			
		||||
  if (this->status_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to create status characteristic");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
void ESP32ImprovComponent::setup_characteristics() {
 | 
			
		||||
  this->status_ = this->service_->create_characteristic(
 | 
			
		||||
      improv::STATUS_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
 | 
			
		||||
  BLEDescriptor *status_descriptor = new BLE2902();
 | 
			
		||||
  this->status_->add_descriptor(status_descriptor);
 | 
			
		||||
 | 
			
		||||
  if (!this->status_->make_2902_descriptor()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to create status descriptor");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  this->error_ = this->service_->create_characteristic(
 | 
			
		||||
      improv::ERROR_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
 | 
			
		||||
  BLEDescriptor *error_descriptor = new BLE2902();
 | 
			
		||||
  this->error_->add_descriptor(error_descriptor);
 | 
			
		||||
 | 
			
		||||
  this->error_ =
 | 
			
		||||
      this->service_->create_characteristic(improv::ERROR_UUID, strnlen(improv::ERROR_UUID, MAX_UUID_LENGTH),
 | 
			
		||||
                                            BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
 | 
			
		||||
  if (this->error_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to create error characteristic");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!this->error_->make_2902_descriptor()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to create error descriptor");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->rpc_ = this->service_->create_characteristic(
 | 
			
		||||
      improv::RPC_COMMAND_UUID, strnlen(improv::RPC_COMMAND_UUID, MAX_UUID_LENGTH), BLECharacteristic::PROPERTY_WRITE);
 | 
			
		||||
  if (this->rpc_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to create rpc characteristic");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE);
 | 
			
		||||
  this->rpc_->on_write([this](const std::vector<uint8_t> &data) {
 | 
			
		||||
    if (!data.empty()) {
 | 
			
		||||
      this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end());
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  if (this->rpc_->make_2902_descriptor() == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to create rpc descriptor");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  BLEDescriptor *rpc_descriptor = new BLE2902();
 | 
			
		||||
  this->rpc_->add_descriptor(rpc_descriptor);
 | 
			
		||||
 | 
			
		||||
  this->rpc_response_ =
 | 
			
		||||
      this->service_->create_characteristic(improv::RPC_RESULT_UUID, strnlen(improv::RPC_RESULT_UUID, MAX_UUID_LENGTH),
 | 
			
		||||
                                            BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
 | 
			
		||||
  if (this->rpc_response_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to create rpc response characteristic");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->rpc_response_->make_2902_descriptor()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to create rpc response descriptor");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->capabilities_ = this->service_->create_characteristic(
 | 
			
		||||
      improv::CAPABILITIES_UUID, strnlen(improv::CAPABILITIES_UUID, MAX_UUID_LENGTH), BLECharacteristic::PROPERTY_READ);
 | 
			
		||||
  if (this->capabilities_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to create capabilities characteristic");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->capabilities_->make_2902_descriptor()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to create capabilities descriptor");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  this->rpc_response_ = this->service_->create_characteristic(
 | 
			
		||||
      improv::RPC_RESULT_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
 | 
			
		||||
  BLEDescriptor *rpc_response_descriptor = new BLE2902();
 | 
			
		||||
  this->rpc_response_->add_descriptor(rpc_response_descriptor);
 | 
			
		||||
 | 
			
		||||
  this->capabilities_ =
 | 
			
		||||
      this->service_->create_characteristic(improv::CAPABILITIES_UUID, BLECharacteristic::PROPERTY_READ);
 | 
			
		||||
  BLEDescriptor *capabilities_descriptor = new BLE2902();
 | 
			
		||||
  this->capabilities_->add_descriptor(capabilities_descriptor);
 | 
			
		||||
  uint8_t capabilities = 0x00;
 | 
			
		||||
#ifdef USE_OUTPUT
 | 
			
		||||
  if (this->status_indicator_ != nullptr)
 | 
			
		||||
@@ -102,7 +64,6 @@ bool ESP32ImprovComponent::setup_characteristics() {
 | 
			
		||||
#endif
 | 
			
		||||
  this->capabilities_->set_value(capabilities);
 | 
			
		||||
  this->setup_complete_ = true;
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ESP32ImprovComponent::loop() {
 | 
			
		||||
@@ -114,11 +75,9 @@ void ESP32ImprovComponent::loop() {
 | 
			
		||||
  if (this->service_ == nullptr) {
 | 
			
		||||
    // Setup the service
 | 
			
		||||
    ESP_LOGD(TAG, "Creating Improv service");
 | 
			
		||||
    this->service_ = global_ble_server->create_service(ESPBTUUID::from_raw(improv::SERVICE_UUID), true);
 | 
			
		||||
    if (!this->setup_characteristics()) {
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    global_ble_server->create_service(ESPBTUUID::from_raw(improv::SERVICE_UUID), true);
 | 
			
		||||
    this->service_ = global_ble_server->get_service(ESPBTUUID::from_raw(improv::SERVICE_UUID));
 | 
			
		||||
    this->setup_characteristics();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!this->incoming_data_.empty())
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent {
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  bool setup_characteristics();
 | 
			
		||||
  void setup_characteristics();
 | 
			
		||||
  void on_client_disconnect() override;
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
@@ -68,12 +68,12 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent {
 | 
			
		||||
  std::vector<uint8_t> incoming_data_;
 | 
			
		||||
  wifi::WiFiAP connecting_sta_;
 | 
			
		||||
 | 
			
		||||
  std::shared_ptr<BLEService> service_{nullptr};
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> status_;
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> error_;
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> rpc_;
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> rpc_response_;
 | 
			
		||||
  std::shared_ptr<BLECharacteristic> capabilities_;
 | 
			
		||||
  BLEService *service_ = nullptr;
 | 
			
		||||
  BLECharacteristic *status_;
 | 
			
		||||
  BLECharacteristic *error_;
 | 
			
		||||
  BLECharacteristic *rpc_;
 | 
			
		||||
  BLECharacteristic *rpc_response_;
 | 
			
		||||
  BLECharacteristic *capabilities_;
 | 
			
		||||
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
  binary_sensor::BinarySensor *authorizer_{nullptr};
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ from esphome.components.esp32.const import (
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DOMAIN,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_VALUE,
 | 
			
		||||
    CONF_MANUAL_IP,
 | 
			
		||||
    CONF_STATIC_IP,
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
@@ -26,6 +27,8 @@ from esphome.const import (
 | 
			
		||||
    CONF_INTERRUPT_PIN,
 | 
			
		||||
    CONF_RESET_PIN,
 | 
			
		||||
    CONF_SPI,
 | 
			
		||||
    CONF_PAGE_ID,
 | 
			
		||||
    CONF_ADDRESS,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.components.network import IPAddress
 | 
			
		||||
@@ -36,11 +39,13 @@ DEPENDENCIES = ["esp32"]
 | 
			
		||||
AUTO_LOAD = ["network"]
 | 
			
		||||
 | 
			
		||||
ethernet_ns = cg.esphome_ns.namespace("ethernet")
 | 
			
		||||
PHYRegister = ethernet_ns.struct("PHYRegister")
 | 
			
		||||
CONF_PHY_ADDR = "phy_addr"
 | 
			
		||||
CONF_MDC_PIN = "mdc_pin"
 | 
			
		||||
CONF_MDIO_PIN = "mdio_pin"
 | 
			
		||||
CONF_CLK_MODE = "clk_mode"
 | 
			
		||||
CONF_POWER_PIN = "power_pin"
 | 
			
		||||
CONF_PHY_REGISTERS = "phy_registers"
 | 
			
		||||
 | 
			
		||||
CONF_CLOCK_SPEED = "clock_speed"
 | 
			
		||||
 | 
			
		||||
@@ -117,6 +122,13 @@ BASE_SCHEMA = cv.Schema(
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
PHY_REGISTER_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ADDRESS): cv.hex_int,
 | 
			
		||||
        cv.Required(CONF_VALUE): cv.hex_int,
 | 
			
		||||
        cv.Optional(CONF_PAGE_ID): cv.hex_int,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
RMII_SCHEMA = BASE_SCHEMA.extend(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
@@ -127,6 +139,7 @@ RMII_SCHEMA = BASE_SCHEMA.extend(
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31),
 | 
			
		||||
            cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number,
 | 
			
		||||
            cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
@@ -198,6 +211,15 @@ def manual_ip(config):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def phy_register(address: int, value: int, page: int):
 | 
			
		||||
    return cg.StructInitializer(
 | 
			
		||||
        PHYRegister,
 | 
			
		||||
        ("address", address),
 | 
			
		||||
        ("value", value),
 | 
			
		||||
        ("page", page),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(60.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
@@ -225,6 +247,13 @@ async def to_code(config):
 | 
			
		||||
        cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]]))
 | 
			
		||||
        if CONF_POWER_PIN in config:
 | 
			
		||||
            cg.add(var.set_power_pin(config[CONF_POWER_PIN]))
 | 
			
		||||
        for register_value in config.get(CONF_PHY_REGISTERS, []):
 | 
			
		||||
            reg = phy_register(
 | 
			
		||||
                register_value.get(CONF_ADDRESS),
 | 
			
		||||
                register_value.get(CONF_VALUE),
 | 
			
		||||
                register_value.get(CONF_PAGE_ID),
 | 
			
		||||
            )
 | 
			
		||||
            cg.add(var.add_phy_register(reg))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]]))
 | 
			
		||||
    cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,13 @@ EthernetComponent *global_eth_component;  // NOLINT(cppcoreguidelines-avoid-non-
 | 
			
		||||
    return; \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#define ESPHL_ERROR_CHECK_RET(err, message, ret) \
 | 
			
		||||
  if ((err) != ESP_OK) { \
 | 
			
		||||
    ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \
 | 
			
		||||
    this->mark_failed(); \
 | 
			
		||||
    return ret; \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
EthernetComponent::EthernetComponent() { global_eth_component = this; }
 | 
			
		||||
 | 
			
		||||
void EthernetComponent::setup() {
 | 
			
		||||
@@ -98,11 +105,15 @@ void EthernetComponent::setup() {
 | 
			
		||||
      .post_cb = nullptr,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
#if USE_ESP_IDF && (ESP_IDF_VERSION_MAJOR >= 5)
 | 
			
		||||
  eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg);
 | 
			
		||||
#else
 | 
			
		||||
  spi_device_handle_t spi_handle = nullptr;
 | 
			
		||||
  err = spi_bus_add_device(host, &devcfg, &spi_handle);
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "SPI bus add device error");
 | 
			
		||||
 | 
			
		||||
  eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle);
 | 
			
		||||
#endif
 | 
			
		||||
  w5500_config.int_gpio_num = this->interrupt_pin_;
 | 
			
		||||
  phy_config.phy_addr = this->phy_addr_spi_;
 | 
			
		||||
  phy_config.reset_gpio_num = this->reset_pin_;
 | 
			
		||||
@@ -184,9 +195,9 @@ void EthernetComponent::setup() {
 | 
			
		||||
    // KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide.
 | 
			
		||||
    this->ksz8081_set_clock_reference_(mac);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->type_ == ETHERNET_TYPE_RTL8201 && this->clk_mode_ == EMAC_CLK_EXT_IN) {
 | 
			
		||||
    // Change in default behavior of RTL8201FI may require register setting to enable external clock
 | 
			
		||||
    this->rtl8201_set_rmii_mode_(mac);
 | 
			
		||||
 | 
			
		||||
  for (const auto &phy_register : this->phy_registers_) {
 | 
			
		||||
    this->write_phy_register_(mac, phy_register);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@@ -406,7 +417,7 @@ void EthernetComponent::start_connect_() {
 | 
			
		||||
  global_eth_component->ipv6_count_ = 0;
 | 
			
		||||
#endif /* USE_NETWORK_IPV6 */
 | 
			
		||||
  this->connect_begin_ = millis();
 | 
			
		||||
  this->status_set_warning();
 | 
			
		||||
  this->status_set_warning("waiting for IP configuration");
 | 
			
		||||
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
  err = esp_netif_set_hostname(this->eth_netif_, App.get_name().c_str());
 | 
			
		||||
@@ -494,22 +505,9 @@ void EthernetComponent::dump_connect_params_() {
 | 
			
		||||
  }
 | 
			
		||||
#endif /* USE_NETWORK_IPV6 */
 | 
			
		||||
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
 | 
			
		||||
  uint8_t mac[6];
 | 
			
		||||
  err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_MAC_ADDR, &mac);
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "ETH_CMD_G_MAC error");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  MAC Address: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
 | 
			
		||||
 | 
			
		||||
  eth_duplex_t duplex_mode;
 | 
			
		||||
  err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex_mode);
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "ETH_CMD_G_DUPLEX_MODE error");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Is Full Duplex: %s", YESNO(duplex_mode == ETH_DUPLEX_FULL));
 | 
			
		||||
 | 
			
		||||
  eth_speed_t speed;
 | 
			
		||||
  err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_SPEED, &speed);
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "ETH_CMD_G_SPEED error");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Link Speed: %u", speed == ETH_SPEED_100M ? 100 : 10);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  MAC Address: %s", this->get_eth_mac_address_pretty().c_str());
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Is Full Duplex: %s", YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Link Speed: %u", this->get_link_speed() == ETH_SPEED_100M ? 100 : 10);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ETHERNET_SPI
 | 
			
		||||
@@ -529,6 +527,7 @@ void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_
 | 
			
		||||
  this->clk_mode_ = clk_mode;
 | 
			
		||||
  this->clk_gpio_ = clk_gpio;
 | 
			
		||||
}
 | 
			
		||||
void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); }
 | 
			
		||||
#endif
 | 
			
		||||
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
 | 
			
		||||
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
 | 
			
		||||
@@ -542,6 +541,34 @@ std::string EthernetComponent::get_use_address() const {
 | 
			
		||||
 | 
			
		||||
void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; }
 | 
			
		||||
 | 
			
		||||
void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) {
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
  err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_MAC_ADDR, mac);
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "ETH_CMD_G_MAC error");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string EthernetComponent::get_eth_mac_address_pretty() {
 | 
			
		||||
  uint8_t mac[6];
 | 
			
		||||
  get_mac_address_raw(mac);
 | 
			
		||||
  return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
eth_duplex_t EthernetComponent::get_duplex_mode() {
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
  eth_duplex_t duplex_mode;
 | 
			
		||||
  err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex_mode);
 | 
			
		||||
  ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_DUPLEX_MODE error", ETH_DUPLEX_HALF);
 | 
			
		||||
  return duplex_mode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
eth_speed_t EthernetComponent::get_link_speed() {
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
  eth_speed_t speed;
 | 
			
		||||
  err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_SPEED, &speed);
 | 
			
		||||
  ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_SPEED error", ETH_SPEED_10M);
 | 
			
		||||
  return speed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool EthernetComponent::powerdown() {
 | 
			
		||||
  ESP_LOGI(TAG, "Powering down ethernet PHY");
 | 
			
		||||
  if (this->phy_ == nullptr) {
 | 
			
		||||
@@ -572,11 +599,11 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
 | 
			
		||||
  /*
 | 
			
		||||
   * Bit 7 is `RMII Reference Clock Select`. Default is `0`.
 | 
			
		||||
   * KSZ8081RNA:
 | 
			
		||||
   *   0 - clock input to XI (Pin 8) is 25 MHz for RMII – 25 MHz clock mode.
 | 
			
		||||
   *   1 - clock input to XI (Pin 8) is 50 MHz for RMII – 50 MHz clock mode.
 | 
			
		||||
   *   0 - clock input to XI (Pin 8) is 25 MHz for RMII - 25 MHz clock mode.
 | 
			
		||||
   *   1 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode.
 | 
			
		||||
   * KSZ8081RND:
 | 
			
		||||
   *   0 - clock input to XI (Pin 8) is 50 MHz for RMII – 50 MHz clock mode.
 | 
			
		||||
   *   1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII – 25 MHz clock mode.
 | 
			
		||||
   *   0 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode.
 | 
			
		||||
   *   1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII - 25 MHz clock mode.
 | 
			
		||||
   */
 | 
			
		||||
  if ((phy_control_2 & (1 << 7)) != (1 << 7)) {
 | 
			
		||||
    phy_control_2 |= 1 << 7;
 | 
			
		||||
@@ -587,44 +614,27 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
 | 
			
		||||
    ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
constexpr uint8_t RTL8201_RMSR_REG_ADDR = 0x10;
 | 
			
		||||
void EthernetComponent::rtl8201_set_rmii_mode_(esp_eth_mac_t *mac) {
 | 
			
		||||
 | 
			
		||||
void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data) {
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
  uint32_t phy_rmii_mode;
 | 
			
		||||
  err = mac->write_phy_reg(mac, this->phy_addr_, 0x1f, 0x07);
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "Setting Page 7 failed");
 | 
			
		||||
  constexpr uint8_t eth_phy_psr_reg_addr = 0x1F;
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * RTL8201 RMII Mode Setting Register (RMSR)
 | 
			
		||||
   * Page 7 Register 16
 | 
			
		||||
   *
 | 
			
		||||
   * bit 0      Reserved            0
 | 
			
		||||
   * bit 1      Rg_rmii_rxdsel      1 (default)
 | 
			
		||||
   * bit 2      Rg_rmii_rxdv_sel:   0 (default)
 | 
			
		||||
   * bit 3      RMII Mode:          1 (RMII Mode)
 | 
			
		||||
   * bit 4~7    Rg_rmii_rx_offset:  1111 (default)
 | 
			
		||||
   * bit 8~11   Rg_rmii_tx_offset:  1111 (default)
 | 
			
		||||
   * bit 12     Rg_rmii_clkdir:     1 (Input)
 | 
			
		||||
   * bit 13~15  Reserved            000
 | 
			
		||||
   *
 | 
			
		||||
   * Binary: 0001 1111 1111 1010
 | 
			
		||||
   * Hex: 0x1FFA
 | 
			
		||||
   *
 | 
			
		||||
   */
 | 
			
		||||
  if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
 | 
			
		||||
    ESP_LOGD(TAG, "Select PHY Register Page: 0x%02" PRIX32, register_data.page);
 | 
			
		||||
    err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, register_data.page);
 | 
			
		||||
    ESPHL_ERROR_CHECK(err, "Select PHY Register page failed");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode));
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed");
 | 
			
		||||
  ESP_LOGV(TAG, "Hardware default RTL8201 RMII Mode Register is: 0x%04X", phy_rmii_mode);
 | 
			
		||||
  ESP_LOGD(TAG, "Writing to PHY Register Address: 0x%02" PRIX32, register_data.address);
 | 
			
		||||
  ESP_LOGD(TAG, "Writing to PHY Register Value: 0x%04" PRIX32, register_data.value);
 | 
			
		||||
  err = mac->write_phy_reg(mac, this->phy_addr_, register_data.address, register_data.value);
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "Writing PHY Register failed");
 | 
			
		||||
 | 
			
		||||
  err = mac->write_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, 0x1FFA);
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "Setting Register 16 RMII Mode Setting failed");
 | 
			
		||||
 | 
			
		||||
  err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode));
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed");
 | 
			
		||||
  ESP_LOGV(TAG, "Setting RTL8201 RMII Mode Register to: 0x%04X", phy_rmii_mode);
 | 
			
		||||
 | 
			
		||||
  err = mac->write_phy_reg(mac, this->phy_addr_, 0x1f, 0x0);
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "Setting Page 0 failed");
 | 
			
		||||
  if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
 | 
			
		||||
    ESP_LOGD(TAG, "Select PHY Register Page 0x%02" PRIX32, 0x0);
 | 
			
		||||
    err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, 0x0);
 | 
			
		||||
    ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@
 | 
			
		||||
#include "esp_eth.h"
 | 
			
		||||
#include "esp_eth_mac.h"
 | 
			
		||||
#include "esp_netif.h"
 | 
			
		||||
#include "esp_mac.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ethernet {
 | 
			
		||||
@@ -34,6 +35,12 @@ struct ManualIP {
 | 
			
		||||
  network::IPAddress dns2;  ///< The second DNS server. 0.0.0.0 for default.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct PHYRegister {
 | 
			
		||||
  uint32_t address;
 | 
			
		||||
  uint32_t value;
 | 
			
		||||
  uint32_t page;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class EthernetComponentState {
 | 
			
		||||
  STOPPED,
 | 
			
		||||
  CONNECTING,
 | 
			
		||||
@@ -65,6 +72,7 @@ class EthernetComponent : public Component {
 | 
			
		||||
  void set_mdc_pin(uint8_t mdc_pin);
 | 
			
		||||
  void set_mdio_pin(uint8_t mdio_pin);
 | 
			
		||||
  void set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio);
 | 
			
		||||
  void add_phy_register(PHYRegister register_value);
 | 
			
		||||
#endif
 | 
			
		||||
  void set_type(EthernetType type);
 | 
			
		||||
  void set_manual_ip(const ManualIP &manual_ip);
 | 
			
		||||
@@ -73,6 +81,10 @@ class EthernetComponent : public Component {
 | 
			
		||||
  network::IPAddress get_dns_address(uint8_t num);
 | 
			
		||||
  std::string get_use_address() const;
 | 
			
		||||
  void set_use_address(const std::string &use_address);
 | 
			
		||||
  void get_eth_mac_address_raw(uint8_t *mac);
 | 
			
		||||
  std::string get_eth_mac_address_pretty();
 | 
			
		||||
  eth_duplex_t get_duplex_mode();
 | 
			
		||||
  eth_speed_t get_link_speed();
 | 
			
		||||
  bool powerdown();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
@@ -86,8 +98,8 @@ class EthernetComponent : public Component {
 | 
			
		||||
  void dump_connect_params_();
 | 
			
		||||
  /// @brief Set `RMII Reference Clock Select` bit for KSZ8081.
 | 
			
		||||
  void ksz8081_set_clock_reference_(esp_eth_mac_t *mac);
 | 
			
		||||
  /// @brief Set `RMII Mode Setting Register` for RTL8201.
 | 
			
		||||
  void rtl8201_set_rmii_mode_(esp_eth_mac_t *mac);
 | 
			
		||||
  /// @brief Set arbitratry PHY registers from config.
 | 
			
		||||
  void write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data);
 | 
			
		||||
 | 
			
		||||
  std::string use_address_;
 | 
			
		||||
#ifdef USE_ETHERNET_SPI
 | 
			
		||||
@@ -106,6 +118,7 @@ class EthernetComponent : public Component {
 | 
			
		||||
  uint8_t mdio_pin_{18};
 | 
			
		||||
  emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN};
 | 
			
		||||
  emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO};
 | 
			
		||||
  std::vector<PHYRegister> phy_registers_{};
 | 
			
		||||
#endif
 | 
			
		||||
  EthernetType type_{ETHERNET_TYPE_UNKNOWN};
 | 
			
		||||
  optional<ManualIP> manual_ip_{};
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ static const char *const TAG = "ethernet_info";
 | 
			
		||||
 | 
			
		||||
void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IPAddress", this); }
 | 
			
		||||
void DNSAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo DNS Address", this); }
 | 
			
		||||
void MACAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo MAC Address", this); }
 | 
			
		||||
 | 
			
		||||
}  // namespace ethernet_info
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -59,6 +59,13 @@ class DNSAddressEthernetInfo : public PollingComponent, public text_sensor::Text
 | 
			
		||||
  std::string last_results_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MACAddressEthernetInfo : public Component, public text_sensor::TextSensor {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override { this->publish_state(ethernet::global_eth_component->get_eth_mac_address_pretty()); }
 | 
			
		||||
  std::string unique_id() override { return get_mac_address() + "-ethernetinfo-mac"; }
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ethernet_info
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ from esphome.components import text_sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_IP_ADDRESS,
 | 
			
		||||
    CONF_DNS_ADDRESS,
 | 
			
		||||
    CONF_MAC_ADDRESS,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -19,6 +20,10 @@ DNSAddressEthernetInfo = ethernet_info_ns.class_(
 | 
			
		||||
    "DNSAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MACAddressEthernetInfo = ethernet_info_ns.class_(
 | 
			
		||||
    "MACAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema(
 | 
			
		||||
@@ -36,6 +41,9 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
        cv.Optional(CONF_DNS_ADDRESS): text_sensor.text_sensor_schema(
 | 
			
		||||
            DNSAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
 | 
			
		||||
        ).extend(cv.polling_component_schema("1s")),
 | 
			
		||||
        cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema(
 | 
			
		||||
            MACAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -51,3 +59,6 @@ async def to_code(config):
 | 
			
		||||
    if conf := config.get(CONF_DNS_ADDRESS):
 | 
			
		||||
        dns_info = await text_sensor.new_text_sensor(config[CONF_DNS_ADDRESS])
 | 
			
		||||
        await cg.register_component(dns_info, config[CONF_DNS_ADDRESS])
 | 
			
		||||
    if conf := config.get(CONF_MAC_ADDRESS):
 | 
			
		||||
        mac_info = await text_sensor.new_text_sensor(config[CONF_MAC_ADDRESS])
 | 
			
		||||
        await cg.register_component(mac_info, config[CONF_MAC_ADDRESS])
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,11 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.automation import maybe_simple_id
 | 
			
		||||
from esphome.components import mqtt
 | 
			
		||||
from esphome.components import mqtt, web_server
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_WEB_SERVER_ID,
 | 
			
		||||
    CONF_OSCILLATING,
 | 
			
		||||
    CONF_OSCILLATION_COMMAND_TOPIC,
 | 
			
		||||
    CONF_OSCILLATION_STATE_TOPIC,
 | 
			
		||||
@@ -79,67 +80,75 @@ FanPresetSetTrigger = fan_ns.class_(
 | 
			
		||||
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
 | 
			
		||||
FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template())
 | 
			
		||||
 | 
			
		||||
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="ALWAYS_OFF"): cv.enum(
 | 
			
		||||
            RESTORE_MODES, upper=True, space="_"
 | 
			
		||||
        ),
 | 
			
		||||
        cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent),
 | 
			
		||||
        cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_SPEED_LEVEL_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_SPEED_LEVEL_COMMAND_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All(
 | 
			
		||||
            cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_STATE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanStateTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOnTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_DIRECTION_SET): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanDirectionSetTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_OSCILLATING_SET): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanOscillatingSetTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_PRESET_SET): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanPresetSetTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
FAN_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(Fan),
 | 
			
		||||
            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),
 | 
			
		||||
            cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_SPEED_LEVEL_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_SPEED_LEVEL_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_STATE): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanStateTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOnTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_DIRECTION_SET): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                        FanDirectionSetTrigger
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_OSCILLATING_SET): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                        FanOscillatingSetTrigger
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_PRESET_SET): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanPresetSetTrigger),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
_PRESET_MODES_SCHEMA = cv.All(
 | 
			
		||||
@@ -209,6 +218,10 @@ async def setup_fan_core_(var, config):
 | 
			
		||||
        if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None:
 | 
			
		||||
            cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic))
 | 
			
		||||
 | 
			
		||||
    if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
 | 
			
		||||
        web_server_ = await cg.get_variable(webserver_id)
 | 
			
		||||
        web_server.add_entity_to_sorting_list(web_server_, var, config)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_STATE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [(Fan.operator("ptr"), "x")], conf)
 | 
			
		||||
 
 | 
			
		||||
@@ -244,7 +244,7 @@ void FeedbackCover::loop() {
 | 
			
		||||
 | 
			
		||||
  // update current position at requested interval, regardless of who started the movement
 | 
			
		||||
  // so that we also update UI if there was an external movement
 | 
			
		||||
  // don´t save intermediate positions
 | 
			
		||||
  // don't save intermediate positions
 | 
			
		||||
  if (now - this->last_publish_time_ > this->update_interval_) {
 | 
			
		||||
    this->publish_state(false);
 | 
			
		||||
    this->last_publish_time_ = now;
 | 
			
		||||
@@ -274,7 +274,7 @@ void FeedbackCover::control(const CoverCall &call) {
 | 
			
		||||
    if (pos == this->position) {
 | 
			
		||||
      // already at target,
 | 
			
		||||
 | 
			
		||||
      // for covers with built in end stop, if we don´t have sensors we should send the command again
 | 
			
		||||
      // for covers with built in end stop, if we don't have sensors we should send the command again
 | 
			
		||||
      // to make sure the assumed state is not wrong
 | 
			
		||||
      if (this->has_built_in_endstop_ && ((pos == COVER_OPEN
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
 
 | 
			
		||||
@@ -377,7 +377,7 @@ uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer)
 | 
			
		||||
  this->write((uint8_t) (wire_length >> 8));
 | 
			
		||||
  this->write((uint8_t) (wire_length & 0xFF));
 | 
			
		||||
 | 
			
		||||
  uint16_t sum = ((wire_length) >> 8) + ((wire_length) &0xFF) + COMMAND;
 | 
			
		||||
  uint16_t sum = (wire_length >> 8) + (wire_length & 0xFF) + COMMAND;
 | 
			
		||||
  for (auto data : *p_data_buffer) {
 | 
			
		||||
    this->write(data);
 | 
			
		||||
    sum += data;
 | 
			
		||||
@@ -541,34 +541,34 @@ void FingerprintGrowComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Sensor Power Pin: %s",
 | 
			
		||||
                this->has_power_pin_ ? this->sensor_power_pin_->dump_summary().c_str() : "None");
 | 
			
		||||
  if (this->idle_period_to_sleep_ms_ < UINT32_MAX) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Idle Period to Sleep: %u ms", this->idle_period_to_sleep_ms_);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Idle Period to Sleep: %" PRIu32 " ms", this->idle_period_to_sleep_ms_);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Idle Period to Sleep: Never");
 | 
			
		||||
  }
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
  if (this->fingerprint_count_sensor_) {
 | 
			
		||||
    LOG_SENSOR("  ", "Fingerprint Count", this->fingerprint_count_sensor_);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Current Value: %d", (uint16_t) this->fingerprint_count_sensor_->get_state());
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Current Value: %u", (uint16_t) this->fingerprint_count_sensor_->get_state());
 | 
			
		||||
  }
 | 
			
		||||
  if (this->status_sensor_) {
 | 
			
		||||
    LOG_SENSOR("  ", "Status", this->status_sensor_);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Current Value: %d", (uint8_t) this->status_sensor_->get_state());
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Current Value: %u", (uint8_t) this->status_sensor_->get_state());
 | 
			
		||||
  }
 | 
			
		||||
  if (this->capacity_sensor_) {
 | 
			
		||||
    LOG_SENSOR("  ", "Capacity", this->capacity_sensor_);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Current Value: %d", (uint16_t) this->capacity_sensor_->get_state());
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Current Value: %u", (uint16_t) this->capacity_sensor_->get_state());
 | 
			
		||||
  }
 | 
			
		||||
  if (this->security_level_sensor_) {
 | 
			
		||||
    LOG_SENSOR("  ", "Security Level", this->security_level_sensor_);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Current Value: %d", (uint8_t) this->security_level_sensor_->get_state());
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Current Value: %u", (uint8_t) this->security_level_sensor_->get_state());
 | 
			
		||||
  }
 | 
			
		||||
  if (this->last_finger_id_sensor_) {
 | 
			
		||||
    LOG_SENSOR("  ", "Last Finger ID", this->last_finger_id_sensor_);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Current Value: %d", (uint32_t) this->last_finger_id_sensor_->get_state());
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Current Value: %" PRIu32, (uint32_t) this->last_finger_id_sensor_->get_state());
 | 
			
		||||
  }
 | 
			
		||||
  if (this->last_confidence_sensor_) {
 | 
			
		||||
    LOG_SENSOR("  ", "Last Confidence", this->last_confidence_sensor_);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Current Value: %d", (uint32_t) this->last_confidence_sensor_->get_state());
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Current Value: %" PRIu32, (uint32_t) this->last_confidence_sensor_->get_state());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
from esphome import pins
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
from esphome.components import i2c, touchscreen
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN
 | 
			
		||||
from .. import ft5x06_ns
 | 
			
		||||
 | 
			
		||||
FT5x06ButtonListener = ft5x06_ns.class_("FT5x06ButtonListener")
 | 
			
		||||
@@ -16,6 +17,7 @@ FT5x06Touchscreen = ft5x06_ns.class_(
 | 
			
		||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(FT5x06Touchscreen),
 | 
			
		||||
        cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
 | 
			
		||||
    }
 | 
			
		||||
).extend(i2c.i2c_device_schema(0x48))
 | 
			
		||||
 | 
			
		||||
@@ -24,3 +26,7 @@ async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await i2c.register_i2c_device(var, config)
 | 
			
		||||
    await touchscreen.register_touchscreen(var, config)
 | 
			
		||||
 | 
			
		||||
    if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
 | 
			
		||||
        pin = await cg.gpio_pin_expression(interrupt_pin)
 | 
			
		||||
        cg.add(var.set_interrupt_pin(pin))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										102
									
								
								esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
#include "ft5x06_touchscreen.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ft5x06 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ft5x06.touchscreen";
 | 
			
		||||
 | 
			
		||||
void FT5x06Touchscreen::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up FT5x06 Touchscreen...");
 | 
			
		||||
  if (this->interrupt_pin_ != nullptr) {
 | 
			
		||||
    this->interrupt_pin_->setup();
 | 
			
		||||
    this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
    this->interrupt_pin_->setup();
 | 
			
		||||
    this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // wait 200ms after reset.
 | 
			
		||||
  this->set_timeout(200, [this] { this->continue_setup_(); });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FT5x06Touchscreen::continue_setup_() {
 | 
			
		||||
  uint8_t data[4];
 | 
			
		||||
  if (!this->set_mode_(FT5X06_OP_MODE))
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (!this->err_check_(this->read_register(FT5X06_VENDOR_ID_REG, data, 1), "Read Vendor ID"))
 | 
			
		||||
    return;
 | 
			
		||||
  switch (data[0]) {
 | 
			
		||||
    case FT5X06_ID_1:
 | 
			
		||||
    case FT5X06_ID_2:
 | 
			
		||||
    case FT5X06_ID_3:
 | 
			
		||||
      this->vendor_id_ = (VendorId) data[0];
 | 
			
		||||
      ESP_LOGD(TAG, "Read vendor ID 0x%X", data[0]);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    default:
 | 
			
		||||
      ESP_LOGE(TAG, "Unknown vendor ID 0x%X", data[0]);
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
  // reading the chip registers to get max x/y does not seem to work.
 | 
			
		||||
  if (this->display_ != nullptr) {
 | 
			
		||||
    if (this->x_raw_max_ == this->x_raw_min_) {
 | 
			
		||||
      this->x_raw_max_ = this->display_->get_native_width();
 | 
			
		||||
    }
 | 
			
		||||
    if (this->y_raw_max_ == this->y_raw_min_) {
 | 
			
		||||
      this->y_raw_max_ = this->display_->get_native_height();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "FT5x06 Touchscreen setup complete");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FT5x06Touchscreen::update_touches() {
 | 
			
		||||
  uint8_t touch_cnt;
 | 
			
		||||
  uint8_t data[MAX_TOUCHES][6];
 | 
			
		||||
 | 
			
		||||
  if (!this->read_byte(FT5X06_TD_STATUS, &touch_cnt) || touch_cnt > MAX_TOUCHES) {
 | 
			
		||||
    ESP_LOGW(TAG, "Failed to read status");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (touch_cnt == 0)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (!this->read_bytes(FT5X06_TOUCH_DATA, (uint8_t *) data, touch_cnt * 6)) {
 | 
			
		||||
    ESP_LOGW(TAG, "Failed to read touch data");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  for (uint8_t i = 0; i != touch_cnt; i++) {
 | 
			
		||||
    uint8_t status = data[i][0] >> 6;
 | 
			
		||||
    uint8_t id = data[i][2] >> 3;
 | 
			
		||||
    uint16_t x = encode_uint16(data[i][0] & 0x0F, data[i][1]);
 | 
			
		||||
    uint16_t y = encode_uint16(data[i][2] & 0xF, data[i][3]);
 | 
			
		||||
 | 
			
		||||
    ESP_LOGD(TAG, "Read %X status, id: %d, pos %d/%d", status, id, x, y);
 | 
			
		||||
    if (status == 0 || status == 2) {
 | 
			
		||||
      this->add_raw_touch_position_(id, x, y);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FT5x06Touchscreen::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "FT5x06 Touchscreen:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Address: 0x%02X", this->address_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Vendor ID: 0x%X", (int) this->vendor_id_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool FT5x06Touchscreen::err_check_(i2c::ErrorCode err, const char *msg) {
 | 
			
		||||
  if (err != i2c::ERROR_OK) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    ESP_LOGE(TAG, "%s failed - err 0x%X", msg, err);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
bool FT5x06Touchscreen::set_mode_(FTMode mode) {
 | 
			
		||||
  return this->err_check_(this->write_register(FT5X06_MODE_REG, (uint8_t *) &mode, 1), "Set mode");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace ft5x06
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -3,14 +3,12 @@
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/touchscreen/touchscreen.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/gpio.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ft5x06 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ft5x06.touchscreen";
 | 
			
		||||
 | 
			
		||||
enum VendorId {
 | 
			
		||||
  FT5X06_ID_UNKNOWN = 0,
 | 
			
		||||
  FT5X06_ID_1 = 0x51,
 | 
			
		||||
@@ -39,91 +37,19 @@ static const size_t MAX_TOUCHES = 5;  // max number of possible touches reported
 | 
			
		||||
 | 
			
		||||
class FT5x06Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override {
 | 
			
		||||
    esph_log_config(TAG, "Setting up FT5x06 Touchscreen...");
 | 
			
		||||
    // wait 200ms after reset.
 | 
			
		||||
    this->set_timeout(200, [this] { this->continue_setup_(); });
 | 
			
		||||
  }
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void update_touches() override;
 | 
			
		||||
 | 
			
		||||
  void continue_setup_(void) {
 | 
			
		||||
    uint8_t data[4];
 | 
			
		||||
    if (!this->set_mode_(FT5X06_OP_MODE))
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    if (!this->err_check_(this->read_register(FT5X06_VENDOR_ID_REG, data, 1), "Read Vendor ID"))
 | 
			
		||||
      return;
 | 
			
		||||
    switch (data[0]) {
 | 
			
		||||
      case FT5X06_ID_1:
 | 
			
		||||
      case FT5X06_ID_2:
 | 
			
		||||
      case FT5X06_ID_3:
 | 
			
		||||
        this->vendor_id_ = (VendorId) data[0];
 | 
			
		||||
        esph_log_d(TAG, "Read vendor ID 0x%X", data[0]);
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        esph_log_e(TAG, "Unknown vendor ID 0x%X", data[0]);
 | 
			
		||||
        this->mark_failed();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    // reading the chip registers to get max x/y does not seem to work.
 | 
			
		||||
    if (this->display_ != nullptr) {
 | 
			
		||||
      if (this->x_raw_max_ == this->x_raw_min_) {
 | 
			
		||||
        this->x_raw_max_ = this->display_->get_native_width();
 | 
			
		||||
      }
 | 
			
		||||
      if (this->y_raw_max_ == this->y_raw_min_) {
 | 
			
		||||
        this->y_raw_max_ = this->display_->get_native_height();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    esph_log_config(TAG, "FT5x06 Touchscreen setup complete");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void update_touches() override {
 | 
			
		||||
    uint8_t touch_cnt;
 | 
			
		||||
    uint8_t data[MAX_TOUCHES][6];
 | 
			
		||||
 | 
			
		||||
    if (!this->read_byte(FT5X06_TD_STATUS, &touch_cnt) || touch_cnt > MAX_TOUCHES) {
 | 
			
		||||
      esph_log_w(TAG, "Failed to read status");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (touch_cnt == 0)
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    if (!this->read_bytes(FT5X06_TOUCH_DATA, (uint8_t *) data, touch_cnt * 6)) {
 | 
			
		||||
      esph_log_w(TAG, "Failed to read touch data");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t i = 0; i != touch_cnt; i++) {
 | 
			
		||||
      uint8_t status = data[i][0] >> 6;
 | 
			
		||||
      uint8_t id = data[i][2] >> 3;
 | 
			
		||||
      uint16_t x = encode_uint16(data[i][0] & 0x0F, data[i][1]);
 | 
			
		||||
      uint16_t y = encode_uint16(data[i][2] & 0xF, data[i][3]);
 | 
			
		||||
 | 
			
		||||
      esph_log_d(TAG, "Read %X status, id: %d, pos %d/%d", status, id, x, y);
 | 
			
		||||
      if (status == 0 || status == 2) {
 | 
			
		||||
        this->add_raw_touch_position_(id, x, y);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void dump_config() override {
 | 
			
		||||
    esph_log_config(TAG, "FT5x06 Touchscreen:");
 | 
			
		||||
    esph_log_config(TAG, "  Address: 0x%02X", this->address_);
 | 
			
		||||
    esph_log_config(TAG, "  Vendor ID: 0x%X", (int) this->vendor_id_);
 | 
			
		||||
  }
 | 
			
		||||
  void set_interrupt_pin(InternalGPIOPin *interrupt_pin) { this->interrupt_pin_ = interrupt_pin; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool err_check_(i2c::ErrorCode err, const char *msg) {
 | 
			
		||||
    if (err != i2c::ERROR_OK) {
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      esph_log_e(TAG, "%s failed - err 0x%X", msg, err);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  bool set_mode_(FTMode mode) {
 | 
			
		||||
    return this->err_check_(this->write_register(FT5X06_MODE_REG, (uint8_t *) &mode, 1), "Set mode");
 | 
			
		||||
  }
 | 
			
		||||
  void continue_setup_();
 | 
			
		||||
  bool err_check_(i2c::ErrorCode err, const char *msg);
 | 
			
		||||
  bool set_mode_(FTMode mode);
 | 
			
		||||
  VendorId vendor_id_{FT5X06_ID_UNKNOWN};
 | 
			
		||||
 | 
			
		||||
  InternalGPIOPin *interrupt_pin_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ft5x06
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								esphome/components/gpio/one_wire/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								esphome/components/gpio/one_wire/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import CONF_ID, CONF_PIN
 | 
			
		||||
from esphome.components.one_wire import OneWireBus
 | 
			
		||||
from .. import gpio_ns
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@ssieb"]
 | 
			
		||||
 | 
			
		||||
GPIOOneWireBus = gpio_ns.class_("GPIOOneWireBus", OneWireBus, cg.Component)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(GPIOOneWireBus),
 | 
			
		||||
        cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    pin = await cg.gpio_pin_expression(config[CONF_PIN])
 | 
			
		||||
    cg.add(var.set_pin(pin))
 | 
			
		||||
							
								
								
									
										199
									
								
								esphome/components/gpio/one_wire/gpio_one_wire.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								esphome/components/gpio/one_wire/gpio_one_wire.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,199 @@
 | 
			
		||||
#include "gpio_one_wire.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace gpio {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "gpio.one_wire";
 | 
			
		||||
 | 
			
		||||
void GPIOOneWireBus::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up 1-wire bus...");
 | 
			
		||||
  this->search();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GPIOOneWireBus::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "GPIO 1-wire bus:");
 | 
			
		||||
  LOG_PIN("  Pin: ", this->t_pin_);
 | 
			
		||||
  this->dump_devices_(TAG);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HOT IRAM_ATTR GPIOOneWireBus::reset() {
 | 
			
		||||
  // See reset here:
 | 
			
		||||
  // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
 | 
			
		||||
  // Wait for communication to clear (delay G)
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  uint8_t retries = 125;
 | 
			
		||||
  do {
 | 
			
		||||
    if (--retries == 0)
 | 
			
		||||
      return false;
 | 
			
		||||
    delayMicroseconds(2);
 | 
			
		||||
  } while (!pin_.digital_read());
 | 
			
		||||
 | 
			
		||||
  bool r;
 | 
			
		||||
 | 
			
		||||
  // Send 480µs LOW TX reset pulse (drive bus low, delay H)
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
  delayMicroseconds(480);
 | 
			
		||||
 | 
			
		||||
  // Release the bus, delay I
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  delayMicroseconds(70);
 | 
			
		||||
 | 
			
		||||
  // sample bus, 0=device(s) present, 1=no device present
 | 
			
		||||
  r = !pin_.digital_read();
 | 
			
		||||
  // delay J
 | 
			
		||||
  delayMicroseconds(410);
 | 
			
		||||
  return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HOT IRAM_ATTR GPIOOneWireBus::write_bit_(bool bit) {
 | 
			
		||||
  // drive bus low
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
 | 
			
		||||
  // from datasheet:
 | 
			
		||||
  // write 0 low time: t_low0: min=60µs, max=120µs
 | 
			
		||||
  // write 1 low time: t_low1: min=1µs, max=15µs
 | 
			
		||||
  // time slot: t_slot: min=60µs, max=120µs
 | 
			
		||||
  // recovery time: t_rec: min=1µs
 | 
			
		||||
  // ds18b20 appears to read the bus after roughly 14µs
 | 
			
		||||
  uint32_t delay0 = bit ? 6 : 60;
 | 
			
		||||
  uint32_t delay1 = bit ? 54 : 5;
 | 
			
		||||
 | 
			
		||||
  // delay A/C
 | 
			
		||||
  delayMicroseconds(delay0);
 | 
			
		||||
  // release bus
 | 
			
		||||
  pin_.digital_write(true);
 | 
			
		||||
  // delay B/D
 | 
			
		||||
  delayMicroseconds(delay1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HOT IRAM_ATTR GPIOOneWireBus::read_bit_() {
 | 
			
		||||
  // drive bus low
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
 | 
			
		||||
  // note: for reading we'll need very accurate timing, as the
 | 
			
		||||
  // timing for the digital_read() is tight; according to the datasheet,
 | 
			
		||||
  // we should read at the end of 16µs starting from the bus low
 | 
			
		||||
  // typically, the ds18b20 pulls the line high after 11µs for a logical 1
 | 
			
		||||
  // and 29µs for a logical 0
 | 
			
		||||
 | 
			
		||||
  uint32_t start = micros();
 | 
			
		||||
  // datasheet says >1µs
 | 
			
		||||
  delayMicroseconds(2);
 | 
			
		||||
 | 
			
		||||
  // release bus, delay E
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
 | 
			
		||||
  // measure from start value directly, to get best accurate timing no matter
 | 
			
		||||
  // how long pin_mode/delayMicroseconds took
 | 
			
		||||
  delayMicroseconds(12 - (micros() - start));
 | 
			
		||||
 | 
			
		||||
  // sample bus to read bit from peer
 | 
			
		||||
  bool r = pin_.digital_read();
 | 
			
		||||
 | 
			
		||||
  // read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
 | 
			
		||||
  uint32_t now = micros();
 | 
			
		||||
  if (now - start < 60)
 | 
			
		||||
    delayMicroseconds(60 - (now - start));
 | 
			
		||||
 | 
			
		||||
  return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR GPIOOneWireBus::write8(uint8_t val) {
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    this->write_bit_(bool((1u << i) & val));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR GPIOOneWireBus::write64(uint64_t val) {
 | 
			
		||||
  for (uint8_t i = 0; i < 64; i++) {
 | 
			
		||||
    this->write_bit_(bool((1ULL << i) & val));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t IRAM_ATTR GPIOOneWireBus::read8() {
 | 
			
		||||
  uint8_t ret = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    ret |= (uint8_t(this->read_bit_()) << i);
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint64_t IRAM_ATTR GPIOOneWireBus::read64() {
 | 
			
		||||
  uint64_t ret = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    ret |= (uint64_t(this->read_bit_()) << i);
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GPIOOneWireBus::reset_search() {
 | 
			
		||||
  this->last_discrepancy_ = 0;
 | 
			
		||||
  this->last_device_flag_ = false;
 | 
			
		||||
  this->address_ = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint64_t IRAM_ATTR GPIOOneWireBus::search_int() {
 | 
			
		||||
  if (this->last_device_flag_)
 | 
			
		||||
    return 0u;
 | 
			
		||||
 | 
			
		||||
  uint8_t last_zero = 0;
 | 
			
		||||
  uint64_t bit_mask = 1;
 | 
			
		||||
  uint64_t address = this->address_;
 | 
			
		||||
 | 
			
		||||
  // Initiate search
 | 
			
		||||
  for (int bit_number = 1; bit_number <= 64; bit_number++, bit_mask <<= 1) {
 | 
			
		||||
    // read bit
 | 
			
		||||
    bool id_bit = this->read_bit_();
 | 
			
		||||
    // read its complement
 | 
			
		||||
    bool cmp_id_bit = this->read_bit_();
 | 
			
		||||
 | 
			
		||||
    if (id_bit && cmp_id_bit) {
 | 
			
		||||
      // No devices participating in search
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool branch;
 | 
			
		||||
 | 
			
		||||
    if (id_bit != cmp_id_bit) {
 | 
			
		||||
      // only chose one branch, the other one doesn't have any devices.
 | 
			
		||||
      branch = id_bit;
 | 
			
		||||
    } else {
 | 
			
		||||
      // there are devices with both 0s and 1s at this bit
 | 
			
		||||
      if (bit_number < this->last_discrepancy_) {
 | 
			
		||||
        branch = (address & bit_mask) > 0;
 | 
			
		||||
      } else {
 | 
			
		||||
        branch = bit_number == this->last_discrepancy_;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!branch) {
 | 
			
		||||
        last_zero = bit_number;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (branch) {
 | 
			
		||||
      address |= bit_mask;
 | 
			
		||||
    } else {
 | 
			
		||||
      address &= ~bit_mask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // choose/announce branch
 | 
			
		||||
    this->write_bit_(branch);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->last_discrepancy_ = last_zero;
 | 
			
		||||
  if (this->last_discrepancy_ == 0) {
 | 
			
		||||
    // we're at root and have no choices left, so this was the last one.
 | 
			
		||||
    this->last_device_flag_ = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->address_ = address;
 | 
			
		||||
  return address;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace gpio
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										41
									
								
								esphome/components/gpio/one_wire/gpio_one_wire.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								esphome/components/gpio/one_wire/gpio_one_wire.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/components/one_wire/one_wire.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace gpio {
 | 
			
		||||
 | 
			
		||||
class GPIOOneWireBus : public one_wire::OneWireBus, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::BUS; }
 | 
			
		||||
 | 
			
		||||
  void set_pin(InternalGPIOPin *pin) {
 | 
			
		||||
    this->t_pin_ = pin;
 | 
			
		||||
    this->pin_ = pin->to_isr();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool reset() override;
 | 
			
		||||
  void write8(uint8_t val) override;
 | 
			
		||||
  void write64(uint64_t val) override;
 | 
			
		||||
  uint8_t read8() override;
 | 
			
		||||
  uint64_t read64() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  InternalGPIOPin *t_pin_;
 | 
			
		||||
  ISRInternalGPIOPin pin_;
 | 
			
		||||
  uint8_t last_discrepancy_{0};
 | 
			
		||||
  bool last_device_flag_{false};
 | 
			
		||||
  uint64_t address_;
 | 
			
		||||
 | 
			
		||||
  void reset_search() override;
 | 
			
		||||
  uint64_t search_int() override;
 | 
			
		||||
  void write_bit_(bool bit);
 | 
			
		||||
  bool read_bit_();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace gpio
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -2,6 +2,8 @@
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace he60r {
 | 
			
		||||
 | 
			
		||||
@@ -54,7 +56,7 @@ void HE60rCover::endstop_reached_(CoverOperation operation) {
 | 
			
		||||
    this->position = new_position;
 | 
			
		||||
    this->current_operation = COVER_OPERATION_IDLE;
 | 
			
		||||
    if (this->last_command_ == operation) {
 | 
			
		||||
      float dur = (now - this->start_dir_time_) / 1e3f;
 | 
			
		||||
      float dur = (float) (now - this->start_dir_time_) / 1e3f;
 | 
			
		||||
      ESP_LOGD(TAG, "'%s' - %s endstop reached. Took %.1fs.", this->name_.c_str(),
 | 
			
		||||
               operation == COVER_OPERATION_OPENING ? "Open" : "Close", dur);
 | 
			
		||||
    }
 | 
			
		||||
@@ -67,7 +69,6 @@ void HE60rCover::set_current_operation_(cover::CoverOperation operation) {
 | 
			
		||||
    this->current_operation = operation;
 | 
			
		||||
    if (operation != COVER_OPERATION_IDLE)
 | 
			
		||||
      this->last_recompute_time_ = millis();
 | 
			
		||||
    this->publish_state();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -124,10 +125,10 @@ void HE60rCover::process_rx_(uint8_t data) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HE60rCover::update_() {
 | 
			
		||||
  if (toggles_needed_ != 0) {
 | 
			
		||||
  if (this->toggles_needed_ != 0) {
 | 
			
		||||
    if ((this->counter_++ & 0x3) == 0) {
 | 
			
		||||
      toggles_needed_--;
 | 
			
		||||
      ESP_LOGD(TAG, "Writing byte 0x30, still needed=%d", toggles_needed_);
 | 
			
		||||
      this->toggles_needed_--;
 | 
			
		||||
      ESP_LOGD(TAG, "Writing byte 0x30, still needed=%u", this->toggles_needed_);
 | 
			
		||||
      this->write_byte(TOGGLE_BYTE);
 | 
			
		||||
    } else {
 | 
			
		||||
      this->write_byte(QUERY_BYTE);
 | 
			
		||||
@@ -233,31 +234,28 @@ void HE60rCover::recompute_position_() {
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  const uint32_t now = millis();
 | 
			
		||||
  float dir;
 | 
			
		||||
  float action_dur;
 | 
			
		||||
 | 
			
		||||
  switch (this->current_operation) {
 | 
			
		||||
    case COVER_OPERATION_OPENING:
 | 
			
		||||
      dir = 1.0f;
 | 
			
		||||
      action_dur = this->open_duration_;
 | 
			
		||||
      break;
 | 
			
		||||
    case COVER_OPERATION_CLOSING:
 | 
			
		||||
      dir = -1.0f;
 | 
			
		||||
      action_dur = this->close_duration_;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (now > this->last_recompute_time_) {
 | 
			
		||||
    auto diff = now - last_recompute_time_;
 | 
			
		||||
    auto delta = dir * diff / action_dur;
 | 
			
		||||
    auto diff = (unsigned) (now - last_recompute_time_);
 | 
			
		||||
    float delta;
 | 
			
		||||
    switch (this->current_operation) {
 | 
			
		||||
      case COVER_OPERATION_OPENING:
 | 
			
		||||
        delta = (float) diff / (float) this->open_duration_;
 | 
			
		||||
        break;
 | 
			
		||||
      case COVER_OPERATION_CLOSING:
 | 
			
		||||
        delta = -(float) diff / (float) this->close_duration_;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // make sure our guesstimate never reaches full open or close.
 | 
			
		||||
    this->position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f);
 | 
			
		||||
    ESP_LOGD(TAG, "Recompute %dms, dir=%f, action_dur=%f, delta=%f, pos=%f", (int) diff, dir, action_dur, delta,
 | 
			
		||||
             this->position);
 | 
			
		||||
    auto new_position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f);
 | 
			
		||||
    ESP_LOGD(TAG, "Recompute %ums, dir=%u, delta=%f, pos=%f", diff, this->current_operation, delta, new_position);
 | 
			
		||||
    this->last_recompute_time_ = now;
 | 
			
		||||
    this->publish_state();
 | 
			
		||||
    if (this->position != new_position) {
 | 
			
		||||
      this->position = new_position;
 | 
			
		||||
      this->publish_state();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,15 +25,14 @@ class HE60rCover : public cover::Cover, public Component, public uart::UARTDevic
 | 
			
		||||
  void control(const cover::CoverCall &call) override;
 | 
			
		||||
  bool is_at_target_() const;
 | 
			
		||||
  void start_direction_(cover::CoverOperation dir);
 | 
			
		||||
  void update_operation_(cover::CoverOperation dir);
 | 
			
		||||
  void endstop_reached_(cover::CoverOperation operation);
 | 
			
		||||
  void recompute_position_();
 | 
			
		||||
  void set_current_operation_(cover::CoverOperation operation);
 | 
			
		||||
  void process_rx_(uint8_t data);
 | 
			
		||||
 | 
			
		||||
  uint32_t open_duration_{0};
 | 
			
		||||
  uint32_t close_duration_{0};
 | 
			
		||||
  uint32_t toggles_needed_{0};
 | 
			
		||||
  unsigned open_duration_{0};
 | 
			
		||||
  unsigned close_duration_{0};
 | 
			
		||||
  unsigned toggles_needed_{0};
 | 
			
		||||
  cover::CoverOperation next_direction_{cover::COVER_OPERATION_IDLE};
 | 
			
		||||
  cover::CoverOperation last_command_{cover::COVER_OPERATION_IDLE};
 | 
			
		||||
  uint32_t last_recompute_time_{0};
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ from .const import KEY_HOST
 | 
			
		||||
# force import gpio to register pin schema
 | 
			
		||||
from .gpio import host_pin_to_code  # noqa
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
CODEOWNERS = ["@esphome/core", "@clydebarrow"]
 | 
			
		||||
AUTO_LOAD = ["network"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								esphome/components/host/time/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								esphome/components/host/time/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import time as time_
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@clydebarrow"]
 | 
			
		||||
 | 
			
		||||
time_ns = cg.esphome_ns.namespace("host")
 | 
			
		||||
HostTime = time_ns.class_("HostTime", time_.RealTimeClock)
 | 
			
		||||
CONFIG_SCHEMA = time_.TIME_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(HostTime),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await time_.register_time(var, config)
 | 
			
		||||
							
								
								
									
										15
									
								
								esphome/components/host/time/host_time.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								esphome/components/host/time/host_time.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/time/real_time_clock.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace host {
 | 
			
		||||
 | 
			
		||||
class HostTime : public time::RealTimeClock {
 | 
			
		||||
 public:
 | 
			
		||||
  void update() override {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace host
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
import urllib.parse as urlparse
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    __version__,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_TIMEOUT,
 | 
			
		||||
    CONF_METHOD,
 | 
			
		||||
@@ -12,67 +11,91 @@ from esphome.const import (
 | 
			
		||||
    CONF_ESP8266_DISABLE_SSL_SUPPORT,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import Lambda, CORE
 | 
			
		||||
from esphome.components import esp32
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["network"]
 | 
			
		||||
AUTO_LOAD = ["json"]
 | 
			
		||||
 | 
			
		||||
http_request_ns = cg.esphome_ns.namespace("http_request")
 | 
			
		||||
HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component)
 | 
			
		||||
HttpRequestArduino = http_request_ns.class_("HttpRequestArduino", HttpRequestComponent)
 | 
			
		||||
HttpRequestIDF = http_request_ns.class_("HttpRequestIDF", HttpRequestComponent)
 | 
			
		||||
 | 
			
		||||
HttpContainer = http_request_ns.class_("HttpContainer")
 | 
			
		||||
 | 
			
		||||
HttpRequestSendAction = http_request_ns.class_(
 | 
			
		||||
    "HttpRequestSendAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
HttpRequestResponseTrigger = http_request_ns.class_(
 | 
			
		||||
    "HttpRequestResponseTrigger", automation.Trigger
 | 
			
		||||
    "HttpRequestResponseTrigger",
 | 
			
		||||
    automation.Trigger.template(
 | 
			
		||||
        cg.std_shared_ptr.template(HttpContainer), cg.std_string
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_HEADERS = "headers"
 | 
			
		||||
CONF_HTTP_REQUEST_ID = "http_request_id"
 | 
			
		||||
 | 
			
		||||
CONF_USERAGENT = "useragent"
 | 
			
		||||
CONF_BODY = "body"
 | 
			
		||||
CONF_JSON = "json"
 | 
			
		||||
CONF_VERIFY_SSL = "verify_ssl"
 | 
			
		||||
CONF_ON_RESPONSE = "on_response"
 | 
			
		||||
CONF_FOLLOW_REDIRECTS = "follow_redirects"
 | 
			
		||||
CONF_REDIRECT_LIMIT = "redirect_limit"
 | 
			
		||||
CONF_WATCHDOG_TIMEOUT = "watchdog_timeout"
 | 
			
		||||
 | 
			
		||||
CONF_MAX_RESPONSE_BUFFER_SIZE = "max_response_buffer_size"
 | 
			
		||||
CONF_ON_RESPONSE = "on_response"
 | 
			
		||||
CONF_HEADERS = "headers"
 | 
			
		||||
CONF_BODY = "body"
 | 
			
		||||
CONF_JSON = "json"
 | 
			
		||||
CONF_CAPTURE_RESPONSE = "capture_response"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_url(value):
 | 
			
		||||
    value = cv.string(value)
 | 
			
		||||
    try:
 | 
			
		||||
        parsed = list(urlparse.urlparse(value))
 | 
			
		||||
    except Exception as err:
 | 
			
		||||
        raise cv.Invalid("Invalid URL") from err
 | 
			
		||||
 | 
			
		||||
    if not parsed[0] or not parsed[1]:
 | 
			
		||||
        raise cv.Invalid("URL must have a URL scheme and host")
 | 
			
		||||
 | 
			
		||||
    if parsed[0] not in ["http", "https"]:
 | 
			
		||||
        raise cv.Invalid("Scheme must be http or https")
 | 
			
		||||
 | 
			
		||||
    if not parsed[2]:
 | 
			
		||||
        parsed[2] = "/"
 | 
			
		||||
 | 
			
		||||
    return urlparse.urlunparse(parsed)
 | 
			
		||||
    value = cv.url(value)
 | 
			
		||||
    if value.startswith("http://") or value.startswith("https://"):
 | 
			
		||||
        return value
 | 
			
		||||
    raise cv.Invalid("URL must start with 'http://' or 'https://'")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_secure_url(config):
 | 
			
		||||
    url_ = config[CONF_URL]
 | 
			
		||||
def validate_ssl_verification(config):
 | 
			
		||||
    error_message = ""
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        if not CORE.using_esp_idf and config[CONF_VERIFY_SSL]:
 | 
			
		||||
            error_message = "ESPHome supports certificate verification only via ESP-IDF"
 | 
			
		||||
 | 
			
		||||
    if CORE.is_rp2040 and config[CONF_VERIFY_SSL]:
 | 
			
		||||
        error_message = "ESPHome does not support certificate verification on RP2040"
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        config.get(CONF_VERIFY_SSL)
 | 
			
		||||
        and not isinstance(url_, Lambda)
 | 
			
		||||
        and url_.lower().startswith("https:")
 | 
			
		||||
        CORE.is_esp8266
 | 
			
		||||
        and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]
 | 
			
		||||
        and config[CONF_VERIFY_SSL]
 | 
			
		||||
    ):
 | 
			
		||||
        error_message = "ESPHome does not support certificate verification on ESP8266"
 | 
			
		||||
 | 
			
		||||
    if len(error_message) > 0:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "Currently ESPHome doesn't support SSL verification. "
 | 
			
		||||
            "Set 'verify_ssl: false' to make insecure HTTPS requests."
 | 
			
		||||
            f"{error_message}. Set '{CONF_VERIFY_SSL}: false' to skip certificate validation and allow less secure HTTPS connections."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _declare_request_class(value):
 | 
			
		||||
    if CORE.using_esp_idf:
 | 
			
		||||
        return cv.declare_id(HttpRequestIDF)(value)
 | 
			
		||||
    if CORE.is_esp8266 or CORE.is_esp32 or CORE.is_rp2040:
 | 
			
		||||
        return cv.declare_id(HttpRequestArduino)(value)
 | 
			
		||||
    return NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(HttpRequestComponent),
 | 
			
		||||
            cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string,
 | 
			
		||||
            cv.GenerateID(): _declare_request_class,
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_USERAGENT, f"ESPHome/{__version__} (https://esphome.io)"
 | 
			
		||||
            ): cv.string,
 | 
			
		||||
            cv.Optional(CONF_FOLLOW_REDIRECTS, True): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_REDIRECT_LIMIT, 3): cv.int_,
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
@@ -81,12 +104,21 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All(
 | 
			
		||||
                cv.only_on_esp8266, cv.boolean
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_WATCHDOG_TIMEOUT): cv.All(
 | 
			
		||||
                cv.Any(cv.only_on_esp32, cv.only_on_rp2040),
 | 
			
		||||
                cv.positive_not_null_time_period,
 | 
			
		||||
                cv.positive_time_period_milliseconds,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.require_framework_version(
 | 
			
		||||
        esp8266_arduino=cv.Version(2, 5, 1),
 | 
			
		||||
        esp32_arduino=cv.Version(0, 0, 0),
 | 
			
		||||
        esp_idf=cv.Version(0, 0, 0),
 | 
			
		||||
        rp2040_arduino=cv.Version(0, 0, 0),
 | 
			
		||||
    ),
 | 
			
		||||
    validate_ssl_verification,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -100,11 +132,30 @@ async def to_code(config):
 | 
			
		||||
    if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]:
 | 
			
		||||
        cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS")
 | 
			
		||||
 | 
			
		||||
    if timeout_ms := config.get(CONF_WATCHDOG_TIMEOUT):
 | 
			
		||||
        cg.add(var.set_watchdog_timeout(timeout_ms))
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        cg.add_library("WiFiClientSecure", None)
 | 
			
		||||
        cg.add_library("HTTPClient", None)
 | 
			
		||||
        if CORE.using_esp_idf:
 | 
			
		||||
            esp32.add_idf_sdkconfig_option(
 | 
			
		||||
                "CONFIG_MBEDTLS_CERTIFICATE_BUNDLE",
 | 
			
		||||
                config.get(CONF_VERIFY_SSL),
 | 
			
		||||
            )
 | 
			
		||||
            esp32.add_idf_sdkconfig_option(
 | 
			
		||||
                "CONFIG_ESP_TLS_INSECURE",
 | 
			
		||||
                not config.get(CONF_VERIFY_SSL),
 | 
			
		||||
            )
 | 
			
		||||
            esp32.add_idf_sdkconfig_option(
 | 
			
		||||
                "CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY",
 | 
			
		||||
                not config.get(CONF_VERIFY_SSL),
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            cg.add_library("WiFiClientSecure", None)
 | 
			
		||||
            cg.add_library("HTTPClient", None)
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        cg.add_library("ESP8266HTTPClient", None)
 | 
			
		||||
    if CORE.is_rp2040 and CORE.using_arduino:
 | 
			
		||||
        cg.add_library("HTTPClient", None)
 | 
			
		||||
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
@@ -116,12 +167,16 @@ HTTP_REQUEST_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
        cv.Optional(CONF_HEADERS): cv.All(
 | 
			
		||||
            cv.Schema({cv.string: cv.templatable(cv.string)})
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_VERIFY_SSL): cv.invalid(
 | 
			
		||||
            f"{CONF_VERIFY_SSL} has moved to the base component configuration."
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_CAPTURE_RESPONSE, default=False): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_ON_RESPONSE): automation.validate_automation(
 | 
			
		||||
            {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(HttpRequestResponseTrigger)}
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_MAX_RESPONSE_BUFFER_SIZE, default="1kB"): cv.validate_bytes,
 | 
			
		||||
    }
 | 
			
		||||
).add_extra(validate_secure_url)
 | 
			
		||||
)
 | 
			
		||||
HTTP_REQUEST_GET_ACTION_SCHEMA = automation.maybe_conf(
 | 
			
		||||
    CONF_URL,
 | 
			
		||||
    HTTP_REQUEST_ACTION_SCHEMA.extend(
 | 
			
		||||
@@ -173,6 +228,9 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
 | 
			
		||||
    cg.add(var.set_url(template_))
 | 
			
		||||
    cg.add(var.set_method(config[CONF_METHOD]))
 | 
			
		||||
    cg.add(var.set_capture_response(config[CONF_CAPTURE_RESPONSE]))
 | 
			
		||||
    cg.add(var.set_max_response_buffer_size(config[CONF_MAX_RESPONSE_BUFFER_SIZE]))
 | 
			
		||||
 | 
			
		||||
    if CONF_BODY in config:
 | 
			
		||||
        template_ = await cg.templatable(config[CONF_BODY], args, cg.std_string)
 | 
			
		||||
        cg.add(var.set_body(template_))
 | 
			
		||||
@@ -196,7 +254,12 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
 | 
			
		||||
        cg.add(var.register_response_trigger(trigger))
 | 
			
		||||
        await automation.build_automation(
 | 
			
		||||
            trigger, [(int, "status_code"), (cg.uint32, "duration_ms")], conf
 | 
			
		||||
            trigger,
 | 
			
		||||
            [
 | 
			
		||||
                (cg.std_shared_ptr.template(HttpContainer), "response"),
 | 
			
		||||
                (cg.std_string, "body"),
 | 
			
		||||
            ],
 | 
			
		||||
            conf,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return var
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include "http_request.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
@@ -14,131 +13,12 @@ void HttpRequestComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "HTTP Request:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Timeout: %ums", this->timeout_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  User-Agent: %s", this->useragent_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Follow Redirects: %d", this->follow_redirects_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Follow redirects: %s", YESNO(this->follow_redirects_));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Redirect limit: %d", this->redirect_limit_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HttpRequestComponent::set_url(std::string url) {
 | 
			
		||||
  this->url_ = std::move(url);
 | 
			
		||||
  this->secure_ = this->url_.compare(0, 6, "https:") == 0;
 | 
			
		||||
 | 
			
		||||
  if (!this->last_url_.empty() && this->url_ != this->last_url_) {
 | 
			
		||||
    // Close connection if url has been changed
 | 
			
		||||
    this->client_.setReuse(false);
 | 
			
		||||
    this->client_.end();
 | 
			
		||||
  if (this->watchdog_timeout_ > 0) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Watchdog Timeout: %" PRIu32 "ms", this->watchdog_timeout_);
 | 
			
		||||
  }
 | 
			
		||||
  this->client_.setReuse(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HttpRequestComponent::send(const std::vector<HttpRequestResponseTrigger *> &response_triggers) {
 | 
			
		||||
  if (!network::is_connected()) {
 | 
			
		||||
    this->client_.end();
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool begin_status = false;
 | 
			
		||||
  const String url = this->url_.c_str();
 | 
			
		||||
#if defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0))
 | 
			
		||||
#if defined(USE_ESP32) || USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
 | 
			
		||||
  if (this->follow_redirects_) {
 | 
			
		||||
    this->client_.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->client_.setFollowRedirects(HTTPC_DISABLE_FOLLOW_REDIRECTS);
 | 
			
		||||
  }
 | 
			
		||||
#else
 | 
			
		||||
  this->client_.setFollowRedirects(this->follow_redirects_);
 | 
			
		||||
#endif
 | 
			
		||||
  this->client_.setRedirectLimit(this->redirect_limit_);
 | 
			
		||||
#endif
 | 
			
		||||
#if defined(USE_ESP32)
 | 
			
		||||
  begin_status = this->client_.begin(url);
 | 
			
		||||
#elif defined(USE_ESP8266)
 | 
			
		||||
  begin_status = this->client_.begin(*this->get_wifi_client_(), url);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (!begin_status) {
 | 
			
		||||
    this->client_.end();
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed at the begin phase. Please check the configuration");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->client_.setTimeout(this->timeout_);
 | 
			
		||||
#if defined(USE_ESP32)
 | 
			
		||||
  this->client_.setConnectTimeout(this->timeout_);
 | 
			
		||||
#endif
 | 
			
		||||
  if (this->useragent_ != nullptr) {
 | 
			
		||||
    this->client_.setUserAgent(this->useragent_);
 | 
			
		||||
  }
 | 
			
		||||
  for (const auto &header : this->headers_) {
 | 
			
		||||
    this->client_.addHeader(header.name, header.value, false, true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint32_t start_time = millis();
 | 
			
		||||
  int http_code = this->client_.sendRequest(this->method_, this->body_.c_str());
 | 
			
		||||
  uint32_t duration = millis() - start_time;
 | 
			
		||||
  for (auto *trigger : response_triggers)
 | 
			
		||||
    trigger->process(http_code, duration);
 | 
			
		||||
 | 
			
		||||
  if (http_code < 0) {
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s; Duration: %u ms", this->url_.c_str(),
 | 
			
		||||
             HTTPClient::errorToString(http_code).c_str(), duration);
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (http_code < 200 || http_code >= 300) {
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Code: %d; Duration: %u ms", this->url_.c_str(), http_code, duration);
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
  ESP_LOGD(TAG, "HTTP Request completed; URL: %s; Code: %d; Duration: %u ms", this->url_.c_str(), http_code, duration);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
std::shared_ptr<WiFiClient> HttpRequestComponent::get_wifi_client_() {
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
  if (this->secure_) {
 | 
			
		||||
    if (this->wifi_client_secure_ == nullptr) {
 | 
			
		||||
      this->wifi_client_secure_ = std::make_shared<BearSSL::WiFiClientSecure>();
 | 
			
		||||
      this->wifi_client_secure_->setInsecure();
 | 
			
		||||
      this->wifi_client_secure_->setBufferSizes(512, 512);
 | 
			
		||||
    }
 | 
			
		||||
    return this->wifi_client_secure_;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (this->wifi_client_ == nullptr) {
 | 
			
		||||
    this->wifi_client_ = std::make_shared<WiFiClient>();
 | 
			
		||||
  }
 | 
			
		||||
  return this->wifi_client_;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void HttpRequestComponent::close() {
 | 
			
		||||
  this->last_url_ = this->url_;
 | 
			
		||||
  this->client_.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const char *HttpRequestComponent::get_string() {
 | 
			
		||||
#if defined(ESP32)
 | 
			
		||||
  // The static variable is here because HTTPClient::getString() returns a String on ESP32,
 | 
			
		||||
  // and we need something to keep a buffer alive.
 | 
			
		||||
  static String str;
 | 
			
		||||
#else
 | 
			
		||||
  // However on ESP8266, HTTPClient::getString() returns a String& to a member variable.
 | 
			
		||||
  // Leaving this the default so that any new platform either doesn't copy, or encounters a compilation error.
 | 
			
		||||
  auto &
 | 
			
		||||
#endif
 | 
			
		||||
  str = this->client_.getString();
 | 
			
		||||
  return str.c_str();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,18 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/json/json_util.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
#include <list>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include <HTTPClient.h>
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
#include <ESP8266HTTPClient.h>
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
#include <WiFiClientSecure.h>
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
#include "esphome/components/json/json_util.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
@@ -31,9 +22,32 @@ struct Header {
 | 
			
		||||
  const char *value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class HttpRequestResponseTrigger : public Trigger<int32_t, uint32_t> {
 | 
			
		||||
class HttpRequestComponent;
 | 
			
		||||
 | 
			
		||||
class HttpContainer : public Parented<HttpRequestComponent> {
 | 
			
		||||
 public:
 | 
			
		||||
  void process(int32_t status_code, uint32_t duration_ms) { this->trigger(status_code, duration_ms); }
 | 
			
		||||
  virtual ~HttpContainer() = default;
 | 
			
		||||
  size_t content_length;
 | 
			
		||||
  int status_code;
 | 
			
		||||
  uint32_t duration_ms;
 | 
			
		||||
 | 
			
		||||
  virtual int read(uint8_t *buf, size_t max_len) = 0;
 | 
			
		||||
  virtual void end() = 0;
 | 
			
		||||
 | 
			
		||||
  void set_secure(bool secure) { this->secure_ = secure; }
 | 
			
		||||
 | 
			
		||||
  size_t get_bytes_read() const { return this->bytes_read_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  size_t bytes_read_{0};
 | 
			
		||||
  bool secure_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class HttpRequestResponseTrigger : public Trigger<std::shared_ptr<HttpContainer>, std::string> {
 | 
			
		||||
 public:
 | 
			
		||||
  void process(std::shared_ptr<HttpContainer> container, std::string response_body) {
 | 
			
		||||
    this->trigger(std::move(container), std::move(response_body));
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class HttpRequestComponent : public Component {
 | 
			
		||||
@@ -41,37 +55,33 @@ class HttpRequestComponent : public Component {
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
 | 
			
		||||
 | 
			
		||||
  void set_url(std::string url);
 | 
			
		||||
  void set_method(const char *method) { this->method_ = method; }
 | 
			
		||||
  void set_useragent(const char *useragent) { this->useragent_ = useragent; }
 | 
			
		||||
  void set_timeout(uint16_t timeout) { this->timeout_ = timeout; }
 | 
			
		||||
  void set_watchdog_timeout(uint32_t watchdog_timeout) { this->watchdog_timeout_ = watchdog_timeout; }
 | 
			
		||||
  uint32_t get_watchdog_timeout() const { return this->watchdog_timeout_; }
 | 
			
		||||
  void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
 | 
			
		||||
  void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; }
 | 
			
		||||
  void set_body(const std::string &body) { this->body_ = body; }
 | 
			
		||||
  void set_headers(std::list<Header> headers) { this->headers_ = std::move(headers); }
 | 
			
		||||
  void send(const std::vector<HttpRequestResponseTrigger *> &response_triggers);
 | 
			
		||||
  void close();
 | 
			
		||||
  const char *get_string();
 | 
			
		||||
 | 
			
		||||
  std::shared_ptr<HttpContainer> get(std::string url) { return this->start(std::move(url), "GET", "", {}); }
 | 
			
		||||
  std::shared_ptr<HttpContainer> get(std::string url, std::list<Header> headers) {
 | 
			
		||||
    return this->start(std::move(url), "GET", "", std::move(headers));
 | 
			
		||||
  }
 | 
			
		||||
  std::shared_ptr<HttpContainer> post(std::string url, std::string body) {
 | 
			
		||||
    return this->start(std::move(url), "POST", std::move(body), {});
 | 
			
		||||
  }
 | 
			
		||||
  std::shared_ptr<HttpContainer> post(std::string url, std::string body, std::list<Header> headers) {
 | 
			
		||||
    return this->start(std::move(url), "POST", std::move(body), std::move(headers));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
 | 
			
		||||
                                               std::list<Header> headers) = 0;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  HTTPClient client_{};
 | 
			
		||||
  std::string url_;
 | 
			
		||||
  std::string last_url_;
 | 
			
		||||
  const char *method_;
 | 
			
		||||
  const char *useragent_{nullptr};
 | 
			
		||||
  bool secure_;
 | 
			
		||||
  bool follow_redirects_;
 | 
			
		||||
  uint16_t redirect_limit_;
 | 
			
		||||
  uint16_t timeout_{5000};
 | 
			
		||||
  std::string body_;
 | 
			
		||||
  std::list<Header> headers_;
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
  std::shared_ptr<WiFiClient> wifi_client_;
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
  std::shared_ptr<BearSSL::WiFiClientSecure> wifi_client_secure_;
 | 
			
		||||
#endif
 | 
			
		||||
  std::shared_ptr<WiFiClient> get_wifi_client_();
 | 
			
		||||
#endif
 | 
			
		||||
  uint32_t watchdog_timeout_{0};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
@@ -80,6 +90,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, url)
 | 
			
		||||
  TEMPLATABLE_VALUE(const char *, method)
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, body)
 | 
			
		||||
  TEMPLATABLE_VALUE(bool, capture_response)
 | 
			
		||||
 | 
			
		||||
  void add_header(const char *key, TemplatableValue<const char *, Ts...> value) { this->headers_.insert({key, value}); }
 | 
			
		||||
 | 
			
		||||
@@ -89,19 +100,22 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
 | 
			
		||||
  void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); }
 | 
			
		||||
 | 
			
		||||
  void set_max_response_buffer_size(size_t max_response_buffer_size) {
 | 
			
		||||
    this->max_response_buffer_size_ = max_response_buffer_size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    this->parent_->set_url(this->url_.value(x...));
 | 
			
		||||
    this->parent_->set_method(this->method_.value(x...));
 | 
			
		||||
    std::string body;
 | 
			
		||||
    if (this->body_.has_value()) {
 | 
			
		||||
      this->parent_->set_body(this->body_.value(x...));
 | 
			
		||||
      body = this->body_.value(x...);
 | 
			
		||||
    }
 | 
			
		||||
    if (!this->json_.empty()) {
 | 
			
		||||
      auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_, this, x..., std::placeholders::_1);
 | 
			
		||||
      this->parent_->set_body(json::build_json(f));
 | 
			
		||||
      body = json::build_json(f);
 | 
			
		||||
    }
 | 
			
		||||
    if (this->json_func_ != nullptr) {
 | 
			
		||||
      auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_func_, this, x..., std::placeholders::_1);
 | 
			
		||||
      this->parent_->set_body(json::build_json(f));
 | 
			
		||||
      body = json::build_json(f);
 | 
			
		||||
    }
 | 
			
		||||
    std::list<Header> headers;
 | 
			
		||||
    for (const auto &item : this->headers_) {
 | 
			
		||||
@@ -111,10 +125,37 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
      header.value = val.value(x...);
 | 
			
		||||
      headers.push_back(header);
 | 
			
		||||
    }
 | 
			
		||||
    this->parent_->set_headers(headers);
 | 
			
		||||
    this->parent_->send(this->response_triggers_);
 | 
			
		||||
    this->parent_->close();
 | 
			
		||||
    this->parent_->set_body("");
 | 
			
		||||
 | 
			
		||||
    auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, headers);
 | 
			
		||||
 | 
			
		||||
    if (container == nullptr) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    size_t content_length = container->content_length;
 | 
			
		||||
    size_t max_length = std::min(content_length, this->max_response_buffer_size_);
 | 
			
		||||
 | 
			
		||||
    std::string response_body;
 | 
			
		||||
    if (this->capture_response_.value(x...)) {
 | 
			
		||||
      ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
 | 
			
		||||
      uint8_t *buf = allocator.allocate(max_length);
 | 
			
		||||
      if (buf != nullptr) {
 | 
			
		||||
        size_t read_index = 0;
 | 
			
		||||
        while (container->get_bytes_read() < max_length) {
 | 
			
		||||
          int read = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512));
 | 
			
		||||
          App.feed_wdt();
 | 
			
		||||
          yield();
 | 
			
		||||
          read_index += read;
 | 
			
		||||
        }
 | 
			
		||||
        response_body.reserve(read_index);
 | 
			
		||||
        response_body.assign((char *) buf, read_index);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (auto *trigger : this->response_triggers_) {
 | 
			
		||||
      trigger->process(container, response_body);
 | 
			
		||||
    }
 | 
			
		||||
    container->end();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
@@ -130,9 +171,9 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
 | 
			
		||||
  std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
 | 
			
		||||
  std::function<void(Ts..., JsonObject)> json_func_{nullptr};
 | 
			
		||||
  std::vector<HttpRequestResponseTrigger *> response_triggers_;
 | 
			
		||||
 | 
			
		||||
  size_t max_response_buffer_size_{SIZE_MAX};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										161
									
								
								esphome/components/http_request/http_request_arduino.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								esphome/components/http_request/http_request_arduino.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,161 @@
 | 
			
		||||
#include "http_request_arduino.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include "watchdog.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "http_request.arduino";
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::string method, std::string body,
 | 
			
		||||
                                                         std::list<Header> headers) {
 | 
			
		||||
  if (!network::is_connected()) {
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::shared_ptr<HttpContainerArduino> container = std::make_shared<HttpContainerArduino>();
 | 
			
		||||
  container->set_parent(this);
 | 
			
		||||
 | 
			
		||||
  const uint32_t start = millis();
 | 
			
		||||
 | 
			
		||||
  bool secure = url.find("https:") != std::string::npos;
 | 
			
		||||
  container->set_secure(secure);
 | 
			
		||||
 | 
			
		||||
  watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP8266)
 | 
			
		||||
  std::unique_ptr<WiFiClient> stream_ptr;
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
  if (secure) {
 | 
			
		||||
    ESP_LOGV(TAG, "ESP8266 HTTPS connection with WiFiClientSecure");
 | 
			
		||||
    stream_ptr = std::make_unique<WiFiClientSecure>();
 | 
			
		||||
    WiFiClientSecure *secure_client = static_cast<WiFiClientSecure *>(stream_ptr.get());
 | 
			
		||||
    secure_client->setBufferSizes(512, 512);
 | 
			
		||||
    secure_client->setInsecure();
 | 
			
		||||
  } else {
 | 
			
		||||
    stream_ptr = std::make_unique<WiFiClient>();
 | 
			
		||||
  }
 | 
			
		||||
#else
 | 
			
		||||
  ESP_LOGV(TAG, "ESP8266 HTTP connection with WiFiClient");
 | 
			
		||||
  if (secure) {
 | 
			
		||||
    ESP_LOGE(TAG, "Can't use HTTPS connection with esp8266_disable_ssl_support");
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
  stream_ptr = std::make_unique<WiFiClient>();
 | 
			
		||||
#endif  // USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
 | 
			
		||||
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 1, 0)  // && USE_ARDUINO_VERSION_CODE < VERSION_CODE(?, ?, ?)
 | 
			
		||||
  if (!secure) {
 | 
			
		||||
    ESP_LOGW(TAG, "Using HTTP on Arduino version >= 3.1 is **very** slow. Consider setting framework version to 3.0.2 "
 | 
			
		||||
                  "in your YAML, or use HTTPS");
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_ARDUINO_VERSION_CODE
 | 
			
		||||
 | 
			
		||||
  container->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
 | 
			
		||||
  bool status = container->client_.begin(*stream_ptr, url.c_str());
 | 
			
		||||
 | 
			
		||||
#elif defined(USE_RP2040)
 | 
			
		||||
  if (secure) {
 | 
			
		||||
    container->client_.setInsecure();
 | 
			
		||||
  }
 | 
			
		||||
  bool status = container->client_.begin(url.c_str());
 | 
			
		||||
#elif defined(USE_ESP32)
 | 
			
		||||
  bool status = container->client_.begin(url.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
 | 
			
		||||
  if (!status) {
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed; URL: %s", url.c_str());
 | 
			
		||||
    container->end();
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  container->client_.setReuse(true);
 | 
			
		||||
  container->client_.setTimeout(this->timeout_);
 | 
			
		||||
#if defined(USE_ESP32)
 | 
			
		||||
  container->client_.setConnectTimeout(this->timeout_);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (this->useragent_ != nullptr) {
 | 
			
		||||
    container->client_.setUserAgent(this->useragent_);
 | 
			
		||||
  }
 | 
			
		||||
  for (const auto &header : headers) {
 | 
			
		||||
    container->client_.addHeader(header.name, header.value, false, true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // returned needed headers must be collected before the requests
 | 
			
		||||
  static const char *header_keys[] = {"Content-Length", "Content-Type"};
 | 
			
		||||
  static const size_t HEADER_COUNT = sizeof(header_keys) / sizeof(header_keys[0]);
 | 
			
		||||
  container->client_.collectHeaders(header_keys, HEADER_COUNT);
 | 
			
		||||
 | 
			
		||||
  container->status_code = container->client_.sendRequest(method.c_str(), body.c_str());
 | 
			
		||||
  if (container->status_code < 0) {
 | 
			
		||||
    ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s", url.c_str(),
 | 
			
		||||
             HTTPClient::errorToString(container->status_code).c_str());
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    container->end();
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (container->status_code < 200 || container->status_code >= 300) {
 | 
			
		||||
    ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code);
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    container->end();
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int content_length = container->client_.getSize();
 | 
			
		||||
  ESP_LOGD(TAG, "Content-Length: %d", content_length);
 | 
			
		||||
  container->content_length = (size_t) content_length;
 | 
			
		||||
  container->duration_ms = millis() - start;
 | 
			
		||||
 | 
			
		||||
  return container;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int HttpContainerArduino::read(uint8_t *buf, size_t max_len) {
 | 
			
		||||
  const uint32_t start = millis();
 | 
			
		||||
  watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
 | 
			
		||||
 | 
			
		||||
  WiFiClient *stream_ptr = this->client_.getStreamPtr();
 | 
			
		||||
  if (stream_ptr == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Stream pointer vanished!");
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int available_data = stream_ptr->available();
 | 
			
		||||
  int bufsize = std::min(max_len, std::min(this->content_length - this->bytes_read_, (size_t) available_data));
 | 
			
		||||
 | 
			
		||||
  if (bufsize == 0) {
 | 
			
		||||
    this->duration_ms += (millis() - start);
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
  int read_len = stream_ptr->readBytes(buf, bufsize);
 | 
			
		||||
  this->bytes_read_ += read_len;
 | 
			
		||||
 | 
			
		||||
  this->duration_ms += (millis() - start);
 | 
			
		||||
 | 
			
		||||
  return read_len;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HttpContainerArduino::end() {
 | 
			
		||||
  watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
 | 
			
		||||
  this->client_.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
							
								
								
									
										40
									
								
								esphome/components/http_request/http_request_arduino.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								esphome/components/http_request/http_request_arduino.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "http_request.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_RP2040)
 | 
			
		||||
#include <HTTPClient.h>
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
#include <ESP8266HTTPClient.h>
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
 | 
			
		||||
#include <WiFiClientSecure.h>
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
class HttpRequestArduino;
 | 
			
		||||
class HttpContainerArduino : public HttpContainer {
 | 
			
		||||
 public:
 | 
			
		||||
  int read(uint8_t *buf, size_t max_len) override;
 | 
			
		||||
  void end() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  friend class HttpRequestArduino;
 | 
			
		||||
  HTTPClient client_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class HttpRequestArduino : public HttpRequestComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
 | 
			
		||||
                                       std::list<Header> headers) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
							
								
								
									
										155
									
								
								esphome/components/http_request/http_request_idf.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								esphome/components/http_request/http_request_idf.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
			
		||||
#include "http_request_idf.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
 | 
			
		||||
#include "esp_crt_bundle.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "watchdog.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "http_request.idf";
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::string method, std::string body,
 | 
			
		||||
                                                     std::list<Header> headers) {
 | 
			
		||||
  if (!network::is_connected()) {
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  esp_http_client_method_t method_idf;
 | 
			
		||||
  if (method == "GET") {
 | 
			
		||||
    method_idf = HTTP_METHOD_GET;
 | 
			
		||||
  } else if (method == "POST") {
 | 
			
		||||
    method_idf = HTTP_METHOD_POST;
 | 
			
		||||
  } else if (method == "PUT") {
 | 
			
		||||
    method_idf = HTTP_METHOD_PUT;
 | 
			
		||||
  } else if (method == "DELETE") {
 | 
			
		||||
    method_idf = HTTP_METHOD_DELETE;
 | 
			
		||||
  } else if (method == "PATCH") {
 | 
			
		||||
    method_idf = HTTP_METHOD_PATCH;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    ESP_LOGE(TAG, "HTTP Request failed; Unsupported method");
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool secure = url.find("https:") != std::string::npos;
 | 
			
		||||
 | 
			
		||||
  esp_http_client_config_t config = {};
 | 
			
		||||
 | 
			
		||||
  config.url = url.c_str();
 | 
			
		||||
  config.method = method_idf;
 | 
			
		||||
  config.timeout_ms = this->timeout_;
 | 
			
		||||
  config.disable_auto_redirect = !this->follow_redirects_;
 | 
			
		||||
  config.max_redirection_count = this->redirect_limit_;
 | 
			
		||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
 | 
			
		||||
  if (secure) {
 | 
			
		||||
    config.crt_bundle_attach = esp_crt_bundle_attach;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (this->useragent_ != nullptr) {
 | 
			
		||||
    config.user_agent = this->useragent_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const uint32_t start = millis();
 | 
			
		||||
  watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
 | 
			
		||||
 | 
			
		||||
  esp_http_client_handle_t client = esp_http_client_init(&config);
 | 
			
		||||
 | 
			
		||||
  std::shared_ptr<HttpContainerIDF> container = std::make_shared<HttpContainerIDF>(client);
 | 
			
		||||
  container->set_parent(this);
 | 
			
		||||
 | 
			
		||||
  container->set_secure(secure);
 | 
			
		||||
 | 
			
		||||
  for (const auto &header : headers) {
 | 
			
		||||
    esp_http_client_set_header(client, header.name, header.value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int body_len = body.length();
 | 
			
		||||
 | 
			
		||||
  esp_err_t err = esp_http_client_open(client, body_len);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err));
 | 
			
		||||
    esp_http_client_cleanup(client);
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (body_len > 0) {
 | 
			
		||||
    int write_left = body_len;
 | 
			
		||||
    int write_index = 0;
 | 
			
		||||
    const char *buf = body.c_str();
 | 
			
		||||
    while (body_len > 0) {
 | 
			
		||||
      int written = esp_http_client_write(client, buf + write_index, write_left);
 | 
			
		||||
      if (written < 0) {
 | 
			
		||||
        err = ESP_FAIL;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      write_left -= written;
 | 
			
		||||
      write_index += written;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err));
 | 
			
		||||
    esp_http_client_cleanup(client);
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  container->content_length = esp_http_client_fetch_headers(client);
 | 
			
		||||
  const auto status_code = esp_http_client_get_status_code(client);
 | 
			
		||||
  container->status_code = status_code;
 | 
			
		||||
 | 
			
		||||
  if (status_code < 200 || status_code >= 300) {
 | 
			
		||||
    ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), status_code);
 | 
			
		||||
    this->status_momentary_error("failed", 1000);
 | 
			
		||||
    esp_http_client_cleanup(client);
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
  container->duration_ms = millis() - start;
 | 
			
		||||
  return container;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
 | 
			
		||||
  const uint32_t start = millis();
 | 
			
		||||
  watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
 | 
			
		||||
 | 
			
		||||
  int bufsize = std::min(max_len, this->content_length - this->bytes_read_);
 | 
			
		||||
 | 
			
		||||
  if (bufsize == 0) {
 | 
			
		||||
    this->duration_ms += (millis() - start);
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
  int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize);
 | 
			
		||||
  this->bytes_read_ += read_len;
 | 
			
		||||
 | 
			
		||||
  this->duration_ms += (millis() - start);
 | 
			
		||||
 | 
			
		||||
  return read_len;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HttpContainerIDF::end() {
 | 
			
		||||
  watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
 | 
			
		||||
 | 
			
		||||
  esp_http_client_close(this->client_);
 | 
			
		||||
  esp_http_client_cleanup(this->client_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
							
								
								
									
										34
									
								
								esphome/components/http_request/http_request_idf.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								esphome/components/http_request/http_request_idf.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "http_request.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
#include <esp_event.h>
 | 
			
		||||
#include <esp_http_client.h>
 | 
			
		||||
#include <esp_netif.h>
 | 
			
		||||
#include <esp_tls.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
class HttpContainerIDF : public HttpContainer {
 | 
			
		||||
 public:
 | 
			
		||||
  HttpContainerIDF(esp_http_client_handle_t client) : client_(client) {}
 | 
			
		||||
  int read(uint8_t *buf, size_t max_len) override;
 | 
			
		||||
  void end() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  esp_http_client_handle_t client_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class HttpRequestIDF : public HttpRequestComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
 | 
			
		||||
                                       std::list<Header> headers) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
							
								
								
									
										100
									
								
								esphome/components/http_request/ota/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								esphome/components/http_request/ota/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_PASSWORD,
 | 
			
		||||
    CONF_URL,
 | 
			
		||||
    CONF_USERNAME,
 | 
			
		||||
)
 | 
			
		||||
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
 | 
			
		||||
from esphome.core import coroutine_with_priority
 | 
			
		||||
from .. import CONF_HTTP_REQUEST_ID, http_request_ns, HttpRequestComponent
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@oarcher"]
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["md5"]
 | 
			
		||||
DEPENDENCIES = ["network", "http_request"]
 | 
			
		||||
 | 
			
		||||
CONF_MD5 = "md5"
 | 
			
		||||
CONF_MD5_URL = "md5_url"
 | 
			
		||||
 | 
			
		||||
OtaHttpRequestComponent = http_request_ns.class_(
 | 
			
		||||
    "OtaHttpRequestComponent", OTAComponent
 | 
			
		||||
)
 | 
			
		||||
OtaHttpRequestComponentFlashAction = http_request_ns.class_(
 | 
			
		||||
    "OtaHttpRequestComponentFlashAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(OtaHttpRequestComponent),
 | 
			
		||||
            cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(BASE_OTA_SCHEMA)
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.require_framework_version(
 | 
			
		||||
        esp8266_arduino=cv.Version(2, 5, 1),
 | 
			
		||||
        esp32_arduino=cv.Version(0, 0, 0),
 | 
			
		||||
        esp_idf=cv.Version(0, 0, 0),
 | 
			
		||||
        rp2040_arduino=cv.Version(0, 0, 0),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(52.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await ota_to_code(var, config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await cg.register_parented(var, config[CONF_HTTP_REQUEST_ID])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.use_id(OtaHttpRequestComponent),
 | 
			
		||||
            cv.Optional(CONF_MD5_URL): cv.templatable(cv.url),
 | 
			
		||||
            cv.Optional(CONF_MD5): cv.templatable(
 | 
			
		||||
                cv.All(cv.string, cv.Length(min=32, max=32))
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PASSWORD): cv.templatable(cv.string),
 | 
			
		||||
            cv.Optional(CONF_USERNAME): cv.templatable(cv.string),
 | 
			
		||||
            cv.Required(CONF_URL): cv.templatable(cv.url),
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    cv.has_exactly_one_key(CONF_MD5, CONF_MD5_URL),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ota.http_request.flash",
 | 
			
		||||
    OtaHttpRequestComponentFlashAction,
 | 
			
		||||
    OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
async def ota_http_request_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
    if md5_url := config.get(CONF_MD5_URL):
 | 
			
		||||
        template_ = await cg.templatable(md5_url, args, cg.std_string)
 | 
			
		||||
        cg.add(var.set_md5_url(template_))
 | 
			
		||||
 | 
			
		||||
    if md5_str := config.get(CONF_MD5):
 | 
			
		||||
        template_ = await cg.templatable(md5_str, args, cg.std_string)
 | 
			
		||||
        cg.add(var.set_md5(template_))
 | 
			
		||||
 | 
			
		||||
    if password_str := config.get(CONF_PASSWORD):
 | 
			
		||||
        template_ = await cg.templatable(password_str, args, cg.std_string)
 | 
			
		||||
        cg.add(var.set_password(template_))
 | 
			
		||||
 | 
			
		||||
    if username_str := config.get(CONF_USERNAME):
 | 
			
		||||
        template_ = await cg.templatable(username_str, args, cg.std_string)
 | 
			
		||||
        cg.add(var.set_username(template_))
 | 
			
		||||
 | 
			
		||||
    template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
 | 
			
		||||
    cg.add(var.set_url(template_))
 | 
			
		||||
 | 
			
		||||
    return var
 | 
			
		||||
							
								
								
									
										42
									
								
								esphome/components/http_request/ota/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								esphome/components/http_request/ota/automation.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "ota_http_request.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class OtaHttpRequestComponentFlashAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  OtaHttpRequestComponentFlashAction(OtaHttpRequestComponent *parent) : parent_(parent) {}
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, md5_url)
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, md5)
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, password)
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, url)
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, username)
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    if (this->md5_url_.has_value()) {
 | 
			
		||||
      this->parent_->set_md5_url(this->md5_url_.value(x...));
 | 
			
		||||
    }
 | 
			
		||||
    if (this->md5_.has_value()) {
 | 
			
		||||
      this->parent_->set_md5(this->md5_.value(x...));
 | 
			
		||||
    }
 | 
			
		||||
    if (this->password_.has_value()) {
 | 
			
		||||
      this->parent_->set_password(this->password_.value(x...));
 | 
			
		||||
    }
 | 
			
		||||
    if (this->username_.has_value()) {
 | 
			
		||||
      this->parent_->set_username(this->username_.value(x...));
 | 
			
		||||
    }
 | 
			
		||||
    this->parent_->set_url(this->url_.value(x...));
 | 
			
		||||
 | 
			
		||||
    this->parent_->flash();
 | 
			
		||||
    // Normally never reached due to reboot
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  OtaHttpRequestComponent *parent_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										269
									
								
								esphome/components/http_request/ota/ota_http_request.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										269
									
								
								esphome/components/http_request/ota/ota_http_request.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,269 @@
 | 
			
		||||
#include "ota_http_request.h"
 | 
			
		||||
#include "../watchdog.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/md5/md5.h"
 | 
			
		||||
#include "esphome/components/ota/ota_backend.h"
 | 
			
		||||
#include "esphome/components/ota/ota_backend_arduino_esp32.h"
 | 
			
		||||
#include "esphome/components/ota/ota_backend_arduino_esp8266.h"
 | 
			
		||||
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
 | 
			
		||||
#include "esphome/components/ota/ota_backend_esp_idf.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "http_request.ota";
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponent::setup() {
 | 
			
		||||
#ifdef USE_OTA_STATE_CALLBACK
 | 
			
		||||
  ota::register_ota_platform(this);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, "Over-The-Air updates via HTTP request"); };
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponent::set_md5_url(const std::string &url) {
 | 
			
		||||
  if (!this->validate_url_(url)) {
 | 
			
		||||
    this->md5_url_.clear();  // URL was not valid; prevent flashing until it is
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->md5_url_ = url;
 | 
			
		||||
  this->md5_expected_.clear();  // to be retrieved later
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponent::set_url(const std::string &url) {
 | 
			
		||||
  if (!this->validate_url_(url)) {
 | 
			
		||||
    this->url_.clear();  // URL was not valid; prevent flashing until it is
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->url_ = url;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponent::flash() {
 | 
			
		||||
  if (this->url_.empty()) {
 | 
			
		||||
    ESP_LOGE(TAG, "URL not set; cannot start update");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "Starting update...");
 | 
			
		||||
#ifdef USE_OTA_STATE_CALLBACK
 | 
			
		||||
  this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  auto ota_status = this->do_ota_();
 | 
			
		||||
 | 
			
		||||
  switch (ota_status) {
 | 
			
		||||
    case ota::OTA_RESPONSE_OK:
 | 
			
		||||
#ifdef USE_OTA_STATE_CALLBACK
 | 
			
		||||
      this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, ota_status);
 | 
			
		||||
#endif
 | 
			
		||||
      delay(10);
 | 
			
		||||
      App.safe_reboot();
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    default:
 | 
			
		||||
#ifdef USE_OTA_STATE_CALLBACK
 | 
			
		||||
      this->state_callback_.call(ota::OTA_ERROR, 0.0f, ota_status);
 | 
			
		||||
#endif
 | 
			
		||||
      this->md5_computed_.clear();  // will be reset at next attempt
 | 
			
		||||
      this->md5_expected_.clear();  // will be reset at next attempt
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OtaHttpRequestComponent::cleanup_(std::unique_ptr<ota::OTABackend> backend,
 | 
			
		||||
                                       const std::shared_ptr<HttpContainer> &container) {
 | 
			
		||||
  if (this->update_started_) {
 | 
			
		||||
    ESP_LOGV(TAG, "Aborting OTA backend");
 | 
			
		||||
    backend->abort();
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "Aborting HTTP connection");
 | 
			
		||||
  container->end();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
uint8_t OtaHttpRequestComponent::do_ota_() {
 | 
			
		||||
  uint8_t buf[OtaHttpRequestComponent::HTTP_RECV_BUFFER + 1];
 | 
			
		||||
  uint32_t last_progress = 0;
 | 
			
		||||
  uint32_t update_start_time = millis();
 | 
			
		||||
  md5::MD5Digest md5_receive;
 | 
			
		||||
  std::unique_ptr<char[]> md5_receive_str(new char[33]);
 | 
			
		||||
 | 
			
		||||
  if (this->md5_expected_.empty() && !this->http_get_md5_()) {
 | 
			
		||||
    return OTA_MD5_INVALID;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "MD5 expected: %s", this->md5_expected_.c_str());
 | 
			
		||||
 | 
			
		||||
  auto url_with_auth = this->get_url_with_auth_(this->url_);
 | 
			
		||||
  if (url_with_auth.empty()) {
 | 
			
		||||
    return OTA_BAD_URL;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
 | 
			
		||||
  ESP_LOGI(TAG, "Connecting to: %s", this->url_.c_str());
 | 
			
		||||
 | 
			
		||||
  auto container = this->parent_->get(url_with_auth);
 | 
			
		||||
 | 
			
		||||
  if (container == nullptr) {
 | 
			
		||||
    return OTA_CONNECTION_ERROR;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // we will compute MD5 on the fly for verification -- Arduino OTA seems to ignore it
 | 
			
		||||
  md5_receive.init();
 | 
			
		||||
  ESP_LOGV(TAG, "MD5Digest initialized");
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "OTA backend begin");
 | 
			
		||||
  auto backend = ota::make_ota_backend();
 | 
			
		||||
  auto error_code = backend->begin(container->content_length);
 | 
			
		||||
  if (error_code != ota::OTA_RESPONSE_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "backend->begin error: %d", error_code);
 | 
			
		||||
    this->cleanup_(std::move(backend), container);
 | 
			
		||||
    return error_code;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  while (container->get_bytes_read() < container->content_length) {
 | 
			
		||||
    // read a maximum of chunk_size bytes into buf. (real read size returned)
 | 
			
		||||
    int bufsize = container->read(buf, OtaHttpRequestComponent::HTTP_RECV_BUFFER);
 | 
			
		||||
    ESP_LOGVV(TAG, "bytes_read_ = %u, body_length_ = %u, bufsize = %i", container->get_bytes_read(),
 | 
			
		||||
              container->content_length, bufsize);
 | 
			
		||||
 | 
			
		||||
    // feed watchdog and give other tasks a chance to run
 | 
			
		||||
    App.feed_wdt();
 | 
			
		||||
    yield();
 | 
			
		||||
 | 
			
		||||
    if (bufsize < 0) {
 | 
			
		||||
      ESP_LOGE(TAG, "Stream closed");
 | 
			
		||||
      this->cleanup_(std::move(backend), container);
 | 
			
		||||
      return OTA_CONNECTION_ERROR;
 | 
			
		||||
    } else if (bufsize > 0 && bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) {
 | 
			
		||||
      // add read bytes to MD5
 | 
			
		||||
      md5_receive.add(buf, bufsize);
 | 
			
		||||
 | 
			
		||||
      // write bytes to OTA backend
 | 
			
		||||
      this->update_started_ = true;
 | 
			
		||||
      error_code = backend->write(buf, bufsize);
 | 
			
		||||
      if (error_code != ota::OTA_RESPONSE_OK) {
 | 
			
		||||
        // error code explanation available at
 | 
			
		||||
        // https://github.com/esphome/esphome/blob/dev/esphome/components/ota/ota_backend.h
 | 
			
		||||
        ESP_LOGE(TAG, "Error code (%02X) writing binary data to flash at offset %d and size %d", error_code,
 | 
			
		||||
                 container->get_bytes_read() - bufsize, container->content_length);
 | 
			
		||||
        this->cleanup_(std::move(backend), container);
 | 
			
		||||
        return error_code;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    uint32_t now = millis();
 | 
			
		||||
    if ((now - last_progress > 1000) or (container->get_bytes_read() == container->content_length)) {
 | 
			
		||||
      last_progress = now;
 | 
			
		||||
      float percentage = container->get_bytes_read() * 100.0f / container->content_length;
 | 
			
		||||
      ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
 | 
			
		||||
#ifdef USE_OTA_STATE_CALLBACK
 | 
			
		||||
      this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0);
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
  }  // while
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "Done in %.0f seconds", float(millis() - update_start_time) / 1000);
 | 
			
		||||
 | 
			
		||||
  // verify MD5 is as expected and act accordingly
 | 
			
		||||
  md5_receive.calculate();
 | 
			
		||||
  md5_receive.get_hex(md5_receive_str.get());
 | 
			
		||||
  this->md5_computed_ = md5_receive_str.get();
 | 
			
		||||
  if (strncmp(this->md5_computed_.c_str(), this->md5_expected_.c_str(), MD5_SIZE) != 0) {
 | 
			
		||||
    ESP_LOGE(TAG, "MD5 computed: %s - Aborting due to MD5 mismatch", this->md5_computed_.c_str());
 | 
			
		||||
    this->cleanup_(std::move(backend), container);
 | 
			
		||||
    return ota::OTA_RESPONSE_ERROR_MD5_MISMATCH;
 | 
			
		||||
  } else {
 | 
			
		||||
    backend->set_update_md5(md5_receive_str.get());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  container->end();
 | 
			
		||||
 | 
			
		||||
  // feed watchdog and give other tasks a chance to run
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
  yield();
 | 
			
		||||
  delay(100);  // NOLINT
 | 
			
		||||
 | 
			
		||||
  error_code = backend->end();
 | 
			
		||||
  if (error_code != ota::OTA_RESPONSE_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error ending update! error_code: %d", error_code);
 | 
			
		||||
    this->cleanup_(std::move(backend), container);
 | 
			
		||||
    return error_code;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "Update complete");
 | 
			
		||||
  return ota::OTA_RESPONSE_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string OtaHttpRequestComponent::get_url_with_auth_(const std::string &url) {
 | 
			
		||||
  if (this->username_.empty() || this->password_.empty()) {
 | 
			
		||||
    return url;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto start_char = url.find("://");
 | 
			
		||||
  if ((start_char == std::string::npos) || (start_char < 4)) {
 | 
			
		||||
    ESP_LOGE(TAG, "Incorrect URL prefix");
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Using basic HTTP authentication");
 | 
			
		||||
 | 
			
		||||
  start_char += 3;  // skip '://' characters
 | 
			
		||||
  auto url_with_auth =
 | 
			
		||||
      url.substr(0, start_char) + this->username_ + ":" + this->password_ + "@" + url.substr(start_char);
 | 
			
		||||
  return url_with_auth;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool OtaHttpRequestComponent::http_get_md5_() {
 | 
			
		||||
  if (this->md5_url_.empty()) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto url_with_auth = this->get_url_with_auth_(this->md5_url_);
 | 
			
		||||
  if (url_with_auth.empty()) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
 | 
			
		||||
  ESP_LOGI(TAG, "Connecting to: %s", this->md5_url_.c_str());
 | 
			
		||||
  auto container = this->parent_->get(url_with_auth);
 | 
			
		||||
  if (container == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Failed to connect to MD5 URL");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  size_t length = container->content_length;
 | 
			
		||||
  if (length == 0) {
 | 
			
		||||
    container->end();
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if (length < MD5_SIZE) {
 | 
			
		||||
    ESP_LOGE(TAG, "MD5 file must be %u bytes; %u bytes reported by HTTP server. Aborting", MD5_SIZE, length);
 | 
			
		||||
    container->end();
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->md5_expected_.resize(MD5_SIZE);
 | 
			
		||||
  int read_len = 0;
 | 
			
		||||
  while (container->get_bytes_read() < MD5_SIZE) {
 | 
			
		||||
    read_len = container->read((uint8_t *) this->md5_expected_.data(), MD5_SIZE);
 | 
			
		||||
    App.feed_wdt();
 | 
			
		||||
    yield();
 | 
			
		||||
  }
 | 
			
		||||
  container->end();
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Read len: %u, MD5 expected: %u", read_len, MD5_SIZE);
 | 
			
		||||
  return read_len == MD5_SIZE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool OtaHttpRequestComponent::validate_url_(const std::string &url) {
 | 
			
		||||
  if ((url.length() < 8) || (url.find("http") != 0) || (url.find("://") == std::string::npos)) {
 | 
			
		||||
    ESP_LOGE(TAG, "URL is invalid and/or must be prefixed with 'http://' or 'https://'");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										61
									
								
								esphome/components/http_request/ota/ota_http_request.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								esphome/components/http_request/ota/ota_http_request.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/ota/ota_backend.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include "../http_request.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
static const uint8_t MD5_SIZE = 32;
 | 
			
		||||
 | 
			
		||||
enum OtaHttpRequestError : uint8_t {
 | 
			
		||||
  OTA_MD5_INVALID = 0x10,
 | 
			
		||||
  OTA_BAD_URL = 0x11,
 | 
			
		||||
  OTA_CONNECTION_ERROR = 0x12,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class OtaHttpRequestComponent : public ota::OTAComponent, public Parented<HttpRequestComponent> {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
 | 
			
		||||
 | 
			
		||||
  void set_md5_url(const std::string &md5_url);
 | 
			
		||||
  void set_md5(const std::string &md5) { this->md5_expected_ = md5; }
 | 
			
		||||
  void set_password(const std::string &password) { this->password_ = password; }
 | 
			
		||||
  void set_url(const std::string &url);
 | 
			
		||||
  void set_username(const std::string &username) { this->username_ = username; }
 | 
			
		||||
 | 
			
		||||
  std::string md5_computed() { return this->md5_computed_; }
 | 
			
		||||
  std::string md5_expected() { return this->md5_expected_; }
 | 
			
		||||
 | 
			
		||||
  void flash();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void cleanup_(std::unique_ptr<ota::OTABackend> backend, const std::shared_ptr<HttpContainer> &container);
 | 
			
		||||
  uint8_t do_ota_();
 | 
			
		||||
  std::string get_url_with_auth_(const std::string &url);
 | 
			
		||||
  bool http_get_md5_();
 | 
			
		||||
  bool validate_url_(const std::string &url);
 | 
			
		||||
 | 
			
		||||
  std::string md5_computed_{};
 | 
			
		||||
  std::string md5_expected_{};
 | 
			
		||||
  std::string md5_url_{};
 | 
			
		||||
  std::string password_{};
 | 
			
		||||
  std::string username_{};
 | 
			
		||||
  std::string url_{};
 | 
			
		||||
  int status_ = -1;
 | 
			
		||||
  bool update_started_ = false;
 | 
			
		||||
  static const uint16_t HTTP_RECV_BUFFER = 256;  // the firmware GET chunk size
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										44
									
								
								esphome/components/http_request/update/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								esphome/components/http_request/update/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
from esphome.components import update
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_SOURCE,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from .. import http_request_ns, CONF_HTTP_REQUEST_ID, HttpRequestComponent
 | 
			
		||||
from ..ota import OtaHttpRequestComponent
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["json"]
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
DEPENDENCIES = ["ota.http_request"]
 | 
			
		||||
 | 
			
		||||
HttpRequestUpdate = http_request_ns.class_(
 | 
			
		||||
    "HttpRequestUpdate", update.UpdateEntity, cg.PollingComponent
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_OTA_ID = "ota_id"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = update.UPDATE_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(HttpRequestUpdate),
 | 
			
		||||
        cv.GenerateID(CONF_OTA_ID): cv.use_id(OtaHttpRequestComponent),
 | 
			
		||||
        cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
 | 
			
		||||
        cv.Required(CONF_SOURCE): cv.url,
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.polling_component_schema("6h"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await update.new_update(config)
 | 
			
		||||
    ota_parent = await cg.get_variable(config[CONF_OTA_ID])
 | 
			
		||||
    cg.add(var.set_ota_parent(ota_parent))
 | 
			
		||||
    request_parent = await cg.get_variable(config[CONF_HTTP_REQUEST_ID])
 | 
			
		||||
    cg.add(var.set_request_parent(request_parent))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_source_url(config[CONF_SOURCE]))
 | 
			
		||||
 | 
			
		||||
    cg.add_define("USE_OTA_STATE_CALLBACK")
 | 
			
		||||
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
							
								
								
									
										157
									
								
								esphome/components/http_request/update/http_request_update.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								esphome/components/http_request/update/http_request_update.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
#include "http_request_update.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/version.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/json/json_util.h"
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "http_request.update";
 | 
			
		||||
 | 
			
		||||
static const size_t MAX_READ_SIZE = 256;
 | 
			
		||||
 | 
			
		||||
void HttpRequestUpdate::setup() {
 | 
			
		||||
  this->ota_parent_->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t err) {
 | 
			
		||||
    if (state == ota::OTAState::OTA_IN_PROGRESS) {
 | 
			
		||||
      this->state_ = update::UPDATE_STATE_INSTALLING;
 | 
			
		||||
      this->update_info_.has_progress = true;
 | 
			
		||||
      this->update_info_.progress = progress;
 | 
			
		||||
      this->publish_state();
 | 
			
		||||
    } else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
 | 
			
		||||
      this->state_ = update::UPDATE_STATE_AVAILABLE;
 | 
			
		||||
      this->status_set_error("Failed to install firmware");
 | 
			
		||||
      this->publish_state();
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HttpRequestUpdate::update() {
 | 
			
		||||
  auto container = this->request_parent_->get(this->source_url_);
 | 
			
		||||
 | 
			
		||||
  if (container == nullptr) {
 | 
			
		||||
    std::string msg = str_sprintf("Failed to fetch manifest from %s", this->source_url_.c_str());
 | 
			
		||||
    this->status_set_error(msg.c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
 | 
			
		||||
  uint8_t *data = allocator.allocate(container->content_length);
 | 
			
		||||
  if (data == nullptr) {
 | 
			
		||||
    std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length);
 | 
			
		||||
    this->status_set_error(msg.c_str());
 | 
			
		||||
    container->end();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  size_t read_index = 0;
 | 
			
		||||
  while (container->get_bytes_read() < container->content_length) {
 | 
			
		||||
    int read_bytes = container->read(data + read_index, MAX_READ_SIZE);
 | 
			
		||||
 | 
			
		||||
    App.feed_wdt();
 | 
			
		||||
    yield();
 | 
			
		||||
 | 
			
		||||
    read_index += read_bytes;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::string response((char *) data, read_index);
 | 
			
		||||
  allocator.deallocate(data, container->content_length);
 | 
			
		||||
 | 
			
		||||
  container->end();
 | 
			
		||||
 | 
			
		||||
  bool valid = json::parse_json(response, [this](JsonObject root) -> bool {
 | 
			
		||||
    if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) {
 | 
			
		||||
      ESP_LOGE(TAG, "Manifest does not contain required fields");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    this->update_info_.title = root["name"].as<std::string>();
 | 
			
		||||
    this->update_info_.latest_version = root["version"].as<std::string>();
 | 
			
		||||
 | 
			
		||||
    for (auto build : root["builds"].as<JsonArray>()) {
 | 
			
		||||
      if (!build.containsKey("chipFamily")) {
 | 
			
		||||
        ESP_LOGE(TAG, "Manifest does not contain required fields");
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      if (build["chipFamily"] == ESPHOME_VARIANT) {
 | 
			
		||||
        if (!build.containsKey("ota")) {
 | 
			
		||||
          ESP_LOGE(TAG, "Manifest does not contain required fields");
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
        auto ota = build["ota"];
 | 
			
		||||
        if (!ota.containsKey("path") || !ota.containsKey("md5")) {
 | 
			
		||||
          ESP_LOGE(TAG, "Manifest does not contain required fields");
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
        this->update_info_.firmware_url = ota["path"].as<std::string>();
 | 
			
		||||
        this->update_info_.md5 = ota["md5"].as<std::string>();
 | 
			
		||||
 | 
			
		||||
        if (ota.containsKey("summary"))
 | 
			
		||||
          this->update_info_.summary = ota["summary"].as<std::string>();
 | 
			
		||||
        if (ota.containsKey("release_url"))
 | 
			
		||||
          this->update_info_.release_url = ota["release_url"].as<std::string>();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  if (!valid) {
 | 
			
		||||
    std::string msg = str_sprintf("Failed to parse JSON from %s", this->source_url_.c_str());
 | 
			
		||||
    this->status_set_error(msg.c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Merge source_url_ and this->update_info_.firmware_url
 | 
			
		||||
  if (this->update_info_.firmware_url.find("http") == std::string::npos) {
 | 
			
		||||
    std::string path = this->update_info_.firmware_url;
 | 
			
		||||
    if (path[0] == '/') {
 | 
			
		||||
      std::string domain = this->source_url_.substr(0, this->source_url_.find('/', 8));
 | 
			
		||||
      this->update_info_.firmware_url = domain + path;
 | 
			
		||||
    } else {
 | 
			
		||||
      std::string domain = this->source_url_.substr(0, this->source_url_.rfind('/') + 1);
 | 
			
		||||
      this->update_info_.firmware_url = domain + path;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::string current_version = this->current_version_;
 | 
			
		||||
  if (current_version.empty()) {
 | 
			
		||||
#ifdef ESPHOME_PROJECT_VERSION
 | 
			
		||||
    current_version = ESPHOME_PROJECT_VERSION;
 | 
			
		||||
#else
 | 
			
		||||
    current_version = ESPHOME_VERSION;
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
  this->update_info_.current_version = current_version;
 | 
			
		||||
 | 
			
		||||
  if (this->update_info_.latest_version.empty()) {
 | 
			
		||||
    this->state_ = update::UPDATE_STATE_NO_UPDATE;
 | 
			
		||||
  } else if (this->update_info_.latest_version != this->current_version_) {
 | 
			
		||||
    this->state_ = update::UPDATE_STATE_AVAILABLE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->update_info_.has_progress = false;
 | 
			
		||||
  this->update_info_.progress = 0.0f;
 | 
			
		||||
 | 
			
		||||
  this->status_clear_error();
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HttpRequestUpdate::perform() {
 | 
			
		||||
  if (this->state_ != update::UPDATE_STATE_AVAILABLE) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->state_ = update::UPDATE_STATE_INSTALLING;
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
 | 
			
		||||
  this->ota_parent_->set_md5(this->update_info.md5);
 | 
			
		||||
  this->ota_parent_->set_url(this->update_info.firmware_url);
 | 
			
		||||
  // Flash in the next loop
 | 
			
		||||
  this->defer([this]() { this->ota_parent_->flash(); });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										37
									
								
								esphome/components/http_request/update/http_request_update.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								esphome/components/http_request/update/http_request_update.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/http_request/http_request.h"
 | 
			
		||||
#include "esphome/components/http_request/ota/ota_http_request.h"
 | 
			
		||||
#include "esphome/components/update/update_entity.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
  void perform() override;
 | 
			
		||||
 | 
			
		||||
  void set_source_url(const std::string &source_url) { this->source_url_ = source_url; }
 | 
			
		||||
 | 
			
		||||
  void set_request_parent(HttpRequestComponent *request_parent) { this->request_parent_ = request_parent; }
 | 
			
		||||
  void set_ota_parent(OtaHttpRequestComponent *ota_parent) { this->ota_parent_ = ota_parent; }
 | 
			
		||||
 | 
			
		||||
  void set_current_version(const std::string ¤t_version) { this->current_version_ = current_version; }
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  HttpRequestComponent *request_parent_;
 | 
			
		||||
  OtaHttpRequestComponent *ota_parent_;
 | 
			
		||||
  std::string source_url_;
 | 
			
		||||
  std::string current_version_{""};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										76
									
								
								esphome/components/http_request/watchdog.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								esphome/components/http_request/watchdog.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
#include "watchdog.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include "esp_idf_version.h"
 | 
			
		||||
#include "esp_task_wdt.h"
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
#include "hardware/watchdog.h"
 | 
			
		||||
#include "pico/stdlib.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
namespace watchdog {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "http_request.watchdog";
 | 
			
		||||
 | 
			
		||||
WatchdogManager::WatchdogManager(uint32_t timeout_ms) : timeout_ms_(timeout_ms) {
 | 
			
		||||
  if (timeout_ms == 0) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->saved_timeout_ms_ = this->get_timeout_();
 | 
			
		||||
  this->set_timeout_(timeout_ms);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WatchdogManager::~WatchdogManager() {
 | 
			
		||||
  if (this->timeout_ms_ == 0) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->set_timeout_(this->saved_timeout_ms_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WatchdogManager::set_timeout_(uint32_t timeout_ms) {
 | 
			
		||||
  ESP_LOGV(TAG, "Adjusting WDT to %" PRIu32 "ms", timeout_ms);
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#if ESP_IDF_VERSION_MAJOR >= 5
 | 
			
		||||
  esp_task_wdt_config_t wdt_config = {
 | 
			
		||||
      .timeout_ms = timeout_ms,
 | 
			
		||||
      .idle_core_mask = 0x03,
 | 
			
		||||
      .trigger_panic = true,
 | 
			
		||||
  };
 | 
			
		||||
  esp_task_wdt_reconfigure(&wdt_config);
 | 
			
		||||
#else
 | 
			
		||||
  esp_task_wdt_init(timeout_ms, true);
 | 
			
		||||
#endif  // ESP_IDF_VERSION_MAJOR
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  watchdog_enable(timeout_ms, true);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t WatchdogManager::get_timeout_() {
 | 
			
		||||
  uint32_t timeout_ms = 0;
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  timeout_ms = (uint32_t) CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000;
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  timeout_ms = watchdog_get_count() / 1000;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  ESP_LOGVV(TAG, "get_timeout: %" PRIu32 "ms", timeout_ms);
 | 
			
		||||
 | 
			
		||||
  return timeout_ms;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace watchdog
 | 
			
		||||
}  // namespace http_request
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user