mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	
							
								
								
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,6 +11,7 @@ on: | |||||||
|       - ".github/workflows/**" |       - ".github/workflows/**" | ||||||
|       - "requirements*.txt" |       - "requirements*.txt" | ||||||
|       - "platformio.ini" |       - "platformio.ini" | ||||||
|  |       - "script/platformio_install_deps.py" | ||||||
|  |  | ||||||
|   pull_request: |   pull_request: | ||||||
|     paths: |     paths: | ||||||
| @@ -18,6 +19,7 @@ on: | |||||||
|       - ".github/workflows/**" |       - ".github/workflows/**" | ||||||
|       - "requirements*.txt" |       - "requirements*.txt" | ||||||
|       - "platformio.ini" |       - "platformio.ini" | ||||||
|  |       - "script/platformio_install_deps.py" | ||||||
|  |  | ||||||
| permissions: | permissions: | ||||||
|   contents: read |   contents: read | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -23,6 +23,7 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|  |       max-parallel: 5 | ||||||
|       matrix: |       matrix: | ||||||
|         include: |         include: | ||||||
|           - id: ci-custom |           - id: ci-custom | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -117,7 +117,7 @@ jobs: | |||||||
|             --suffix "${{ matrix.image.suffix }}" |             --suffix "${{ matrix.image.suffix }}" | ||||||
|  |  | ||||||
|       - name: Build and push |       - name: Build and push | ||||||
|         uses: docker/build-push-action@v3 |         uses: docker/build-push-action@v4 | ||||||
|         with: |         with: | ||||||
|           context: . |           context: . | ||||||
|           file: ./docker/Dockerfile |           file: ./docker/Dockerfile | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							| @@ -48,7 +48,7 @@ jobs: | |||||||
|           echo "$delimiter" >> $GITHUB_OUTPUT |           echo "$delimiter" >> $GITHUB_OUTPUT | ||||||
|  |  | ||||||
|       - name: Commit changes |       - name: Commit changes | ||||||
|         uses: peter-evans/create-pull-request@v4 |         uses: peter-evans/create-pull-request@v5 | ||||||
|         with: |         with: | ||||||
|           commit-message: "Synchronise Device Classes from Home Assistant" |           commit-message: "Synchronise Device Classes from Home Assistant" | ||||||
|           committer: esphomebot <esphome@nabucasa.com> |           committer: esphomebot <esphome@nabucasa.com> | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ repos: | |||||||
|           - --branch=release |           - --branch=release | ||||||
|           - --branch=beta |           - --branch=beta | ||||||
|   - repo: https://github.com/asottile/pyupgrade |   - repo: https://github.com/asottile/pyupgrade | ||||||
|     rev: v3.3.1 |     rev: v3.3.2 | ||||||
|     hooks: |     hooks: | ||||||
|       - id: pyupgrade |       - id: pyupgrade | ||||||
|         args: [--py39-plus] |         args: [--py39-plus] | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								CODEOWNERS
									
									
									
									
									
								
							| @@ -21,6 +21,7 @@ esphome/components/airthings_wave_mini/* @ncareau | |||||||
| esphome/components/airthings_wave_plus/* @jeromelaban | esphome/components/airthings_wave_plus/* @jeromelaban | ||||||
| esphome/components/am43/* @buxtronix | esphome/components/am43/* @buxtronix | ||||||
| esphome/components/am43/cover/* @buxtronix | esphome/components/am43/cover/* @buxtronix | ||||||
|  | esphome/components/am43/sensor/* @buxtronix | ||||||
| esphome/components/analog_threshold/* @ianchi | esphome/components/analog_threshold/* @ianchi | ||||||
| esphome/components/animation/* @syndlex | esphome/components/animation/* @syndlex | ||||||
| esphome/components/anova/* @buxtronix | esphome/components/anova/* @buxtronix | ||||||
| @@ -83,6 +84,7 @@ esphome/components/esp32_ble_server/* @jesserockz | |||||||
| esphome/components/esp32_camera_web_server/* @ayufan | esphome/components/esp32_camera_web_server/* @ayufan | ||||||
| esphome/components/esp32_can/* @Sympatron | esphome/components/esp32_can/* @Sympatron | ||||||
| esphome/components/esp32_improv/* @jesserockz | esphome/components/esp32_improv/* @jesserockz | ||||||
|  | esphome/components/esp32_rmt_led_strip/* @jesserockz | ||||||
| esphome/components/esp8266/* @esphome/core | esphome/components/esp8266/* @esphome/core | ||||||
| esphome/components/ethernet_info/* @gtjadsonsantos | esphome/components/ethernet_info/* @gtjadsonsantos | ||||||
| esphome/components/exposure_notifications/* @OttoWinter | esphome/components/exposure_notifications/* @OttoWinter | ||||||
| @@ -94,6 +96,7 @@ esphome/components/feedback/* @ianchi | |||||||
| esphome/components/fingerprint_grow/* @OnFreund @loongyh | esphome/components/fingerprint_grow/* @OnFreund @loongyh | ||||||
| esphome/components/fs3000/* @kahrendt | esphome/components/fs3000/* @kahrendt | ||||||
| esphome/components/globals/* @esphome/core | esphome/components/globals/* @esphome/core | ||||||
|  | esphome/components/gp8403/* @jesserockz | ||||||
| esphome/components/gpio/* @esphome/core | esphome/components/gpio/* @esphome/core | ||||||
| esphome/components/gps/* @coogle | esphome/components/gps/* @coogle | ||||||
| esphome/components/graph/* @synco | esphome/components/graph/* @synco | ||||||
| @@ -106,13 +109,16 @@ esphome/components/heatpumpir/* @rob-deutsch | |||||||
| esphome/components/hitachi_ac424/* @sourabhjaiswal | esphome/components/hitachi_ac424/* @sourabhjaiswal | ||||||
| esphome/components/homeassistant/* @OttoWinter | esphome/components/homeassistant/* @OttoWinter | ||||||
| esphome/components/honeywellabp/* @RubyBailey | esphome/components/honeywellabp/* @RubyBailey | ||||||
|  | esphome/components/host/* @esphome/core | ||||||
| esphome/components/hrxl_maxsonar_wr/* @netmikey | esphome/components/hrxl_maxsonar_wr/* @netmikey | ||||||
| esphome/components/hte501/* @Stock-M | esphome/components/hte501/* @Stock-M | ||||||
| esphome/components/hydreon_rgxx/* @functionpointer | esphome/components/hydreon_rgxx/* @functionpointer | ||||||
|  | esphome/components/hyt271/* @Philippe12 | ||||||
| esphome/components/i2c/* @esphome/core | esphome/components/i2c/* @esphome/core | ||||||
| esphome/components/i2s_audio/* @jesserockz | esphome/components/i2s_audio/* @jesserockz | ||||||
| esphome/components/i2s_audio/media_player/* @jesserockz | esphome/components/i2s_audio/media_player/* @jesserockz | ||||||
| esphome/components/i2s_audio/microphone/* @jesserockz | esphome/components/i2s_audio/microphone/* @jesserockz | ||||||
|  | esphome/components/i2s_audio/speaker/* @jesserockz | ||||||
| esphome/components/ili9xxx/* @nielsnl68 | esphome/components/ili9xxx/* @nielsnl68 | ||||||
| esphome/components/improv_base/* @esphome/core | esphome/components/improv_base/* @esphome/core | ||||||
| esphome/components/improv_serial/* @esphome/core | esphome/components/improv_serial/* @esphome/core | ||||||
| @@ -138,6 +144,7 @@ esphome/components/ltr390/* @sjtrny | |||||||
| esphome/components/matrix_keypad/* @ssieb | esphome/components/matrix_keypad/* @ssieb | ||||||
| esphome/components/max31865/* @DAVe3283 | esphome/components/max31865/* @DAVe3283 | ||||||
| esphome/components/max44009/* @berfenger | esphome/components/max44009/* @berfenger | ||||||
|  | esphome/components/max6956/* @looping40 | ||||||
| esphome/components/max7219digit/* @rspaargaren | esphome/components/max7219digit/* @rspaargaren | ||||||
| esphome/components/max9611/* @mckaymatthew | esphome/components/max9611/* @mckaymatthew | ||||||
| esphome/components/mcp23008/* @jesserockz | esphome/components/mcp23008/* @jesserockz | ||||||
| @@ -162,6 +169,7 @@ esphome/components/midea/* @dudanov | |||||||
| esphome/components/midea_ir/* @dudanov | esphome/components/midea_ir/* @dudanov | ||||||
| esphome/components/mitsubishi/* @RubyBailey | esphome/components/mitsubishi/* @RubyBailey | ||||||
| esphome/components/mlx90393/* @functionpointer | esphome/components/mlx90393/* @functionpointer | ||||||
|  | esphome/components/mlx90614/* @jesserockz | ||||||
| esphome/components/mmc5603/* @benhoff | esphome/components/mmc5603/* @benhoff | ||||||
| esphome/components/modbus_controller/* @martgras | esphome/components/modbus_controller/* @martgras | ||||||
| esphome/components/modbus_controller/binary_sensor/* @martgras | esphome/components/modbus_controller/binary_sensor/* @martgras | ||||||
| @@ -186,6 +194,7 @@ esphome/components/nfc/* @jesserockz | |||||||
| esphome/components/number/* @esphome/core | esphome/components/number/* @esphome/core | ||||||
| esphome/components/ota/* @esphome/core | esphome/components/ota/* @esphome/core | ||||||
| esphome/components/output/* @esphome/core | esphome/components/output/* @esphome/core | ||||||
|  | esphome/components/pca6416a/* @Mat931 | ||||||
| esphome/components/pca9554/* @hwstar | esphome/components/pca9554/* @hwstar | ||||||
| esphome/components/pcf85063/* @brogon | esphome/components/pcf85063/* @brogon | ||||||
| esphome/components/pid/* @OttoWinter | esphome/components/pid/* @OttoWinter | ||||||
| @@ -232,7 +241,7 @@ esphome/components/shutdown/* @esphome/core @jsuanet | |||||||
| esphome/components/sigma_delta_output/* @Cat-Ion | esphome/components/sigma_delta_output/* @Cat-Ion | ||||||
| esphome/components/sim800l/* @glmnet | esphome/components/sim800l/* @glmnet | ||||||
| esphome/components/sm10bit_base/* @Cossid | esphome/components/sm10bit_base/* @Cossid | ||||||
| esphome/components/sm2135/* @BoukeHaarsma23 | esphome/components/sm2135/* @BoukeHaarsma23 @dd32 @matika77 | ||||||
| esphome/components/sm2235/* @Cossid | esphome/components/sm2235/* @Cossid | ||||||
| esphome/components/sm2335/* @Cossid | esphome/components/sm2335/* @Cossid | ||||||
| esphome/components/sml/* @alengwenus | esphome/components/sml/* @alengwenus | ||||||
| @@ -240,6 +249,7 @@ esphome/components/smt100/* @piechade | |||||||
| esphome/components/sn74hc165/* @jesserockz | esphome/components/sn74hc165/* @jesserockz | ||||||
| esphome/components/socket/* @esphome/core | esphome/components/socket/* @esphome/core | ||||||
| esphome/components/sonoff_d1/* @anatoly-savchenkov | esphome/components/sonoff_d1/* @anatoly-savchenkov | ||||||
|  | esphome/components/speaker/* @jesserockz | ||||||
| esphome/components/spi/* @esphome/core | esphome/components/spi/* @esphome/core | ||||||
| esphome/components/sprinkler/* @kbx81 | esphome/components/sprinkler/* @kbx81 | ||||||
| esphome/components/sps30/* @martgras | esphome/components/sps30/* @martgras | ||||||
|   | |||||||
| @@ -24,8 +24,9 @@ RUN \ | |||||||
|         python3-setuptools=52.0.0-4 \ |         python3-setuptools=52.0.0-4 \ | ||||||
|         python3-pil=8.1.2+dfsg-0.3+deb11u1 \ |         python3-pil=8.1.2+dfsg-0.3+deb11u1 \ | ||||||
|         python3-cryptography=3.3.2-1 \ |         python3-cryptography=3.3.2-1 \ | ||||||
|  |         python3-venv=3.9.2-3 \ | ||||||
|         iputils-ping=3:20210202-1 \ |         iputils-ping=3:20210202-1 \ | ||||||
|         git=1:2.30.2-1 \ |         git=1:2.30.2-1+deb11u2 \ | ||||||
|         curl=7.74.0-1.3+deb11u7 \ |         curl=7.74.0-1.3+deb11u7 \ | ||||||
|         openssh-client=1:8.4p1-5+deb11u1 \ |         openssh-client=1:8.4p1-5+deb11u1 \ | ||||||
|     && rm -rf \ |     && rm -rf \ | ||||||
| @@ -59,10 +60,10 @@ RUN \ | |||||||
|  |  | ||||||
|  |  | ||||||
| # First install requirements to leverage caching when requirements don't change | # First install requirements to leverage caching when requirements don't change | ||||||
| COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / | COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini / | ||||||
| RUN \ | RUN \ | ||||||
|     pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ |     pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ | ||||||
|     && /platformio_install_deps.py /platformio.ini |     && /platformio_install_deps.py /platformio.ini --libraries | ||||||
|  |  | ||||||
|  |  | ||||||
| # ======================= docker-type image ======================= | # ======================= docker-type image ======================= | ||||||
|   | |||||||
| @@ -1,30 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # This script is used in the docker containers to preinstall |  | ||||||
| # all platformio libraries in the global storage |  | ||||||
|  |  | ||||||
| import configparser |  | ||||||
| import subprocess |  | ||||||
| import sys |  | ||||||
|  |  | ||||||
| config = configparser.ConfigParser(inline_comment_prefixes=(';', )) |  | ||||||
| config.read(sys.argv[1]) |  | ||||||
|  |  | ||||||
| libs = [] |  | ||||||
| # Extract from every lib_deps key in all sections |  | ||||||
| for section in config.sections(): |  | ||||||
|     conf = config[section] |  | ||||||
|     if "lib_deps" not in conf: |  | ||||||
|         continue |  | ||||||
|     for lib_dep in conf["lib_deps"].splitlines(): |  | ||||||
|         if not lib_dep: |  | ||||||
|             # Empty line or comment |  | ||||||
|             continue |  | ||||||
|         if lib_dep.startswith("${"): |  | ||||||
|             # Extending from another section |  | ||||||
|             continue |  | ||||||
|         if "@" not in lib_dep: |  | ||||||
|             # No version pinned, this is an internal lib |  | ||||||
|             continue |  | ||||||
|         libs.append(lib_dep) |  | ||||||
|  |  | ||||||
| subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) |  | ||||||
| @@ -1 +1,118 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome import pins | ||||||
|  | from esphome.const import CONF_INPUT | ||||||
|  |  | ||||||
|  | from esphome.core import CORE | ||||||
|  | from esphome.components.esp32 import get_esp32_variant | ||||||
|  | from esphome.components.esp32.const import ( | ||||||
|  |     VARIANT_ESP32, | ||||||
|  |     VARIANT_ESP32C3, | ||||||
|  |     VARIANT_ESP32H2, | ||||||
|  |     VARIANT_ESP32S2, | ||||||
|  |     VARIANT_ESP32S3, | ||||||
|  | ) | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
|  |  | ||||||
|  | ATTENUATION_MODES = { | ||||||
|  |     "0db": cg.global_ns.ADC_ATTEN_DB_0, | ||||||
|  |     "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, | ||||||
|  |     "6db": cg.global_ns.ADC_ATTEN_DB_6, | ||||||
|  |     "11db": cg.global_ns.ADC_ATTEN_DB_11, | ||||||
|  |     "auto": "auto", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | adc1_channel_t = cg.global_ns.enum("adc1_channel_t") | ||||||
|  |  | ||||||
|  | # From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h | ||||||
|  | # pin to adc1 channel mapping | ||||||
|  | ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { | ||||||
|  |     VARIANT_ESP32: { | ||||||
|  |         36: adc1_channel_t.ADC1_CHANNEL_0, | ||||||
|  |         37: adc1_channel_t.ADC1_CHANNEL_1, | ||||||
|  |         38: adc1_channel_t.ADC1_CHANNEL_2, | ||||||
|  |         39: adc1_channel_t.ADC1_CHANNEL_3, | ||||||
|  |         32: adc1_channel_t.ADC1_CHANNEL_4, | ||||||
|  |         33: adc1_channel_t.ADC1_CHANNEL_5, | ||||||
|  |         34: adc1_channel_t.ADC1_CHANNEL_6, | ||||||
|  |         35: adc1_channel_t.ADC1_CHANNEL_7, | ||||||
|  |     }, | ||||||
|  |     VARIANT_ESP32S2: { | ||||||
|  |         1: adc1_channel_t.ADC1_CHANNEL_0, | ||||||
|  |         2: adc1_channel_t.ADC1_CHANNEL_1, | ||||||
|  |         3: adc1_channel_t.ADC1_CHANNEL_2, | ||||||
|  |         4: adc1_channel_t.ADC1_CHANNEL_3, | ||||||
|  |         5: adc1_channel_t.ADC1_CHANNEL_4, | ||||||
|  |         6: adc1_channel_t.ADC1_CHANNEL_5, | ||||||
|  |         7: adc1_channel_t.ADC1_CHANNEL_6, | ||||||
|  |         8: adc1_channel_t.ADC1_CHANNEL_7, | ||||||
|  |         9: adc1_channel_t.ADC1_CHANNEL_8, | ||||||
|  |         10: adc1_channel_t.ADC1_CHANNEL_9, | ||||||
|  |     }, | ||||||
|  |     VARIANT_ESP32S3: { | ||||||
|  |         1: adc1_channel_t.ADC1_CHANNEL_0, | ||||||
|  |         2: adc1_channel_t.ADC1_CHANNEL_1, | ||||||
|  |         3: adc1_channel_t.ADC1_CHANNEL_2, | ||||||
|  |         4: adc1_channel_t.ADC1_CHANNEL_3, | ||||||
|  |         5: adc1_channel_t.ADC1_CHANNEL_4, | ||||||
|  |         6: adc1_channel_t.ADC1_CHANNEL_5, | ||||||
|  |         7: adc1_channel_t.ADC1_CHANNEL_6, | ||||||
|  |         8: adc1_channel_t.ADC1_CHANNEL_7, | ||||||
|  |         9: adc1_channel_t.ADC1_CHANNEL_8, | ||||||
|  |         10: adc1_channel_t.ADC1_CHANNEL_9, | ||||||
|  |     }, | ||||||
|  |     VARIANT_ESP32C3: { | ||||||
|  |         0: adc1_channel_t.ADC1_CHANNEL_0, | ||||||
|  |         1: adc1_channel_t.ADC1_CHANNEL_1, | ||||||
|  |         2: adc1_channel_t.ADC1_CHANNEL_2, | ||||||
|  |         3: adc1_channel_t.ADC1_CHANNEL_3, | ||||||
|  |         4: adc1_channel_t.ADC1_CHANNEL_4, | ||||||
|  |     }, | ||||||
|  |     VARIANT_ESP32H2: { | ||||||
|  |         0: adc1_channel_t.ADC1_CHANNEL_0, | ||||||
|  |         1: adc1_channel_t.ADC1_CHANNEL_1, | ||||||
|  |         2: adc1_channel_t.ADC1_CHANNEL_2, | ||||||
|  |         3: adc1_channel_t.ADC1_CHANNEL_3, | ||||||
|  |         4: adc1_channel_t.ADC1_CHANNEL_4, | ||||||
|  |     }, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_adc_pin(value): | ||||||
|  |     if str(value).upper() == "VCC": | ||||||
|  |         return cv.only_on_esp8266("VCC") | ||||||
|  |  | ||||||
|  |     if str(value).upper() == "TEMPERATURE": | ||||||
|  |         return cv.only_on_rp2040("TEMPERATURE") | ||||||
|  |  | ||||||
|  |     if CORE.is_esp32: | ||||||
|  |         value = pins.internal_gpio_input_pin_number(value) | ||||||
|  |         variant = get_esp32_variant() | ||||||
|  |         if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL: | ||||||
|  |             raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported") | ||||||
|  |  | ||||||
|  |         if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]: | ||||||
|  |             raise cv.Invalid(f"{variant} doesn't support ADC on this pin") | ||||||
|  |         return pins.internal_gpio_input_pin_schema(value) | ||||||
|  |  | ||||||
|  |     if CORE.is_esp8266: | ||||||
|  |         from esphome.components.esp8266.gpio import CONF_ANALOG | ||||||
|  |  | ||||||
|  |         value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( | ||||||
|  |             value | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         if value != 17:  # A0 | ||||||
|  |             raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.") | ||||||
|  |         return pins.gpio_pin_schema( | ||||||
|  |             {CONF_ANALOG: True, CONF_INPUT: True}, internal=True | ||||||
|  |         )(value) | ||||||
|  |  | ||||||
|  |     if CORE.is_rp2040: | ||||||
|  |         value = pins.internal_gpio_input_pin_number(value) | ||||||
|  |         if value not in (26, 27, 28, 29): | ||||||
|  |             raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.") | ||||||
|  |         return pins.internal_gpio_input_pin_schema(value) | ||||||
|  |  | ||||||
|  |     raise NotImplementedError | ||||||
|   | |||||||
| @@ -1,133 +1,27 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import pins |  | ||||||
| from esphome.components import sensor, voltage_sampler | from esphome.components import sensor, voltage_sampler | ||||||
|  | from esphome.components.esp32 import get_esp32_variant | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ATTENUATION, |     CONF_ATTENUATION, | ||||||
|     CONF_RAW, |  | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_INPUT, |  | ||||||
|     CONF_NUMBER, |     CONF_NUMBER, | ||||||
|     CONF_PIN, |     CONF_PIN, | ||||||
|  |     CONF_RAW, | ||||||
|     DEVICE_CLASS_VOLTAGE, |     DEVICE_CLASS_VOLTAGE, | ||||||
|     STATE_CLASS_MEASUREMENT, |     STATE_CLASS_MEASUREMENT, | ||||||
|     UNIT_VOLT, |     UNIT_VOLT, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE | from esphome.core import CORE | ||||||
| from esphome.components.esp32 import get_esp32_variant |  | ||||||
| from esphome.components.esp32.const import ( |  | ||||||
|     VARIANT_ESP32, |  | ||||||
|     VARIANT_ESP32C3, |  | ||||||
|     VARIANT_ESP32H2, |  | ||||||
|     VARIANT_ESP32S2, |  | ||||||
|     VARIANT_ESP32S3, |  | ||||||
| ) |  | ||||||
|  |  | ||||||
|  | from . import ( | ||||||
|  |     ATTENUATION_MODES, | ||||||
|  |     ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, | ||||||
|  |     validate_adc_pin, | ||||||
|  | ) | ||||||
|  |  | ||||||
| AUTO_LOAD = ["voltage_sampler"] | AUTO_LOAD = ["voltage_sampler"] | ||||||
|  |  | ||||||
| ATTENUATION_MODES = { |  | ||||||
|     "0db": cg.global_ns.ADC_ATTEN_DB_0, |  | ||||||
|     "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, |  | ||||||
|     "6db": cg.global_ns.ADC_ATTEN_DB_6, |  | ||||||
|     "11db": cg.global_ns.ADC_ATTEN_DB_11, |  | ||||||
|     "auto": "auto", |  | ||||||
| } |  | ||||||
|  |  | ||||||
| adc1_channel_t = cg.global_ns.enum("adc1_channel_t") |  | ||||||
|  |  | ||||||
| # From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h |  | ||||||
| # pin to adc1 channel mapping |  | ||||||
| ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { |  | ||||||
|     VARIANT_ESP32: { |  | ||||||
|         36: adc1_channel_t.ADC1_CHANNEL_0, |  | ||||||
|         37: adc1_channel_t.ADC1_CHANNEL_1, |  | ||||||
|         38: adc1_channel_t.ADC1_CHANNEL_2, |  | ||||||
|         39: adc1_channel_t.ADC1_CHANNEL_3, |  | ||||||
|         32: adc1_channel_t.ADC1_CHANNEL_4, |  | ||||||
|         33: adc1_channel_t.ADC1_CHANNEL_5, |  | ||||||
|         34: adc1_channel_t.ADC1_CHANNEL_6, |  | ||||||
|         35: adc1_channel_t.ADC1_CHANNEL_7, |  | ||||||
|     }, |  | ||||||
|     VARIANT_ESP32S2: { |  | ||||||
|         1: adc1_channel_t.ADC1_CHANNEL_0, |  | ||||||
|         2: adc1_channel_t.ADC1_CHANNEL_1, |  | ||||||
|         3: adc1_channel_t.ADC1_CHANNEL_2, |  | ||||||
|         4: adc1_channel_t.ADC1_CHANNEL_3, |  | ||||||
|         5: adc1_channel_t.ADC1_CHANNEL_4, |  | ||||||
|         6: adc1_channel_t.ADC1_CHANNEL_5, |  | ||||||
|         7: adc1_channel_t.ADC1_CHANNEL_6, |  | ||||||
|         8: adc1_channel_t.ADC1_CHANNEL_7, |  | ||||||
|         9: adc1_channel_t.ADC1_CHANNEL_8, |  | ||||||
|         10: adc1_channel_t.ADC1_CHANNEL_9, |  | ||||||
|     }, |  | ||||||
|     VARIANT_ESP32S3: { |  | ||||||
|         1: adc1_channel_t.ADC1_CHANNEL_0, |  | ||||||
|         2: adc1_channel_t.ADC1_CHANNEL_1, |  | ||||||
|         3: adc1_channel_t.ADC1_CHANNEL_2, |  | ||||||
|         4: adc1_channel_t.ADC1_CHANNEL_3, |  | ||||||
|         5: adc1_channel_t.ADC1_CHANNEL_4, |  | ||||||
|         6: adc1_channel_t.ADC1_CHANNEL_5, |  | ||||||
|         7: adc1_channel_t.ADC1_CHANNEL_6, |  | ||||||
|         8: adc1_channel_t.ADC1_CHANNEL_7, |  | ||||||
|         9: adc1_channel_t.ADC1_CHANNEL_8, |  | ||||||
|         10: adc1_channel_t.ADC1_CHANNEL_9, |  | ||||||
|     }, |  | ||||||
|     VARIANT_ESP32C3: { |  | ||||||
|         0: adc1_channel_t.ADC1_CHANNEL_0, |  | ||||||
|         1: adc1_channel_t.ADC1_CHANNEL_1, |  | ||||||
|         2: adc1_channel_t.ADC1_CHANNEL_2, |  | ||||||
|         3: adc1_channel_t.ADC1_CHANNEL_3, |  | ||||||
|         4: adc1_channel_t.ADC1_CHANNEL_4, |  | ||||||
|     }, |  | ||||||
|     VARIANT_ESP32H2: { |  | ||||||
|         0: adc1_channel_t.ADC1_CHANNEL_0, |  | ||||||
|         1: adc1_channel_t.ADC1_CHANNEL_1, |  | ||||||
|         2: adc1_channel_t.ADC1_CHANNEL_2, |  | ||||||
|         3: adc1_channel_t.ADC1_CHANNEL_3, |  | ||||||
|         4: adc1_channel_t.ADC1_CHANNEL_4, |  | ||||||
|     }, |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_adc_pin(value): |  | ||||||
|     if str(value).upper() == "VCC": |  | ||||||
|         return cv.only_on_esp8266("VCC") |  | ||||||
|  |  | ||||||
|     if str(value).upper() == "TEMPERATURE": |  | ||||||
|         return cv.only_on_rp2040("TEMPERATURE") |  | ||||||
|  |  | ||||||
|     if CORE.is_esp32: |  | ||||||
|         value = pins.internal_gpio_input_pin_number(value) |  | ||||||
|         variant = get_esp32_variant() |  | ||||||
|         if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL: |  | ||||||
|             raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported") |  | ||||||
|  |  | ||||||
|         if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]: |  | ||||||
|             raise cv.Invalid(f"{variant} doesn't support ADC on this pin") |  | ||||||
|         return pins.internal_gpio_input_pin_schema(value) |  | ||||||
|  |  | ||||||
|     if CORE.is_esp8266: |  | ||||||
|         from esphome.components.esp8266.gpio import CONF_ANALOG |  | ||||||
|  |  | ||||||
|         value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( |  | ||||||
|             value |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         if value != 17:  # A0 |  | ||||||
|             raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.") |  | ||||||
|         return pins.gpio_pin_schema( |  | ||||||
|             {CONF_ANALOG: True, CONF_INPUT: True}, internal=True |  | ||||||
|         )(value) |  | ||||||
|  |  | ||||||
|     if CORE.is_rp2040: |  | ||||||
|         value = pins.internal_gpio_input_pin_number(value) |  | ||||||
|         if value not in (26, 27, 28, 29): |  | ||||||
|             raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.") |  | ||||||
|         return pins.internal_gpio_input_pin_schema(value) |  | ||||||
|  |  | ||||||
|     raise NotImplementedError |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_config(config): | def validate_config(config): | ||||||
|     if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto": |     if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto": | ||||||
|   | |||||||
| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ["@buxtronix"] | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ from esphome.const import CONF_ID, CONF_PIN | |||||||
|  |  | ||||||
| CODEOWNERS = ["@buxtronix"] | CODEOWNERS = ["@buxtronix"] | ||||||
| DEPENDENCIES = ["ble_client"] | DEPENDENCIES = ["ble_client"] | ||||||
| AUTO_LOAD = ["am43", "sensor"] | AUTO_LOAD = ["am43"] | ||||||
|  |  | ||||||
| CONF_INVERT_POSITION = "invert_position" | CONF_INVERT_POSITION = "invert_position" | ||||||
|  |  | ||||||
| @@ -27,10 +27,10 @@ CONFIG_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     cg.add(var.set_pin(config[CONF_PIN])) |     cg.add(var.set_pin(config[CONF_PIN])) | ||||||
|     cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) |     cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) | ||||||
|     yield cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     yield cover.register_cover(var, config) |     await cover.register_cover(var, config) | ||||||
|     yield ble_client.register_ble_node(var, config) |     await ble_client.register_ble_node(var, config) | ||||||
|   | |||||||
| @@ -40,6 +40,7 @@ void Am43Component::loop() { | |||||||
|  |  | ||||||
| CoverTraits Am43Component::get_traits() { | CoverTraits Am43Component::get_traits() { | ||||||
|   auto traits = CoverTraits(); |   auto traits = CoverTraits(); | ||||||
|  |   traits.set_supports_stop(true); | ||||||
|   traits.set_supports_position(true); |   traits.set_supports_position(true); | ||||||
|   traits.set_supports_tilt(false); |   traits.set_supports_tilt(false); | ||||||
|   traits.set_is_assumed_state(false); |   traits.set_is_assumed_state(false); | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ from esphome.const import ( | |||||||
|     UNIT_PERCENT, |     UNIT_PERCENT, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | AUTO_LOAD = ["am43"] | ||||||
| CODEOWNERS = ["@buxtronix"] | CODEOWNERS = ["@buxtronix"] | ||||||
| 
 | 
 | ||||||
| am43_ns = cg.esphome_ns.namespace("am43") | am43_ns = cg.esphome_ns.namespace("am43") | ||||||
| @@ -38,15 +39,15 @@ CONFIG_SCHEMA = ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     yield cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     yield ble_client.register_ble_node(var, config) |     await ble_client.register_ble_node(var, config) | ||||||
| 
 | 
 | ||||||
|     if CONF_BATTERY_LEVEL in config: |     if CONF_BATTERY_LEVEL in config: | ||||||
|         sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) |         sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) | ||||||
|         cg.add(var.set_battery(sens)) |         cg.add(var.set_battery(sens)) | ||||||
| 
 | 
 | ||||||
|     if CONF_ILLUMINANCE in config: |     if CONF_ILLUMINANCE in config: | ||||||
|         sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE]) |         sens = await sensor.new_sensor(config[CONF_ILLUMINANCE]) | ||||||
|         cg.add(var.set_illuminance(sens)) |         cg.add(var.set_illuminance(sens)) | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| #include "am43.h" | #include "am43_sensor.h" | ||||||
| #include "esphome/core/log.h" |  | ||||||
| #include "esphome/core/hal.h" | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
| 
 | 
 | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
| 
 | 
 | ||||||
| @@ -4,7 +4,6 @@ from esphome.components import i2c | |||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
| DEPENDENCIES = ["i2c"] | DEPENDENCIES = ["i2c"] | ||||||
| AUTO_LOAD = ["sensor", "binary_sensor"] |  | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
|  |  | ||||||
| CONF_APDS9960_ID = "apds9960_id" | CONF_APDS9960_ID = "apds9960_id" | ||||||
|   | |||||||
| @@ -116,8 +116,12 @@ void APDS9960::setup() { | |||||||
|   APDS9960_WRITE_BYTE(0x80, val); |   APDS9960_WRITE_BYTE(0x80, val); | ||||||
| } | } | ||||||
| bool APDS9960::is_color_enabled_() const { | bool APDS9960::is_color_enabled_() const { | ||||||
|   return this->red_channel_ != nullptr || this->green_channel_ != nullptr || this->blue_channel_ != nullptr || | #ifdef USE_SENSOR | ||||||
|          this->clear_channel_ != nullptr; |   return this->red_sensor_ != nullptr || this->green_sensor_ != nullptr || this->blue_sensor_ != nullptr || | ||||||
|  |          this->clear_sensor_ != nullptr; | ||||||
|  | #else | ||||||
|  |   return false; | ||||||
|  | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| void APDS9960::dump_config() { | void APDS9960::dump_config() { | ||||||
| @@ -125,6 +129,15 @@ void APDS9960::dump_config() { | |||||||
|   LOG_I2C_DEVICE(this); |   LOG_I2C_DEVICE(this); | ||||||
|  |  | ||||||
|   LOG_UPDATE_INTERVAL(this); |   LOG_UPDATE_INTERVAL(this); | ||||||
|  |  | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   LOG_SENSOR("  ", "Red channel", this->red_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Green channel", this->green_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Blue channel", this->blue_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Clear channel", this->clear_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Proximity", this->proximity_sensor_); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   if (this->is_failed()) { |   if (this->is_failed()) { | ||||||
|     switch (this->error_code_) { |     switch (this->error_code_) { | ||||||
|       case COMMUNICATION_FAILED: |       case COMMUNICATION_FAILED: | ||||||
| @@ -181,17 +194,22 @@ void APDS9960::read_color_data_(uint8_t status) { | |||||||
|   float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f; |   float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f; | ||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "Got clear=%.1f%% red=%.1f%% green=%.1f%% blue=%.1f%%", clear_perc, red_perc, green_perc, blue_perc); |   ESP_LOGD(TAG, "Got clear=%.1f%% red=%.1f%% green=%.1f%% blue=%.1f%%", clear_perc, red_perc, green_perc, blue_perc); | ||||||
|   if (this->clear_channel_ != nullptr) | #ifdef USE_SENSOR | ||||||
|     this->clear_channel_->publish_state(clear_perc); |   if (this->clear_sensor_ != nullptr) | ||||||
|   if (this->red_channel_ != nullptr) |     this->clear_sensor_->publish_state(clear_perc); | ||||||
|     this->red_channel_->publish_state(red_perc); |   if (this->red_sensor_ != nullptr) | ||||||
|   if (this->green_channel_ != nullptr) |     this->red_sensor_->publish_state(red_perc); | ||||||
|     this->green_channel_->publish_state(green_perc); |   if (this->green_sensor_ != nullptr) | ||||||
|   if (this->blue_channel_ != nullptr) |     this->green_sensor_->publish_state(green_perc); | ||||||
|     this->blue_channel_->publish_state(blue_perc); |   if (this->blue_sensor_ != nullptr) | ||||||
|  |     this->blue_sensor_->publish_state(blue_perc); | ||||||
|  | #endif | ||||||
| } | } | ||||||
| void APDS9960::read_proximity_data_(uint8_t status) { | void APDS9960::read_proximity_data_(uint8_t status) { | ||||||
|   if (this->proximity_ == nullptr) | #ifndef USE_SENSOR | ||||||
|  |   return; | ||||||
|  | #else | ||||||
|  |   if (this->proximity_sensor_ == nullptr) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   if ((status & 0b10) == 0x00) { |   if ((status & 0b10) == 0x00) { | ||||||
| @@ -204,7 +222,8 @@ void APDS9960::read_proximity_data_(uint8_t status) { | |||||||
|  |  | ||||||
|   float prox_perc = (prox / float(UINT8_MAX)) * 100.0f; |   float prox_perc = (prox / float(UINT8_MAX)) * 100.0f; | ||||||
|   ESP_LOGD(TAG, "Got proximity=%.1f%%", prox_perc); |   ESP_LOGD(TAG, "Got proximity=%.1f%%", prox_perc); | ||||||
|   this->proximity_->publish_state(prox_perc); |   this->proximity_sensor_->publish_state(prox_perc); | ||||||
|  | #endif | ||||||
| } | } | ||||||
| void APDS9960::read_gesture_data_() { | void APDS9960::read_gesture_data_() { | ||||||
|   if (!this->is_gesture_enabled_()) |   if (!this->is_gesture_enabled_()) | ||||||
| @@ -256,28 +275,29 @@ void APDS9960::read_gesture_data_() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| void APDS9960::report_gesture_(int gesture) { | void APDS9960::report_gesture_(int gesture) { | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|   binary_sensor::BinarySensor *bin; |   binary_sensor::BinarySensor *bin; | ||||||
|   switch (gesture) { |   switch (gesture) { | ||||||
|     case 1: |     case 1: | ||||||
|       bin = this->up_direction_; |       bin = this->up_direction_binary_sensor_; | ||||||
|       this->gesture_up_started_ = false; |       this->gesture_up_started_ = false; | ||||||
|       this->gesture_down_started_ = false; |       this->gesture_down_started_ = false; | ||||||
|       ESP_LOGD(TAG, "Got gesture UP"); |       ESP_LOGD(TAG, "Got gesture UP"); | ||||||
|       break; |       break; | ||||||
|     case 2: |     case 2: | ||||||
|       bin = this->down_direction_; |       bin = this->down_direction_binary_sensor_; | ||||||
|       this->gesture_up_started_ = false; |       this->gesture_up_started_ = false; | ||||||
|       this->gesture_down_started_ = false; |       this->gesture_down_started_ = false; | ||||||
|       ESP_LOGD(TAG, "Got gesture DOWN"); |       ESP_LOGD(TAG, "Got gesture DOWN"); | ||||||
|       break; |       break; | ||||||
|     case 3: |     case 3: | ||||||
|       bin = this->left_direction_; |       bin = this->left_direction_binary_sensor_; | ||||||
|       this->gesture_left_started_ = false; |       this->gesture_left_started_ = false; | ||||||
|       this->gesture_right_started_ = false; |       this->gesture_right_started_ = false; | ||||||
|       ESP_LOGD(TAG, "Got gesture LEFT"); |       ESP_LOGD(TAG, "Got gesture LEFT"); | ||||||
|       break; |       break; | ||||||
|     case 4: |     case 4: | ||||||
|       bin = this->right_direction_; |       bin = this->right_direction_binary_sensor_; | ||||||
|       this->gesture_left_started_ = false; |       this->gesture_left_started_ = false; | ||||||
|       this->gesture_right_started_ = false; |       this->gesture_right_started_ = false; | ||||||
|       ESP_LOGD(TAG, "Got gesture RIGHT"); |       ESP_LOGD(TAG, "Got gesture RIGHT"); | ||||||
| @@ -290,6 +310,7 @@ void APDS9960::report_gesture_(int gesture) { | |||||||
|     bin->publish_state(true); |     bin->publish_state(true); | ||||||
|     bin->publish_state(false); |     bin->publish_state(false); | ||||||
|   } |   } | ||||||
|  | #endif | ||||||
| } | } | ||||||
| void APDS9960::process_dataset_(int up, int down, int left, int right) { | void APDS9960::process_dataset_(int up, int down, int left, int right) { | ||||||
|   /* Algorithm: (see Figure 11 in datasheet) |   /* Algorithm: (see Figure 11 in datasheet) | ||||||
| @@ -365,10 +386,22 @@ void APDS9960::process_dataset_(int up, int down, int left, int right) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| float APDS9960::get_setup_priority() const { return setup_priority::DATA; } | float APDS9960::get_setup_priority() const { return setup_priority::DATA; } | ||||||
| bool APDS9960::is_proximity_enabled_() const { return this->proximity_ != nullptr || this->is_gesture_enabled_(); } | bool APDS9960::is_proximity_enabled_() const { | ||||||
|  |   return | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |       this->proximity_sensor_ != nullptr | ||||||
|  | #else | ||||||
|  |       false | ||||||
|  | #endif | ||||||
|  |       || this->is_gesture_enabled_(); | ||||||
|  | } | ||||||
| bool APDS9960::is_gesture_enabled_() const { | bool APDS9960::is_gesture_enabled_() const { | ||||||
|   return this->up_direction_ != nullptr || this->left_direction_ != nullptr || this->down_direction_ != nullptr || | #ifdef USE_BINARY_SENSOR | ||||||
|          this->right_direction_ != nullptr; |   return this->up_direction_binary_sensor_ != nullptr || this->left_direction_binary_sensor_ != nullptr || | ||||||
|  |          this->down_direction_binary_sensor_ != nullptr || this->right_direction_binary_sensor_ != nullptr; | ||||||
|  | #else | ||||||
|  |   return false; | ||||||
|  | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| }  // namespace apds9960 | }  // namespace apds9960 | ||||||
|   | |||||||
| @@ -1,14 +1,34 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/core/component.h" |  | ||||||
| #include "esphome/components/i2c/i2c.h" | #include "esphome/components/i2c/i2c.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #ifdef USE_SENSOR | ||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | #include "esphome/components/binary_sensor/binary_sensor.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace apds9960 { | namespace apds9960 { | ||||||
|  |  | ||||||
| class APDS9960 : public PollingComponent, public i2c::I2CDevice { | class APDS9960 : public PollingComponent, public i2c::I2CDevice { | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   SUB_SENSOR(red) | ||||||
|  |   SUB_SENSOR(green) | ||||||
|  |   SUB_SENSOR(blue) | ||||||
|  |   SUB_SENSOR(clear) | ||||||
|  |   SUB_SENSOR(proximity) | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |   SUB_BINARY_SENSOR(up_direction) | ||||||
|  |   SUB_BINARY_SENSOR(right_direction) | ||||||
|  |   SUB_BINARY_SENSOR(down_direction) | ||||||
|  |   SUB_BINARY_SENSOR(left_direction) | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
| @@ -23,16 +43,6 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice { | |||||||
|   void set_gesture_gain(uint8_t gain) { this->gesture_gain_ = gain; } |   void set_gesture_gain(uint8_t gain) { this->gesture_gain_ = gain; } | ||||||
|   void set_gesture_wait_time(uint8_t wait_time) { this->gesture_wait_time_ = wait_time; } |   void set_gesture_wait_time(uint8_t wait_time) { this->gesture_wait_time_ = wait_time; } | ||||||
|  |  | ||||||
|   void set_red_channel(sensor::Sensor *red_channel) { red_channel_ = red_channel; } |  | ||||||
|   void set_green_channel(sensor::Sensor *green_channel) { green_channel_ = green_channel; } |  | ||||||
|   void set_blue_channel(sensor::Sensor *blue_channel) { blue_channel_ = blue_channel; } |  | ||||||
|   void set_clear_channel(sensor::Sensor *clear_channel) { clear_channel_ = clear_channel; } |  | ||||||
|   void set_up_direction(binary_sensor::BinarySensor *up_direction) { up_direction_ = up_direction; } |  | ||||||
|   void set_right_direction(binary_sensor::BinarySensor *right_direction) { right_direction_ = right_direction; } |  | ||||||
|   void set_down_direction(binary_sensor::BinarySensor *down_direction) { down_direction_ = down_direction; } |  | ||||||
|   void set_left_direction(binary_sensor::BinarySensor *left_direction) { left_direction_ = left_direction; } |  | ||||||
|   void set_proximity(sensor::Sensor *proximity) { proximity_ = proximity; } |  | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   bool is_color_enabled_() const; |   bool is_color_enabled_() const; | ||||||
|   bool is_proximity_enabled_() const; |   bool is_proximity_enabled_() const; | ||||||
| @@ -50,15 +60,6 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice { | |||||||
|   uint8_t gesture_gain_; |   uint8_t gesture_gain_; | ||||||
|   uint8_t gesture_wait_time_; |   uint8_t gesture_wait_time_; | ||||||
|  |  | ||||||
|   sensor::Sensor *red_channel_{nullptr}; |  | ||||||
|   sensor::Sensor *green_channel_{nullptr}; |  | ||||||
|   sensor::Sensor *blue_channel_{nullptr}; |  | ||||||
|   sensor::Sensor *clear_channel_{nullptr}; |  | ||||||
|   binary_sensor::BinarySensor *up_direction_{nullptr}; |  | ||||||
|   binary_sensor::BinarySensor *right_direction_{nullptr}; |  | ||||||
|   binary_sensor::BinarySensor *down_direction_{nullptr}; |  | ||||||
|   binary_sensor::BinarySensor *left_direction_{nullptr}; |  | ||||||
|   sensor::Sensor *proximity_{nullptr}; |  | ||||||
|   enum ErrorCode { |   enum ErrorCode { | ||||||
|     NONE = 0, |     NONE = 0, | ||||||
|     COMMUNICATION_FAILED, |     COMMUNICATION_FAILED, | ||||||
|   | |||||||
| @@ -6,19 +6,14 @@ from . import APDS9960, CONF_APDS9960_ID | |||||||
|  |  | ||||||
| DEPENDENCIES = ["apds9960"] | DEPENDENCIES = ["apds9960"] | ||||||
|  |  | ||||||
| DIRECTIONS = { | DIRECTIONS = ["up", "down", "left", "right"] | ||||||
|     "UP": "set_up_direction", |  | ||||||
|     "DOWN": "set_down_direction", |  | ||||||
|     "LEFT": "set_left_direction", |  | ||||||
|     "RIGHT": "set_right_direction", |  | ||||||
| } |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = binary_sensor.binary_sensor_schema( | CONFIG_SCHEMA = binary_sensor.binary_sensor_schema( | ||||||
|     device_class=DEVICE_CLASS_MOVING |     device_class=DEVICE_CLASS_MOVING | ||||||
| ).extend( | ).extend( | ||||||
|     { |     { | ||||||
|         cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), |         cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), | ||||||
|         cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True), |         cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, lower=True), | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -26,5 +21,5 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema( | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     hub = await cg.get_variable(config[CONF_APDS9960_ID]) |     hub = await cg.get_variable(config[CONF_APDS9960_ID]) | ||||||
|     var = await binary_sensor.new_binary_sensor(config) |     var = await binary_sensor.new_binary_sensor(config) | ||||||
|     func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]]) |     func = getattr(hub, f"set_{config[CONF_DIRECTION]}_direction_binary_sensor") | ||||||
|     cg.add(func(var)) |     cg.add(func(var)) | ||||||
|   | |||||||
| @@ -11,13 +11,7 @@ from . import APDS9960, CONF_APDS9960_ID | |||||||
|  |  | ||||||
| DEPENDENCIES = ["apds9960"] | DEPENDENCIES = ["apds9960"] | ||||||
|  |  | ||||||
| TYPES = { | TYPES = ["clear", "red", "green", "blue", "proximity"] | ||||||
|     "CLEAR": "set_clear_channel", |  | ||||||
|     "RED": "set_red_channel", |  | ||||||
|     "GREEN": "set_green_channel", |  | ||||||
|     "BLUE": "set_blue_channel", |  | ||||||
|     "PROXIMITY": "set_proximity", |  | ||||||
| } |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = sensor.sensor_schema( | CONFIG_SCHEMA = sensor.sensor_schema( | ||||||
|     unit_of_measurement=UNIT_PERCENT, |     unit_of_measurement=UNIT_PERCENT, | ||||||
| @@ -26,7 +20,7 @@ CONFIG_SCHEMA = sensor.sensor_schema( | |||||||
|     state_class=STATE_CLASS_MEASUREMENT, |     state_class=STATE_CLASS_MEASUREMENT, | ||||||
| ).extend( | ).extend( | ||||||
|     { |     { | ||||||
|         cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True), |         cv.Required(CONF_TYPE): cv.one_of(*TYPES, lower=True), | ||||||
|         cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), |         cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
| @@ -35,5 +29,5 @@ CONFIG_SCHEMA = sensor.sensor_schema( | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     hub = await cg.get_variable(config[CONF_APDS9960_ID]) |     hub = await cg.get_variable(config[CONF_APDS9960_ID]) | ||||||
|     var = await sensor.new_sensor(config) |     var = await sensor.new_sensor(config) | ||||||
|     func = getattr(hub, TYPES[config[CONF_TYPE]]) |     func = getattr(hub, f"set_{config[CONF_TYPE]}_sensor") | ||||||
|     cg.add(func(var)) |     cg.add(func(var)) | ||||||
|   | |||||||
| @@ -288,6 +288,7 @@ message ListEntitiesCoverResponse { | |||||||
|   bool disabled_by_default = 9; |   bool disabled_by_default = 9; | ||||||
|   string icon = 10; |   string icon = 10; | ||||||
|   EntityCategory entity_category = 11; |   EntityCategory entity_category = 11; | ||||||
|  |   bool supports_stop = 12; | ||||||
| } | } | ||||||
|  |  | ||||||
| enum LegacyCoverState { | enum LegacyCoverState { | ||||||
| @@ -861,8 +862,7 @@ message ClimateStateResponse { | |||||||
|   float target_temperature = 4; |   float target_temperature = 4; | ||||||
|   float target_temperature_low = 5; |   float target_temperature_low = 5; | ||||||
|   float target_temperature_high = 6; |   float target_temperature_high = 6; | ||||||
|   // For older peers, equal to preset == CLIMATE_PRESET_AWAY |   bool unused_legacy_away = 7; | ||||||
|   bool legacy_away = 7; |  | ||||||
|   ClimateAction action = 8; |   ClimateAction action = 8; | ||||||
|   ClimateFanMode fan_mode = 9; |   ClimateFanMode fan_mode = 9; | ||||||
|   ClimateSwingMode swing_mode = 10; |   ClimateSwingMode swing_mode = 10; | ||||||
| @@ -885,9 +885,8 @@ message ClimateCommandRequest { | |||||||
|   float target_temperature_low = 7; |   float target_temperature_low = 7; | ||||||
|   bool has_target_temperature_high = 8; |   bool has_target_temperature_high = 8; | ||||||
|   float target_temperature_high = 9; |   float target_temperature_high = 9; | ||||||
|   // legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset |   bool unused_has_legacy_away = 10; | ||||||
|   bool has_legacy_away = 10; |   bool unused_legacy_away = 11; | ||||||
|   bool legacy_away = 11; |  | ||||||
|   bool has_fan_mode = 12; |   bool has_fan_mode = 12; | ||||||
|   ClimateFanMode fan_mode = 13; |   ClimateFanMode fan_mode = 13; | ||||||
|   bool has_swing_mode = 14; |   bool has_swing_mode = 14; | ||||||
|   | |||||||
| @@ -223,6 +223,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { | |||||||
|   msg.assumed_state = traits.get_is_assumed_state(); |   msg.assumed_state = traits.get_is_assumed_state(); | ||||||
|   msg.supports_position = traits.get_supports_position(); |   msg.supports_position = traits.get_supports_position(); | ||||||
|   msg.supports_tilt = traits.get_supports_tilt(); |   msg.supports_tilt = traits.get_supports_tilt(); | ||||||
|  |   msg.supports_stop = traits.get_supports_stop(); | ||||||
|   msg.device_class = cover->get_device_class(); |   msg.device_class = cover->get_device_class(); | ||||||
|   msg.disabled_by_default = cover->is_disabled_by_default(); |   msg.disabled_by_default = cover->is_disabled_by_default(); | ||||||
|   msg.icon = cover->get_icon(); |   msg.icon = cover->get_icon(); | ||||||
| @@ -530,7 +531,6 @@ bool APIConnection::send_climate_state(climate::Climate *climate) { | |||||||
|     resp.custom_fan_mode = climate->custom_fan_mode.value(); |     resp.custom_fan_mode = climate->custom_fan_mode.value(); | ||||||
|   if (traits.get_supports_presets() && climate->preset.has_value()) { |   if (traits.get_supports_presets() && climate->preset.has_value()) { | ||||||
|     resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value()); |     resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value()); | ||||||
|     resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY; |  | ||||||
|   } |   } | ||||||
|   if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) |   if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) | ||||||
|     resp.custom_preset = climate->custom_preset.value(); |     resp.custom_preset = climate->custom_preset.value(); | ||||||
| @@ -591,8 +591,6 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { | |||||||
|     call.set_target_temperature_low(msg.target_temperature_low); |     call.set_target_temperature_low(msg.target_temperature_low); | ||||||
|   if (msg.has_target_temperature_high) |   if (msg.has_target_temperature_high) | ||||||
|     call.set_target_temperature_high(msg.target_temperature_high); |     call.set_target_temperature_high(msg.target_temperature_high); | ||||||
|   if (msg.has_legacy_away) |  | ||||||
|     call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME); |  | ||||||
|   if (msg.has_fan_mode) |   if (msg.has_fan_mode) | ||||||
|     call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode)); |     call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode)); | ||||||
|   if (msg.has_custom_fan_mode) |   if (msg.has_custom_fan_mode) | ||||||
| @@ -944,7 +942,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { | |||||||
|  |  | ||||||
|   HelloResponse resp; |   HelloResponse resp; | ||||||
|   resp.api_version_major = 1; |   resp.api_version_major = 1; | ||||||
|   resp.api_version_minor = 7; |   resp.api_version_minor = 8; | ||||||
|   resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; |   resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; | ||||||
|   resp.name = App.get_name(); |   resp.name = App.get_name(); | ||||||
|  |  | ||||||
| @@ -981,6 +979,8 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { | |||||||
|   resp.manufacturer = "Espressif"; |   resp.manufacturer = "Espressif"; | ||||||
| #elif defined(USE_RP2040) | #elif defined(USE_RP2040) | ||||||
|   resp.manufacturer = "Raspberry Pi"; |   resp.manufacturer = "Raspberry Pi"; | ||||||
|  | #elif defined(USE_HOST) | ||||||
|  |   resp.manufacturer = "Host"; | ||||||
| #endif | #endif | ||||||
|   resp.model = ESPHOME_BOARD; |   resp.model = ESPHOME_BOARD; | ||||||
| #ifdef USE_DEEP_SLEEP | #ifdef USE_DEEP_SLEEP | ||||||
| @@ -999,7 +999,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { | |||||||
|                                      : bluetooth_proxy::PASSIVE_ONLY_VERSION; |                                      : bluetooth_proxy::PASSIVE_ONLY_VERSION; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_VOICE_ASSISTANT | #ifdef USE_VOICE_ASSISTANT | ||||||
|   resp.voice_assistant_version = 1; |   resp.voice_assistant_version = voice_assistant::global_voice_assistant->get_version(); | ||||||
| #endif | #endif | ||||||
|   return resp; |   return resp; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -941,6 +941,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 12: { | ||||||
|  |       this->supports_stop = value.as_bool(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -993,6 +997,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(9, this->disabled_by_default); |   buffer.encode_bool(9, this->disabled_by_default); | ||||||
|   buffer.encode_string(10, this->icon); |   buffer.encode_string(10, this->icon); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(11, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(11, this->entity_category); | ||||||
|  |   buffer.encode_bool(12, this->supports_stop); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesCoverResponse::dump_to(std::string &out) const { | void ListEntitiesCoverResponse::dump_to(std::string &out) const { | ||||||
| @@ -1042,6 +1047,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  supports_stop: "); | ||||||
|  |   out.append(YESNO(this->supports_stop)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -3649,7 +3658,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | |||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|     case 7: { |     case 7: { | ||||||
|       this->legacy_away = value.as_bool(); |       this->unused_legacy_away = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|     case 8: { |     case 8: { | ||||||
| @@ -3719,7 +3728,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_float(4, this->target_temperature); |   buffer.encode_float(4, this->target_temperature); | ||||||
|   buffer.encode_float(5, this->target_temperature_low); |   buffer.encode_float(5, this->target_temperature_low); | ||||||
|   buffer.encode_float(6, this->target_temperature_high); |   buffer.encode_float(6, this->target_temperature_high); | ||||||
|   buffer.encode_bool(7, this->legacy_away); |   buffer.encode_bool(7, this->unused_legacy_away); | ||||||
|   buffer.encode_enum<enums::ClimateAction>(8, this->action); |   buffer.encode_enum<enums::ClimateAction>(8, this->action); | ||||||
|   buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode); |   buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode); | ||||||
|   buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode); |   buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode); | ||||||
| @@ -3760,8 +3769,8 @@ void ClimateStateResponse::dump_to(std::string &out) const { | |||||||
|   out.append(buffer); |   out.append(buffer); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|   out.append("  legacy_away: "); |   out.append("  unused_legacy_away: "); | ||||||
|   out.append(YESNO(this->legacy_away)); |   out.append(YESNO(this->unused_legacy_away)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|   out.append("  action: "); |   out.append("  action: "); | ||||||
| @@ -3813,11 +3822,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) | |||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|     case 10: { |     case 10: { | ||||||
|       this->has_legacy_away = value.as_bool(); |       this->unused_has_legacy_away = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|     case 11: { |     case 11: { | ||||||
|       this->legacy_away = value.as_bool(); |       this->unused_legacy_away = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|     case 12: { |     case 12: { | ||||||
| @@ -3902,8 +3911,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_float(7, this->target_temperature_low); |   buffer.encode_float(7, this->target_temperature_low); | ||||||
|   buffer.encode_bool(8, this->has_target_temperature_high); |   buffer.encode_bool(8, this->has_target_temperature_high); | ||||||
|   buffer.encode_float(9, this->target_temperature_high); |   buffer.encode_float(9, this->target_temperature_high); | ||||||
|   buffer.encode_bool(10, this->has_legacy_away); |   buffer.encode_bool(10, this->unused_has_legacy_away); | ||||||
|   buffer.encode_bool(11, this->legacy_away); |   buffer.encode_bool(11, this->unused_legacy_away); | ||||||
|   buffer.encode_bool(12, this->has_fan_mode); |   buffer.encode_bool(12, this->has_fan_mode); | ||||||
|   buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode); |   buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode); | ||||||
|   buffer.encode_bool(14, this->has_swing_mode); |   buffer.encode_bool(14, this->has_swing_mode); | ||||||
| @@ -3959,12 +3968,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const { | |||||||
|   out.append(buffer); |   out.append(buffer); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|   out.append("  has_legacy_away: "); |   out.append("  unused_has_legacy_away: "); | ||||||
|   out.append(YESNO(this->has_legacy_away)); |   out.append(YESNO(this->unused_has_legacy_away)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|   out.append("  legacy_away: "); |   out.append("  unused_legacy_away: "); | ||||||
|   out.append(YESNO(this->legacy_away)); |   out.append(YESNO(this->unused_legacy_away)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|   out.append("  has_fan_mode: "); |   out.append("  has_fan_mode: "); | ||||||
|   | |||||||
| @@ -375,6 +375,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { | |||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|   std::string icon{}; |   std::string icon{}; | ||||||
|   enums::EntityCategory entity_category{}; |   enums::EntityCategory entity_category{}; | ||||||
|  |   bool supports_stop{false}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -958,7 +959,7 @@ class ClimateStateResponse : public ProtoMessage { | |||||||
|   float target_temperature{0.0f}; |   float target_temperature{0.0f}; | ||||||
|   float target_temperature_low{0.0f}; |   float target_temperature_low{0.0f}; | ||||||
|   float target_temperature_high{0.0f}; |   float target_temperature_high{0.0f}; | ||||||
|   bool legacy_away{false}; |   bool unused_legacy_away{false}; | ||||||
|   enums::ClimateAction action{}; |   enums::ClimateAction action{}; | ||||||
|   enums::ClimateFanMode fan_mode{}; |   enums::ClimateFanMode fan_mode{}; | ||||||
|   enums::ClimateSwingMode swing_mode{}; |   enums::ClimateSwingMode swing_mode{}; | ||||||
| @@ -986,8 +987,8 @@ class ClimateCommandRequest : public ProtoMessage { | |||||||
|   float target_temperature_low{0.0f}; |   float target_temperature_low{0.0f}; | ||||||
|   bool has_target_temperature_high{false}; |   bool has_target_temperature_high{false}; | ||||||
|   float target_temperature_high{0.0f}; |   float target_temperature_high{0.0f}; | ||||||
|   bool has_legacy_away{false}; |   bool unused_has_legacy_away{false}; | ||||||
|   bool legacy_away{false}; |   bool unused_legacy_away{false}; | ||||||
|   bool has_fan_mode{false}; |   bool has_fan_mode{false}; | ||||||
|   enums::ClimateFanMode fan_mode{}; |   enums::ClimateFanMode fan_mode{}; | ||||||
|   bool has_swing_mode{false}; |   bool has_swing_mode{false}; | ||||||
|   | |||||||
| @@ -18,5 +18,5 @@ async def to_code(config): | |||||||
|         # https://github.com/esphome/AsyncTCP/blob/master/library.json |         # https://github.com/esphome/AsyncTCP/blob/master/library.json | ||||||
|         cg.add_library("esphome/AsyncTCP-esphome", "1.2.2") |         cg.add_library("esphome/AsyncTCP-esphome", "1.2.2") | ||||||
|     elif CORE.is_esp8266: |     elif CORE.is_esp8266: | ||||||
|         # https://github.com/OttoWinter/ESPAsyncTCP |         # https://github.com/esphome/ESPAsyncTCP | ||||||
|         cg.add_library("ottowinter/ESPAsyncTCP-esphome", "1.2.3") |         cg.add_library("esphome/ESPAsyncTCP-esphome", "1.2.3") | ||||||
|   | |||||||
| @@ -43,12 +43,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) { | |||||||
| } | } | ||||||
|  |  | ||||||
| BinarySensor::BinarySensor() : state(false) {} | BinarySensor::BinarySensor() : state(false) {} | ||||||
| void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } |  | ||||||
| std::string BinarySensor::get_device_class() { |  | ||||||
|   if (this->device_class_.has_value()) |  | ||||||
|     return *this->device_class_; |  | ||||||
|   return ""; |  | ||||||
| } |  | ||||||
| void BinarySensor::add_filter(Filter *filter) { | void BinarySensor::add_filter(Filter *filter) { | ||||||
|   filter->parent_ = this; |   filter->parent_ = this; | ||||||
|   if (this->filter_list_ == nullptr) { |   if (this->filter_list_ == nullptr) { | ||||||
|   | |||||||
| @@ -34,7 +34,7 @@ namespace binary_sensor { | |||||||
|  * The sub classes should notify the front-end of new states via the publish_state() method which |  * The sub classes should notify the front-end of new states via the publish_state() method which | ||||||
|  * handles inverted inputs for you. |  * handles inverted inputs for you. | ||||||
|  */ |  */ | ||||||
| class BinarySensor : public EntityBase { | class BinarySensor : public EntityBase, public EntityBase_DeviceClass { | ||||||
|  public: |  public: | ||||||
|   explicit BinarySensor(); |   explicit BinarySensor(); | ||||||
|  |  | ||||||
| @@ -60,12 +60,6 @@ class BinarySensor : public EntityBase { | |||||||
|   /// The current reported state of the binary sensor. |   /// The current reported state of the binary sensor. | ||||||
|   bool state; |   bool state; | ||||||
|  |  | ||||||
|   /// Manually set the Home Assistant device class (see binary_sensor::device_class) |  | ||||||
|   void set_device_class(const std::string &device_class); |  | ||||||
|  |  | ||||||
|   /// Get the device class for this binary sensor, using the manual override if specified. |  | ||||||
|   std::string get_device_class(); |  | ||||||
|  |  | ||||||
|   void add_filter(Filter *filter); |   void add_filter(Filter *filter); | ||||||
|   void add_filters(const std::vector<Filter *> &filters); |   void add_filters(const std::vector<Filter *> &filters); | ||||||
|  |  | ||||||
| @@ -82,7 +76,6 @@ class BinarySensor : public EntityBase { | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   CallbackManager<void(bool)> state_callback_{}; |   CallbackManager<void(bool)> state_callback_{}; | ||||||
|   optional<std::string> device_class_{};  ///< Stores the override of the device class |  | ||||||
|   Filter *filter_list_{nullptr}; |   Filter *filter_list_{nullptr}; | ||||||
|   bool has_state_{false}; |   bool has_state_{false}; | ||||||
|   bool publish_initial_state_{false}; |   bool publish_initial_state_{false}; | ||||||
|   | |||||||
| @@ -16,6 +16,9 @@ void BinarySensorMap::loop() { | |||||||
|     case BINARY_SENSOR_MAP_TYPE_SUM: |     case BINARY_SENSOR_MAP_TYPE_SUM: | ||||||
|       this->process_sum_(); |       this->process_sum_(); | ||||||
|       break; |       break; | ||||||
|  |     case BINARY_SENSOR_MAP_TYPE_BAYESIAN: | ||||||
|  |       this->process_bayesian_(); | ||||||
|  |       break; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -23,46 +26,51 @@ void BinarySensorMap::process_group_() { | |||||||
|   float total_current_value = 0.0; |   float total_current_value = 0.0; | ||||||
|   uint8_t num_active_sensors = 0; |   uint8_t num_active_sensors = 0; | ||||||
|   uint64_t mask = 0x00; |   uint64_t mask = 0x00; | ||||||
|   // check all binary_sensors for its state. when active add its value to total_current_value. |  | ||||||
|   // create a bitmask for the binary_sensor status on all channels |   // - check all binary_sensors for its state | ||||||
|  |   //  - if active, add its value to total_current_value. | ||||||
|  |   // - creates a bitmask for the binary_sensor states on all channels | ||||||
|   for (size_t i = 0; i < this->channels_.size(); i++) { |   for (size_t i = 0; i < this->channels_.size(); i++) { | ||||||
|     auto bs = this->channels_[i]; |     auto bs = this->channels_[i]; | ||||||
|     if (bs.binary_sensor->state) { |     if (bs.binary_sensor->state) { | ||||||
|       num_active_sensors++; |       num_active_sensors++; | ||||||
|       total_current_value += bs.sensor_value; |       total_current_value += bs.parameters.sensor_value; | ||||||
|       mask |= 1ULL << i; |       mask |= 1ULL << i; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   // check if the sensor map was touched |  | ||||||
|  |   // potentially update state only if a binary_sensor is active | ||||||
|   if (mask != 0ULL) { |   if (mask != 0ULL) { | ||||||
|     // did the bit_mask change or is it a new sensor touch |     // publish the average if the bitmask has changed | ||||||
|     if (this->last_mask_ != mask) { |     if (this->last_mask_ != mask) { | ||||||
|       float publish_value = total_current_value / num_active_sensors; |       float publish_value = total_current_value / num_active_sensors; | ||||||
|       this->publish_state(publish_value); |       this->publish_state(publish_value); | ||||||
|     } |     } | ||||||
|   } else if (this->last_mask_ != 0ULL) { |   } else if (this->last_mask_ != 0ULL) { | ||||||
|     // is this a new sensor release |     // no buttons are pressed and the states have changed since last run, so publish NAN | ||||||
|     ESP_LOGV(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str()); |     ESP_LOGV(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str()); | ||||||
|     this->publish_state(NAN); |     this->publish_state(NAN); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->last_mask_ = mask; |   this->last_mask_ = mask; | ||||||
| } | } | ||||||
|  |  | ||||||
| void BinarySensorMap::process_sum_() { | void BinarySensorMap::process_sum_() { | ||||||
|   float total_current_value = 0.0; |   float total_current_value = 0.0; | ||||||
|   uint64_t mask = 0x00; |   uint64_t mask = 0x00; | ||||||
|  |  | ||||||
|   // - check all binary_sensor states |   // - check all binary_sensor states | ||||||
|   // - if active, add its value to total_current_value |   // - if active, add its value to total_current_value | ||||||
|   // - creates a bitmask for the binary_sensor status on all channels |   // - creates a bitmask for the binary_sensor states on all channels | ||||||
|   for (size_t i = 0; i < this->channels_.size(); i++) { |   for (size_t i = 0; i < this->channels_.size(); i++) { | ||||||
|     auto bs = this->channels_[i]; |     auto bs = this->channels_[i]; | ||||||
|     if (bs.binary_sensor->state) { |     if (bs.binary_sensor->state) { | ||||||
|       total_current_value += bs.sensor_value; |       total_current_value += bs.parameters.sensor_value; | ||||||
|       mask |= 1ULL << i; |       mask |= 1ULL << i; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // update state only if the binary sensor states have changed or if no state has ever been sent on boot |   // update state only if any binary_sensor states have changed or if no state has ever been sent on boot | ||||||
|   if ((this->last_mask_ != mask) || (!this->has_state())) { |   if ((this->last_mask_ != mask) || (!this->has_state())) { | ||||||
|     this->publish_state(total_current_value); |     this->publish_state(total_current_value); | ||||||
|   } |   } | ||||||
| @@ -70,15 +78,65 @@ void BinarySensorMap::process_sum_() { | |||||||
|   this->last_mask_ = mask; |   this->last_mask_ = mask; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void BinarySensorMap::process_bayesian_() { | ||||||
|  |   float posterior_probability = this->bayesian_prior_; | ||||||
|  |   uint64_t mask = 0x00; | ||||||
|  |  | ||||||
|  |   // - compute the posterior probability by taking the product of the predicate probablities for each observation | ||||||
|  |   // - create a bitmask for the binary_sensor states on all channels/observations | ||||||
|  |   for (size_t i = 0; i < this->channels_.size(); i++) { | ||||||
|  |     auto bs = this->channels_[i]; | ||||||
|  |  | ||||||
|  |     posterior_probability *= | ||||||
|  |         this->bayesian_predicate_(bs.binary_sensor->state, posterior_probability, | ||||||
|  |                                   bs.parameters.probabilities.given_true, bs.parameters.probabilities.given_false); | ||||||
|  |  | ||||||
|  |     mask |= ((uint64_t) (bs.binary_sensor->state)) << i; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // update state only if any binary_sensor states have changed or if no state has ever been sent on boot | ||||||
|  |   if ((this->last_mask_ != mask) || (!this->has_state())) { | ||||||
|  |     this->publish_state(posterior_probability); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->last_mask_ = mask; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float BinarySensorMap::bayesian_predicate_(bool sensor_state, float prior, float prob_given_true, | ||||||
|  |                                            float prob_given_false) { | ||||||
|  |   float prob_state_source_true = prob_given_true; | ||||||
|  |   float prob_state_source_false = prob_given_false; | ||||||
|  |  | ||||||
|  |   // if sensor is off, then we use the probabilities for the observation's complement | ||||||
|  |   if (!sensor_state) { | ||||||
|  |     prob_state_source_true = 1 - prob_given_true; | ||||||
|  |     prob_state_source_false = 1 - prob_given_false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return prob_state_source_true / (prior * prob_state_source_true + (1.0 - prior) * prob_state_source_false); | ||||||
|  | } | ||||||
|  |  | ||||||
| void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) { | void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) { | ||||||
|   BinarySensorMapChannel sensor_channel{ |   BinarySensorMapChannel sensor_channel{ | ||||||
|       .binary_sensor = sensor, |       .binary_sensor = sensor, | ||||||
|  |       .parameters{ | ||||||
|           .sensor_value = value, |           .sensor_value = value, | ||||||
|  |       }, | ||||||
|   }; |   }; | ||||||
|   this->channels_.push_back(sensor_channel); |   this->channels_.push_back(sensor_channel); | ||||||
| } | } | ||||||
|  |  | ||||||
| void BinarySensorMap::set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; } | void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float prob_given_true, float prob_given_false) { | ||||||
|  |   BinarySensorMapChannel sensor_channel{ | ||||||
|  |       .binary_sensor = sensor, | ||||||
|  |       .parameters{ | ||||||
|  |           .probabilities{ | ||||||
|  |               .given_true = prob_given_true, | ||||||
|  |               .given_false = prob_given_false, | ||||||
|  |           }, | ||||||
|  |       }, | ||||||
|  |   }; | ||||||
|  |   this->channels_.push_back(sensor_channel); | ||||||
|  | } | ||||||
| }  // namespace binary_sensor_map | }  // namespace binary_sensor_map | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -12,51 +12,88 @@ namespace binary_sensor_map { | |||||||
| enum BinarySensorMapType { | enum BinarySensorMapType { | ||||||
|   BINARY_SENSOR_MAP_TYPE_GROUP, |   BINARY_SENSOR_MAP_TYPE_GROUP, | ||||||
|   BINARY_SENSOR_MAP_TYPE_SUM, |   BINARY_SENSOR_MAP_TYPE_SUM, | ||||||
|  |   BINARY_SENSOR_MAP_TYPE_BAYESIAN, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct BinarySensorMapChannel { | struct BinarySensorMapChannel { | ||||||
|   binary_sensor::BinarySensor *binary_sensor; |   binary_sensor::BinarySensor *binary_sensor; | ||||||
|  |   union { | ||||||
|     float sensor_value; |     float sensor_value; | ||||||
|  |     struct { | ||||||
|  |       float given_true; | ||||||
|  |       float given_false; | ||||||
|  |     } probabilities; | ||||||
|  |   } parameters; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** Class to group binary_sensors to one Sensor. | /** Class to map one or more binary_sensors to one Sensor. | ||||||
|  * |  * | ||||||
|  * Each binary sensor represents a float value in the group. |  * Each binary sensor has configured parameters that each mapping type uses to compute the single numerical result | ||||||
|  */ |  */ | ||||||
| class BinarySensorMap : public sensor::Sensor, public Component { | class BinarySensorMap : public sensor::Sensor, public Component { | ||||||
|  public: |  public: | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * The loop checks all binary_sensor states |    * The loop calls the configured type processing method | ||||||
|    * When the binary_sensor reports a true value for its state, then the float value it represents is added to the |  | ||||||
|    * total_current_value |  | ||||||
|    * |    * | ||||||
|    * Only when the total_current_value changed and at least one sensor reports an active state we publish the sensors |    * The processing method loops through all sensors and calculates the numerical result | ||||||
|    * average value. When the value changed and no sensors ar active we publish NAN. |    * The result is only published if a binary sensor state has changed or, for some types, on initial boot | ||||||
|    * */ |    */ | ||||||
|   void loop() override; |   void loop() override; | ||||||
|   float get_setup_priority() const override { return setup_priority::DATA; } |  | ||||||
|   /** Add binary_sensors to the group. |   /** | ||||||
|    * Each binary_sensor represents a float value when its state is true |    * Add binary_sensors to the group when only one parameter is needed for the configured mapping type. | ||||||
|    * |    * | ||||||
|    * @param *sensor The binary sensor. |    * @param *sensor The binary sensor. | ||||||
|    * @param value  The value this binary_sensor represents |    * @param value  The value this binary_sensor represents | ||||||
|    */ |    */ | ||||||
|   void add_channel(binary_sensor::BinarySensor *sensor, float value); |   void add_channel(binary_sensor::BinarySensor *sensor, float value); | ||||||
|   void set_sensor_type(BinarySensorMapType sensor_type); |  | ||||||
|  |   /** | ||||||
|  |    * Add binary_sensors to the group when two parameters are needed for the Bayesian mapping type. | ||||||
|  |    * | ||||||
|  |    * @param *sensor The binary sensor. | ||||||
|  |    * @param prob_given_true Probability this observation is on when the Bayesian event is true | ||||||
|  |    * @param prob_given_false Probability this observation is on when the Bayesian event is false | ||||||
|  |    */ | ||||||
|  |   void add_channel(binary_sensor::BinarySensor *sensor, float prob_given_true, float prob_given_false); | ||||||
|  |  | ||||||
|  |   void set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; } | ||||||
|  |  | ||||||
|  |   void set_bayesian_prior(float prior) { this->bayesian_prior_ = prior; }; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   std::vector<BinarySensorMapChannel> channels_{}; |   std::vector<BinarySensorMapChannel> channels_{}; | ||||||
|   BinarySensorMapType sensor_type_{BINARY_SENSOR_MAP_TYPE_GROUP}; |   BinarySensorMapType sensor_type_{BINARY_SENSOR_MAP_TYPE_GROUP}; | ||||||
|   // this gives max 64 channels per binary_sensor_map |  | ||||||
|  |   // this allows a max of 64 channels/observations in order to keep track of binary_sensor states | ||||||
|   uint64_t last_mask_{0x00}; |   uint64_t last_mask_{0x00}; | ||||||
|  |  | ||||||
|  |   // Bayesian event prior probability before taking into account any observations | ||||||
|  |   float bayesian_prior_{}; | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * methods to process the types of binary_sensor_maps |    * Methods to process the binary_sensor_maps types | ||||||
|    * GROUP: process_group_() just map to a value |    * | ||||||
|  |    * GROUP: process_group_() averages all the values | ||||||
|    * ADD: process_add_() adds all the values |    * ADD: process_add_() adds all the values | ||||||
|  |    * BAYESIAN: process_bayesian_() computes the predicate probability | ||||||
|    * */ |    * */ | ||||||
|   void process_group_(); |   void process_group_(); | ||||||
|   void process_sum_(); |   void process_sum_(); | ||||||
|  |   void process_bayesian_(); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Computes the Bayesian predicate for a specific observation | ||||||
|  |    * If the sensor state is false, then we use the parameters' probabilities for the observatiosn complement | ||||||
|  |    * | ||||||
|  |    * @param sensor_state  State of observation | ||||||
|  |    * @param prior Prior probability before accounting for this observation | ||||||
|  |    * @param prob_given_true Probability this observation is on when the Bayesian event is true | ||||||
|  |    * @param prob_given_false Probability this observation is on when the Bayesian event is false | ||||||
|  |    * */ | ||||||
|  |   float bayesian_predicate_(bool sensor_state, float prior, float prob_given_true, float prob_given_false); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace binary_sensor_map | }  // namespace binary_sensor_map | ||||||
|   | |||||||
| @@ -20,16 +20,29 @@ BinarySensorMap = binary_sensor_map_ns.class_( | |||||||
| ) | ) | ||||||
| SensorMapType = binary_sensor_map_ns.enum("SensorMapType") | SensorMapType = binary_sensor_map_ns.enum("SensorMapType") | ||||||
|  |  | ||||||
|  | CONF_BAYESIAN = "bayesian" | ||||||
|  | CONF_PRIOR = "prior" | ||||||
|  | CONF_PROB_GIVEN_TRUE = "prob_given_true" | ||||||
|  | CONF_PROB_GIVEN_FALSE = "prob_given_false" | ||||||
|  | CONF_OBSERVATIONS = "observations" | ||||||
|  |  | ||||||
| SENSOR_MAP_TYPES = { | SENSOR_MAP_TYPES = { | ||||||
|     CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP, |     CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP, | ||||||
|     CONF_SUM: SensorMapType.BINARY_SENSOR_MAP_TYPE_SUM, |     CONF_SUM: SensorMapType.BINARY_SENSOR_MAP_TYPE_SUM, | ||||||
|  |     CONF_BAYESIAN: SensorMapType.BINARY_SENSOR_MAP_TYPE_BAYESIAN, | ||||||
| } | } | ||||||
|  |  | ||||||
| entry = { | entry_one_parameter = { | ||||||
|     cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor), |     cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor), | ||||||
|     cv.Required(CONF_VALUE): cv.float_, |     cv.Required(CONF_VALUE): cv.float_, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | entry_bayesian_parameters = { | ||||||
|  |     cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor), | ||||||
|  |     cv.Required(CONF_PROB_GIVEN_TRUE): cv.float_range(min=0, max=1), | ||||||
|  |     cv.Required(CONF_PROB_GIVEN_FALSE): cv.float_range(min=0, max=1), | ||||||
|  | } | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.typed_schema( | CONFIG_SCHEMA = cv.typed_schema( | ||||||
|     { |     { | ||||||
|         CONF_GROUP: sensor.sensor_schema( |         CONF_GROUP: sensor.sensor_schema( | ||||||
| @@ -39,7 +52,7 @@ CONFIG_SCHEMA = cv.typed_schema( | |||||||
|         ).extend( |         ).extend( | ||||||
|             { |             { | ||||||
|                 cv.Required(CONF_CHANNELS): cv.All( |                 cv.Required(CONF_CHANNELS): cv.All( | ||||||
|                     cv.ensure_list(entry), cv.Length(min=1, max=64) |                     cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64) | ||||||
|                 ), |                 ), | ||||||
|             } |             } | ||||||
|         ), |         ), | ||||||
| @@ -50,7 +63,18 @@ CONFIG_SCHEMA = cv.typed_schema( | |||||||
|         ).extend( |         ).extend( | ||||||
|             { |             { | ||||||
|                 cv.Required(CONF_CHANNELS): cv.All( |                 cv.Required(CONF_CHANNELS): cv.All( | ||||||
|                     cv.ensure_list(entry), cv.Length(min=1, max=64) |                     cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64) | ||||||
|  |                 ), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         CONF_BAYESIAN: sensor.sensor_schema( | ||||||
|  |             BinarySensorMap, | ||||||
|  |             accuracy_decimals=2, | ||||||
|  |         ).extend( | ||||||
|  |             { | ||||||
|  |                 cv.Required(CONF_PRIOR): cv.float_range(min=0, max=1), | ||||||
|  |                 cv.Required(CONF_OBSERVATIONS): cv.All( | ||||||
|  |                     cv.ensure_list(entry_bayesian_parameters), cv.Length(min=1, max=64) | ||||||
|                 ), |                 ), | ||||||
|             } |             } | ||||||
|         ), |         ), | ||||||
| @@ -66,6 +90,17 @@ async def to_code(config): | |||||||
|     constant = SENSOR_MAP_TYPES[config[CONF_TYPE]] |     constant = SENSOR_MAP_TYPES[config[CONF_TYPE]] | ||||||
|     cg.add(var.set_sensor_type(constant)) |     cg.add(var.set_sensor_type(constant)) | ||||||
|  |  | ||||||
|  |     if config[CONF_TYPE] == CONF_BAYESIAN: | ||||||
|  |         cg.add(var.set_bayesian_prior(config[CONF_PRIOR])) | ||||||
|  |  | ||||||
|  |         for obs in config[CONF_OBSERVATIONS]: | ||||||
|  |             input_var = await cg.get_variable(obs[CONF_BINARY_SENSOR]) | ||||||
|  |             cg.add( | ||||||
|  |                 var.add_channel( | ||||||
|  |                     input_var, obs[CONF_PROB_GIVEN_TRUE], obs[CONF_PROB_GIVEN_FALSE] | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |     else: | ||||||
|         for ch in config[CONF_CHANNELS]: |         for ch in config[CONF_CHANNELS]: | ||||||
|             input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR]) |             input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR]) | ||||||
|             cg.add(var.add_channel(input_var, ch[CONF_VALUE])) |             cg.add(var.add_channel(input_var, ch[CONF_VALUE])) | ||||||
|   | |||||||
| @@ -29,8 +29,35 @@ BLEClientConnectTrigger = ble_client_ns.class_( | |||||||
| BLEClientDisconnectTrigger = ble_client_ns.class_( | BLEClientDisconnectTrigger = ble_client_ns.class_( | ||||||
|     "BLEClientDisconnectTrigger", automation.Trigger.template(BLEClientNodeConstRef) |     "BLEClientDisconnectTrigger", automation.Trigger.template(BLEClientNodeConstRef) | ||||||
| ) | ) | ||||||
|  | BLEClientPasskeyRequestTrigger = ble_client_ns.class_( | ||||||
|  |     "BLEClientPasskeyRequestTrigger", automation.Trigger.template(BLEClientNodeConstRef) | ||||||
|  | ) | ||||||
|  | BLEClientPasskeyNotificationTrigger = ble_client_ns.class_( | ||||||
|  |     "BLEClientPasskeyNotificationTrigger", | ||||||
|  |     automation.Trigger.template(BLEClientNodeConstRef, cg.uint32), | ||||||
|  | ) | ||||||
|  | BLEClientNumericComparisonRequestTrigger = ble_client_ns.class_( | ||||||
|  |     "BLEClientNumericComparisonRequestTrigger", | ||||||
|  |     automation.Trigger.template(BLEClientNodeConstRef, cg.uint32), | ||||||
|  | ) | ||||||
|  |  | ||||||
| # Actions | # Actions | ||||||
| BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action) | BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action) | ||||||
|  | BLEPasskeyReplyAction = ble_client_ns.class_( | ||||||
|  |     "BLEClientPasskeyReplyAction", automation.Action | ||||||
|  | ) | ||||||
|  | BLENumericComparisonReplyAction = ble_client_ns.class_( | ||||||
|  |     "BLEClientNumericComparisonReplyAction", automation.Action | ||||||
|  | ) | ||||||
|  | BLERemoveBondAction = ble_client_ns.class_( | ||||||
|  |     "BLEClientRemoveBondAction", automation.Action | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONF_PASSKEY = "passkey" | ||||||
|  | CONF_ACCEPT = "accept" | ||||||
|  | CONF_ON_PASSKEY_REQUEST = "on_passkey_request" | ||||||
|  | CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification" | ||||||
|  | CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request" | ||||||
|  |  | ||||||
| # Espressif platformio framework is built with MAX_BLE_CONN to 3, so | # Espressif platformio framework is built with MAX_BLE_CONN to 3, so | ||||||
| # enforce this in yaml checks. | # enforce this in yaml checks. | ||||||
| @@ -56,6 +83,29 @@ CONFIG_SCHEMA = ( | |||||||
|                     ), |                     ), | ||||||
|                 } |                 } | ||||||
|             ), |             ), | ||||||
|  |             cv.Optional(CONF_ON_PASSKEY_REQUEST): automation.validate_automation( | ||||||
|  |                 { | ||||||
|  |                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||||
|  |                         BLEClientPasskeyRequestTrigger | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_ON_PASSKEY_NOTIFICATION): automation.validate_automation( | ||||||
|  |                 { | ||||||
|  |                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||||
|  |                         BLEClientPasskeyNotificationTrigger | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |             cv.Optional( | ||||||
|  |                 CONF_ON_NUMERIC_COMPARISON_REQUEST | ||||||
|  |             ): automation.validate_automation( | ||||||
|  |                 { | ||||||
|  |                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||||
|  |                         BLEClientNumericComparisonRequestTrigger | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|     .extend(cv.COMPONENT_SCHEMA) |     .extend(cv.COMPONENT_SCHEMA) | ||||||
| @@ -85,13 +135,34 @@ BLE_WRITE_ACTION_SCHEMA = cv.Schema( | |||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_ID): cv.use_id(BLEClient), | ||||||
|  |         cv.Required(CONF_ACCEPT): cv.templatable(cv.boolean), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | BLE_PASSKEY_REPLY_ACTION_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_ID): cv.use_id(BLEClient), | ||||||
|  |         cv.Required(CONF_PASSKEY): cv.templatable(cv.int_range(min=0, max=999999)), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | BLE_REMOVE_BOND_ACTION_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_ID): cv.use_id(BLEClient), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action( | @automation.register_action( | ||||||
|     "ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA |     "ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA | ||||||
| ) | ) | ||||||
| async def ble_write_to_code(config, action_id, template_arg, args): | async def ble_write_to_code(config, action_id, template_arg, args): | ||||||
|     paren = await cg.get_variable(config[CONF_ID]) |     parent = await cg.get_variable(config[CONF_ID]) | ||||||
|     var = cg.new_Pvariable(action_id, template_arg, paren) |     var = cg.new_Pvariable(action_id, template_arg, parent) | ||||||
|  |  | ||||||
|     value = config[CONF_VALUE] |     value = config[CONF_VALUE] | ||||||
|     if cg.is_template(value): |     if cg.is_template(value): | ||||||
| @@ -137,6 +208,54 @@ async def ble_write_to_code(config, action_id, template_arg, args): | |||||||
|     return var |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "ble_client.numeric_comparison_reply", | ||||||
|  |     BLENumericComparisonReplyAction, | ||||||
|  |     BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA, | ||||||
|  | ) | ||||||
|  | async def numeric_comparison_reply_to_code(config, action_id, template_arg, args): | ||||||
|  |     parent = await cg.get_variable(config[CONF_ID]) | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg, parent) | ||||||
|  |  | ||||||
|  |     accept = config[CONF_ACCEPT] | ||||||
|  |     if cg.is_template(accept): | ||||||
|  |         templ = await cg.templatable(accept, args, cg.bool_) | ||||||
|  |         cg.add(var.set_value_template(templ)) | ||||||
|  |     else: | ||||||
|  |         cg.add(var.set_value_simple(accept)) | ||||||
|  |  | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "ble_client.passkey_reply", BLEPasskeyReplyAction, BLE_PASSKEY_REPLY_ACTION_SCHEMA | ||||||
|  | ) | ||||||
|  | async def passkey_reply_to_code(config, action_id, template_arg, args): | ||||||
|  |     parent = await cg.get_variable(config[CONF_ID]) | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg, parent) | ||||||
|  |  | ||||||
|  |     passkey = config[CONF_PASSKEY] | ||||||
|  |     if cg.is_template(passkey): | ||||||
|  |         templ = await cg.templatable(passkey, args, cg.uint32) | ||||||
|  |         cg.add(var.set_value_template(templ)) | ||||||
|  |     else: | ||||||
|  |         cg.add(var.set_value_simple(passkey)) | ||||||
|  |  | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "ble_client.remove_bond", | ||||||
|  |     BLERemoveBondAction, | ||||||
|  |     BLE_REMOVE_BOND_ACTION_SCHEMA, | ||||||
|  | ) | ||||||
|  | async def remove_bond_to_code(config, action_id, template_arg, args): | ||||||
|  |     parent = await cg.get_variable(config[CONF_ID]) | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg, parent) | ||||||
|  |  | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
| @@ -148,3 +267,12 @@ async def to_code(config): | |||||||
|     for conf in config.get(CONF_ON_DISCONNECT, []): |     for conf in config.get(CONF_ON_DISCONNECT, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|         await automation.build_automation(trigger, [], conf) |         await automation.build_automation(trigger, [], conf) | ||||||
|  |     for conf in config.get(CONF_ON_PASSKEY_REQUEST, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         await automation.build_automation(trigger, [], conf) | ||||||
|  |     for conf in config.get(CONF_ON_PASSKEY_NOTIFICATION, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf) | ||||||
|  |     for conf in config.get(CONF_ON_NUMERIC_COMPARISON_REQUEST, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf) | ||||||
|   | |||||||
| @@ -37,6 +37,44 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode { | |||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class BLEClientPasskeyRequestTrigger : public Trigger<>, public BLEClientNode { | ||||||
|  |  public: | ||||||
|  |   explicit BLEClientPasskeyRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||||
|  |   void loop() override {} | ||||||
|  |   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { | ||||||
|  |     if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT && | ||||||
|  |         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { | ||||||
|  |       this->trigger(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class BLEClientPasskeyNotificationTrigger : public Trigger<uint32_t>, public BLEClientNode { | ||||||
|  |  public: | ||||||
|  |   explicit BLEClientPasskeyNotificationTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||||
|  |   void loop() override {} | ||||||
|  |   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { | ||||||
|  |     if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT && | ||||||
|  |         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { | ||||||
|  |       uint32_t passkey = param->ble_security.key_notif.passkey; | ||||||
|  |       this->trigger(passkey); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class BLEClientNumericComparisonRequestTrigger : public Trigger<uint32_t>, public BLEClientNode { | ||||||
|  |  public: | ||||||
|  |   explicit BLEClientNumericComparisonRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||||
|  |   void loop() override {} | ||||||
|  |   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { | ||||||
|  |     if (event == ESP_GAP_BLE_NC_REQ_EVT && | ||||||
|  |         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { | ||||||
|  |       uint32_t passkey = param->ble_security.key_notif.passkey; | ||||||
|  |       this->trigger(passkey); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
| class BLEWriterClientNode : public BLEClientNode { | class BLEWriterClientNode : public BLEClientNode { | ||||||
|  public: |  public: | ||||||
|   BLEWriterClientNode(BLEClient *ble_client) { |   BLEWriterClientNode(BLEClient *ble_client) { | ||||||
| @@ -94,6 +132,86 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ | |||||||
|   std::function<std::vector<uint8_t>(Ts...)> value_template_{}; |   std::function<std::vector<uint8_t>(Ts...)> value_template_{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   BLEClientPasskeyReplyAction(BLEClient *ble_client) { parent_ = ble_client; } | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { | ||||||
|  |     uint32_t passkey; | ||||||
|  |     if (has_simple_value_) { | ||||||
|  |       passkey = this->value_simple_; | ||||||
|  |     } else { | ||||||
|  |       passkey = this->value_template_(x...); | ||||||
|  |     } | ||||||
|  |     if (passkey > 999999) | ||||||
|  |       return; | ||||||
|  |     esp_bd_addr_t remote_bda; | ||||||
|  |     memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); | ||||||
|  |     esp_ble_passkey_reply(remote_bda, true, passkey); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void set_value_template(std::function<uint32_t(Ts...)> func) { | ||||||
|  |     this->value_template_ = std::move(func); | ||||||
|  |     has_simple_value_ = false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void set_value_simple(const uint32_t &value) { | ||||||
|  |     this->value_simple_ = value; | ||||||
|  |     has_simple_value_ = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   BLEClient *parent_{nullptr}; | ||||||
|  |   bool has_simple_value_ = true; | ||||||
|  |   uint32_t value_simple_{0}; | ||||||
|  |   std::function<uint32_t(Ts...)> value_template_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; } | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { | ||||||
|  |     esp_bd_addr_t remote_bda; | ||||||
|  |     memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); | ||||||
|  |     if (has_simple_value_) { | ||||||
|  |       esp_ble_confirm_reply(remote_bda, this->value_simple_); | ||||||
|  |     } else { | ||||||
|  |       esp_ble_confirm_reply(remote_bda, this->value_template_(x...)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void set_value_template(std::function<bool(Ts...)> func) { | ||||||
|  |     this->value_template_ = std::move(func); | ||||||
|  |     has_simple_value_ = false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void set_value_simple(const bool &value) { | ||||||
|  |     this->value_simple_ = value; | ||||||
|  |     has_simple_value_ = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   BLEClient *parent_{nullptr}; | ||||||
|  |   bool has_simple_value_ = true; | ||||||
|  |   bool value_simple_{false}; | ||||||
|  |   std::function<bool(Ts...)> value_template_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   BLEClientRemoveBondAction(BLEClient *ble_client) { parent_ = ble_client; } | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { | ||||||
|  |     esp_bd_addr_t remote_bda; | ||||||
|  |     memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); | ||||||
|  |     esp_ble_remove_bond_device(remote_bda); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   BLEClient *parent_{nullptr}; | ||||||
|  | }; | ||||||
|  |  | ||||||
| }  // namespace ble_client | }  // namespace ble_client | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ class BLEClient; | |||||||
| class BLEClientNode { | class BLEClientNode { | ||||||
|  public: |  public: | ||||||
|   virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, |   virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                                    esp_ble_gattc_cb_param_t *param) = 0; |                                    esp_ble_gattc_cb_param_t *param){}; | ||||||
|   virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {} |   virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {} | ||||||
|   virtual void loop() {} |   virtual void loop() {} | ||||||
|   void set_address(uint64_t address) { address_ = address; } |   void set_address(uint64_t address) { address_ = address; } | ||||||
|   | |||||||
| @@ -13,8 +13,5 @@ void Button::press() { | |||||||
| } | } | ||||||
| void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); } | void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); } | ||||||
|  |  | ||||||
| void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } |  | ||||||
| std::string Button::get_device_class() { return this->device_class_; } |  | ||||||
|  |  | ||||||
| }  // namespace button | }  // namespace button | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ namespace button { | |||||||
|  * |  * | ||||||
|  * A button is just a momentary switch that does not have a state, only a trigger. |  * A button is just a momentary switch that does not have a state, only a trigger. | ||||||
|  */ |  */ | ||||||
| class Button : public EntityBase { | class Button : public EntityBase, public EntityBase_DeviceClass { | ||||||
|  public: |  public: | ||||||
|   /** Press this button. This is called by the front-end. |   /** Press this button. This is called by the front-end. | ||||||
|    * |    * | ||||||
| @@ -40,19 +40,12 @@ class Button : public EntityBase { | |||||||
|    */ |    */ | ||||||
|   void add_on_press_callback(std::function<void()> &&callback); |   void add_on_press_callback(std::function<void()> &&callback); | ||||||
|  |  | ||||||
|   /// Set the Home Assistant device class (see button::device_class). |  | ||||||
|   void set_device_class(const std::string &device_class); |  | ||||||
|  |  | ||||||
|   /// Get the device class for this button. |  | ||||||
|   std::string get_device_class(); |  | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   /** You should implement this virtual method if you want to create your own button. |   /** You should implement this virtual method if you want to create your own button. | ||||||
|    */ |    */ | ||||||
|   virtual void press_action() = 0; |   virtual void press_action() = 0; | ||||||
|  |  | ||||||
|   CallbackManager<void()> press_callback_{}; |   CallbackManager<void()> press_callback_{}; | ||||||
|   std::string device_class_{}; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace button | }  // namespace button | ||||||
|   | |||||||
| @@ -343,7 +343,7 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema( | |||||||
|         cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature), |         cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature), | ||||||
|         cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature), |         cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature), | ||||||
|         cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature), |         cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature), | ||||||
|         cv.Optional(CONF_AWAY): cv.templatable(cv.boolean), |         cv.Optional(CONF_AWAY): cv.invalid("Use preset instead"), | ||||||
|         cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable( |         cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable( | ||||||
|             validate_climate_fan_mode |             validate_climate_fan_mode | ||||||
|         ), |         ), | ||||||
| @@ -379,9 +379,6 @@ async def climate_control_to_code(config, action_id, template_arg, args): | |||||||
|             config[CONF_TARGET_TEMPERATURE_HIGH], args, float |             config[CONF_TARGET_TEMPERATURE_HIGH], args, float | ||||||
|         ) |         ) | ||||||
|         cg.add(var.set_target_temperature_high(template_)) |         cg.add(var.set_target_temperature_high(template_)) | ||||||
|     if CONF_AWAY in config: |  | ||||||
|         template_ = await cg.templatable(config[CONF_AWAY], args, bool) |  | ||||||
|         cg.add(var.set_away(template_)) |  | ||||||
|     if CONF_FAN_MODE in config: |     if CONF_FAN_MODE in config: | ||||||
|         template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode) |         template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode) | ||||||
|         cg.add(var.set_fan_mode(template_)) |         cg.add(var.set_fan_mode(template_)) | ||||||
|   | |||||||
| @@ -264,25 +264,11 @@ const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_; | |||||||
| const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; } | const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; } | ||||||
| const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; } | const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; } | ||||||
| const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; } | const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; } | ||||||
| optional<bool> ClimateCall::get_away() const { |  | ||||||
|   if (!this->preset_.has_value()) |  | ||||||
|     return {}; |  | ||||||
|   return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY; |  | ||||||
| } |  | ||||||
| const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; } | const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; } | ||||||
| const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; } | const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; } | ||||||
| const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; } | const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; } | ||||||
| const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; } | const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; } | ||||||
| const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; } | const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; } | ||||||
| ClimateCall &ClimateCall::set_away(bool away) { |  | ||||||
|   this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; |  | ||||||
|   return *this; |  | ||||||
| } |  | ||||||
| ClimateCall &ClimateCall::set_away(optional<bool> away) { |  | ||||||
|   if (away.has_value()) |  | ||||||
|     this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; |  | ||||||
|   return *this; |  | ||||||
| } |  | ||||||
| ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) { | ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) { | ||||||
|   this->target_temperature_high_ = target_temperature_high; |   this->target_temperature_high_ = target_temperature_high; | ||||||
|   return *this; |   return *this; | ||||||
|   | |||||||
| @@ -64,10 +64,6 @@ class ClimateCall { | |||||||
|    * For climate devices with two point target temperature control |    * For climate devices with two point target temperature control | ||||||
|    */ |    */ | ||||||
|   ClimateCall &set_target_temperature_high(optional<float> target_temperature_high); |   ClimateCall &set_target_temperature_high(optional<float> target_temperature_high); | ||||||
|   ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20") |  | ||||||
|   ClimateCall &set_away(bool away); |  | ||||||
|   ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20") |  | ||||||
|   ClimateCall &set_away(optional<bool> away); |  | ||||||
|   /// Set the fan mode of the climate device. |   /// Set the fan mode of the climate device. | ||||||
|   ClimateCall &set_fan_mode(ClimateFanMode fan_mode); |   ClimateCall &set_fan_mode(ClimateFanMode fan_mode); | ||||||
|   /// Set the fan mode of the climate device. |   /// Set the fan mode of the climate device. | ||||||
| @@ -97,8 +93,6 @@ class ClimateCall { | |||||||
|   const optional<float> &get_target_temperature() const; |   const optional<float> &get_target_temperature() const; | ||||||
|   const optional<float> &get_target_temperature_low() const; |   const optional<float> &get_target_temperature_low() const; | ||||||
|   const optional<float> &get_target_temperature_high() const; |   const optional<float> &get_target_temperature_high() const; | ||||||
|   ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead", "v1.20") |  | ||||||
|   optional<bool> get_away() const; |  | ||||||
|   const optional<ClimateFanMode> &get_fan_mode() const; |   const optional<ClimateFanMode> &get_fan_mode() const; | ||||||
|   const optional<ClimateSwingMode> &get_swing_mode() const; |   const optional<ClimateSwingMode> &get_swing_mode() const; | ||||||
|   const optional<std::string> &get_custom_fan_mode() const; |   const optional<std::string> &get_custom_fan_mode() const; | ||||||
| @@ -184,14 +178,6 @@ class Climate : public EntityBase { | |||||||
|     }; |     }; | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   /** Whether the climate device is in away mode. |  | ||||||
|    * |  | ||||||
|    * Away allows climate devices to have two different target temperature configs: |  | ||||||
|    * one for normal mode and one for away mode. |  | ||||||
|    */ |  | ||||||
|   ESPDEPRECATED("away is deprecated, use preset instead", "v1.20") |  | ||||||
|   bool away{false}; |  | ||||||
|  |  | ||||||
|   /// The active fan mode of the climate device. |   /// The active fan mode of the climate device. | ||||||
|   optional<ClimateFanMode> fan_mode; |   optional<ClimateFanMode> fan_mode; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -117,15 +117,6 @@ class ClimateTraits { | |||||||
|   bool supports_custom_preset(const std::string &custom_preset) const { |   bool supports_custom_preset(const std::string &custom_preset) const { | ||||||
|     return supported_custom_presets_.count(custom_preset); |     return supported_custom_presets_.count(custom_preset); | ||||||
|   } |   } | ||||||
|   ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead", "v1.20") |  | ||||||
|   void set_supports_away(bool supports) { |  | ||||||
|     if (supports) { |  | ||||||
|       supported_presets_.insert(CLIMATE_PRESET_AWAY); |  | ||||||
|       supported_presets_.insert(CLIMATE_PRESET_HOME); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   ESPDEPRECATED("This method is deprecated, use supports_preset() instead", "v1.20") |  | ||||||
|   bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); } |  | ||||||
|  |  | ||||||
|   void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); } |   void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); } | ||||||
|   void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); } |   void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); } | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ cover::CoverTraits CopyCover::get_traits() { | |||||||
|   // copy traits manually so it doesn't break when new options are added |   // copy traits manually so it doesn't break when new options are added | ||||||
|   // but the control() method hasn't implemented them yet. |   // but the control() method hasn't implemented them yet. | ||||||
|   traits.set_is_assumed_state(base.get_is_assumed_state()); |   traits.set_is_assumed_state(base.get_is_assumed_state()); | ||||||
|  |   traits.set_supports_stop(base.get_supports_stop()); | ||||||
|   traits.set_supports_position(base.get_supports_position()); |   traits.set_supports_position(base.get_supports_position()); | ||||||
|   traits.set_supports_tilt(base.get_supports_tilt()); |   traits.set_supports_tilt(base.get_supports_tilt()); | ||||||
|   traits.set_supports_toggle(base.get_supports_toggle()); |   traits.set_supports_toggle(base.get_supports_toggle()); | ||||||
|   | |||||||
| @@ -145,7 +145,7 @@ CoverCall &CoverCall::set_stop(bool stop) { | |||||||
|   return *this; |   return *this; | ||||||
| } | } | ||||||
| bool CoverCall::get_stop() const { return this->stop_; } | bool CoverCall::get_stop() const { return this->stop_; } | ||||||
| void Cover::set_device_class(const std::string &device_class) { this->device_class_override_ = device_class; } |  | ||||||
| CoverCall Cover::make_call() { return {this}; } | CoverCall Cover::make_call() { return {this}; } | ||||||
| void Cover::open() { | void Cover::open() { | ||||||
|   auto call = this->make_call(); |   auto call = this->make_call(); | ||||||
| @@ -204,11 +204,7 @@ optional<CoverRestoreState> Cover::restore_state_() { | |||||||
|     return {}; |     return {}; | ||||||
|   return recovered; |   return recovered; | ||||||
| } | } | ||||||
| std::string Cover::get_device_class() { |  | ||||||
|   if (this->device_class_override_.has_value()) |  | ||||||
|     return *this->device_class_override_; |  | ||||||
|   return ""; |  | ||||||
| } |  | ||||||
| bool Cover::is_fully_open() const { return this->position == COVER_OPEN; } | bool Cover::is_fully_open() const { return this->position == COVER_OPEN; } | ||||||
| bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; } | bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -108,7 +108,7 @@ const char *cover_operation_to_str(CoverOperation op); | |||||||
|  * to control all values of the cover. Also implement get_traits() to return what operations |  * to control all values of the cover. Also implement get_traits() to return what operations | ||||||
|  * the cover supports. |  * the cover supports. | ||||||
|  */ |  */ | ||||||
| class Cover : public EntityBase { | class Cover : public EntityBase, public EntityBase_DeviceClass { | ||||||
|  public: |  public: | ||||||
|   explicit Cover(); |   explicit Cover(); | ||||||
|  |  | ||||||
| @@ -156,8 +156,6 @@ class Cover : public EntityBase { | |||||||
|   void publish_state(bool save = true); |   void publish_state(bool save = true); | ||||||
|  |  | ||||||
|   virtual CoverTraits get_traits() = 0; |   virtual CoverTraits get_traits() = 0; | ||||||
|   void set_device_class(const std::string &device_class); |  | ||||||
|   std::string get_device_class(); |  | ||||||
|  |  | ||||||
|   /// Helper method to check if the cover is fully open. Equivalent to comparing .position against 1.0 |   /// Helper method to check if the cover is fully open. Equivalent to comparing .position against 1.0 | ||||||
|   bool is_fully_open() const; |   bool is_fully_open() const; | ||||||
| @@ -172,7 +170,6 @@ class Cover : public EntityBase { | |||||||
|   optional<CoverRestoreState> restore_state_(); |   optional<CoverRestoreState> restore_state_(); | ||||||
|  |  | ||||||
|   CallbackManager<void()> state_callback_{}; |   CallbackManager<void()> state_callback_{}; | ||||||
|   optional<std::string> device_class_override_{}; |  | ||||||
|  |  | ||||||
|   ESPPreferenceObject rtc_; |   ESPPreferenceObject rtc_; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -15,12 +15,15 @@ class CoverTraits { | |||||||
|   void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; } |   void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; } | ||||||
|   bool get_supports_toggle() const { return this->supports_toggle_; } |   bool get_supports_toggle() const { return this->supports_toggle_; } | ||||||
|   void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; } |   void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; } | ||||||
|  |   bool get_supports_stop() const { return this->supports_stop_; } | ||||||
|  |   void set_supports_stop(bool supports_stop) { this->supports_stop_ = supports_stop; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   bool is_assumed_state_{false}; |   bool is_assumed_state_{false}; | ||||||
|   bool supports_position_{false}; |   bool supports_position_{false}; | ||||||
|   bool supports_tilt_{false}; |   bool supports_tilt_{false}; | ||||||
|   bool supports_toggle_{false}; |   bool supports_toggle_{false}; | ||||||
|  |   bool supports_stop_{false}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace cover | }  // namespace cover | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ using namespace esphome::cover; | |||||||
|  |  | ||||||
| CoverTraits CurrentBasedCover::get_traits() { | CoverTraits CurrentBasedCover::get_traits() { | ||||||
|   auto traits = CoverTraits(); |   auto traits = CoverTraits(); | ||||||
|  |   traits.set_supports_stop(true); | ||||||
|   traits.set_supports_position(true); |   traits.set_supports_position(true); | ||||||
|   traits.set_supports_toggle(true); |   traits.set_supports_toggle(true); | ||||||
|   traits.set_is_assumed_state(false); |   traits.set_is_assumed_state(false); | ||||||
|   | |||||||
| @@ -7,9 +7,10 @@ import requests | |||||||
|  |  | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
|  | import esphome.final_validate as fv | ||||||
| from esphome import git | from esphome import git | ||||||
| from esphome.components.packages import validate_source_shorthand | from esphome.components.packages import validate_source_shorthand | ||||||
| from esphome.const import CONF_REF, CONF_WIFI | from esphome.const import CONF_REF, CONF_WIFI, CONF_ESPHOME, CONF_PROJECT | ||||||
| from esphome.wizard import wizard_file | from esphome.wizard import wizard_file | ||||||
| from esphome.yaml_util import dump | from esphome.yaml_util import dump | ||||||
|  |  | ||||||
| @@ -52,6 +53,17 @@ CONFIG_SCHEMA = cv.All( | |||||||
|     validate_full_url, |     validate_full_url, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _final_validate(config): | ||||||
|  |     full_config = fv.full_config.get()[CONF_ESPHOME] | ||||||
|  |     if CONF_PROJECT not in full_config: | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             "Dashboard import requires the `esphome` -> `project` information to be provided." | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FINAL_VALIDATE_SCHEMA = _final_validate | ||||||
|  |  | ||||||
| WIFI_CONFIG = """ | WIFI_CONFIG = """ | ||||||
|  |  | ||||||
| wifi: | wifi: | ||||||
|   | |||||||
| @@ -72,6 +72,7 @@ class DemoCover : public cover::Cover, public Component { | |||||||
|         traits.set_supports_tilt(true); |         traits.set_supports_tilt(true); | ||||||
|         break; |         break; | ||||||
|       case DemoCoverType::TYPE_4: |       case DemoCoverType::TYPE_4: | ||||||
|  |         traits.set_supports_stop(true); | ||||||
|         traits.set_is_assumed_state(true); |         traits.set_is_assumed_state(true); | ||||||
|         traits.set_supports_tilt(true); |         traits.set_supports_tilt(true); | ||||||
|         break; |         break; | ||||||
|   | |||||||
| @@ -40,6 +40,7 @@ DEVICE = { | |||||||
|  |  | ||||||
| NextAction = dfplayer_ns.class_("NextAction", automation.Action) | NextAction = dfplayer_ns.class_("NextAction", automation.Action) | ||||||
| PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action) | PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action) | ||||||
|  | PlayMp3Action = dfplayer_ns.class_("PlayMp3Action", automation.Action) | ||||||
| PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action) | PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action) | ||||||
| PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action) | PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action) | ||||||
| SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action) | SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action) | ||||||
| @@ -113,6 +114,25 @@ async def dfplayer_previous_to_code(config, action_id, template_arg, args): | |||||||
|     return var |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "dfplayer.play_mp3", | ||||||
|  |     PlayMp3Action, | ||||||
|  |     cv.maybe_simple_value( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |             cv.Required(CONF_FILE): cv.templatable(cv.int_), | ||||||
|  |         }, | ||||||
|  |         key=CONF_FILE, | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | async def dfplayer_play_mp3_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     await cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     template_ = await cg.templatable(config[CONF_FILE], args, float) | ||||||
|  |     cg.add(var.set_file(template_)) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action( | @automation.register_action( | ||||||
|     "dfplayer.play", |     "dfplayer.play", | ||||||
|     PlayFileAction, |     PlayFileAction, | ||||||
|   | |||||||
| @@ -7,10 +7,10 @@ namespace dfplayer { | |||||||
| static const char *const TAG = "dfplayer"; | static const char *const TAG = "dfplayer"; | ||||||
|  |  | ||||||
| void DFPlayer::play_folder(uint16_t folder, uint16_t file) { | void DFPlayer::play_folder(uint16_t folder, uint16_t file) { | ||||||
|   if (folder < 100 && file < 256) { |   if (folder <= 10 && file <= 1000) { | ||||||
|     this->ack_set_is_playing_ = true; |     this->ack_set_is_playing_ = true; | ||||||
|     this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file); |     this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file); | ||||||
|   } else if (folder <= 10 && file <= 1000) { |   } else if (folder < 100 && file < 256) { | ||||||
|     this->ack_set_is_playing_ = true; |     this->ack_set_is_playing_ = true; | ||||||
|     this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file); |     this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file); | ||||||
|   } else { |   } else { | ||||||
|   | |||||||
| @@ -35,6 +35,10 @@ class DFPlayer : public uart::UARTDevice, public Component { | |||||||
|     this->ack_set_is_playing_ = true; |     this->ack_set_is_playing_ = true; | ||||||
|     this->send_cmd_(0x02); |     this->send_cmd_(0x02); | ||||||
|   } |   } | ||||||
|  |   void play_mp3(uint16_t file) { | ||||||
|  |     this->ack_set_is_playing_ = true; | ||||||
|  |     this->send_cmd_(0x12, file); | ||||||
|  |   } | ||||||
|   void play_file(uint16_t file) { |   void play_file(uint16_t file) { | ||||||
|     this->ack_set_is_playing_ = true; |     this->ack_set_is_playing_ = true; | ||||||
|     this->send_cmd_(0x03, file); |     this->send_cmd_(0x03, file); | ||||||
| @@ -113,6 +117,16 @@ class DFPlayer : public uart::UARTDevice, public Component { | |||||||
| DFPLAYER_SIMPLE_ACTION(NextAction, next) | DFPLAYER_SIMPLE_ACTION(NextAction, next) | ||||||
| DFPLAYER_SIMPLE_ACTION(PreviousAction, previous) | DFPLAYER_SIMPLE_ACTION(PreviousAction, previous) | ||||||
|  |  | ||||||
|  | template<typename... Ts> class PlayMp3Action : public Action<Ts...>, public Parented<DFPlayer> { | ||||||
|  |  public: | ||||||
|  |   TEMPLATABLE_VALUE(uint16_t, file) | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { | ||||||
|  |     auto file = this->file_.value(x...); | ||||||
|  |     this->parent_->play_mp3(file); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
| template<typename... Ts> class PlayFileAction : public Action<Ts...>, public Parented<DFPlayer> { | template<typename... Ts> class PlayFileAction : public Action<Ts...>, public Parented<DFPlayer> { | ||||||
|  public: |  public: | ||||||
|   TEMPLATABLE_VALUE(uint16_t, file) |   TEMPLATABLE_VALUE(uint16_t, file) | ||||||
|   | |||||||
| @@ -282,13 +282,17 @@ void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align | |||||||
|     int scan_x1, scan_y1, scan_width, scan_height; |     int scan_x1, scan_y1, scan_width, scan_height; | ||||||
|     glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); |     glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); | ||||||
|  |  | ||||||
|     for (int glyph_x = scan_x1; glyph_x < scan_x1 + scan_width; glyph_x++) { |     { | ||||||
|       for (int glyph_y = scan_y1; glyph_y < scan_y1 + scan_height; glyph_y++) { |       const int glyph_x_max = scan_x1 + scan_width; | ||||||
|  |       const int glyph_y_max = scan_y1 + scan_height; | ||||||
|  |       for (int glyph_x = scan_x1; glyph_x < glyph_x_max; glyph_x++) { | ||||||
|  |         for (int glyph_y = scan_y1; glyph_y < glyph_y_max; glyph_y++) { | ||||||
|           if (glyph.get_pixel(glyph_x, glyph_y)) { |           if (glyph.get_pixel(glyph_x, glyph_y)) { | ||||||
|             this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color); |             this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     x_at += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; |     x_at += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ using namespace esphome::cover; | |||||||
|  |  | ||||||
| CoverTraits EndstopCover::get_traits() { | CoverTraits EndstopCover::get_traits() { | ||||||
|   auto traits = CoverTraits(); |   auto traits = CoverTraits(); | ||||||
|  |   traits.set_supports_stop(true); | ||||||
|   traits.set_supports_position(true); |   traits.set_supports_position(true); | ||||||
|   traits.set_supports_toggle(true); |   traits.set_supports_toggle(true); | ||||||
|   traits.set_is_assumed_state(false); |   traits.set_is_assumed_state(false); | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ | |||||||
| #include <freertos/task.h> | #include <freertos/task.h> | ||||||
| #include <esp_idf_version.h> | #include <esp_idf_version.h> | ||||||
| #include <esp_task_wdt.h> | #include <esp_task_wdt.h> | ||||||
|  | #include <esp_timer.h> | ||||||
| #include <soc/rtc.h> | #include <soc/rtc.h> | ||||||
|  |  | ||||||
| #if ESP_IDF_VERSION_MAJOR >= 4 | #if ESP_IDF_VERSION_MAJOR >= 4 | ||||||
|   | |||||||
| @@ -9,10 +9,9 @@ CODEOWNERS = ["@jesserockz"] | |||||||
| CONFLICTS_WITH = ["esp32_ble_beacon"] | CONFLICTS_WITH = ["esp32_ble_beacon"] | ||||||
|  |  | ||||||
| CONF_BLE_ID = "ble_id" | CONF_BLE_ID = "ble_id" | ||||||
|  | CONF_IO_CAPABILITY = "io_capability" | ||||||
|  |  | ||||||
| NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2] | NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] | ||||||
|  |  | ||||||
| NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2] |  | ||||||
|  |  | ||||||
| esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble") | esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble") | ||||||
| ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component) | ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component) | ||||||
| @@ -21,17 +20,28 @@ GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler") | |||||||
| GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler") | GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler") | ||||||
| GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler") | GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler") | ||||||
|  |  | ||||||
|  | IoCapability = esp32_ble_ns.enum("IoCapability") | ||||||
|  | IO_CAPABILITY = { | ||||||
|  |     "none": IoCapability.IO_CAP_NONE, | ||||||
|  |     "keyboard_only": IoCapability.IO_CAP_IN, | ||||||
|  |     "keyboard_display": IoCapability.IO_CAP_KBDISP, | ||||||
|  |     "display_only": IoCapability.IO_CAP_OUT, | ||||||
|  |     "display_yes_no": IoCapability.IO_CAP_IO, | ||||||
|  | } | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema( | CONFIG_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.GenerateID(): cv.declare_id(ESP32BLE), |         cv.GenerateID(): cv.declare_id(ESP32BLE), | ||||||
|  |         cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum( | ||||||
|  |             IO_CAPABILITY, lower=True | ||||||
|  |         ), | ||||||
|     } |     } | ||||||
| ).extend(cv.COMPONENT_SCHEMA) | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_variant(_): | def validate_variant(_): | ||||||
|     variant = get_esp32_variant() |     variant = get_esp32_variant() | ||||||
|     if variant in NO_BLUTOOTH_VARIANTS: |     if variant in NO_BLUETOOTH_VARIANTS: | ||||||
|         raise cv.Invalid(f"{variant} does not support Bluetooth") |         raise cv.Invalid(f"{variant} does not support Bluetooth") | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -41,6 +51,7 @@ FINAL_VALIDATE_SCHEMA = validate_variant | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |     cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) | ||||||
|  |  | ||||||
|     if CORE.using_esp_idf: |     if CORE.using_esp_idf: | ||||||
|         add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) |         add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) | ||||||
|   | |||||||
| @@ -134,8 +134,7 @@ bool ESP32BLE::ble_setup_() { | |||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; |   err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &(this->io_cap_), sizeof(uint8_t)); | ||||||
|   err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); |  | ||||||
|   if (err != ESP_OK) { |   if (err != ESP_OK) { | ||||||
|     ESP_LOGE(TAG, "esp_ble_gap_set_security_param failed: %d", err); |     ESP_LOGE(TAG, "esp_ble_gap_set_security_param failed: %d", err); | ||||||
|     return false; |     return false; | ||||||
| @@ -215,9 +214,31 @@ float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; } | |||||||
| void ESP32BLE::dump_config() { | void ESP32BLE::dump_config() { | ||||||
|   const uint8_t *mac_address = esp_bt_dev_get_address(); |   const uint8_t *mac_address = esp_bt_dev_get_address(); | ||||||
|   if (mac_address) { |   if (mac_address) { | ||||||
|  |     const char *io_capability_s; | ||||||
|  |     switch (this->io_cap_) { | ||||||
|  |       case ESP_IO_CAP_OUT: | ||||||
|  |         io_capability_s = "display_only"; | ||||||
|  |         break; | ||||||
|  |       case ESP_IO_CAP_IO: | ||||||
|  |         io_capability_s = "display_yes_no"; | ||||||
|  |         break; | ||||||
|  |       case ESP_IO_CAP_IN: | ||||||
|  |         io_capability_s = "keyboard_only"; | ||||||
|  |         break; | ||||||
|  |       case ESP_IO_CAP_NONE: | ||||||
|  |         io_capability_s = "none"; | ||||||
|  |         break; | ||||||
|  |       case ESP_IO_CAP_KBDISP: | ||||||
|  |         io_capability_s = "keyboard_display"; | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         io_capability_s = "invalid"; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|     ESP_LOGCONFIG(TAG, "ESP32 BLE:"); |     ESP_LOGCONFIG(TAG, "ESP32 BLE:"); | ||||||
|     ESP_LOGCONFIG(TAG, "  MAC address: %02X:%02X:%02X:%02X:%02X:%02X", mac_address[0], mac_address[1], mac_address[2], |     ESP_LOGCONFIG(TAG, "  MAC address: %02X:%02X:%02X:%02X:%02X:%02X", mac_address[0], mac_address[1], mac_address[2], | ||||||
|                   mac_address[3], mac_address[4], mac_address[5]); |                   mac_address[3], mac_address[4], mac_address[5]); | ||||||
|  |     ESP_LOGCONFIG(TAG, "  IO Capability: %s", io_capability_s); | ||||||
|   } else { |   } else { | ||||||
|     ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled"); |     ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled"); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -25,6 +25,14 @@ typedef struct { | |||||||
|   uint16_t mtu; |   uint16_t mtu; | ||||||
| } conn_status_t; | } conn_status_t; | ||||||
|  |  | ||||||
|  | enum IoCapability { | ||||||
|  |   IO_CAP_OUT = ESP_IO_CAP_OUT, | ||||||
|  |   IO_CAP_IO = ESP_IO_CAP_IO, | ||||||
|  |   IO_CAP_IN = ESP_IO_CAP_IN, | ||||||
|  |   IO_CAP_NONE = ESP_IO_CAP_NONE, | ||||||
|  |   IO_CAP_KBDISP = ESP_IO_CAP_KBDISP, | ||||||
|  | }; | ||||||
|  |  | ||||||
| class GAPEventHandler { | class GAPEventHandler { | ||||||
|  public: |  public: | ||||||
|   virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; |   virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; | ||||||
| @@ -44,6 +52,8 @@ class GATTsEventHandler { | |||||||
|  |  | ||||||
| class ESP32BLE : public Component { | class ESP32BLE : public Component { | ||||||
|  public: |  public: | ||||||
|  |   void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } | ||||||
|  |  | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void loop() override; |   void loop() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
| @@ -72,6 +82,7 @@ class ESP32BLE : public Component { | |||||||
|  |  | ||||||
|   Queue<BLEEvent> ble_events_; |   Queue<BLEEvent> ble_events_; | ||||||
|   BLEAdvertising *advertising_; |   BLEAdvertising *advertising_; | ||||||
|  |   esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								esphome/components/esp32_rmt_led_strip/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/esp32_rmt_led_strip/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										207
									
								
								esphome/components/esp32_rmt_led_strip/led_strip.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								esphome/components/esp32_rmt_led_strip/led_strip.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | |||||||
|  | #include "led_strip.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #include <esp_attr.h> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace esp32_rmt_led_strip { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "esp32_rmt_led_strip"; | ||||||
|  |  | ||||||
|  | static const uint8_t RMT_CLK_DIV = 2; | ||||||
|  |  | ||||||
|  | void ESP32RMTLEDStripLightOutput::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip..."); | ||||||
|  |  | ||||||
|  |   size_t buffer_size = this->get_buffer_size_(); | ||||||
|  |  | ||||||
|  |   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); | ||||||
|  |   this->buf_ = allocator.allocate(buffer_size); | ||||||
|  |   if (this->buf_ == nullptr) { | ||||||
|  |     ESP_LOGE(TAG, "Cannot allocate LED buffer!"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->effect_data_ = allocator.allocate(this->num_leds_); | ||||||
|  |   if (this->effect_data_ == nullptr) { | ||||||
|  |     ESP_LOGE(TAG, "Cannot allocate effect data!"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ExternalRAMAllocator<rmt_item32_t> rmt_allocator(ExternalRAMAllocator<rmt_item32_t>::ALLOW_FAILURE); | ||||||
|  |   this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8);  // 8 bits per byte, 1 rmt_item32_t per bit | ||||||
|  |  | ||||||
|  |   rmt_config_t config; | ||||||
|  |   memset(&config, 0, sizeof(config)); | ||||||
|  |   config.channel = this->channel_; | ||||||
|  |   config.rmt_mode = RMT_MODE_TX; | ||||||
|  |   config.gpio_num = gpio_num_t(this->pin_); | ||||||
|  |   config.mem_block_num = 1; | ||||||
|  |   config.clk_div = RMT_CLK_DIV; | ||||||
|  |   config.tx_config.loop_en = false; | ||||||
|  |   config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; | ||||||
|  |   config.tx_config.carrier_en = false; | ||||||
|  |   config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; | ||||||
|  |   config.tx_config.idle_output_en = true; | ||||||
|  |  | ||||||
|  |   if (rmt_config(&config) != ESP_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Cannot initialize RMT!"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (rmt_driver_install(config.channel, 0, 0) != ESP_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Cannot install RMT driver!"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, | ||||||
|  |                                                  uint32_t bit1_low) { | ||||||
|  |   float ratio = (float) APB_CLK_FREQ / RMT_CLK_DIV / 1e09f; | ||||||
|  |  | ||||||
|  |   // 0-bit | ||||||
|  |   this->bit0_.duration0 = (uint32_t) (ratio * bit0_high); | ||||||
|  |   this->bit0_.level0 = 1; | ||||||
|  |   this->bit0_.duration1 = (uint32_t) (ratio * bit0_low); | ||||||
|  |   this->bit0_.level1 = 0; | ||||||
|  |   // 1-bit | ||||||
|  |   this->bit1_.duration0 = (uint32_t) (ratio * bit1_high); | ||||||
|  |   this->bit1_.level0 = 1; | ||||||
|  |   this->bit1_.duration1 = (uint32_t) (ratio * bit1_low); | ||||||
|  |   this->bit1_.level1 = 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | ||||||
|  |   // protect from refreshing too often | ||||||
|  |   uint32_t now = micros(); | ||||||
|  |   if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) { | ||||||
|  |     // try again next loop iteration, so that this change won't get lost | ||||||
|  |     this->schedule_show(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->last_refresh_ = now; | ||||||
|  |   this->mark_shown_(); | ||||||
|  |  | ||||||
|  |   ESP_LOGVV(TAG, "Writing RGB values to bus..."); | ||||||
|  |  | ||||||
|  |   if (rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000)) != ESP_OK) { | ||||||
|  |     ESP_LOGE(TAG, "RMT TX timeout"); | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   delayMicroseconds(50); | ||||||
|  |  | ||||||
|  |   size_t buffer_size = this->get_buffer_size_(); | ||||||
|  |  | ||||||
|  |   size_t size = 0; | ||||||
|  |   size_t len = 0; | ||||||
|  |   uint8_t *psrc = this->buf_; | ||||||
|  |   rmt_item32_t *pdest = this->rmt_buf_; | ||||||
|  |   while (size < buffer_size) { | ||||||
|  |     uint8_t b = *psrc; | ||||||
|  |     for (int i = 0; i < 8; i++) { | ||||||
|  |       pdest->val = b & (1 << (7 - i)) ? this->bit1_.val : this->bit0_.val; | ||||||
|  |       pdest++; | ||||||
|  |       len++; | ||||||
|  |     } | ||||||
|  |     size++; | ||||||
|  |     psrc++; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (rmt_write_items(this->channel_, this->rmt_buf_, len, false) != ESP_OK) { | ||||||
|  |     ESP_LOGE(TAG, "RMT TX error"); | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->status_clear_warning(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index) const { | ||||||
|  |   int32_t r = 0, g = 0, b = 0; | ||||||
|  |   switch (this->rgb_order_) { | ||||||
|  |     case ORDER_RGB: | ||||||
|  |       r = 0; | ||||||
|  |       g = 1; | ||||||
|  |       b = 2; | ||||||
|  |       break; | ||||||
|  |     case ORDER_RBG: | ||||||
|  |       r = 0; | ||||||
|  |       g = 2; | ||||||
|  |       b = 1; | ||||||
|  |       break; | ||||||
|  |     case ORDER_GRB: | ||||||
|  |       r = 1; | ||||||
|  |       g = 0; | ||||||
|  |       b = 2; | ||||||
|  |       break; | ||||||
|  |     case ORDER_GBR: | ||||||
|  |       r = 2; | ||||||
|  |       g = 0; | ||||||
|  |       b = 1; | ||||||
|  |       break; | ||||||
|  |     case ORDER_BGR: | ||||||
|  |       r = 2; | ||||||
|  |       g = 1; | ||||||
|  |       b = 0; | ||||||
|  |       break; | ||||||
|  |     case ORDER_BRG: | ||||||
|  |       r = 1; | ||||||
|  |       g = 2; | ||||||
|  |       b = 0; | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |   uint8_t multiplier = this->is_rgbw_ ? 4 : 3; | ||||||
|  |   return {this->buf_ + (index * multiplier) + r, | ||||||
|  |           this->buf_ + (index * multiplier) + g, | ||||||
|  |           this->buf_ + (index * multiplier) + b, | ||||||
|  |           this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr, | ||||||
|  |           &this->effect_data_[index], | ||||||
|  |           &this->correction_}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ESP32RMTLEDStripLightOutput::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Pin: %u", this->pin_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Channel: %u", this->channel_); | ||||||
|  |   const char *rgb_order; | ||||||
|  |   switch (this->rgb_order_) { | ||||||
|  |     case ORDER_RGB: | ||||||
|  |       rgb_order = "RGB"; | ||||||
|  |       break; | ||||||
|  |     case ORDER_RBG: | ||||||
|  |       rgb_order = "RBG"; | ||||||
|  |       break; | ||||||
|  |     case ORDER_GRB: | ||||||
|  |       rgb_order = "GRB"; | ||||||
|  |       break; | ||||||
|  |     case ORDER_GBR: | ||||||
|  |       rgb_order = "GBR"; | ||||||
|  |       break; | ||||||
|  |     case ORDER_BGR: | ||||||
|  |       rgb_order = "BGR"; | ||||||
|  |       break; | ||||||
|  |     case ORDER_BRG: | ||||||
|  |       rgb_order = "BRG"; | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       rgb_order = "UNKNOWN"; | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |   ESP_LOGCONFIG(TAG, "  RGB Order: %s", rgb_order); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Max refresh rate: %u", *this->max_refresh_rate_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Number of LEDs: %u", this->num_leds_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float ESP32RMTLEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||||
|  |  | ||||||
|  | }  // namespace esp32_rmt_led_strip | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_ESP32 | ||||||
							
								
								
									
										87
									
								
								esphome/components/esp32_rmt_led_strip/led_strip.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								esphome/components/esp32_rmt_led_strip/led_strip.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | #include "esphome/components/light/addressable_light.h" | ||||||
|  | #include "esphome/components/light/light_output.h" | ||||||
|  | #include "esphome/core/color.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | #include <driver/gpio.h> | ||||||
|  | #include <driver/rmt.h> | ||||||
|  | #include <esp_err.h> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace esp32_rmt_led_strip { | ||||||
|  |  | ||||||
|  | enum RGBOrder : uint8_t { | ||||||
|  |   ORDER_RGB, | ||||||
|  |   ORDER_RBG, | ||||||
|  |   ORDER_GRB, | ||||||
|  |   ORDER_GBR, | ||||||
|  |   ORDER_BGR, | ||||||
|  |   ORDER_BRG, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void write_state(light::LightState *state) override; | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|  |   int32_t size() const override { return this->num_leds_; } | ||||||
|  |   light::LightTraits get_traits() override { | ||||||
|  |     auto traits = light::LightTraits(); | ||||||
|  |     if (this->is_rgbw_) { | ||||||
|  |       traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::RGB_WHITE}); | ||||||
|  |     } else { | ||||||
|  |       traits.set_supported_color_modes({light::ColorMode::RGB}); | ||||||
|  |     } | ||||||
|  |     return traits; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void set_pin(uint8_t pin) { this->pin_ = pin; } | ||||||
|  |   void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; } | ||||||
|  |   void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; } | ||||||
|  |  | ||||||
|  |   /// Set a maximum refresh rate in µs as some lights do not like being updated too often. | ||||||
|  |   void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; } | ||||||
|  |  | ||||||
|  |   void set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low); | ||||||
|  |  | ||||||
|  |   void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; } | ||||||
|  |   void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; } | ||||||
|  |  | ||||||
|  |   void clear_effect_data() override { | ||||||
|  |     for (int i = 0; i < this->size(); i++) | ||||||
|  |       this->effect_data_[i] = 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   light::ESPColorView get_view_internal(int32_t index) const override; | ||||||
|  |  | ||||||
|  |   size_t get_buffer_size_() const { return this->num_leds_ * (3 + this->is_rgbw_); } | ||||||
|  |  | ||||||
|  |   uint8_t *buf_{nullptr}; | ||||||
|  |   uint8_t *effect_data_{nullptr}; | ||||||
|  |   rmt_item32_t *rmt_buf_{nullptr}; | ||||||
|  |  | ||||||
|  |   uint8_t pin_; | ||||||
|  |   uint16_t num_leds_; | ||||||
|  |   bool is_rgbw_; | ||||||
|  |  | ||||||
|  |   rmt_item32_t bit0_, bit1_; | ||||||
|  |   RGBOrder rgb_order_; | ||||||
|  |   rmt_channel_t channel_; | ||||||
|  |  | ||||||
|  |   uint32_t last_refresh_{0}; | ||||||
|  |   optional<uint32_t> max_refresh_rate_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace esp32_rmt_led_strip | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_ESP32 | ||||||
							
								
								
									
										151
									
								
								esphome/components/esp32_rmt_led_strip/light.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								esphome/components/esp32_rmt_led_strip/light.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | |||||||
|  | from dataclasses import dataclass | ||||||
|  |  | ||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome import pins | ||||||
|  | from esphome.components import esp32, light | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_CHIPSET, | ||||||
|  |     CONF_MAX_REFRESH_RATE, | ||||||
|  |     CONF_NUM_LEDS, | ||||||
|  |     CONF_OUTPUT_ID, | ||||||
|  |     CONF_PIN, | ||||||
|  |     CONF_RGB_ORDER, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@jesserockz"] | ||||||
|  | DEPENDENCIES = ["esp32"] | ||||||
|  |  | ||||||
|  | esp32_rmt_led_strip_ns = cg.esphome_ns.namespace("esp32_rmt_led_strip") | ||||||
|  | ESP32RMTLEDStripLightOutput = esp32_rmt_led_strip_ns.class_( | ||||||
|  |     "ESP32RMTLEDStripLightOutput", light.AddressableLight | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | rmt_channel_t = cg.global_ns.enum("rmt_channel_t") | ||||||
|  |  | ||||||
|  | RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder") | ||||||
|  |  | ||||||
|  | RGB_ORDERS = { | ||||||
|  |     "RGB": RGBOrder.ORDER_RGB, | ||||||
|  |     "RBG": RGBOrder.ORDER_RBG, | ||||||
|  |     "GRB": RGBOrder.ORDER_GRB, | ||||||
|  |     "GBR": RGBOrder.ORDER_GBR, | ||||||
|  |     "BGR": RGBOrder.ORDER_BGR, | ||||||
|  |     "BRG": RGBOrder.ORDER_BRG, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass | ||||||
|  | class LEDStripTimings: | ||||||
|  |     bit0_high: int | ||||||
|  |     bit0_low: int | ||||||
|  |     bit1_high: int | ||||||
|  |     bit1_low: int | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CHIPSETS = { | ||||||
|  |     "WS2812": LEDStripTimings(400, 1000, 1000, 400), | ||||||
|  |     "SK6812": LEDStripTimings(300, 900, 600, 600), | ||||||
|  |     "APA106": LEDStripTimings(350, 1360, 1360, 350), | ||||||
|  |     "SM16703": LEDStripTimings(300, 900, 1360, 350), | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONF_IS_RGBW = "is_rgbw" | ||||||
|  | CONF_BIT0_HIGH = "bit0_high" | ||||||
|  | CONF_BIT0_LOW = "bit0_low" | ||||||
|  | CONF_BIT1_HIGH = "bit1_high" | ||||||
|  | CONF_BIT1_LOW = "bit1_low" | ||||||
|  | CONF_RMT_CHANNEL = "rmt_channel" | ||||||
|  |  | ||||||
|  | RMT_CHANNELS = { | ||||||
|  |     esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7], | ||||||
|  |     esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3], | ||||||
|  |     esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3], | ||||||
|  |     esp32.const.VARIANT_ESP32C3: [0, 1], | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate_rmt_channel(value): | ||||||
|  |     variant = esp32.get_esp32_variant() | ||||||
|  |     if variant not in RMT_CHANNELS: | ||||||
|  |         raise cv.Invalid(f"ESP32 variant {variant} does not support RMT.") | ||||||
|  |     if value not in RMT_CHANNELS[variant]: | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             f"RMT channel {value} is not supported for ESP32 variant {variant}." | ||||||
|  |         ) | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     light.ADDRESSABLE_LIGHT_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ESP32RMTLEDStripLightOutput), | ||||||
|  |             cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, | ||||||
|  |             cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, | ||||||
|  |             cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), | ||||||
|  |             cv.Required(CONF_RMT_CHANNEL): _validate_rmt_channel, | ||||||
|  |             cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, | ||||||
|  |             cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), | ||||||
|  |             cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, | ||||||
|  |             cv.Inclusive( | ||||||
|  |                 CONF_BIT0_HIGH, | ||||||
|  |                 "custom", | ||||||
|  |             ): cv.positive_time_period_microseconds, | ||||||
|  |             cv.Inclusive( | ||||||
|  |                 CONF_BIT0_LOW, | ||||||
|  |                 "custom", | ||||||
|  |             ): cv.positive_time_period_microseconds, | ||||||
|  |             cv.Inclusive( | ||||||
|  |                 CONF_BIT1_HIGH, | ||||||
|  |                 "custom", | ||||||
|  |             ): cv.positive_time_period_microseconds, | ||||||
|  |             cv.Inclusive( | ||||||
|  |                 CONF_BIT1_LOW, | ||||||
|  |                 "custom", | ||||||
|  |             ): cv.positive_time_period_microseconds, | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  |     cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) | ||||||
|  |     await light.register_light(var, config) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_num_leds(config[CONF_NUM_LEDS])) | ||||||
|  |     cg.add(var.set_pin(config[CONF_PIN])) | ||||||
|  |  | ||||||
|  |     if CONF_MAX_REFRESH_RATE in config: | ||||||
|  |         cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE])) | ||||||
|  |  | ||||||
|  |     if CONF_CHIPSET in config: | ||||||
|  |         chipset = CHIPSETS[config[CONF_CHIPSET]] | ||||||
|  |         cg.add( | ||||||
|  |             var.set_led_params( | ||||||
|  |                 chipset.bit0_high, | ||||||
|  |                 chipset.bit0_low, | ||||||
|  |                 chipset.bit1_high, | ||||||
|  |                 chipset.bit1_low, | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     else: | ||||||
|  |         cg.add( | ||||||
|  |             var.set_led_params( | ||||||
|  |                 config[CONF_BIT0_HIGH], | ||||||
|  |                 config[CONF_BIT0_LOW], | ||||||
|  |                 config[CONF_BIT1_HIGH], | ||||||
|  |                 config[CONF_BIT1_LOW], | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_rgb_order(config[CONF_RGB_ORDER])) | ||||||
|  |     cg.add(var.set_is_rgbw(config[CONF_IS_RGBW])) | ||||||
|  |  | ||||||
|  |     cg.add( | ||||||
|  |         var.set_rmt_channel( | ||||||
|  |             getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}") | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
| @@ -106,20 +106,18 @@ void EZOSensor::loop() { | |||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", buf, EZO_COMMAND_TYPE_STRINGS[to_run->command_type]); |   ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", &buf[1], EZO_COMMAND_TYPE_STRINGS[to_run->command_type]); | ||||||
|  |  | ||||||
|   if ((buf[0] == 1) || (to_run->command_type == EzoCommandType::EZO_CALIBRATION)) {  // EZO_CALIBRATION returns 0-3 |   if (buf[0] == 1) { | ||||||
|     // some sensors return multiple comma-separated values, terminate string after first one |  | ||||||
|     for (size_t i = 1; i < sizeof(buf) - 1; i++) { |  | ||||||
|       if (buf[i] == ',') { |  | ||||||
|         buf[i] = '\0'; |  | ||||||
|         break; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     std::string payload = reinterpret_cast<char *>(&buf[1]); |     std::string payload = reinterpret_cast<char *>(&buf[1]); | ||||||
|     if (!payload.empty()) { |     if (!payload.empty()) { | ||||||
|       switch (to_run->command_type) { |       switch (to_run->command_type) { | ||||||
|         case EzoCommandType::EZO_READ: { |         case EzoCommandType::EZO_READ: { | ||||||
|  |           // some sensors return multiple comma-separated values, terminate string after first one | ||||||
|  |           int start_location = 0; | ||||||
|  |           if ((start_location = payload.find(',')) != std::string::npos) { | ||||||
|  |             payload.erase(start_location); | ||||||
|  |           } | ||||||
|           auto val = parse_number<float>(payload); |           auto val = parse_number<float>(payload); | ||||||
|           if (!val.has_value()) { |           if (!val.has_value()) { | ||||||
|             ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); |             ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); | ||||||
| @@ -154,7 +152,10 @@ void EZOSensor::loop() { | |||||||
|           break; |           break; | ||||||
|         } |         } | ||||||
|         case EzoCommandType::EZO_T: { |         case EzoCommandType::EZO_T: { | ||||||
|           this->t_callback_.call(payload); |           int start_location = 0; | ||||||
|  |           if ((start_location = payload.find(',')) != std::string::npos) { | ||||||
|  |             this->t_callback_.call(payload.substr(start_location + 1)); | ||||||
|  |           } | ||||||
|           break; |           break; | ||||||
|         } |         } | ||||||
|         case EzoCommandType::EZO_CUSTOM: { |         case EzoCommandType::EZO_CUSTOM: { | ||||||
|   | |||||||
| @@ -41,6 +41,7 @@ void FeedbackCover::setup() { | |||||||
|  |  | ||||||
| CoverTraits FeedbackCover::get_traits() { | CoverTraits FeedbackCover::get_traits() { | ||||||
|   auto traits = CoverTraits(); |   auto traits = CoverTraits(); | ||||||
|  |   traits.set_supports_stop(true); | ||||||
|   traits.set_supports_position(true); |   traits.set_supports_position(true); | ||||||
|   traits.set_supports_toggle(true); |   traits.set_supports_toggle(true); | ||||||
|   traits.set_is_assumed_state(this->assumed_state_); |   traits.set_is_assumed_state(this->assumed_state_); | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								esphome/components/gp8403/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								esphome/components/gp8403/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | import esphome.config_validation as cv | ||||||
|  | import esphome.codegen as cg | ||||||
|  |  | ||||||
|  | from esphome.components import i2c | ||||||
|  | from esphome.const import CONF_ID, CONF_VOLTAGE | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@jesserockz"] | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  | MULTI_CONF = True | ||||||
|  |  | ||||||
|  | gp8403_ns = cg.esphome_ns.namespace("gp8403") | ||||||
|  | GP8403 = gp8403_ns.class_("GP8403", cg.Component, i2c.I2CDevice) | ||||||
|  |  | ||||||
|  | GP8403Voltage = gp8403_ns.enum("GP8403Voltage") | ||||||
|  |  | ||||||
|  | CONF_GP8403_ID = "gp8403_id" | ||||||
|  |  | ||||||
|  | VOLTAGES = { | ||||||
|  |     "5V": GP8403Voltage.GP8403_VOLTAGE_5V, | ||||||
|  |     "10V": GP8403Voltage.GP8403_VOLTAGE_10V, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(GP8403), | ||||||
|  |             cv.Required(CONF_VOLTAGE): cv.enum(VOLTAGES, upper=True), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x58)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_voltage(config[CONF_VOLTAGE])) | ||||||
							
								
								
									
										21
									
								
								esphome/components/gp8403/gp8403.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								esphome/components/gp8403/gp8403.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | #include "gp8403.h" | ||||||
|  |  | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace gp8403 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "gp8403"; | ||||||
|  |  | ||||||
|  | static const uint8_t RANGE_REGISTER = 0x01; | ||||||
|  |  | ||||||
|  | void GP8403::setup() { this->write_register(RANGE_REGISTER, (uint8_t *) (&this->voltage_), 1); } | ||||||
|  |  | ||||||
|  | void GP8403::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "GP8403:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Voltage: %dV", this->voltage_ == GP8403_VOLTAGE_5V ? 5 : 10); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace gp8403 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										27
									
								
								esphome/components/gp8403/gp8403.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/gp8403/gp8403.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace gp8403 { | ||||||
|  |  | ||||||
|  | enum GP8403Voltage { | ||||||
|  |   GP8403_VOLTAGE_5V = 0x00, | ||||||
|  |   GP8403_VOLTAGE_10V = 0x11, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class GP8403 : public Component, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  |   void set_voltage(gp8403::GP8403Voltage voltage) { this->voltage_ = voltage; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   GP8403Voltage voltage_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace gp8403 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										31
									
								
								esphome/components/gp8403/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								esphome/components/gp8403/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | import esphome.config_validation as cv | ||||||
|  | import esphome.codegen as cg | ||||||
|  |  | ||||||
|  | from esphome.components import i2c, output | ||||||
|  | from esphome.const import CONF_ID, CONF_CHANNEL | ||||||
|  |  | ||||||
|  | from .. import gp8403_ns, GP8403, CONF_GP8403_ID | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["gp8403"] | ||||||
|  |  | ||||||
|  | GP8403Output = gp8403_ns.class_( | ||||||
|  |     "GP8403Output", cg.Component, i2c.I2CDevice, output.FloatOutput | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(GP8403Output), | ||||||
|  |         cv.GenerateID(CONF_GP8403_ID): cv.use_id(GP8403), | ||||||
|  |         cv.Required(CONF_CHANNEL): cv.one_of(0, 1), | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await output.register_output(var, config) | ||||||
|  |  | ||||||
|  |     await cg.register_parented(var, config[CONF_GP8403_ID]) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_channel(config[CONF_CHANNEL])) | ||||||
							
								
								
									
										26
									
								
								esphome/components/gp8403/output/gp8403_output.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/gp8403/output/gp8403_output.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | #include "gp8403_output.h" | ||||||
|  |  | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace gp8403 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "gp8403.output"; | ||||||
|  |  | ||||||
|  | static const uint8_t OUTPUT_REGISTER = 0x02; | ||||||
|  |  | ||||||
|  | void GP8403Output::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "GP8403 Output:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Channel: %u", this->channel_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GP8403Output::write_state(float state) { | ||||||
|  |   uint16_t value = ((uint16_t) (state * 4095)) << 4; | ||||||
|  |   i2c::ErrorCode err = this->parent_->write_register(OUTPUT_REGISTER + (2 * this->channel_), (uint8_t *) &value, 2); | ||||||
|  |   if (err != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Error writing to GP8403, code %d", err); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace gp8403 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										25
									
								
								esphome/components/gp8403/output/gp8403_output.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								esphome/components/gp8403/output/gp8403_output.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/output/float_output.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
|  | #include "esphome/components/gp8403/gp8403.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace gp8403 { | ||||||
|  |  | ||||||
|  | class GP8403Output : public Component, public output::FloatOutput, public Parented<GP8403> { | ||||||
|  |  public: | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override { return setup_priority::DATA - 1; } | ||||||
|  |  | ||||||
|  |   void set_channel(uint8_t channel) { this->channel_ = channel; } | ||||||
|  |  | ||||||
|  |   void write_state(float state) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint8_t channel_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace gp8403 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										38
									
								
								esphome/components/host/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								esphome/components/host/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | from esphome.const import ( | ||||||
|  |     KEY_CORE, | ||||||
|  |     KEY_FRAMEWORK_VERSION, | ||||||
|  |     KEY_TARGET_FRAMEWORK, | ||||||
|  |     KEY_TARGET_PLATFORM, | ||||||
|  | ) | ||||||
|  | from esphome.core import CORE | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | import esphome.codegen as cg | ||||||
|  |  | ||||||
|  | from .const import KEY_HOST | ||||||
|  |  | ||||||
|  | # force import gpio to register pin schema | ||||||
|  | from .gpio import host_pin_to_code  # noqa | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@esphome/core"] | ||||||
|  | AUTO_LOAD = ["network"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def set_core_data(config): | ||||||
|  |     CORE.data[KEY_HOST] = {} | ||||||
|  |     CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "host" | ||||||
|  |     CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "host" | ||||||
|  |     CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version(1, 0, 0) | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.Schema({}), | ||||||
|  |     set_core_data, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     cg.add_build_flag("-DUSE_HOST") | ||||||
|  |     cg.add_define("ESPHOME_BOARD", "host") | ||||||
|  |     cg.add_platformio_option("platform", "platformio/native") | ||||||
							
								
								
									
										5
									
								
								esphome/components/host/const.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								esphome/components/host/const.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  |  | ||||||
|  | KEY_HOST = "host" | ||||||
|  |  | ||||||
|  | host_ns = cg.esphome_ns.namespace("host") | ||||||
							
								
								
									
										77
									
								
								esphome/components/host/core.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								esphome/components/host/core.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | #ifdef USE_HOST | ||||||
|  |  | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "preferences.h" | ||||||
|  |  | ||||||
|  | #include <sched.h> | ||||||
|  | #include <time.h> | ||||||
|  | #include <cmath> | ||||||
|  | #include <cstdlib> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  |  | ||||||
|  | void IRAM_ATTR HOT yield() { ::sched_yield(); } | ||||||
|  | uint32_t IRAM_ATTR HOT millis() { | ||||||
|  |   struct timespec spec; | ||||||
|  |   clock_gettime(CLOCK_MONOTONIC, &spec); | ||||||
|  |   time_t seconds = spec.tv_sec; | ||||||
|  |   uint32_t ms = round(spec.tv_nsec / 1e6); | ||||||
|  |   return ((uint32_t) seconds) * 1000U + ms; | ||||||
|  | } | ||||||
|  | void IRAM_ATTR HOT delay(uint32_t ms) { | ||||||
|  |   struct timespec ts; | ||||||
|  |   ts.tv_sec = ms / 1000; | ||||||
|  |   ts.tv_nsec = (ms % 1000) * 1000000; | ||||||
|  |   int res; | ||||||
|  |   do { | ||||||
|  |     res = nanosleep(&ts, &ts); | ||||||
|  |   } while (res != 0 && errno == EINTR); | ||||||
|  | } | ||||||
|  | uint32_t IRAM_ATTR HOT micros() { | ||||||
|  |   struct timespec spec; | ||||||
|  |   clock_gettime(CLOCK_MONOTONIC, &spec); | ||||||
|  |   time_t seconds = spec.tv_sec; | ||||||
|  |   uint32_t us = round(spec.tv_nsec / 1e3); | ||||||
|  |   return ((uint32_t) seconds) * 1000000U + us; | ||||||
|  | } | ||||||
|  | void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { | ||||||
|  |   struct timespec ts; | ||||||
|  |   ts.tv_sec = us / 1000000U; | ||||||
|  |   ts.tv_nsec = (us % 1000000U) * 1000U; | ||||||
|  |   int res; | ||||||
|  |   do { | ||||||
|  |     res = nanosleep(&ts, &ts); | ||||||
|  |   } while (res != 0 && errno == EINTR); | ||||||
|  | } | ||||||
|  | void arch_restart() { exit(0); } | ||||||
|  | void arch_init() { | ||||||
|  |   // pass | ||||||
|  | } | ||||||
|  | void IRAM_ATTR HOT arch_feed_wdt() { | ||||||
|  |   // pass | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } | ||||||
|  | uint32_t arch_get_cpu_cycle_count() { | ||||||
|  |   struct timespec spec; | ||||||
|  |   clock_gettime(CLOCK_MONOTONIC, &spec); | ||||||
|  |   time_t seconds = spec.tv_sec; | ||||||
|  |   uint32_t us = spec.tv_nsec; | ||||||
|  |   return ((uint32_t) seconds) * 1000000000U + us; | ||||||
|  | } | ||||||
|  | uint32_t arch_get_cpu_freq_hz() { return 1000000000U; } | ||||||
|  |  | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | void setup(); | ||||||
|  | void loop(); | ||||||
|  | int main() { | ||||||
|  |   esphome::host::setup_preferences(); | ||||||
|  |   setup(); | ||||||
|  |   while (true) { | ||||||
|  |     loop(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif  // USE_HOST | ||||||
							
								
								
									
										59
									
								
								esphome/components/host/gpio.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								esphome/components/host/gpio.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | #ifdef USE_HOST | ||||||
|  |  | ||||||
|  | #include "gpio.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace host { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "host"; | ||||||
|  |  | ||||||
|  | struct ISRPinArg { | ||||||
|  |   uint8_t pin; | ||||||
|  |   bool inverted; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | ISRInternalGPIOPin HostGPIOPin::to_isr() const { | ||||||
|  |   auto *arg = new ISRPinArg{};  // NOLINT(cppcoreguidelines-owning-memory) | ||||||
|  |   arg->pin = pin_; | ||||||
|  |   arg->inverted = inverted_; | ||||||
|  |   return ISRInternalGPIOPin((void *) arg); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HostGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { | ||||||
|  |   ESP_LOGD(TAG, "Attaching interrupt %p to pin %d and mode %d", func, pin_, (uint32_t) type); | ||||||
|  | } | ||||||
|  | void HostGPIOPin::pin_mode(gpio::Flags flags) { ESP_LOGD(TAG, "Setting pin %d mode to %02X", pin_, (uint32_t) flags); } | ||||||
|  |  | ||||||
|  | std::string HostGPIOPin::dump_summary() const { | ||||||
|  |   char buffer[32]; | ||||||
|  |   snprintf(buffer, sizeof(buffer), "GPIO%u", pin_); | ||||||
|  |   return buffer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool HostGPIOPin::digital_read() { return inverted_; } | ||||||
|  | void HostGPIOPin::digital_write(bool value) { | ||||||
|  |   // pass | ||||||
|  |   ESP_LOGD(TAG, "Setting pin %d to %s", pin_, value != inverted_ ? "HIGH" : "LOW"); | ||||||
|  | } | ||||||
|  | void HostGPIOPin::detach_interrupt() const {} | ||||||
|  |  | ||||||
|  | }  // namespace host | ||||||
|  |  | ||||||
|  | using namespace host; | ||||||
|  |  | ||||||
|  | bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { | ||||||
|  |   auto *arg = reinterpret_cast<ISRPinArg *>(arg_); | ||||||
|  |   return arg->inverted; | ||||||
|  | } | ||||||
|  | void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { | ||||||
|  |   // pass | ||||||
|  | } | ||||||
|  | void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { | ||||||
|  |   auto *arg = reinterpret_cast<ISRPinArg *>(arg_); | ||||||
|  |   ESP_LOGD(TAG, "Clearing interrupt for pin %d", arg->pin); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_HOST | ||||||
							
								
								
									
										37
									
								
								esphome/components/host/gpio.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								esphome/components/host/gpio.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #ifdef USE_HOST | ||||||
|  |  | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace host { | ||||||
|  |  | ||||||
|  | class HostGPIOPin : public InternalGPIOPin { | ||||||
|  |  public: | ||||||
|  |   void set_pin(uint8_t pin) { pin_ = pin; } | ||||||
|  |   void set_inverted(bool inverted) { inverted_ = inverted; } | ||||||
|  |   void set_flags(gpio::Flags flags) { flags_ = flags; } | ||||||
|  |  | ||||||
|  |   void setup() override { pin_mode(flags_); } | ||||||
|  |   void pin_mode(gpio::Flags flags) override; | ||||||
|  |   bool digital_read() override; | ||||||
|  |   void digital_write(bool value) override; | ||||||
|  |   std::string dump_summary() const override; | ||||||
|  |   void detach_interrupt() const override; | ||||||
|  |   ISRInternalGPIOPin to_isr() const override; | ||||||
|  |   uint8_t get_pin() const override { return pin_; } | ||||||
|  |   bool is_inverted() const override { return inverted_; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; | ||||||
|  |  | ||||||
|  |   uint8_t pin_; | ||||||
|  |   bool inverted_; | ||||||
|  |   gpio::Flags flags_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace host | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_HOST | ||||||
							
								
								
									
										73
									
								
								esphome/components/host/gpio.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								esphome/components/host/gpio.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | import logging | ||||||
|  |  | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_INPUT, | ||||||
|  |     CONF_INVERTED, | ||||||
|  |     CONF_MODE, | ||||||
|  |     CONF_NUMBER, | ||||||
|  |     CONF_OPEN_DRAIN, | ||||||
|  |     CONF_OUTPUT, | ||||||
|  |     CONF_PULLDOWN, | ||||||
|  |     CONF_PULLUP, | ||||||
|  | ) | ||||||
|  | from esphome import pins | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | import esphome.codegen as cg | ||||||
|  |  | ||||||
|  | from .const import host_ns | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | HostGPIOPin = host_ns.class_("HostGPIOPin", cg.InternalGPIOPin) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _translate_pin(value): | ||||||
|  |     if isinstance(value, dict) or value is None: | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             "This variable only supports pin numbers, not full pin schemas " | ||||||
|  |             "(with inverted and mode)." | ||||||
|  |         ) | ||||||
|  |     if isinstance(value, int): | ||||||
|  |         return value | ||||||
|  |     try: | ||||||
|  |         return int(value) | ||||||
|  |     except ValueError: | ||||||
|  |         pass | ||||||
|  |     if value.startswith("GPIO"): | ||||||
|  |         return cv.int_(value[len("GPIO") :].strip()) | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_gpio_pin(value): | ||||||
|  |     return _translate_pin(value) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | HOST_PIN_SCHEMA = cv.All( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(HostGPIOPin), | ||||||
|  |         cv.Required(CONF_NUMBER): validate_gpio_pin, | ||||||
|  |         cv.Optional(CONF_MODE, default={}): cv.Schema( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(CONF_INPUT, default=False): cv.boolean, | ||||||
|  |                 cv.Optional(CONF_OUTPUT, default=False): cv.boolean, | ||||||
|  |                 cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean, | ||||||
|  |                 cv.Optional(CONF_PULLUP, default=False): cv.boolean, | ||||||
|  |                 cv.Optional(CONF_PULLDOWN, default=False): cv.boolean, | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_INVERTED, default=False): cv.boolean, | ||||||
|  |     }, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pins.PIN_SCHEMA_REGISTRY.register("host", HOST_PIN_SCHEMA) | ||||||
|  | async def host_pin_to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     num = config[CONF_NUMBER] | ||||||
|  |     cg.add(var.set_pin(num)) | ||||||
|  |     cg.add(var.set_inverted(config[CONF_INVERTED])) | ||||||
|  |     cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) | ||||||
|  |     return var | ||||||
							
								
								
									
										36
									
								
								esphome/components/host/preferences.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								esphome/components/host/preferences.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | #ifdef USE_HOST | ||||||
|  |  | ||||||
|  | #include "preferences.h" | ||||||
|  | #include <cstring> | ||||||
|  | #include "esphome/core/preferences.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace host { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "host.preferences"; | ||||||
|  |  | ||||||
|  | class HostPreferences : public ESPPreferences { | ||||||
|  |  public: | ||||||
|  |   ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { return {}; } | ||||||
|  |  | ||||||
|  |   ESPPreferenceObject make_preference(size_t length, uint32_t type) override { return {}; } | ||||||
|  |  | ||||||
|  |   bool sync() override { return true; } | ||||||
|  |   bool reset() override { return true; } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | void setup_preferences() { | ||||||
|  |   auto *pref = new HostPreferences();  // NOLINT(cppcoreguidelines-owning-memory) | ||||||
|  |   global_preferences = pref; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace host | ||||||
|  |  | ||||||
|  | ESPPreferences *global_preferences;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
|  |  | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_HOST | ||||||
							
								
								
									
										13
									
								
								esphome/components/host/preferences.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								esphome/components/host/preferences.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #ifdef USE_HOST | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace host { | ||||||
|  |  | ||||||
|  | void setup_preferences(); | ||||||
|  |  | ||||||
|  | }  // namespace host | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_HOST | ||||||
							
								
								
									
										1
									
								
								esphome/components/hyt271/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/hyt271/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ["@Philippe12"] | ||||||
							
								
								
									
										52
									
								
								esphome/components/hyt271/hyt271.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								esphome/components/hyt271/hyt271.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | #include "hyt271.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace hyt271 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "hyt271"; | ||||||
|  |  | ||||||
|  | static const uint8_t HYT271_ADDRESS = 0x28; | ||||||
|  |  | ||||||
|  | void HYT271Component::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "HYT271:"); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  |   LOG_SENSOR("  ", "Temperature", this->temperature_); | ||||||
|  |   LOG_SENSOR("  ", "Humidity", this->humidity_); | ||||||
|  | } | ||||||
|  | void HYT271Component::update() { | ||||||
|  |   uint8_t raw_data[4]; | ||||||
|  |  | ||||||
|  |   if (this->write(&raw_data[0], 0) != i2c::ERROR_OK) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     ESP_LOGE(TAG, "Communication with HYT271 failed! => Ask new values"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->set_timeout("wait_convert", 50, [this]() { | ||||||
|  |     uint8_t raw_data[4]; | ||||||
|  |     if (this->read(raw_data, 4) != i2c::ERROR_OK) { | ||||||
|  |       this->status_set_warning(); | ||||||
|  |       ESP_LOGE(TAG, "Communication with HYT271 failed! => Read values"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     uint16_t raw_temperature = ((raw_data[2] << 8) | raw_data[3]) >> 2; | ||||||
|  |     uint16_t raw_humidity = ((raw_data[0] & 0x3F) << 8) | raw_data[1]; | ||||||
|  |  | ||||||
|  |     float temperature = ((float(raw_temperature)) * (165.0f / 16383.0f)) - 40.0f; | ||||||
|  |     float humidity = (float(raw_humidity)) * (100.0f / 16383.0f); | ||||||
|  |  | ||||||
|  |     ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity); | ||||||
|  |  | ||||||
|  |     if (this->temperature_ != nullptr) | ||||||
|  |       this->temperature_->publish_state(temperature); | ||||||
|  |     if (this->humidity_ != nullptr) | ||||||
|  |       this->humidity_->publish_state(humidity); | ||||||
|  |     this->status_clear_warning(); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | float HYT271Component::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | }  // namespace hyt271 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										27
									
								
								esphome/components/hyt271/hyt271.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/hyt271/hyt271.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace hyt271 { | ||||||
|  |  | ||||||
|  | class HYT271Component : public PollingComponent, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } | ||||||
|  |   void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } | ||||||
|  |  | ||||||
|  |   void dump_config() override; | ||||||
|  |   /// Update the sensor values (temperature+humidity). | ||||||
|  |   void update() override; | ||||||
|  |  | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   sensor::Sensor *temperature_{nullptr}; | ||||||
|  |   sensor::Sensor *humidity_{nullptr}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace hyt271 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										56
									
								
								esphome/components/hyt271/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								esphome/components/hyt271/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import i2c, sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_HUMIDITY, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_HUMIDITY, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_PERCENT, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | hyt271_ns = cg.esphome_ns.namespace("hyt271") | ||||||
|  | HYT271Component = hyt271_ns.class_( | ||||||
|  |     "HYT271Component", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(HYT271Component), | ||||||
|  |             cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Required(CONF_HUMIDITY): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_PERCENT, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_HUMIDITY, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x28)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
|  |  | ||||||
|  |     if CONF_TEMPERATURE in config: | ||||||
|  |         sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) | ||||||
|  |         cg.add(var.set_temperature(sens)) | ||||||
|  |  | ||||||
|  |     if CONF_HUMIDITY in config: | ||||||
|  |         sens = await sensor.new_sensor(config[CONF_HUMIDITY]) | ||||||
|  |         cg.add(var.set_humidity(sens)) | ||||||
| @@ -42,8 +42,8 @@ I2S_PORTS = { | |||||||
| CONFIG_SCHEMA = cv.Schema( | CONFIG_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.GenerateID(): cv.declare_id(I2SAudioComponent), |         cv.GenerateID(): cv.declare_id(I2SAudioComponent), | ||||||
|         cv.Required(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number, |  | ||||||
|         cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number, |         cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number, | ||||||
|  |         cv.Optional(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number, | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -66,5 +66,6 @@ async def to_code(config): | |||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|     cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN])) |  | ||||||
|     cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN])) |     cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN])) | ||||||
|  |     if CONF_I2S_BCLK_PIN in config: | ||||||
|  |         cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN])) | ||||||
|   | |||||||
| @@ -19,15 +19,6 @@ class I2SAudioComponent : public Component { | |||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
|  |  | ||||||
|   void register_audio_in(I2SAudioIn *in) { |  | ||||||
|     this->audio_in_ = in; |  | ||||||
|     in->set_parent(this); |  | ||||||
|   } |  | ||||||
|   void register_audio_out(I2SAudioOut *out) { |  | ||||||
|     this->audio_out_ = out; |  | ||||||
|     out->set_parent(this); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   i2s_pin_config_t get_pin_config() const { |   i2s_pin_config_t get_pin_config() const { | ||||||
|     return { |     return { | ||||||
|         .mck_io_num = I2S_PIN_NO_CHANGE, |         .mck_io_num = I2S_PIN_NO_CHANGE, | ||||||
| @@ -38,8 +29,8 @@ class I2SAudioComponent : public Component { | |||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void set_bclk_pin(uint8_t pin) { this->bclk_pin_ = pin; } |   void set_bclk_pin(int pin) { this->bclk_pin_ = pin; } | ||||||
|   void set_lrclk_pin(uint8_t pin) { this->lrclk_pin_ = pin; } |   void set_lrclk_pin(int pin) { this->lrclk_pin_ = pin; } | ||||||
|  |  | ||||||
|   void lock() { this->lock_.lock(); } |   void lock() { this->lock_.lock(); } | ||||||
|   bool try_lock() { return this->lock_.try_lock(); } |   bool try_lock() { return this->lock_.try_lock(); } | ||||||
| @@ -53,8 +44,8 @@ class I2SAudioComponent : public Component { | |||||||
|   I2SAudioIn *audio_in_{nullptr}; |   I2SAudioIn *audio_in_{nullptr}; | ||||||
|   I2SAudioOut *audio_out_{nullptr}; |   I2SAudioOut *audio_out_{nullptr}; | ||||||
|  |  | ||||||
|   uint8_t bclk_pin_; |   int bclk_pin_{I2S_PIN_NO_CHANGE}; | ||||||
|   uint8_t lrclk_pin_; |   int lrclk_pin_; | ||||||
|   i2s_port_t port_{}; |   i2s_port_t port_{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -84,8 +84,7 @@ async def to_code(config): | |||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     await media_player.register_media_player(var, config) |     await media_player.register_media_player(var, config) | ||||||
|  |  | ||||||
|     parent = await cg.get_variable(config[CONF_I2S_AUDIO_ID]) |     await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) | ||||||
|     cg.add(parent.register_audio_out(var)) |  | ||||||
|  |  | ||||||
|     if config[CONF_DAC_TYPE] == "internal": |     if config[CONF_DAC_TYPE] == "internal": | ||||||
|         cg.add(var.set_internal_dac_mode(config[CONF_MODE])) |         cg.add(var.set_internal_dac_mode(config[CONF_MODE])) | ||||||
| @@ -98,5 +97,5 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     cg.add_library("WiFiClientSecure", None) |     cg.add_library("WiFiClientSecure", None) | ||||||
|     cg.add_library("HTTPClient", None) |     cg.add_library("HTTPClient", None) | ||||||
|     cg.add_library("esphome/ESP32-audioI2S", "2.0.6") |     cg.add_library("esphome/ESP32-audioI2S", "2.0.7") | ||||||
|     cg.add_build_flag("-DAUDIO_NO_SD_FS") |     cg.add_build_flag("-DAUDIO_NO_SD_FS") | ||||||
|   | |||||||
| @@ -22,14 +22,14 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) { | |||||||
|       this->start(); |       this->start(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   if (this->i2s_state_ != I2S_STATE_RUNNING) { |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   if (call.get_volume().has_value()) { |   if (call.get_volume().has_value()) { | ||||||
|     this->volume = call.get_volume().value(); |     this->volume = call.get_volume().value(); | ||||||
|     this->set_volume_(volume); |     this->set_volume_(volume); | ||||||
|     this->unmute_(); |     this->unmute_(); | ||||||
|   } |   } | ||||||
|  |   if (this->i2s_state_ != I2S_STATE_RUNNING) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|   if (call.get_command().has_value()) { |   if (call.get_command().has_value()) { | ||||||
|     switch (call.get_command().value()) { |     switch (call.get_command().value()) { | ||||||
|       case media_player::MEDIA_PLAYER_COMMAND_PLAY: |       case media_player::MEDIA_PLAYER_COMMAND_PLAY: | ||||||
| @@ -97,6 +97,7 @@ void I2SAudioMediaPlayer::unmute_() { | |||||||
|   this->muted_ = false; |   this->muted_ = false; | ||||||
| } | } | ||||||
| void I2SAudioMediaPlayer::set_volume_(float volume, bool publish) { | void I2SAudioMediaPlayer::set_volume_(float volume, bool publish) { | ||||||
|  |   if (this->audio_ != nullptr) | ||||||
|     this->audio_->setVolume(remap<uint8_t, float>(volume, 0.0f, 1.0f, 0, 21)); |     this->audio_->setVolume(remap<uint8_t, float>(volume, 0.0f, 1.0f, 0, 21)); | ||||||
|   if (publish) |   if (publish) | ||||||
|     this->volume = volume; |     this->volume = volume; | ||||||
| @@ -157,13 +158,23 @@ void I2SAudioMediaPlayer::start_() { | |||||||
| #endif | #endif | ||||||
|   this->i2s_state_ = I2S_STATE_RUNNING; |   this->i2s_state_ = I2S_STATE_RUNNING; | ||||||
|   this->high_freq_.start(); |   this->high_freq_.start(); | ||||||
|  |   this->audio_->setVolume(remap<uint8_t, float>(this->volume, 0.0f, 1.0f, 0, 21)); | ||||||
|   if (this->current_url_.has_value()) { |   if (this->current_url_.has_value()) { | ||||||
|     this->audio_->connecttohost(this->current_url_.value().c_str()); |     this->audio_->connecttohost(this->current_url_.value().c_str()); | ||||||
|     this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; |     this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; | ||||||
|     this->publish_state(); |     this->publish_state(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| void I2SAudioMediaPlayer::stop() { this->i2s_state_ = I2S_STATE_STOPPING; } | void I2SAudioMediaPlayer::stop() { | ||||||
|  |   if (this->i2s_state_ == I2S_STATE_STOPPED) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (this->i2s_state_ == I2S_STATE_STARTING) { | ||||||
|  |     this->i2s_state_ = I2S_STATE_STOPPED; | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->i2s_state_ = I2S_STATE_STOPPING; | ||||||
|  | } | ||||||
| void I2SAudioMediaPlayer::stop_() { | void I2SAudioMediaPlayer::stop_() { | ||||||
|   if (this->audio_->isRunning()) { |   if (this->audio_->isRunning()) { | ||||||
|     this->audio_->stopSong(); |     this->audio_->stopSong(); | ||||||
|   | |||||||
| @@ -2,8 +2,9 @@ import esphome.config_validation as cv | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  |  | ||||||
| from esphome import pins | from esphome import pins | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID, CONF_NUMBER | ||||||
| from esphome.components import microphone | from esphome.components import microphone, esp32 | ||||||
|  | from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin | ||||||
|  |  | ||||||
| from .. import ( | from .. import ( | ||||||
|     i2s_audio_ns, |     i2s_audio_ns, | ||||||
| @@ -16,26 +17,73 @@ from .. import ( | |||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
| DEPENDENCIES = ["i2s_audio"] | DEPENDENCIES = ["i2s_audio"] | ||||||
|  |  | ||||||
|  | CONF_ADC_PIN = "adc_pin" | ||||||
|  | CONF_ADC_TYPE = "adc_type" | ||||||
|  | CONF_PDM = "pdm" | ||||||
|  |  | ||||||
| I2SAudioMicrophone = i2s_audio_ns.class_( | I2SAudioMicrophone = i2s_audio_ns.class_( | ||||||
|     "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component |     "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = microphone.MICROPHONE_SCHEMA.extend( | INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32] | ||||||
|  | PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_esp32_variant(config): | ||||||
|  |     variant = esp32.get_esp32_variant() | ||||||
|  |     if config[CONF_ADC_TYPE] == "external": | ||||||
|  |         if config[CONF_PDM]: | ||||||
|  |             if variant not in PDM_VARIANTS: | ||||||
|  |                 raise cv.Invalid(f"{variant} does not support PDM") | ||||||
|  |         return config | ||||||
|  |     if config[CONF_ADC_TYPE] == "internal": | ||||||
|  |         if variant not in INTERNAL_ADC_VARIANTS: | ||||||
|  |             raise cv.Invalid(f"{variant} does not have an internal ADC") | ||||||
|  |         return config | ||||||
|  |     raise NotImplementedError | ||||||
|  |  | ||||||
|  |  | ||||||
|  | BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend( | ||||||
|     { |     { | ||||||
|         cv.GenerateID(): cv.declare_id(I2SAudioMicrophone), |         cv.GenerateID(): cv.declare_id(I2SAudioMicrophone), | ||||||
|         cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), |         cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), | ||||||
|         cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number, |  | ||||||
|     } |     } | ||||||
| ).extend(cv.COMPONENT_SCHEMA) | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.typed_schema( | ||||||
|  |         { | ||||||
|  |             "internal": BASE_SCHEMA.extend( | ||||||
|  |                 { | ||||||
|  |                     cv.Required(CONF_ADC_PIN): validate_adc_pin, | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |             "external": BASE_SCHEMA.extend( | ||||||
|  |                 { | ||||||
|  |                     cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number, | ||||||
|  |                     cv.Required(CONF_PDM): cv.boolean, | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |         }, | ||||||
|  |         key=CONF_ADC_TYPE, | ||||||
|  |     ), | ||||||
|  |     validate_esp32_variant, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|     parent = await cg.get_variable(config[CONF_I2S_AUDIO_ID]) |     await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) | ||||||
|     cg.add(parent.register_audio_in(var)) |  | ||||||
|  |  | ||||||
|  |     if config[CONF_ADC_TYPE] == "internal": | ||||||
|  |         variant = esp32.get_esp32_variant() | ||||||
|  |         pin_num = config[CONF_ADC_PIN][CONF_NUMBER] | ||||||
|  |         channel = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num] | ||||||
|  |         cg.add(var.set_adc_channel(channel)) | ||||||
|  |     else: | ||||||
|         cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN])) |         cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN])) | ||||||
|  |         cg.add(var.set_pdm(config[CONF_PDM])) | ||||||
|  |  | ||||||
|     await microphone.register_microphone(var, config) |     await microphone.register_microphone(var, config) | ||||||
|   | |||||||
| @@ -17,15 +17,36 @@ static const char *const TAG = "i2s_audio.microphone"; | |||||||
| void I2SAudioMicrophone::setup() { | void I2SAudioMicrophone::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone..."); |   ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone..."); | ||||||
|   this->buffer_.resize(BUFFER_SIZE); |   this->buffer_.resize(BUFFER_SIZE); | ||||||
|  |  | ||||||
|  | #if SOC_I2S_SUPPORTS_ADC | ||||||
|  |   if (this->adc_) { | ||||||
|  |     if (this->parent_->get_port() != I2S_NUM_0) { | ||||||
|  |       ESP_LOGE(TAG, "Internal ADC only works on I2S0!"); | ||||||
|  |       this->mark_failed(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } else | ||||||
|  | #endif | ||||||
|  |       if (this->pdm_) { | ||||||
|  |     if (this->parent_->get_port() != I2S_NUM_0) { | ||||||
|  |       ESP_LOGE(TAG, "PDM only works on I2S0!"); | ||||||
|  |       this->mark_failed(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void I2SAudioMicrophone::start() { this->state_ = microphone::STATE_STARTING; } | void I2SAudioMicrophone::start() { | ||||||
|  |   if (this->is_failed()) | ||||||
|  |     return; | ||||||
|  |   this->state_ = microphone::STATE_STARTING; | ||||||
|  | } | ||||||
| void I2SAudioMicrophone::start_() { | void I2SAudioMicrophone::start_() { | ||||||
|   if (!this->parent_->try_lock()) { |   if (!this->parent_->try_lock()) { | ||||||
|     return;  // Waiting for another i2s to return lock |     return;  // Waiting for another i2s to return lock | ||||||
|   } |   } | ||||||
|   i2s_driver_config_t config = { |   i2s_driver_config_t config = { | ||||||
|       .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM), |       .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX), | ||||||
|       .sample_rate = 16000, |       .sample_rate = 16000, | ||||||
|       .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, |       .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, | ||||||
|       .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, |       .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, | ||||||
| @@ -40,19 +61,38 @@ void I2SAudioMicrophone::start_() { | |||||||
|       .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, |       .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  | #if SOC_I2S_SUPPORTS_ADC | ||||||
|  |   if (this->adc_) { | ||||||
|  |     config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN); | ||||||
|  |     i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); | ||||||
|  |  | ||||||
|  |     i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_); | ||||||
|  |     i2s_adc_enable(this->parent_->get_port()); | ||||||
|  |   } else { | ||||||
|  | #endif | ||||||
|  |     if (this->pdm_) | ||||||
|  |       config.mode = (i2s_mode_t) (config.mode | I2S_MODE_PDM); | ||||||
|  |  | ||||||
|     i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); |     i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); | ||||||
|  |  | ||||||
|     i2s_pin_config_t pin_config = this->parent_->get_pin_config(); |     i2s_pin_config_t pin_config = this->parent_->get_pin_config(); | ||||||
|     pin_config.data_in_num = this->din_pin_; |     pin_config.data_in_num = this->din_pin_; | ||||||
|  |  | ||||||
|     i2s_set_pin(this->parent_->get_port(), &pin_config); |     i2s_set_pin(this->parent_->get_port(), &pin_config); | ||||||
|  | #if SOC_I2S_SUPPORTS_ADC | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|   this->state_ = microphone::STATE_RUNNING; |   this->state_ = microphone::STATE_RUNNING; | ||||||
|   this->high_freq_.start(); |   this->high_freq_.start(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void I2SAudioMicrophone::stop() { | void I2SAudioMicrophone::stop() { | ||||||
|   if (this->state_ == microphone::STATE_STOPPED) |   if (this->state_ == microphone::STATE_STOPPED || this->is_failed()) | ||||||
|     return; |     return; | ||||||
|  |   if (this->state_ == microphone::STATE_STARTING) { | ||||||
|  |     this->state_ = microphone::STATE_STOPPED; | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|   this->state_ = microphone::STATE_STOPPING; |   this->state_ = microphone::STATE_STOPPING; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,14 +18,27 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub | |||||||
|  |  | ||||||
|   void loop() override; |   void loop() override; | ||||||
|  |  | ||||||
|   void set_din_pin(uint8_t pin) { this->din_pin_ = pin; } |   void set_din_pin(int8_t pin) { this->din_pin_ = pin; } | ||||||
|  |   void set_pdm(bool pdm) { this->pdm_ = pdm; } | ||||||
|  |  | ||||||
|  | #if SOC_I2S_SUPPORTS_ADC | ||||||
|  |   void set_adc_channel(adc1_channel_t channel) { | ||||||
|  |     this->adc_channel_ = channel; | ||||||
|  |     this->adc_ = true; | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void start_(); |   void start_(); | ||||||
|   void stop_(); |   void stop_(); | ||||||
|   void read_(); |   void read_(); | ||||||
|  |  | ||||||
|   uint8_t din_pin_{0}; |   int8_t din_pin_{I2S_PIN_NO_CHANGE}; | ||||||
|  | #if SOC_I2S_SUPPORTS_ADC | ||||||
|  |   adc1_channel_t adc_channel_{ADC1_CHANNEL_MAX}; | ||||||
|  |   bool adc_{false}; | ||||||
|  | #endif | ||||||
|  |   bool pdm_{false}; | ||||||
|   std::vector<uint8_t> buffer_; |   std::vector<uint8_t> buffer_; | ||||||
|  |  | ||||||
|   HighFrequencyLoopRequester high_freq_; |   HighFrequencyLoopRequester high_freq_; | ||||||
|   | |||||||
							
								
								
									
										87
									
								
								esphome/components/i2s_audio/speaker/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								esphome/components/i2s_audio/speaker/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome import pins | ||||||
|  | from esphome.const import CONF_ID, CONF_MODE | ||||||
|  | from esphome.components import esp32, speaker | ||||||
|  |  | ||||||
|  | from .. import ( | ||||||
|  |     CONF_I2S_AUDIO_ID, | ||||||
|  |     CONF_I2S_DOUT_PIN, | ||||||
|  |     I2SAudioComponent, | ||||||
|  |     I2SAudioOut, | ||||||
|  |     i2s_audio_ns, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@jesserockz"] | ||||||
|  | DEPENDENCIES = ["i2s_audio"] | ||||||
|  |  | ||||||
|  | I2SAudioSpeaker = i2s_audio_ns.class_( | ||||||
|  |     "I2SAudioSpeaker", cg.Component, speaker.Speaker, I2SAudioOut | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t") | ||||||
|  |  | ||||||
|  | CONF_MUTE_PIN = "mute_pin" | ||||||
|  | CONF_DAC_TYPE = "dac_type" | ||||||
|  |  | ||||||
|  | INTERNAL_DAC_OPTIONS = { | ||||||
|  |     "left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, | ||||||
|  |     "right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN, | ||||||
|  |     "stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | EXTERNAL_DAC_OPTIONS = ["mono", "stereo"] | ||||||
|  |  | ||||||
|  | NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_esp32_variant(config): | ||||||
|  |     if config[CONF_DAC_TYPE] != "internal": | ||||||
|  |         return config | ||||||
|  |     variant = esp32.get_esp32_variant() | ||||||
|  |     if variant in NO_INTERNAL_DAC_VARIANTS: | ||||||
|  |         raise cv.Invalid(f"{variant} does not have an internal DAC") | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.typed_schema( | ||||||
|  |         { | ||||||
|  |             "internal": speaker.SPEAKER_SCHEMA.extend( | ||||||
|  |                 { | ||||||
|  |                     cv.GenerateID(): cv.declare_id(I2SAudioSpeaker), | ||||||
|  |                     cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), | ||||||
|  |                     cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True), | ||||||
|  |                 } | ||||||
|  |             ).extend(cv.COMPONENT_SCHEMA), | ||||||
|  |             "external": speaker.SPEAKER_SCHEMA.extend( | ||||||
|  |                 { | ||||||
|  |                     cv.GenerateID(): cv.declare_id(I2SAudioSpeaker), | ||||||
|  |                     cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), | ||||||
|  |                     cv.Required( | ||||||
|  |                         CONF_I2S_DOUT_PIN | ||||||
|  |                     ): pins.internal_gpio_output_pin_number, | ||||||
|  |                     cv.Optional(CONF_MODE, default="mono"): cv.one_of( | ||||||
|  |                         *EXTERNAL_DAC_OPTIONS, lower=True | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ).extend(cv.COMPONENT_SCHEMA), | ||||||
|  |         }, | ||||||
|  |         key=CONF_DAC_TYPE, | ||||||
|  |     ), | ||||||
|  |     validate_esp32_variant, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await speaker.register_speaker(var, config) | ||||||
|  |  | ||||||
|  |     await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) | ||||||
|  |  | ||||||
|  |     if config[CONF_DAC_TYPE] == "internal": | ||||||
|  |         cg.add(var.set_internal_dac_mode(config[CONF_MODE])) | ||||||
|  |     else: | ||||||
|  |         cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN])) | ||||||
|  |         cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1)) | ||||||
							
								
								
									
										212
									
								
								esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,212 @@ | |||||||
|  | #include "i2s_audio_speaker.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | #include <driver/i2s.h> | ||||||
|  |  | ||||||
|  | #include "esphome/core/application.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace i2s_audio { | ||||||
|  |  | ||||||
|  | static const size_t BUFFER_COUNT = 10; | ||||||
|  |  | ||||||
|  | static const char *const TAG = "i2s_audio.speaker"; | ||||||
|  |  | ||||||
|  | void I2SAudioSpeaker::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up I2S Audio Speaker..."); | ||||||
|  |  | ||||||
|  |   this->buffer_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(DataEvent)); | ||||||
|  |   this->event_queue_ = xQueueCreate(20, sizeof(TaskEvent)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void I2SAudioSpeaker::start() { this->state_ = speaker::STATE_STARTING; } | ||||||
|  | void I2SAudioSpeaker::start_() { | ||||||
|  |   if (!this->parent_->try_lock()) { | ||||||
|  |     return;  // Waiting for another i2s component to return lock | ||||||
|  |   } | ||||||
|  |   this->state_ = speaker::STATE_RUNNING; | ||||||
|  |  | ||||||
|  |   xTaskCreate(I2SAudioSpeaker::player_task, "speaker_task", 8192, (void *) this, 0, &this->player_task_handle_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void I2SAudioSpeaker::player_task(void *params) { | ||||||
|  |   I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params; | ||||||
|  |  | ||||||
|  |   TaskEvent event; | ||||||
|  |   event.type = TaskEventType::STARTING; | ||||||
|  |   xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); | ||||||
|  |  | ||||||
|  |   i2s_driver_config_t config = { | ||||||
|  |       .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX), | ||||||
|  |       .sample_rate = 16000, | ||||||
|  |       .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, | ||||||
|  |       .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, | ||||||
|  |       .communication_format = I2S_COMM_FORMAT_STAND_I2S, | ||||||
|  |       .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, | ||||||
|  |       .dma_buf_count = 8, | ||||||
|  |       .dma_buf_len = 1024, | ||||||
|  |       .use_apll = false, | ||||||
|  |       .tx_desc_auto_clear = true, | ||||||
|  |       .fixed_mclk = I2S_PIN_NO_CHANGE, | ||||||
|  |       .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, | ||||||
|  |       .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, | ||||||
|  |   }; | ||||||
|  | #if SOC_I2S_SUPPORTS_DAC | ||||||
|  |   if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) { | ||||||
|  |     config.mode = (i2s_mode_t) (config.mode | I2S_MODE_DAC_BUILT_IN); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   i2s_driver_install(this_speaker->parent_->get_port(), &config, 0, nullptr); | ||||||
|  |  | ||||||
|  | #if SOC_I2S_SUPPORTS_DAC | ||||||
|  |   if (this_speaker->internal_dac_mode_ == I2S_DAC_CHANNEL_DISABLE) { | ||||||
|  | #endif | ||||||
|  |     i2s_pin_config_t pin_config = this_speaker->parent_->get_pin_config(); | ||||||
|  |     pin_config.data_out_num = this_speaker->dout_pin_; | ||||||
|  |  | ||||||
|  |     i2s_set_pin(this_speaker->parent_->get_port(), &pin_config); | ||||||
|  | #if SOC_I2S_SUPPORTS_DAC | ||||||
|  |   } else { | ||||||
|  |     i2s_set_dac_mode(this_speaker->internal_dac_mode_); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   DataEvent data_event; | ||||||
|  |  | ||||||
|  |   event.type = TaskEventType::STARTED; | ||||||
|  |   xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); | ||||||
|  |  | ||||||
|  |   int16_t buffer[BUFFER_SIZE / 2]; | ||||||
|  |  | ||||||
|  |   while (true) { | ||||||
|  |     if (xQueueReceive(this_speaker->buffer_queue_, &data_event, 100 / portTICK_PERIOD_MS) != pdTRUE) { | ||||||
|  |       break;  // End of audio from main thread | ||||||
|  |     } | ||||||
|  |     if (data_event.stop) { | ||||||
|  |       // Stop signal from main thread | ||||||
|  |       while (xQueueReceive(this_speaker->buffer_queue_, &data_event, 0) == pdTRUE) { | ||||||
|  |         // Flush queue | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     size_t bytes_written; | ||||||
|  |  | ||||||
|  |     memmove(buffer, data_event.data, data_event.len); | ||||||
|  |     size_t remaining = data_event.len / 2; | ||||||
|  |     size_t current = 0; | ||||||
|  |  | ||||||
|  |     while (remaining > 0) { | ||||||
|  |       uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF); | ||||||
|  |  | ||||||
|  |       esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written, | ||||||
|  |                                 (100 / portTICK_PERIOD_MS)); | ||||||
|  |       if (err != ESP_OK) { | ||||||
|  |         event = {.type = TaskEventType::WARNING, .err = err}; | ||||||
|  |         xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |       remaining--; | ||||||
|  |       current++; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     event.type = TaskEventType::PLAYING; | ||||||
|  |     xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   i2s_zero_dma_buffer(this_speaker->parent_->get_port()); | ||||||
|  |  | ||||||
|  |   event.type = TaskEventType::STOPPING; | ||||||
|  |   xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); | ||||||
|  |  | ||||||
|  |   i2s_stop(this_speaker->parent_->get_port()); | ||||||
|  |   i2s_driver_uninstall(this_speaker->parent_->get_port()); | ||||||
|  |  | ||||||
|  |   event.type = TaskEventType::STOPPED; | ||||||
|  |   xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); | ||||||
|  |  | ||||||
|  |   while (true) { | ||||||
|  |     delay(10); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void I2SAudioSpeaker::stop() { | ||||||
|  |   if (this->state_ == speaker::STATE_STOPPED) | ||||||
|  |     return; | ||||||
|  |   if (this->state_ == speaker::STATE_STARTING) { | ||||||
|  |     this->state_ = speaker::STATE_STOPPED; | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->state_ = speaker::STATE_STOPPING; | ||||||
|  |   DataEvent data; | ||||||
|  |   data.stop = true; | ||||||
|  |   xQueueSendToFront(this->buffer_queue_, &data, portMAX_DELAY); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void I2SAudioSpeaker::watch_() { | ||||||
|  |   TaskEvent event; | ||||||
|  |   if (xQueueReceive(this->event_queue_, &event, 0) == pdTRUE) { | ||||||
|  |     switch (event.type) { | ||||||
|  |       case TaskEventType::STARTING: | ||||||
|  |       case TaskEventType::STARTED: | ||||||
|  |       case TaskEventType::STOPPING: | ||||||
|  |         break; | ||||||
|  |       case TaskEventType::PLAYING: | ||||||
|  |         this->status_clear_warning(); | ||||||
|  |         break; | ||||||
|  |       case TaskEventType::STOPPED: | ||||||
|  |         this->parent_->unlock(); | ||||||
|  |         this->state_ = speaker::STATE_STOPPED; | ||||||
|  |         vTaskDelete(this->player_task_handle_); | ||||||
|  |         this->player_task_handle_ = nullptr; | ||||||
|  |         break; | ||||||
|  |       case TaskEventType::WARNING: | ||||||
|  |         ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(event.err)); | ||||||
|  |         this->status_set_warning(); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void I2SAudioSpeaker::loop() { | ||||||
|  |   switch (this->state_) { | ||||||
|  |     case speaker::STATE_STARTING: | ||||||
|  |       this->start_(); | ||||||
|  |       break; | ||||||
|  |     case speaker::STATE_RUNNING: | ||||||
|  |       this->watch_(); | ||||||
|  |       break; | ||||||
|  |     case speaker::STATE_STOPPING: | ||||||
|  |     case speaker::STATE_STOPPED: | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool I2SAudioSpeaker::play(const uint8_t *data, size_t length) { | ||||||
|  |   if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) { | ||||||
|  |     this->start(); | ||||||
|  |   } | ||||||
|  |   size_t remaining = length; | ||||||
|  |   size_t index = 0; | ||||||
|  |   while (remaining > 0) { | ||||||
|  |     DataEvent event; | ||||||
|  |     event.stop = false; | ||||||
|  |     size_t to_send_length = std::min(remaining, BUFFER_SIZE); | ||||||
|  |     event.len = to_send_length; | ||||||
|  |     memcpy(event.data, data + index, to_send_length); | ||||||
|  |     if (xQueueSend(this->buffer_queue_, &event, 100 / portTICK_PERIOD_MS) == pdTRUE) { | ||||||
|  |       remaining -= to_send_length; | ||||||
|  |       index += to_send_length; | ||||||
|  |     } | ||||||
|  |     App.feed_wdt(); | ||||||
|  |   } | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace i2s_audio | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_ESP32 | ||||||
							
								
								
									
										81
									
								
								esphome/components/i2s_audio/speaker/i2s_audio_speaker.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								esphome/components/i2s_audio/speaker/i2s_audio_speaker.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | #include "../i2s_audio.h" | ||||||
|  |  | ||||||
|  | #include <driver/i2s.h> | ||||||
|  | #include <freertos/FreeRTOS.h> | ||||||
|  | #include <freertos/queue.h> | ||||||
|  |  | ||||||
|  | #include "esphome/components/speaker/speaker.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/gpio.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace i2s_audio { | ||||||
|  |  | ||||||
|  | static const size_t BUFFER_SIZE = 1024; | ||||||
|  |  | ||||||
|  | enum class TaskEventType : uint8_t { | ||||||
|  |   STARTING = 0, | ||||||
|  |   STARTED, | ||||||
|  |   PLAYING, | ||||||
|  |   STOPPING, | ||||||
|  |   STOPPED, | ||||||
|  |   WARNING = 255, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct TaskEvent { | ||||||
|  |   TaskEventType type; | ||||||
|  |   esp_err_t err; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct DataEvent { | ||||||
|  |   bool stop; | ||||||
|  |   size_t len; | ||||||
|  |   uint8_t data[BUFFER_SIZE]; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAudioOut { | ||||||
|  |  public: | ||||||
|  |   float get_setup_priority() const override { return esphome::setup_priority::LATE; } | ||||||
|  |  | ||||||
|  |   void setup() override; | ||||||
|  |   void loop() override; | ||||||
|  |  | ||||||
|  |   void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; } | ||||||
|  | #if SOC_I2S_SUPPORTS_DAC | ||||||
|  |   void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; } | ||||||
|  | #endif | ||||||
|  |   void set_external_dac_channels(uint8_t channels) { this->external_dac_channels_ = channels; } | ||||||
|  |  | ||||||
|  |   void start(); | ||||||
|  |   void stop() override; | ||||||
|  |  | ||||||
|  |   bool play(const uint8_t *data, size_t length) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void start_(); | ||||||
|  |   // void stop_(); | ||||||
|  |   void watch_(); | ||||||
|  |  | ||||||
|  |   static void player_task(void *params); | ||||||
|  |  | ||||||
|  |   TaskHandle_t player_task_handle_{nullptr}; | ||||||
|  |   QueueHandle_t buffer_queue_; | ||||||
|  |   QueueHandle_t event_queue_; | ||||||
|  |  | ||||||
|  |   uint8_t dout_pin_{0}; | ||||||
|  |  | ||||||
|  | #if SOC_I2S_SUPPORTS_DAC | ||||||
|  |   i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE}; | ||||||
|  | #endif | ||||||
|  |   uint8_t external_dac_channels_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace i2s_audio | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_ESP32 | ||||||
| @@ -84,10 +84,19 @@ void ILI9XXXDisplay::fill(Color color) { | |||||||
|       break; |       break; | ||||||
|     case BITS_16: |     case BITS_16: | ||||||
|       new_color = display::ColorUtil::color_to_565(color); |       new_color = display::ColorUtil::color_to_565(color); | ||||||
|       for (uint32_t i = 0; i < this->get_buffer_length_() * 2; i = i + 2) { |       { | ||||||
|  |         const uint32_t buffer_length_16_bits = this->get_buffer_length_() * 2; | ||||||
|  |         if (((uint8_t) (new_color >> 8)) == ((uint8_t) new_color)) { | ||||||
|  |           // Upper and lower is equal can use quicker memset operation. Takes ~20ms. | ||||||
|  |           memset(this->buffer_, (uint8_t) new_color, buffer_length_16_bits); | ||||||
|  |         } else { | ||||||
|  |           // Slower set of both buffers. Takes ~30ms. | ||||||
|  |           for (uint32_t i = 0; i < buffer_length_16_bits; i = i + 2) { | ||||||
|             this->buffer_[i] = (uint8_t) (new_color >> 8); |             this->buffer_[i] = (uint8_t) (new_color >> 8); | ||||||
|             this->buffer_[i + 1] = (uint8_t) new_color; |             this->buffer_[i + 1] = (uint8_t) new_color; | ||||||
|           } |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|       return; |       return; | ||||||
|       break; |       break; | ||||||
|     default: |     default: | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
|     cv.COMPONENT_SCHEMA.extend( |     cv.COMPONENT_SCHEMA.extend( | ||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(KeyCollector), |             cv.GenerateID(): cv.declare_id(KeyCollector), | ||||||
|             cv.GenerateID(CONF_SOURCE_ID): cv.use_id(key_provider.KeyProvider), |             cv.Optional(CONF_SOURCE_ID): cv.use_id(key_provider.KeyProvider), | ||||||
|             cv.Optional(CONF_MIN_LENGTH): cv.int_, |             cv.Optional(CONF_MIN_LENGTH): cv.int_, | ||||||
|             cv.Optional(CONF_MAX_LENGTH): cv.int_, |             cv.Optional(CONF_MAX_LENGTH): cv.int_, | ||||||
|             cv.Optional(CONF_START_KEYS): cv.string, |             cv.Optional(CONF_START_KEYS): cv.string, | ||||||
| @@ -55,6 +55,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |     if CONF_SOURCE_ID in config: | ||||||
|         source = await cg.get_variable(config[CONF_SOURCE_ID]) |         source = await cg.get_variable(config[CONF_SOURCE_ID]) | ||||||
|         cg.add(var.set_provider(source)) |         cg.add(var.set_provider(source)) | ||||||
|     if CONF_MIN_LENGTH in config: |     if CONF_MIN_LENGTH in config: | ||||||
|   | |||||||
| @@ -52,6 +52,8 @@ void KeyCollector::clear(bool progress_update) { | |||||||
|     this->progress_trigger_->trigger(this->result_, 0); |     this->progress_trigger_->trigger(this->result_, 0); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void KeyCollector::send_key(uint8_t key) { this->key_pressed_(key); } | ||||||
|  |  | ||||||
| void KeyCollector::key_pressed_(uint8_t key) { | void KeyCollector::key_pressed_(uint8_t key) { | ||||||
|   this->last_key_time_ = millis(); |   this->last_key_time_ = millis(); | ||||||
|   if (!this->start_keys_.empty() && !this->start_key_) { |   if (!this->start_keys_.empty() && !this->start_key_) { | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ class KeyCollector : public Component { | |||||||
|   void set_timeout(int timeout) { this->timeout_ = timeout; }; |   void set_timeout(int timeout) { this->timeout_ = timeout; }; | ||||||
|  |  | ||||||
|   void clear(bool progress_update = true); |   void clear(bool progress_update = true); | ||||||
|  |   void send_key(uint8_t key); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void key_pressed_(uint8_t key); |   void key_pressed_(uint8_t key); | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ class PulseLightEffect : public LightEffect { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     auto call = this->state_->turn_on(); |     auto call = this->state_->turn_on(); | ||||||
|     float out = this->on_ ? 1.0 : 0.0; |     float out = this->on_ ? this->max_brightness : this->min_brightness; | ||||||
|     call.set_brightness_if_supported(out); |     call.set_brightness_if_supported(out); | ||||||
|     this->on_ = !this->on_; |     this->on_ = !this->on_; | ||||||
|     call.set_transition_length_if_supported(this->transition_length_); |     call.set_transition_length_if_supported(this->transition_length_); | ||||||
| @@ -41,11 +41,18 @@ class PulseLightEffect : public LightEffect { | |||||||
|  |  | ||||||
|   void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } |   void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } | ||||||
|  |  | ||||||
|  |   void set_min_max_brightness(float min, float max) { | ||||||
|  |     this->min_brightness = min; | ||||||
|  |     this->max_brightness = max; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   bool on_ = false; |   bool on_ = false; | ||||||
|   uint32_t last_color_change_{0}; |   uint32_t last_color_change_{0}; | ||||||
|   uint32_t transition_length_{}; |   uint32_t transition_length_{}; | ||||||
|   uint32_t update_interval_{}; |   uint32_t update_interval_{}; | ||||||
|  |   float min_brightness{0.0}; | ||||||
|  |   float max_brightness{1.0}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// Random effect. Sets random colors every 10 seconds and slowly transitions between them. | /// Random effect. Sets random colors every 10 seconds and slowly transitions between them. | ||||||
|   | |||||||
| @@ -28,6 +28,8 @@ from esphome.const import ( | |||||||
|     CONF_NUM_LEDS, |     CONF_NUM_LEDS, | ||||||
|     CONF_RANDOM, |     CONF_RANDOM, | ||||||
|     CONF_SEQUENCE, |     CONF_SEQUENCE, | ||||||
|  |     CONF_MAX_BRIGHTNESS, | ||||||
|  |     CONF_MIN_BRIGHTNESS, | ||||||
| ) | ) | ||||||
| from esphome.util import Registry | from esphome.util import Registry | ||||||
| from .types import ( | from .types import ( | ||||||
| @@ -174,12 +176,19 @@ async def automation_effect_to_code(config, effect_id): | |||||||
|         cv.Optional( |         cv.Optional( | ||||||
|             CONF_UPDATE_INTERVAL, default="1s" |             CONF_UPDATE_INTERVAL, default="1s" | ||||||
|         ): cv.positive_time_period_milliseconds, |         ): cv.positive_time_period_milliseconds, | ||||||
|  |         cv.Optional(CONF_MIN_BRIGHTNESS, default="0%"): cv.percentage, | ||||||
|  |         cv.Optional(CONF_MAX_BRIGHTNESS, default="100%"): cv.percentage, | ||||||
|     }, |     }, | ||||||
| ) | ) | ||||||
| async def pulse_effect_to_code(config, effect_id): | async def pulse_effect_to_code(config, effect_id): | ||||||
|     effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) |     effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) | ||||||
|     cg.add(effect.set_transition_length(config[CONF_TRANSITION_LENGTH])) |     cg.add(effect.set_transition_length(config[CONF_TRANSITION_LENGTH])) | ||||||
|     cg.add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL])) |     cg.add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL])) | ||||||
|  |     cg.add( | ||||||
|  |         effect.set_min_max_brightness( | ||||||
|  |             config[CONF_MIN_BRIGHTNESS], config[CONF_MAX_BRIGHTNESS] | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|     return effect |     return effect | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,6 +17,9 @@ from esphome.const import ( | |||||||
|     CONF_TAG, |     CONF_TAG, | ||||||
|     CONF_TRIGGER_ID, |     CONF_TRIGGER_ID, | ||||||
|     CONF_TX_BUFFER_SIZE, |     CONF_TX_BUFFER_SIZE, | ||||||
|  |     PLATFORM_ESP32, | ||||||
|  |     PLATFORM_ESP8266, | ||||||
|  |     PLATFORM_RP2040, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority | from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority | ||||||
| from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant | from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant | ||||||
| @@ -141,7 +144,10 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                 esp8266=UART0, |                 esp8266=UART0, | ||||||
|                 esp32=UART0, |                 esp32=UART0, | ||||||
|                 rp2040=USB_CDC, |                 rp2040=USB_CDC, | ||||||
|             ): uart_selection, |             ): cv.All( | ||||||
|  |                 cv.only_on([PLATFORM_ESP8266, PLATFORM_ESP32, PLATFORM_RP2040]), | ||||||
|  |                 uart_selection, | ||||||
|  |             ), | ||||||
|             cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level, |             cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level, | ||||||
|             cv.Optional(CONF_LOGS, default={}): cv.Schema( |             cv.Optional(CONF_LOGS, default={}): cv.Schema( | ||||||
|                 { |                 { | ||||||
|   | |||||||
| @@ -145,6 +145,9 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { | |||||||
|   if (xPortGetFreeHeapSize() < 2048) |   if (xPortGetFreeHeapSize() < 2048) | ||||||
|     return; |     return; | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_HOST | ||||||
|  |   puts(msg); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   this->log_callback_.call(level, tag, msg); |   this->log_callback_.call(level, tag, msg); | ||||||
| } | } | ||||||
| @@ -262,7 +265,11 @@ void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } | |||||||
| void Logger::set_log_level(const std::string &tag, int log_level) { | void Logger::set_log_level(const std::string &tag, int log_level) { | ||||||
|   this->log_levels_.push_back(LogLevelOverride{tag, log_level}); |   this->log_levels_.push_back(LogLevelOverride{tag, log_level}); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) | ||||||
| UARTSelection Logger::get_uart() const { return this->uart_; } | UARTSelection Logger::get_uart() const { return this->uart_; } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) { | void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) { | ||||||
|   this->log_callback_.add(std::move(callback)); |   this->log_callback_.add(std::move(callback)); | ||||||
| } | } | ||||||
| @@ -294,7 +301,10 @@ void Logger::dump_config() { | |||||||
|   ESP_LOGCONFIG(TAG, "Logger:"); |   ESP_LOGCONFIG(TAG, "Logger:"); | ||||||
|   ESP_LOGCONFIG(TAG, "  Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]); |   ESP_LOGCONFIG(TAG, "  Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]); | ||||||
|   ESP_LOGCONFIG(TAG, "  Log Baud Rate: %" PRIu32, this->baud_rate_); |   ESP_LOGCONFIG(TAG, "  Log Baud Rate: %" PRIu32, this->baud_rate_); | ||||||
|  | #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) | ||||||
|   ESP_LOGCONFIG(TAG, "  Hardware UART: %s", UART_SELECTIONS[this->uart_]); |   ESP_LOGCONFIG(TAG, "  Hardware UART: %s", UART_SELECTIONS[this->uart_]); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   for (auto &it : this->log_levels_) { |   for (auto &it : this->log_levels_) { | ||||||
|     ESP_LOGCONFIG(TAG, "  Level for '%s': %s", it.tag.c_str(), LOG_LEVELS[it.level]); |     ESP_LOGCONFIG(TAG, "  Level for '%s': %s", it.tag.c_str(), LOG_LEVELS[it.level]); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ namespace esphome { | |||||||
|  |  | ||||||
| namespace logger { | namespace logger { | ||||||
|  |  | ||||||
|  | #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) | ||||||
| /** Enum for logging UART selection | /** Enum for logging UART selection | ||||||
|  * |  * | ||||||
|  * Advanced configuration (pin selection, etc) is not supported. |  * Advanced configuration (pin selection, etc) is not supported. | ||||||
| @@ -52,6 +53,7 @@ enum UARTSelection { | |||||||
|   UART_SELECTION_USB_CDC, |   UART_SELECTION_USB_CDC, | ||||||
| #endif  // USE_RP2040 | #endif  // USE_RP2040 | ||||||
| }; | }; | ||||||
|  | #endif  // USE_ESP32 || USE_ESP8266 | ||||||
|  |  | ||||||
| class Logger : public Component { | class Logger : public Component { | ||||||
|  public: |  public: | ||||||
| @@ -66,10 +68,11 @@ class Logger : public Component { | |||||||
| #ifdef USE_ESP_IDF | #ifdef USE_ESP_IDF | ||||||
|   uart_port_t get_uart_num() const { return uart_num_; } |   uart_port_t get_uart_num() const { return uart_num_; } | ||||||
| #endif | #endif | ||||||
|  | #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) | ||||||
|   void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; } |   void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; } | ||||||
|   /// Get the UART used by the logger. |   /// Get the UART used by the logger. | ||||||
|   UARTSelection get_uart() const; |   UARTSelection get_uart() const; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   /// Set the log level of the specified tag. |   /// Set the log level of the specified tag. | ||||||
|   void set_log_level(const std::string &tag, int log_level); |   void set_log_level(const std::string &tag, int log_level); | ||||||
| @@ -139,7 +142,9 @@ class Logger : public Component { | |||||||
|   char *tx_buffer_{nullptr}; |   char *tx_buffer_{nullptr}; | ||||||
|   int tx_buffer_at_{0}; |   int tx_buffer_at_{0}; | ||||||
|   int tx_buffer_size_{0}; |   int tx_buffer_size_{0}; | ||||||
|  | #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) | ||||||
|   UARTSelection uart_{UART_SELECTION_UART0}; |   UARTSelection uart_{UART_SELECTION_UART0}; | ||||||
|  | #endif | ||||||
| #ifdef USE_ARDUINO | #ifdef USE_ARDUINO | ||||||
|   Stream *hw_serial_{nullptr}; |   Stream *hw_serial_{nullptr}; | ||||||
| #endif | #endif | ||||||
|   | |||||||
							
								
								
									
										148
									
								
								esphome/components/max6956/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								esphome/components/max6956/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome import pins, automation | ||||||
|  | from esphome.components import i2c | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_NUMBER, | ||||||
|  |     CONF_MODE, | ||||||
|  |     CONF_INVERTED, | ||||||
|  |     CONF_INPUT, | ||||||
|  |     CONF_OUTPUT, | ||||||
|  |     CONF_PULLUP, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@looping40"] | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  | MULTI_CONF = True | ||||||
|  |  | ||||||
|  | CONF_BRIGHTNESS_MODE = "brightness_mode" | ||||||
|  | CONF_BRIGHTNESS_GLOBAL = "brightness_global" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | max6956_ns = cg.esphome_ns.namespace("max6956") | ||||||
|  |  | ||||||
|  | MAX6956 = max6956_ns.class_("MAX6956", cg.Component, i2c.I2CDevice) | ||||||
|  | MAX6956GPIOPin = max6956_ns.class_("MAX6956GPIOPin", cg.GPIOPin) | ||||||
|  |  | ||||||
|  | # Actions | ||||||
|  | SetCurrentGlobalAction = max6956_ns.class_("SetCurrentGlobalAction", automation.Action) | ||||||
|  | SetCurrentModeAction = max6956_ns.class_("SetCurrentModeAction", automation.Action) | ||||||
|  |  | ||||||
|  | MAX6956_CURRENTMODE = max6956_ns.enum("MAX6956CURRENTMODE") | ||||||
|  | CURRENT_MODES = { | ||||||
|  |     "global": MAX6956_CURRENTMODE.GLOBAL, | ||||||
|  |     "segment": MAX6956_CURRENTMODE.SEGMENT, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_ID): cv.declare_id(MAX6956), | ||||||
|  |             cv.Optional(CONF_BRIGHTNESS_GLOBAL, default="0"): cv.int_range( | ||||||
|  |                 min=0, max=15 | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_BRIGHTNESS_MODE, default="global"): cv.enum( | ||||||
|  |                 CURRENT_MODES, lower=True | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x40)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
|  |     cg.add(var.set_brightness_mode(config[CONF_BRIGHTNESS_MODE])) | ||||||
|  |     cg.add(var.set_brightness_global(config[CONF_BRIGHTNESS_GLOBAL])) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_mode(value): | ||||||
|  |     if not (value[CONF_INPUT] or value[CONF_OUTPUT]): | ||||||
|  |         raise cv.Invalid("Mode must be either input or output") | ||||||
|  |     if value[CONF_INPUT] and value[CONF_OUTPUT]: | ||||||
|  |         raise cv.Invalid("Mode must be either input or output") | ||||||
|  |     if value[CONF_PULLUP] and not value[CONF_INPUT]: | ||||||
|  |         raise cv.Invalid("Pullup only available with input") | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONF_MAX6956 = "max6956" | ||||||
|  |  | ||||||
|  | MAX6956_PIN_SCHEMA = cv.All( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(MAX6956GPIOPin), | ||||||
|  |         cv.Required(CONF_MAX6956): cv.use_id(MAX6956), | ||||||
|  |         cv.Required(CONF_NUMBER): cv.int_range(min=4, max=31), | ||||||
|  |         cv.Optional(CONF_MODE, default={}): cv.All( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(CONF_INPUT, default=False): cv.boolean, | ||||||
|  |                 cv.Optional(CONF_PULLUP, default=False): cv.boolean, | ||||||
|  |                 cv.Optional(CONF_OUTPUT, default=False): cv.boolean, | ||||||
|  |             }, | ||||||
|  |             validate_mode, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_INVERTED, default=False): cv.boolean, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pins.PIN_SCHEMA_REGISTRY.register(CONF_MAX6956, MAX6956_PIN_SCHEMA) | ||||||
|  | async def max6956_pin_to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     parent = await cg.get_variable(config[CONF_MAX6956]) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_parent(parent)) | ||||||
|  |  | ||||||
|  |     num = config[CONF_NUMBER] | ||||||
|  |     cg.add(var.set_pin(num)) | ||||||
|  |     cg.add(var.set_inverted(config[CONF_INVERTED])) | ||||||
|  |     cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "max6956.set_brightness_global", | ||||||
|  |     SetCurrentGlobalAction, | ||||||
|  |     cv.maybe_simple_value( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(CONF_ID): cv.use_id(MAX6956), | ||||||
|  |             cv.Required(CONF_BRIGHTNESS_GLOBAL): cv.templatable( | ||||||
|  |                 cv.int_range(min=0, max=15) | ||||||
|  |             ), | ||||||
|  |         }, | ||||||
|  |         key=CONF_BRIGHTNESS_GLOBAL, | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | async def max6956_set_brightness_global_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) | ||||||
|  |     template_ = await cg.templatable(config[CONF_BRIGHTNESS_GLOBAL], args, float) | ||||||
|  |     cg.add(var.set_brightness_global(template_)) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "max6956.set_brightness_mode", | ||||||
|  |     SetCurrentModeAction, | ||||||
|  |     cv.maybe_simple_value( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_ID): cv.use_id(MAX6956), | ||||||
|  |             cv.Required(CONF_BRIGHTNESS_MODE): cv.templatable( | ||||||
|  |                 cv.enum(CURRENT_MODES, lower=True) | ||||||
|  |             ), | ||||||
|  |         }, | ||||||
|  |         key=CONF_BRIGHTNESS_MODE, | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | async def max6956_set_brightness_mode_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) | ||||||
|  |     template_ = await cg.templatable(config[CONF_BRIGHTNESS_MODE], args, float) | ||||||
|  |     cg.add(var.set_brightness_mode(template_)) | ||||||
|  |     return var | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user