mirror of
https://github.com/esphome/esphome.git
synced 2024-11-27 17:27:59 +01:00
Merge commit '8822b6c808881390b1bf77423ac78430bc0b6346' into optolink
This commit is contained in:
commit
25ee78d9b0
117 changed files with 4286 additions and 358 deletions
|
@ -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]
|
||||||
|
|
10
CODEOWNERS
10
CODEOWNERS
|
@ -84,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
|
||||||
|
@ -95,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
|
||||||
|
@ -107,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
|
||||||
|
@ -139,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
|
||||||
|
@ -189,6 +195,7 @@ esphome/components/number/* @esphome/core
|
||||||
esphome/components/optolink/* @j0ta29
|
esphome/components/optolink/* @j0ta29
|
||||||
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
|
||||||
|
@ -235,7 +242,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
|
||||||
|
@ -243,6 +250,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
|
||||||
|
|
|
@ -26,7 +26,7 @@ RUN \
|
||||||
python3-cryptography=3.3.2-1 \
|
python3-cryptography=3.3.2-1 \
|
||||||
python3-venv=3.9.2-3 \
|
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 \
|
||||||
|
@ -63,7 +63,7 @@ RUN \
|
||||||
COPY requirements.txt requirements_optional.txt script/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 +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 (
|
from . import (
|
||||||
VARIANT_ESP32,
|
ATTENUATION_MODES,
|
||||||
VARIANT_ESP32C3,
|
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
|
||||||
VARIANT_ESP32H2,
|
validate_adc_pin,
|
||||||
VARIANT_ESP32S2,
|
|
||||||
VARIANT_ESP32S3,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
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":
|
||||||
|
|
|
@ -978,6 +978,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
|
||||||
|
@ -996,7 +998,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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
|
@ -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,10 +282,14 @@ 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;
|
||||||
if (glyph.get_pixel(glyph_x, glyph_y)) {
|
const int glyph_y_max = scan_y1 + scan_height;
|
||||||
this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
|
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)) {
|
||||||
|
this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ 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_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
||||||
|
|
||||||
|
@ -19,10 +20,21 @@ 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)
|
||||||
|
|
||||||
|
@ -39,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: {
|
||||||
|
|
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")
|
||||||
|
|
|
@ -141,7 +141,7 @@ void I2SAudioMediaPlayer::start_() {
|
||||||
this->audio_ = make_unique<Audio>(true, this->internal_dac_mode_, this->parent_->get_port());
|
this->audio_ = make_unique<Audio>(true, this->internal_dac_mode_, this->parent_->get_port());
|
||||||
} else {
|
} else {
|
||||||
#endif
|
#endif
|
||||||
this->audio_ = make_unique<Audio>(false, I2S_DAC_CHANNEL_BOTH_EN, this->parent_->get_port());
|
this->audio_ = make_unique<Audio>(false, 3, this->parent_->get_port());
|
||||||
|
|
||||||
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_out_num = this->dout_pin_;
|
pin_config.data_out_num = this->dout_pin_;
|
||||||
|
|
|
@ -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))
|
|
||||||
|
|
||||||
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
|
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_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,18 +61,33 @@ void I2SAudioMicrophone::start_() {
|
||||||
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
||||||
};
|
};
|
||||||
|
|
||||||
i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
|
#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_pin_config_t pin_config = this->parent_->get_pin_config();
|
i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_);
|
||||||
pin_config.data_in_num = this->din_pin_;
|
i2s_adc_enable(this->parent_->get_port());
|
||||||
|
} else {
|
||||||
|
#endif
|
||||||
|
if (this->pdm_)
|
||||||
|
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_PDM);
|
||||||
|
|
||||||
i2s_set_pin(this->parent_->get_port(), &pin_config);
|
i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
|
||||||
|
|
||||||
|
i2s_pin_config_t pin_config = this->parent_->get_pin_config();
|
||||||
|
pin_config.data_in_num = this->din_pin_;
|
||||||
|
|
||||||
|
i2s_set_pin(this->parent_->get_port(), &pin_config);
|
||||||
|
#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;
|
||||||
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))
|
208
esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp
Normal file
208
esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
#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;
|
||||||
|
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,9 +84,18 @@ 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) {
|
{
|
||||||
this->buffer_[i] = (uint8_t) (new_color >> 8);
|
const uint32_t buffer_length_16_bits = this->get_buffer_length_() * 2;
|
||||||
this->buffer_[i + 1] = (uint8_t) new_color;
|
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 + 1] = (uint8_t) new_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -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
|
40
esphome/components/max6956/automation.h
Normal file
40
esphome/components/max6956/automation.h
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/components/max6956/max6956.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace max6956 {
|
||||||
|
|
||||||
|
template<typename... Ts> class SetCurrentGlobalAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
SetCurrentGlobalAction(MAX6956 *max6956) : max6956_(max6956) {}
|
||||||
|
|
||||||
|
TEMPLATABLE_VALUE(uint8_t, brightness_global)
|
||||||
|
|
||||||
|
void play(Ts... x) override {
|
||||||
|
this->max6956_->set_brightness_global(this->brightness_global_.value(x...));
|
||||||
|
this->max6956_->write_brightness_global();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MAX6956 *max6956_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SetCurrentModeAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
SetCurrentModeAction(MAX6956 *max6956) : max6956_(max6956) {}
|
||||||
|
|
||||||
|
TEMPLATABLE_VALUE(max6956::MAX6956CURRENTMODE, brightness_mode)
|
||||||
|
|
||||||
|
void play(Ts... x) override {
|
||||||
|
this->max6956_->set_brightness_mode(this->brightness_mode_.value(x...));
|
||||||
|
this->max6956_->write_brightness_mode();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MAX6956 *max6956_;
|
||||||
|
};
|
||||||
|
} // namespace max6956
|
||||||
|
} // namespace esphome
|
170
esphome/components/max6956/max6956.cpp
Normal file
170
esphome/components/max6956/max6956.cpp
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
#include "max6956.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace max6956 {
|
||||||
|
|
||||||
|
static const char *const TAG = "max6956";
|
||||||
|
|
||||||
|
/// Masks for MAX6956 Configuration register
|
||||||
|
const uint32_t MASK_TRANSITION_DETECTION = 0x80;
|
||||||
|
const uint32_t MASK_INDIVIDUAL_CURRENT = 0x40;
|
||||||
|
const uint32_t MASK_NORMAL_OPERATION = 0x01;
|
||||||
|
|
||||||
|
const uint32_t MASK_1PORT_VALUE = 0x03;
|
||||||
|
const uint32_t MASK_PORT_CONFIG = 0x03;
|
||||||
|
const uint8_t MASK_CONFIG_CURRENT = 0x40;
|
||||||
|
const uint8_t MASK_CURRENT_PIN = 0x0F;
|
||||||
|
|
||||||
|
/**************************************
|
||||||
|
* MAX6956 *
|
||||||
|
**************************************/
|
||||||
|
void MAX6956::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up MAX6956...");
|
||||||
|
uint8_t configuration;
|
||||||
|
if (!this->read_reg_(MAX6956_CONFIGURATION, &configuration)) {
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_brightness_global();
|
||||||
|
write_brightness_mode();
|
||||||
|
|
||||||
|
/** TO DO : read transition detection in yaml
|
||||||
|
TO DO : read indivdual current in yaml **/
|
||||||
|
this->read_reg_(MAX6956_CONFIGURATION, &configuration);
|
||||||
|
ESP_LOGD(TAG, "Initial reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration);
|
||||||
|
configuration = configuration | MASK_NORMAL_OPERATION;
|
||||||
|
this->write_reg_(MAX6956_CONFIGURATION, configuration);
|
||||||
|
|
||||||
|
ESP_LOGCONFIG(TAG, "Enabling normal operation");
|
||||||
|
ESP_LOGD(TAG, "setup reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MAX6956::digital_read(uint8_t pin) {
|
||||||
|
uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin;
|
||||||
|
uint8_t value = 0;
|
||||||
|
this->read_reg_(reg_addr, &value);
|
||||||
|
return (value & MASK_1PORT_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAX6956::digital_write(uint8_t pin, bool value) {
|
||||||
|
uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin;
|
||||||
|
this->write_reg_(reg_addr, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAX6956::pin_mode(uint8_t pin, gpio::Flags flags) {
|
||||||
|
uint8_t reg_addr = MAX6956_PORT_CONFIG_START + (pin - MAX6956_MIN) / 4;
|
||||||
|
uint8_t config = 0;
|
||||||
|
uint8_t shift = 2 * (pin % 4);
|
||||||
|
MAX6956GPIOMode mode = MAX6956_INPUT;
|
||||||
|
|
||||||
|
if (flags == gpio::FLAG_INPUT) {
|
||||||
|
mode = MAX6956GPIOMode::MAX6956_INPUT;
|
||||||
|
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||||
|
mode = MAX6956GPIOMode::MAX6956_INPUT_PULLUP;
|
||||||
|
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||||
|
mode = MAX6956GPIOMode::MAX6956_OUTPUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->read_reg_(reg_addr, &config);
|
||||||
|
config &= ~(MASK_PORT_CONFIG << shift);
|
||||||
|
config |= (mode << shift);
|
||||||
|
this->write_reg_(reg_addr, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAX6956::pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags) {
|
||||||
|
uint8_t reg_addr = MAX6956_PORT_CONFIG_START + (pin - MAX6956_MIN) / 4;
|
||||||
|
uint8_t config = 0;
|
||||||
|
uint8_t shift = 2 * (pin % 4);
|
||||||
|
MAX6956GPIOMode mode = MAX6956GPIOMode::MAX6956_LED;
|
||||||
|
|
||||||
|
if (flags == max6956::FLAG_LED) {
|
||||||
|
mode = MAX6956GPIOMode::MAX6956_LED;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->read_reg_(reg_addr, &config);
|
||||||
|
config &= ~(MASK_PORT_CONFIG << shift);
|
||||||
|
config |= (mode << shift);
|
||||||
|
this->write_reg_(reg_addr, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAX6956::set_brightness_global(uint8_t current) {
|
||||||
|
if (current > 15) {
|
||||||
|
ESP_LOGE(TAG, "Global brightness out off range (%u)", current);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
global_brightness_ = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAX6956::write_brightness_global() { this->write_reg_(MAX6956_GLOBAL_CURRENT, global_brightness_); }
|
||||||
|
|
||||||
|
void MAX6956::set_brightness_mode(max6956::MAX6956CURRENTMODE brightness_mode) { brightness_mode_ = brightness_mode; };
|
||||||
|
|
||||||
|
void MAX6956::write_brightness_mode() {
|
||||||
|
uint8_t reg_addr = MAX6956_CONFIGURATION;
|
||||||
|
uint8_t config = 0;
|
||||||
|
|
||||||
|
this->read_reg_(reg_addr, &config);
|
||||||
|
config &= ~MASK_CONFIG_CURRENT;
|
||||||
|
config |= brightness_mode_ << 6;
|
||||||
|
this->write_reg_(reg_addr, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAX6956::set_pin_brightness(uint8_t pin, float brightness) {
|
||||||
|
uint8_t reg_addr = MAX6956_CURRENT_START + (pin - MAX6956_MIN) / 2;
|
||||||
|
uint8_t config = 0;
|
||||||
|
uint8_t shift = 4 * (pin % 2);
|
||||||
|
uint8_t bright = roundf(brightness * 15);
|
||||||
|
|
||||||
|
if (prev_bright_[pin - MAX6956_MIN] == bright)
|
||||||
|
return;
|
||||||
|
|
||||||
|
prev_bright_[pin - MAX6956_MIN] = bright;
|
||||||
|
|
||||||
|
this->read_reg_(reg_addr, &config);
|
||||||
|
config &= ~(MASK_CURRENT_PIN << shift);
|
||||||
|
config |= (bright << shift);
|
||||||
|
this->write_reg_(reg_addr, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MAX6956::read_reg_(uint8_t reg, uint8_t *value) {
|
||||||
|
if (this->is_failed())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return this->read_byte(reg, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MAX6956::write_reg_(uint8_t reg, uint8_t value) {
|
||||||
|
if (this->is_failed())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return this->write_byte(reg, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAX6956::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "MAX6956");
|
||||||
|
|
||||||
|
if (brightness_mode_ == MAX6956CURRENTMODE::GLOBAL) {
|
||||||
|
ESP_LOGCONFIG(TAG, "current mode: global");
|
||||||
|
ESP_LOGCONFIG(TAG, "global brightness: %u", global_brightness_);
|
||||||
|
} else {
|
||||||
|
ESP_LOGCONFIG(TAG, "current mode: segment");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************
|
||||||
|
* MAX6956GPIOPin *
|
||||||
|
**************************************/
|
||||||
|
void MAX6956GPIOPin::setup() { pin_mode(flags_); }
|
||||||
|
void MAX6956GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
||||||
|
bool MAX6956GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
||||||
|
void MAX6956GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
|
||||||
|
std::string MAX6956GPIOPin::dump_summary() const {
|
||||||
|
char buffer[32];
|
||||||
|
snprintf(buffer, sizeof(buffer), "%u via Max6956", pin_);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace max6956
|
||||||
|
} // namespace esphome
|
94
esphome/components/max6956/max6956.h
Normal file
94
esphome/components/max6956/max6956.h
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace max6956 {
|
||||||
|
|
||||||
|
/// Modes for MAX6956 pins
|
||||||
|
enum MAX6956GPIOMode : uint8_t {
|
||||||
|
MAX6956_LED = 0x00,
|
||||||
|
MAX6956_OUTPUT = 0x01,
|
||||||
|
MAX6956_INPUT = 0x02,
|
||||||
|
MAX6956_INPUT_PULLUP = 0x03
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Range for MAX6956 pins
|
||||||
|
enum MAX6956GPIORange : uint8_t {
|
||||||
|
MAX6956_MIN = 4,
|
||||||
|
MAX6956_MAX = 31,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum MAX6956GPIORegisters {
|
||||||
|
MAX6956_GLOBAL_CURRENT = 0x02,
|
||||||
|
MAX6956_CONFIGURATION = 0x04,
|
||||||
|
MAX6956_TRANSITION_DETECT_MASK = 0x06,
|
||||||
|
MAX6956_DISPLAY_TEST = 0x07,
|
||||||
|
MAX6956_PORT_CONFIG_START = 0x09, // Port Configuration P7, P6, P5, P4
|
||||||
|
MAX6956_CURRENT_START = 0x12, // Current054
|
||||||
|
MAX6956_1PORT_VALUE_START = 0x20, // Port 0 only (virtual port, no action)
|
||||||
|
MAX6956_8PORTS_VALUE_START = 0x44, // 8 ports 4–11 (data bits D0–D7)
|
||||||
|
};
|
||||||
|
|
||||||
|
enum MAX6956GPIOFlag { FLAG_LED = 0x20 };
|
||||||
|
|
||||||
|
enum MAX6956CURRENTMODE { GLOBAL = 0x00, SEGMENT = 0x01 };
|
||||||
|
|
||||||
|
class MAX6956 : public Component, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
MAX6956() = default;
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
|
||||||
|
bool digital_read(uint8_t pin);
|
||||||
|
void digital_write(uint8_t pin, bool value);
|
||||||
|
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||||
|
void pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags);
|
||||||
|
|
||||||
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
|
void set_brightness_global(uint8_t current);
|
||||||
|
void set_brightness_mode(max6956::MAX6956CURRENTMODE brightness_mode);
|
||||||
|
void set_pin_brightness(uint8_t pin, float brightness);
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
void write_brightness_global();
|
||||||
|
void write_brightness_mode();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// read a given register
|
||||||
|
bool read_reg_(uint8_t reg, uint8_t *value);
|
||||||
|
// write a value to a given register
|
||||||
|
bool write_reg_(uint8_t reg, uint8_t value);
|
||||||
|
max6956::MAX6956CURRENTMODE brightness_mode_;
|
||||||
|
uint8_t global_brightness_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int8_t prev_bright_[28] = {0};
|
||||||
|
};
|
||||||
|
|
||||||
|
class MAX6956GPIOPin : public GPIOPin {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void pin_mode(gpio::Flags flags) override;
|
||||||
|
bool digital_read() override;
|
||||||
|
void digital_write(bool value) override;
|
||||||
|
std::string dump_summary() const override;
|
||||||
|
|
||||||
|
void set_parent(MAX6956 *parent) { parent_ = parent; }
|
||||||
|
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||||
|
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||||
|
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MAX6956 *parent_;
|
||||||
|
uint8_t pin_;
|
||||||
|
bool inverted_;
|
||||||
|
gpio::Flags flags_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace max6956
|
||||||
|
} // namespace esphome
|
28
esphome/components/max6956/output/__init__.py
Normal file
28
esphome/components/max6956/output/__init__.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import output
|
||||||
|
from esphome.const import CONF_PIN, CONF_ID
|
||||||
|
from .. import MAX6956, max6956_ns, CONF_MAX6956
|
||||||
|
|
||||||
|
DEPENDENCIES = ["max6956"]
|
||||||
|
|
||||||
|
MAX6956LedChannel = max6956_ns.class_(
|
||||||
|
"MAX6956LedChannel", output.FloatOutput, cg.Component
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_ID): cv.declare_id(MAX6956LedChannel),
|
||||||
|
cv.GenerateID(CONF_MAX6956): cv.use_id(MAX6956),
|
||||||
|
cv.Required(CONF_PIN): cv.int_range(min=4, max=31),
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
parent = await cg.get_variable(config[CONF_MAX6956])
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await output.register_output(var, config)
|
||||||
|
cg.add(var.set_pin(config[CONF_PIN]))
|
||||||
|
cg.add(var.set_parent(parent))
|
26
esphome/components/max6956/output/max6956_led_output.cpp
Normal file
26
esphome/components/max6956/output/max6956_led_output.cpp
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#include "max6956_led_output.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace max6956 {
|
||||||
|
|
||||||
|
static const char *const TAG = "max6956_led_channel";
|
||||||
|
|
||||||
|
void MAX6956LedChannel::write_state(float state) { this->parent_->set_pin_brightness(this->pin_, state); }
|
||||||
|
|
||||||
|
void MAX6956LedChannel::write_state(bool state) { this->parent_->digital_write(this->pin_, state); }
|
||||||
|
|
||||||
|
void MAX6956LedChannel::setup() {
|
||||||
|
this->parent_->pin_mode(this->pin_, max6956::FLAG_LED);
|
||||||
|
this->turn_off();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAX6956LedChannel::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "MAX6956 current:");
|
||||||
|
ESP_LOGCONFIG(TAG, " MAX6956 pin: %d", this->pin_);
|
||||||
|
LOG_FLOAT_OUTPUT(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace max6956
|
||||||
|
} // namespace esphome
|
28
esphome/components/max6956/output/max6956_led_output.h
Normal file
28
esphome/components/max6956/output/max6956_led_output.h
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/max6956/max6956.h"
|
||||||
|
#include "esphome/components/output/float_output.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace max6956 {
|
||||||
|
|
||||||
|
class MAX6956;
|
||||||
|
|
||||||
|
class MAX6956LedChannel : public output::FloatOutput, public Component {
|
||||||
|
public:
|
||||||
|
void set_parent(MAX6956 *parent) { this->parent_ = parent; }
|
||||||
|
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void write_state(float state) override;
|
||||||
|
void write_state(bool state) override;
|
||||||
|
|
||||||
|
MAX6956 *parent_;
|
||||||
|
uint8_t pin_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace max6956
|
||||||
|
} // namespace esphome
|
18
esphome/components/mdns/mdns_host.cpp
Normal file
18
esphome/components/mdns/mdns_host.cpp
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#ifdef USE_HOST
|
||||||
|
|
||||||
|
#include "esphome/components/network/ip_address.h"
|
||||||
|
#include "esphome/components/network/util.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "mdns_component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace mdns {
|
||||||
|
|
||||||
|
void MDNSComponent::setup() { this->compile_records_(); }
|
||||||
|
|
||||||
|
void MDNSComponent::on_shutdown() {}
|
||||||
|
|
||||||
|
} // namespace mdns
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
|
@ -23,6 +23,9 @@ bool is_connected() {
|
||||||
return wifi::global_wifi_component->is_connected();
|
return wifi::global_wifi_component->is_connected();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_HOST
|
||||||
|
return true; // Assume its connected
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,15 +6,5 @@ namespace number {
|
||||||
|
|
||||||
static const char *const TAG = "number";
|
static const char *const TAG = "number";
|
||||||
|
|
||||||
void NumberTraits::set_unit_of_measurement(const std::string &unit_of_measurement) {
|
|
||||||
this->unit_of_measurement_ = unit_of_measurement;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string NumberTraits::get_unit_of_measurement() {
|
|
||||||
if (this->unit_of_measurement_.has_value())
|
|
||||||
return *this->unit_of_measurement_;
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace number
|
} // namespace number
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -12,7 +12,7 @@ enum NumberMode : uint8_t {
|
||||||
NUMBER_MODE_SLIDER = 2,
|
NUMBER_MODE_SLIDER = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
class NumberTraits : public EntityBase_DeviceClass {
|
class NumberTraits : public EntityBase_DeviceClass, public EntityBase_UnitOfMeasurement {
|
||||||
public:
|
public:
|
||||||
// Set/get the number value boundaries.
|
// Set/get the number value boundaries.
|
||||||
void set_min_value(float min_value) { min_value_ = min_value; }
|
void set_min_value(float min_value) { min_value_ = min_value; }
|
||||||
|
@ -24,11 +24,6 @@ class NumberTraits : public EntityBase_DeviceClass {
|
||||||
void set_step(float step) { step_ = step; }
|
void set_step(float step) { step_ = step; }
|
||||||
float get_step() const { return step_; }
|
float get_step() const { return step_; }
|
||||||
|
|
||||||
/// Manually set the unit of measurement.
|
|
||||||
void set_unit_of_measurement(const std::string &unit_of_measurement);
|
|
||||||
/// Get the unit of measurement, using the manual override if set.
|
|
||||||
std::string get_unit_of_measurement();
|
|
||||||
|
|
||||||
// Set/get the frontend mode.
|
// Set/get the frontend mode.
|
||||||
void set_mode(NumberMode mode) { this->mode_ = mode; }
|
void set_mode(NumberMode mode) { this->mode_ = mode; }
|
||||||
NumberMode get_mode() const { return this->mode_; }
|
NumberMode get_mode() const { return this->mode_; }
|
||||||
|
@ -37,7 +32,6 @@ class NumberTraits : public EntityBase_DeviceClass {
|
||||||
float min_value_ = NAN;
|
float min_value_ = NAN;
|
||||||
float max_value_ = NAN;
|
float max_value_ = NAN;
|
||||||
float step_ = NAN;
|
float step_ = NAN;
|
||||||
optional<std::string> unit_of_measurement_; ///< Unit of measurement override
|
|
||||||
NumberMode mode_{NUMBER_MODE_AUTO};
|
NumberMode mode_{NUMBER_MODE_AUTO};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
78
esphome/components/pca6416a/__init__.py
Normal file
78
esphome/components/pca6416a/__init__.py
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome import pins
|
||||||
|
from esphome.components import i2c
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_INPUT,
|
||||||
|
CONF_NUMBER,
|
||||||
|
CONF_MODE,
|
||||||
|
CONF_INVERTED,
|
||||||
|
CONF_OUTPUT,
|
||||||
|
CONF_PULLUP,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@Mat931"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
MULTI_CONF = True
|
||||||
|
pca6416a_ns = cg.esphome_ns.namespace("pca6416a")
|
||||||
|
|
||||||
|
PCA6416AComponent = pca6416a_ns.class_("PCA6416AComponent", cg.Component, i2c.I2CDevice)
|
||||||
|
PCA6416AGPIOPin = pca6416a_ns.class_(
|
||||||
|
"PCA6416AGPIOPin", cg.GPIOPin, cg.Parented.template(PCA6416AComponent)
|
||||||
|
)
|
||||||
|
|
||||||
|
CONF_PCA6416A = "pca6416a"
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema({cv.Required(CONF_ID): cv.declare_id(PCA6416AComponent)})
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(i2c.i2c_device_schema(0x21))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
PCA6416A_PIN_SCHEMA = cv.All(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(PCA6416AGPIOPin),
|
||||||
|
cv.Required(CONF_PCA6416A): cv.use_id(PCA6416AComponent),
|
||||||
|
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=16),
|
||||||
|
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("pca6416a", PCA6416A_PIN_SCHEMA)
|
||||||
|
async def pca6416a_pin_to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
parent = await cg.get_variable(config[CONF_PCA6416A])
|
||||||
|
|
||||||
|
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
|
174
esphome/components/pca6416a/pca6416a.cpp
Normal file
174
esphome/components/pca6416a/pca6416a.cpp
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
#include "pca6416a.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace pca6416a {
|
||||||
|
|
||||||
|
enum PCA6416AGPIORegisters {
|
||||||
|
// 0 side
|
||||||
|
PCA6416A_INPUT0 = 0x00,
|
||||||
|
PCA6416A_OUTPUT0 = 0x02,
|
||||||
|
PCA6416A_INVERT0 = 0x04,
|
||||||
|
PCA6416A_CONFIG0 = 0x06,
|
||||||
|
PCAL6416A_PULL_EN0 = 0x46,
|
||||||
|
PCAL6416A_PULL_DIR0 = 0x48,
|
||||||
|
// 1 side
|
||||||
|
PCA6416A_INPUT1 = 0x01,
|
||||||
|
PCA6416A_OUTPUT1 = 0x03,
|
||||||
|
PCA6416A_INVERT1 = 0x05,
|
||||||
|
PCA6416A_CONFIG1 = 0x07,
|
||||||
|
PCAL6416A_PULL_EN1 = 0x47,
|
||||||
|
PCAL6416A_PULL_DIR1 = 0x49,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *const TAG = "pca6416a";
|
||||||
|
|
||||||
|
void PCA6416AComponent::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up PCA6416A...");
|
||||||
|
// Test to see if device exists
|
||||||
|
uint8_t value;
|
||||||
|
if (!this->read_register_(PCA6416A_INPUT0, &value)) {
|
||||||
|
ESP_LOGE(TAG, "PCA6416A not available under 0x%02X", this->address_);
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test to see if the device supports pull-up resistors
|
||||||
|
if (this->read_register(PCAL6416A_PULL_EN0, &value, 1, true) == esphome::i2c::ERROR_OK) {
|
||||||
|
this->has_pullup_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No polarity inversion
|
||||||
|
this->write_register_(PCA6416A_INVERT0, 0);
|
||||||
|
this->write_register_(PCA6416A_INVERT1, 0);
|
||||||
|
// Set all pins to input
|
||||||
|
this->write_register_(PCA6416A_CONFIG0, 0xff);
|
||||||
|
this->write_register_(PCA6416A_CONFIG1, 0xff);
|
||||||
|
// Read current output register state
|
||||||
|
this->read_register_(PCA6416A_OUTPUT0, &this->output_0_);
|
||||||
|
this->read_register_(PCA6416A_OUTPUT1, &this->output_1_);
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
|
||||||
|
this->status_has_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PCA6416AComponent::dump_config() {
|
||||||
|
if (this->has_pullup_) {
|
||||||
|
ESP_LOGCONFIG(TAG, "PCAL6416A:");
|
||||||
|
} else {
|
||||||
|
ESP_LOGCONFIG(TAG, "PCA6416A:");
|
||||||
|
}
|
||||||
|
LOG_I2C_DEVICE(this)
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, "Communication with PCA6416A failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PCA6416AComponent::digital_read(uint8_t pin) {
|
||||||
|
uint8_t bit = pin % 8;
|
||||||
|
uint8_t reg_addr = pin < 8 ? PCA6416A_INPUT0 : PCA6416A_INPUT1;
|
||||||
|
uint8_t value = 0;
|
||||||
|
this->read_register_(reg_addr, &value);
|
||||||
|
return value & (1 << bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PCA6416AComponent::digital_write(uint8_t pin, bool value) {
|
||||||
|
uint8_t reg_addr = pin < 8 ? PCA6416A_OUTPUT0 : PCA6416A_OUTPUT1;
|
||||||
|
this->update_register_(pin, value, reg_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PCA6416AComponent::pin_mode(uint8_t pin, gpio::Flags flags) {
|
||||||
|
uint8_t io_dir = pin < 8 ? PCA6416A_CONFIG0 : PCA6416A_CONFIG1;
|
||||||
|
uint8_t pull_en = pin < 8 ? PCAL6416A_PULL_EN0 : PCAL6416A_PULL_EN1;
|
||||||
|
uint8_t pull_dir = pin < 8 ? PCAL6416A_PULL_DIR0 : PCAL6416A_PULL_DIR1;
|
||||||
|
if (flags == gpio::FLAG_INPUT) {
|
||||||
|
this->update_register_(pin, true, io_dir);
|
||||||
|
if (has_pullup_) {
|
||||||
|
this->update_register_(pin, true, pull_dir);
|
||||||
|
this->update_register_(pin, false, pull_en);
|
||||||
|
}
|
||||||
|
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||||
|
this->update_register_(pin, true, io_dir);
|
||||||
|
if (has_pullup_) {
|
||||||
|
this->update_register_(pin, true, pull_dir);
|
||||||
|
this->update_register_(pin, true, pull_en);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "Your PCA6416A does not support pull-up resistors");
|
||||||
|
}
|
||||||
|
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||||
|
this->update_register_(pin, false, io_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PCA6416AComponent::read_register_(uint8_t reg, uint8_t *value) {
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGD(TAG, "Device marked failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this->last_error_ = this->read_register(reg, value, 1, true)) != esphome::i2c::ERROR_OK) {
|
||||||
|
this->status_set_warning();
|
||||||
|
ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->status_clear_warning();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PCA6416AComponent::write_register_(uint8_t reg, uint8_t value) {
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGD(TAG, "Device marked failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this->last_error_ = this->write_register(reg, &value, 1, true)) != esphome::i2c::ERROR_OK) {
|
||||||
|
this->status_set_warning();
|
||||||
|
ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->status_clear_warning();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PCA6416AComponent::update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr) {
|
||||||
|
uint8_t bit = pin % 8;
|
||||||
|
uint8_t reg_value = 0;
|
||||||
|
if (reg_addr == PCA6416A_OUTPUT0) {
|
||||||
|
reg_value = this->output_0_;
|
||||||
|
} else if (reg_addr == PCA6416A_OUTPUT1) {
|
||||||
|
reg_value = this->output_1_;
|
||||||
|
} else {
|
||||||
|
this->read_register_(reg_addr, ®_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pin_value) {
|
||||||
|
reg_value |= 1 << bit;
|
||||||
|
} else {
|
||||||
|
reg_value &= ~(1 << bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->write_register_(reg_addr, reg_value);
|
||||||
|
|
||||||
|
if (reg_addr == PCA6416A_OUTPUT0) {
|
||||||
|
this->output_0_ = reg_value;
|
||||||
|
} else if (reg_addr == PCA6416A_OUTPUT1) {
|
||||||
|
this->output_1_ = reg_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float PCA6416AComponent::get_setup_priority() const { return setup_priority::IO; }
|
||||||
|
|
||||||
|
void PCA6416AGPIOPin::setup() { pin_mode(flags_); }
|
||||||
|
void PCA6416AGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
||||||
|
bool PCA6416AGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
||||||
|
void PCA6416AGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
|
||||||
|
std::string PCA6416AGPIOPin::dump_summary() const {
|
||||||
|
char buffer[32];
|
||||||
|
snprintf(buffer, sizeof(buffer), "%u via PCA6416A", pin_);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pca6416a
|
||||||
|
} // namespace esphome
|
63
esphome/components/pca6416a/pca6416a.h
Normal file
63
esphome/components/pca6416a/pca6416a.h
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace pca6416a {
|
||||||
|
|
||||||
|
class PCA6416AComponent : public Component, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
PCA6416AComponent() = default;
|
||||||
|
|
||||||
|
/// Check i2c availability and setup masks
|
||||||
|
void setup() override;
|
||||||
|
/// Helper function to read the value of a pin.
|
||||||
|
bool digital_read(uint8_t pin);
|
||||||
|
/// Helper function to write the value of a pin.
|
||||||
|
void digital_write(uint8_t pin, bool value);
|
||||||
|
/// Helper function to set the pin mode of a pin.
|
||||||
|
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||||
|
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool read_register_(uint8_t reg, uint8_t *value);
|
||||||
|
bool write_register_(uint8_t reg, uint8_t value);
|
||||||
|
void update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr);
|
||||||
|
|
||||||
|
/// The mask to write as output state - 1 means HIGH, 0 means LOW
|
||||||
|
uint8_t output_0_{0x00};
|
||||||
|
uint8_t output_1_{0x00};
|
||||||
|
/// Storage for last I2C error seen
|
||||||
|
esphome::i2c::ErrorCode last_error_;
|
||||||
|
/// Only the PCAL6416A has pull-up resistors
|
||||||
|
bool has_pullup_{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Helper class to expose a PCA6416A pin as an internal input GPIO pin.
|
||||||
|
class PCA6416AGPIOPin : public GPIOPin {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void pin_mode(gpio::Flags flags) override;
|
||||||
|
bool digital_read() override;
|
||||||
|
void digital_write(bool value) override;
|
||||||
|
std::string dump_summary() const override;
|
||||||
|
|
||||||
|
void set_parent(PCA6416AComponent *parent) { parent_ = parent; }
|
||||||
|
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||||
|
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||||
|
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PCA6416AComponent *parent_;
|
||||||
|
uint8_t pin_;
|
||||||
|
bool inverted_;
|
||||||
|
gpio::Flags flags_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pca6416a
|
||||||
|
} // namespace esphome
|
|
@ -81,7 +81,32 @@ void PN532::setup() {
|
||||||
this->turn_off_rf_();
|
this->turn_off_rf_();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PN532::powerdown() {
|
||||||
|
updates_enabled_ = false;
|
||||||
|
requested_read_ = false;
|
||||||
|
ESP_LOGI(TAG, "Powering down PN532");
|
||||||
|
if (!this->write_command_({PN532_COMMAND_POWERDOWN, 0b10100000})) { // enable i2c,spi wakeup
|
||||||
|
ESP_LOGE(TAG, "Error writing powerdown command to PN532");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::vector<uint8_t> response;
|
||||||
|
if (!this->read_response(PN532_COMMAND_POWERDOWN, response)) {
|
||||||
|
ESP_LOGE(TAG, "Error reading PN532 powerdown response");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (response[0] != 0x00) {
|
||||||
|
ESP_LOGE(TAG, "Error on PN532 powerdown: %02x", response[0]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ESP_LOGV(TAG, "Powerdown successful");
|
||||||
|
delay(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void PN532::update() {
|
void PN532::update() {
|
||||||
|
if (!updates_enabled_)
|
||||||
|
return;
|
||||||
|
|
||||||
for (auto *obj : this->binary_sensors_)
|
for (auto *obj : this->binary_sensors_)
|
||||||
obj->on_scan_end();
|
obj->on_scan_end();
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ static const uint8_t PN532_COMMAND_SAMCONFIGURATION = 0x14;
|
||||||
static const uint8_t PN532_COMMAND_RFCONFIGURATION = 0x32;
|
static const uint8_t PN532_COMMAND_RFCONFIGURATION = 0x32;
|
||||||
static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40;
|
static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40;
|
||||||
static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A;
|
static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A;
|
||||||
|
static const uint8_t PN532_COMMAND_POWERDOWN = 0x16;
|
||||||
|
|
||||||
class PN532BinarySensor;
|
class PN532BinarySensor;
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@ class PN532 : public PollingComponent {
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
void on_shutdown() override { powerdown(); }
|
||||||
|
|
||||||
void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
|
void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
|
||||||
void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
|
void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
|
||||||
|
@ -45,6 +47,7 @@ class PN532 : public PollingComponent {
|
||||||
void clean_mode();
|
void clean_mode();
|
||||||
void format_mode();
|
void format_mode();
|
||||||
void write_mode(nfc::NdefMessage *message);
|
void write_mode(nfc::NdefMessage *message);
|
||||||
|
bool powerdown();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void turn_off_rf_();
|
void turn_off_rf_();
|
||||||
|
@ -79,6 +82,7 @@ class PN532 : public PollingComponent {
|
||||||
bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
|
bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
|
||||||
bool clean_mifare_ultralight_();
|
bool clean_mifare_ultralight_();
|
||||||
|
|
||||||
|
bool updates_enabled_{true};
|
||||||
bool requested_read_{false};
|
bool requested_read_{false};
|
||||||
std::vector<PN532BinarySensor *> binary_sensors_;
|
std::vector<PN532BinarySensor *> binary_sensors_;
|
||||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
|
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
|
||||||
|
|
|
@ -791,6 +791,57 @@ async def raw_action(var, config, args):
|
||||||
cg.add(var.set_carrier_frequency(templ))
|
cg.add(var.set_carrier_frequency(templ))
|
||||||
|
|
||||||
|
|
||||||
|
# Drayton
|
||||||
|
(
|
||||||
|
DraytonData,
|
||||||
|
DraytonBinarySensor,
|
||||||
|
DraytonTrigger,
|
||||||
|
DraytonAction,
|
||||||
|
DraytonDumper,
|
||||||
|
) = declare_protocol("Drayton")
|
||||||
|
DRAYTON_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFF)),
|
||||||
|
cv.Required(CONF_CHANNEL): cv.All(cv.hex_int, cv.Range(min=0, max=0x1F)),
|
||||||
|
cv.Required(CONF_COMMAND): cv.All(cv.hex_int, cv.Range(min=0, max=0x7F)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_binary_sensor("drayton", DraytonBinarySensor, DRAYTON_SCHEMA)
|
||||||
|
def drayton_binary_sensor(var, config):
|
||||||
|
cg.add(
|
||||||
|
var.set_data(
|
||||||
|
cg.StructInitializer(
|
||||||
|
DraytonData,
|
||||||
|
("address", config[CONF_ADDRESS]),
|
||||||
|
("channel", config[CONF_CHANNEL]),
|
||||||
|
("command", config[CONF_COMMAND]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_trigger("drayton", DraytonTrigger, DraytonData)
|
||||||
|
def drayton_trigger(var, config):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@register_dumper("drayton", DraytonDumper)
|
||||||
|
def drayton_dumper(var, config):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@register_action("drayton", DraytonAction, DRAYTON_SCHEMA)
|
||||||
|
async def drayton_action(var, config, args):
|
||||||
|
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16)
|
||||||
|
cg.add(var.set_address(template_))
|
||||||
|
template_ = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)
|
||||||
|
cg.add(var.set_channel(template_))
|
||||||
|
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8)
|
||||||
|
cg.add(var.set_command(template_))
|
||||||
|
|
||||||
|
|
||||||
# RC5
|
# RC5
|
||||||
RC5Data, RC5BinarySensor, RC5Trigger, RC5Action, RC5Dumper = declare_protocol("RC5")
|
RC5Data, RC5BinarySensor, RC5Trigger, RC5Action, RC5Dumper = declare_protocol("RC5")
|
||||||
RC5_SCHEMA = cv.Schema(
|
RC5_SCHEMA = cv.Schema(
|
||||||
|
|
213
esphome/components/remote_base/drayton_protocol.cpp
Normal file
213
esphome/components/remote_base/drayton_protocol.cpp
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
#include "drayton_protocol.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace remote_base {
|
||||||
|
|
||||||
|
static const char *const TAG = "remote.drayton";
|
||||||
|
|
||||||
|
static const uint32_t BIT_TIME_US = 500;
|
||||||
|
static const uint8_t CARRIER_KHZ = 2;
|
||||||
|
static const uint8_t NBITS_PREAMBLE = 12;
|
||||||
|
static const uint8_t NBITS_SYNC = 4;
|
||||||
|
static const uint8_t NBITS_ADDRESS = 16;
|
||||||
|
static const uint8_t NBITS_CHANNEL = 5;
|
||||||
|
static const uint8_t NBITS_COMMAND = 7;
|
||||||
|
static const uint8_t NBITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND;
|
||||||
|
|
||||||
|
static const uint8_t CMD_ON = 0x41;
|
||||||
|
static const uint8_t CMD_OFF = 0x02;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Drayton Protocol
|
||||||
|
Using an oscilloscope to capture the data transmitted by the Digistat two
|
||||||
|
distinct packets for 'On' and 'Off' are transmitted. Each transmitted bit
|
||||||
|
has a period of 500us, a bit rate of 2000 baud.
|
||||||
|
|
||||||
|
Each packet consists of an initial 1010 pattern to set up the receiver bias.
|
||||||
|
The number of these bits seen at the receiver varies depending on the state
|
||||||
|
of the bias when the packet transmission starts. The receiver algoritmn takes
|
||||||
|
account of this.
|
||||||
|
|
||||||
|
The packet appears to be Manchester encoded, with a '10' tranmitted pair
|
||||||
|
representing a '1' bit and a '01' pair representing a '0' bit. Each packet is
|
||||||
|
begun with a '1100' syncronisation symbol which breaks this rule. Following
|
||||||
|
the sync are 28 '01' or '10' pairs.
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Boiler On Command as received:
|
||||||
|
101010101010110001101001010101101001010101010101100101010101101001011001
|
||||||
|
ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-1-0-0-0-0-0-1-1-0-0-1-0
|
||||||
|
|
||||||
|
(Where pppp represents the preamble bits and SSSS represents the sync symbol)
|
||||||
|
|
||||||
|
28 bits of data received 01100001100000001000001 10010 (bin) or 6180832 (hex)
|
||||||
|
|
||||||
|
Boiler Off Command as received:
|
||||||
|
101010101010110001101001010101101001010101010101010101010110011001011001
|
||||||
|
ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-0-0-0-0-0-1-0-1-0-0-1-0
|
||||||
|
|
||||||
|
28 bits of data received 0110000110000000000001010010 (bin) or 6180052 (hex)
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
I have used 'RFLink' software (RLink Firmware Version: 1.1 Revision: 48) to
|
||||||
|
capture and retransmit the Digistat packets. RFLink splits each packet into an
|
||||||
|
ID, SWITCH, and CMD field.
|
||||||
|
|
||||||
|
0;17;Drayton;ID=c300;SWITCH=12;CMD=ON;
|
||||||
|
20;18;Drayton;ID=c300;SWITCH=12;CMD=OFF;
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Spliting my received data into three parts of 16, 7 and 5 bits gives address,
|
||||||
|
channel and Command values of:
|
||||||
|
|
||||||
|
On 6180832 0110000110000000 1000001 10010
|
||||||
|
address: '0x6180' channel: '0x12' command: '0x41'
|
||||||
|
|
||||||
|
Off 6180052 0110000110000000 0000010 10010
|
||||||
|
address: '0x6180' channel: '0x12' command: '0x02'
|
||||||
|
|
||||||
|
These values are slightly different to those used by RFLink (the RFLink
|
||||||
|
ID/Adress value is rotated/manipulated), and I don't know who's interpretation
|
||||||
|
is correct. A larger data sample would help (I have only found five different
|
||||||
|
packet captures online) or definitive information from Drayton.
|
||||||
|
|
||||||
|
Splitting each packet in this way works well for me with esphome. Any
|
||||||
|
corrections or additional data samples would be gratefully received.
|
||||||
|
|
||||||
|
marshn
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
void DraytonProtocol::encode(RemoteTransmitData *dst, const DraytonData &data) {
|
||||||
|
uint16_t khz = CARRIER_KHZ;
|
||||||
|
dst->set_carrier_frequency(khz * 1000);
|
||||||
|
|
||||||
|
// Preamble = 101010101010
|
||||||
|
uint32_t out_data = 0x0AAA;
|
||||||
|
for (uint32_t mask = 1UL << (NBITS_PREAMBLE - 1); mask != 0; mask >>= 1) {
|
||||||
|
if (out_data & mask) {
|
||||||
|
dst->mark(BIT_TIME_US);
|
||||||
|
} else {
|
||||||
|
dst->space(BIT_TIME_US);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync = 1100
|
||||||
|
out_data = 0x000C;
|
||||||
|
for (uint32_t mask = 1UL << (NBITS_SYNC - 1); mask != 0; mask >>= 1) {
|
||||||
|
if (out_data & mask) {
|
||||||
|
dst->mark(BIT_TIME_US);
|
||||||
|
} else {
|
||||||
|
dst->space(BIT_TIME_US);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Send Drayton: address=%04x channel=%03x cmd=%02x", data.address, data.channel, data.command);
|
||||||
|
|
||||||
|
out_data = data.address;
|
||||||
|
out_data <<= NBITS_COMMAND;
|
||||||
|
out_data |= data.command;
|
||||||
|
out_data <<= NBITS_CHANNEL;
|
||||||
|
out_data |= data.channel;
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "Send Drayton: out_data %08x", out_data);
|
||||||
|
|
||||||
|
for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) {
|
||||||
|
if (out_data & mask) {
|
||||||
|
dst->mark(BIT_TIME_US);
|
||||||
|
dst->space(BIT_TIME_US);
|
||||||
|
} else {
|
||||||
|
dst->space(BIT_TIME_US);
|
||||||
|
dst->mark(BIT_TIME_US);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
||||||
|
DraytonData out{
|
||||||
|
.address = 0,
|
||||||
|
.channel = 0,
|
||||||
|
.command = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (src.size() < 45) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "Decode Drayton: %d, %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(),
|
||||||
|
src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7),
|
||||||
|
src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14),
|
||||||
|
src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19));
|
||||||
|
|
||||||
|
// If first preamble item is a space, skip it
|
||||||
|
if (src.peek_space_at_least(1)) {
|
||||||
|
src.advance(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for sync pulse, after. If sucessful index points to space of sync symbol
|
||||||
|
for (uint16_t preamble = 0; preamble <= NBITS_PREAMBLE * 2; preamble += 2) {
|
||||||
|
ESP_LOGVV(TAG, "Decode Drayton: preamble %d %d %d", preamble, src.peek(preamble), src.peek(preamble + 1));
|
||||||
|
if (src.peek_mark(2 * BIT_TIME_US, preamble) &&
|
||||||
|
(src.peek_space(2 * BIT_TIME_US, preamble + 1) || src.peek_space(3 * BIT_TIME_US, preamble + 1))) {
|
||||||
|
src.advance(preamble + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read data. Index points to space of sync symbol
|
||||||
|
// Extract first bit
|
||||||
|
// Checks next bit to leave index pointing correctly
|
||||||
|
uint32_t out_data = 0;
|
||||||
|
uint8_t bit = NBITS_ADDRESS + NBITS_COMMAND + NBITS_CHANNEL - 1;
|
||||||
|
if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
|
||||||
|
out_data |= 0 << bit;
|
||||||
|
} else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) &&
|
||||||
|
(src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
|
||||||
|
out_data |= 1 << bit;
|
||||||
|
} else {
|
||||||
|
ESP_LOGV(TAG, "Decode Drayton: Fail 1, - %d", src.get_index());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before/after each bit is read the index points to the transition at the start of the bit period or,
|
||||||
|
// if there is no transition at the start of the bit period, then the transition in the middle of
|
||||||
|
// the previous bit period.
|
||||||
|
while (--bit >= 1) {
|
||||||
|
ESP_LOGVV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
|
||||||
|
if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) &&
|
||||||
|
(src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
|
||||||
|
out_data |= 0 << bit;
|
||||||
|
} else if ((src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) &&
|
||||||
|
(src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
|
||||||
|
out_data |= 1 << bit;
|
||||||
|
} else {
|
||||||
|
ESP_LOGVV(TAG, "Decode Drayton: Fail 2, %2d %08x", bit, out_data);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) {
|
||||||
|
out_data |= 0;
|
||||||
|
} else if (src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) {
|
||||||
|
out_data |= 1;
|
||||||
|
}
|
||||||
|
ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
|
||||||
|
|
||||||
|
out.channel = (uint8_t) (out_data & 0x1F);
|
||||||
|
out_data >>= NBITS_CHANNEL;
|
||||||
|
out.command = (uint8_t) (out_data & 0x7F);
|
||||||
|
out_data >>= NBITS_COMMAND;
|
||||||
|
out.address = (uint16_t) (out_data & 0xFFFF);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
void DraytonProtocol::dump(const DraytonData &data) {
|
||||||
|
ESP_LOGD(TAG, "Received Drayton: address=0x%04X (0x%04x), channel=0x%03x command=0x%03X", data.address,
|
||||||
|
((data.address << 1) & 0xffff), data.channel, data.command);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace remote_base
|
||||||
|
} // namespace esphome
|
44
esphome/components/remote_base/drayton_protocol.h
Normal file
44
esphome/components/remote_base/drayton_protocol.h
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "remote_base.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace remote_base {
|
||||||
|
|
||||||
|
struct DraytonData {
|
||||||
|
uint16_t address;
|
||||||
|
uint8_t channel;
|
||||||
|
uint8_t command;
|
||||||
|
|
||||||
|
bool operator==(const DraytonData &rhs) const {
|
||||||
|
return address == rhs.address && channel == rhs.channel && command == rhs.command;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class DraytonProtocol : public RemoteProtocol<DraytonData> {
|
||||||
|
public:
|
||||||
|
void encode(RemoteTransmitData *dst, const DraytonData &data) override;
|
||||||
|
optional<DraytonData> decode(RemoteReceiveData src) override;
|
||||||
|
void dump(const DraytonData &data) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
DECLARE_REMOTE_PROTOCOL(Drayton)
|
||||||
|
|
||||||
|
template<typename... Ts> class DraytonAction : public RemoteTransmitterActionBase<Ts...> {
|
||||||
|
public:
|
||||||
|
TEMPLATABLE_VALUE(uint16_t, address)
|
||||||
|
TEMPLATABLE_VALUE(uint8_t, channel)
|
||||||
|
TEMPLATABLE_VALUE(uint8_t, command)
|
||||||
|
|
||||||
|
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||||
|
DraytonData data{};
|
||||||
|
data.address = this->address_.value(x...);
|
||||||
|
data.channel = this->channel_.value(x...);
|
||||||
|
data.command = this->command_.value(x...);
|
||||||
|
DraytonProtocol().encode(dst, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace remote_base
|
||||||
|
} // namespace esphome
|
|
@ -22,15 +22,6 @@ std::string state_class_to_string(StateClass state_class) {
|
||||||
|
|
||||||
Sensor::Sensor() : state(NAN), raw_state(NAN) {}
|
Sensor::Sensor() : state(NAN), raw_state(NAN) {}
|
||||||
|
|
||||||
std::string Sensor::get_unit_of_measurement() {
|
|
||||||
if (this->unit_of_measurement_.has_value())
|
|
||||||
return *this->unit_of_measurement_;
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) {
|
|
||||||
this->unit_of_measurement_ = unit_of_measurement;
|
|
||||||
}
|
|
||||||
|
|
||||||
int8_t Sensor::get_accuracy_decimals() {
|
int8_t Sensor::get_accuracy_decimals() {
|
||||||
if (this->accuracy_decimals_.has_value())
|
if (this->accuracy_decimals_.has_value())
|
||||||
return *this->accuracy_decimals_;
|
return *this->accuracy_decimals_;
|
||||||
|
|
|
@ -54,15 +54,10 @@ std::string state_class_to_string(StateClass state_class);
|
||||||
*
|
*
|
||||||
* A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy.
|
* A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy.
|
||||||
*/
|
*/
|
||||||
class Sensor : public EntityBase, public EntityBase_DeviceClass {
|
class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBase_UnitOfMeasurement {
|
||||||
public:
|
public:
|
||||||
explicit Sensor();
|
explicit Sensor();
|
||||||
|
|
||||||
/// Get the unit of measurement, using the manual override if set.
|
|
||||||
std::string get_unit_of_measurement();
|
|
||||||
/// Manually set the unit of measurement.
|
|
||||||
void set_unit_of_measurement(const std::string &unit_of_measurement);
|
|
||||||
|
|
||||||
/// Get the accuracy in decimals, using the manual override if set.
|
/// Get the accuracy in decimals, using the manual override if set.
|
||||||
int8_t get_accuracy_decimals();
|
int8_t get_accuracy_decimals();
|
||||||
/// Manually set the accuracy in decimals.
|
/// Manually set the accuracy in decimals.
|
||||||
|
@ -158,7 +153,6 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass {
|
||||||
|
|
||||||
Filter *filter_list_{nullptr}; ///< Store all active filters.
|
Filter *filter_list_{nullptr}; ///< Store all active filters.
|
||||||
|
|
||||||
optional<std::string> unit_of_measurement_; ///< Unit of measurement override
|
|
||||||
optional<int8_t> accuracy_decimals_; ///< Accuracy in decimals override
|
optional<int8_t> accuracy_decimals_; ///< Accuracy in decimals override
|
||||||
optional<StateClass> state_class_{STATE_CLASS_NONE}; ///< State class override
|
optional<StateClass> state_class_{STATE_CLASS_NONE}; ///< State class override
|
||||||
bool force_update_{false}; ///< Force update mode
|
bool force_update_{false}; ///< Force update mode
|
||||||
|
|
|
@ -8,17 +8,49 @@ from esphome.const import (
|
||||||
)
|
)
|
||||||
|
|
||||||
AUTO_LOAD = ["output"]
|
AUTO_LOAD = ["output"]
|
||||||
CODEOWNERS = ["@BoukeHaarsma23"]
|
CODEOWNERS = ["@BoukeHaarsma23", "@matika77", "@dd32"]
|
||||||
|
|
||||||
sm2135_ns = cg.esphome_ns.namespace("sm2135")
|
sm2135_ns = cg.esphome_ns.namespace("sm2135")
|
||||||
SM2135 = sm2135_ns.class_("SM2135", cg.Component)
|
SM2135 = sm2135_ns.class_("SM2135", cg.Component)
|
||||||
|
|
||||||
|
CONF_RGB_CURRENT = "rgb_current"
|
||||||
|
CONF_CW_CURRENT = "cw_current"
|
||||||
|
|
||||||
|
SM2135Current = sm2135_ns.enum("SM2135Current")
|
||||||
|
|
||||||
|
DRIVE_STRENGTHS_CW = {
|
||||||
|
"10mA": SM2135Current.SM2135_CURRENT_10MA,
|
||||||
|
"15mA": SM2135Current.SM2135_CURRENT_15MA,
|
||||||
|
"20mA": SM2135Current.SM2135_CURRENT_20MA,
|
||||||
|
"25mA": SM2135Current.SM2135_CURRENT_25MA,
|
||||||
|
"30mA": SM2135Current.SM2135_CURRENT_30MA,
|
||||||
|
"35mA": SM2135Current.SM2135_CURRENT_35MA,
|
||||||
|
"40mA": SM2135Current.SM2135_CURRENT_40MA,
|
||||||
|
"45mA": SM2135Current.SM2135_CURRENT_45MA,
|
||||||
|
"50mA": SM2135Current.SM2135_CURRENT_50MA,
|
||||||
|
"55mA": SM2135Current.SM2135_CURRENT_55MA,
|
||||||
|
"60mA": SM2135Current.SM2135_CURRENT_60MA,
|
||||||
|
}
|
||||||
|
DRIVE_STRENGTHS_RGB = {
|
||||||
|
"10mA": SM2135Current.SM2135_CURRENT_10MA,
|
||||||
|
"15mA": SM2135Current.SM2135_CURRENT_15MA,
|
||||||
|
"20mA": SM2135Current.SM2135_CURRENT_20MA,
|
||||||
|
"25mA": SM2135Current.SM2135_CURRENT_25MA,
|
||||||
|
"30mA": SM2135Current.SM2135_CURRENT_30MA,
|
||||||
|
"35mA": SM2135Current.SM2135_CURRENT_35MA,
|
||||||
|
"40mA": SM2135Current.SM2135_CURRENT_40MA,
|
||||||
|
"45mA": SM2135Current.SM2135_CURRENT_45MA,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(SM2135),
|
cv.GenerateID(): cv.declare_id(SM2135),
|
||||||
cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema,
|
cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema,
|
||||||
cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema,
|
cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema,
|
||||||
|
cv.Optional(CONF_RGB_CURRENT, "20mA"): cv.enum(DRIVE_STRENGTHS_RGB),
|
||||||
|
cv.Optional(CONF_CW_CURRENT, "10mA"): cv.enum(DRIVE_STRENGTHS_CW),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
@ -31,3 +63,6 @@ async def to_code(config):
|
||||||
cg.add(var.set_data_pin(data))
|
cg.add(var.set_data_pin(data))
|
||||||
clock = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN])
|
clock = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN])
|
||||||
cg.add(var.set_clock_pin(clock))
|
cg.add(var.set_clock_pin(clock))
|
||||||
|
|
||||||
|
cg.add(var.set_rgb_current(config[CONF_RGB_CURRENT]))
|
||||||
|
cg.add(var.set_cw_current(config[CONF_CW_CURRENT]))
|
||||||
|
|
|
@ -19,63 +19,125 @@ static const uint8_t SM2135_ADDR_W = 0xC6; // Warm
|
||||||
static const uint8_t SM2135_RGB = 0x00; // RGB channel
|
static const uint8_t SM2135_RGB = 0x00; // RGB channel
|
||||||
static const uint8_t SM2135_CW = 0x80; // CW channel (Chip default)
|
static const uint8_t SM2135_CW = 0x80; // CW channel (Chip default)
|
||||||
|
|
||||||
static const uint8_t SM2135_10MA = 0x00;
|
|
||||||
static const uint8_t SM2135_15MA = 0x01;
|
|
||||||
static const uint8_t SM2135_20MA = 0x02; // RGB max current (Chip default)
|
|
||||||
static const uint8_t SM2135_25MA = 0x03;
|
|
||||||
static const uint8_t SM2135_30MA = 0x04; // CW max current (Chip default)
|
|
||||||
static const uint8_t SM2135_35MA = 0x05;
|
|
||||||
static const uint8_t SM2135_40MA = 0x06;
|
|
||||||
static const uint8_t SM2135_45MA = 0x07; // Max value for RGB
|
|
||||||
static const uint8_t SM2135_50MA = 0x08;
|
|
||||||
static const uint8_t SM2135_55MA = 0x09;
|
|
||||||
static const uint8_t SM2135_60MA = 0x0A;
|
|
||||||
|
|
||||||
static const uint8_t SM2135_CURRENT = (SM2135_20MA << 4) | SM2135_10MA;
|
|
||||||
|
|
||||||
void SM2135::setup() {
|
void SM2135::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up SM2135OutputComponent...");
|
ESP_LOGCONFIG(TAG, "Setting up SM2135OutputComponent...");
|
||||||
this->data_pin_->setup();
|
this->data_pin_->setup();
|
||||||
this->data_pin_->digital_write(true);
|
this->data_pin_->digital_write(false);
|
||||||
|
this->data_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||||
this->clock_pin_->setup();
|
this->clock_pin_->setup();
|
||||||
this->clock_pin_->digital_write(true);
|
this->clock_pin_->digital_write(false);
|
||||||
|
this->data_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||||
|
|
||||||
|
this->data_pin_->pin_mode(gpio::FLAG_PULLUP);
|
||||||
|
this->clock_pin_->pin_mode(gpio::FLAG_PULLUP);
|
||||||
|
|
||||||
this->pwm_amounts_.resize(5, 0);
|
this->pwm_amounts_.resize(5, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SM2135::dump_config() {
|
void SM2135::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "SM2135:");
|
ESP_LOGCONFIG(TAG, "SM2135:");
|
||||||
LOG_PIN(" Data Pin: ", this->data_pin_);
|
LOG_PIN(" Data Pin: ", this->data_pin_);
|
||||||
LOG_PIN(" Clock Pin: ", this->clock_pin_);
|
LOG_PIN(" Clock Pin: ", this->clock_pin_);
|
||||||
|
ESP_LOGCONFIG(TAG, " CW Current: %dmA", 10 + (this->cw_current_ * 5));
|
||||||
|
ESP_LOGCONFIG(TAG, " RGB Current: %dmA", 10 + (this->rgb_current_ * 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SM2135::write_byte_(uint8_t data) {
|
||||||
|
for (uint8_t mask = 0x80; mask; mask >>= 1) {
|
||||||
|
if (mask & data) {
|
||||||
|
this->sm2135_set_high_(this->data_pin_);
|
||||||
|
} else {
|
||||||
|
this->sm2135_set_low_(this->data_pin_);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->sm2135_set_high_(this->clock_pin_);
|
||||||
|
delayMicroseconds(4);
|
||||||
|
this->sm2135_set_low_(clock_pin_);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->sm2135_set_high_(this->data_pin_);
|
||||||
|
this->sm2135_set_high_(this->clock_pin_);
|
||||||
|
delayMicroseconds(2);
|
||||||
|
this->sm2135_set_low_(this->clock_pin_);
|
||||||
|
delayMicroseconds(2);
|
||||||
|
this->sm2135_set_low_(this->data_pin_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SM2135::sm2135_start_() {
|
||||||
|
this->sm2135_set_low_(this->data_pin_);
|
||||||
|
delayMicroseconds(4);
|
||||||
|
this->sm2135_set_low_(this->clock_pin_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SM2135::sm2135_stop_() {
|
||||||
|
this->sm2135_set_low_(this->data_pin_);
|
||||||
|
delayMicroseconds(4);
|
||||||
|
this->sm2135_set_high_(this->clock_pin_);
|
||||||
|
delayMicroseconds(4);
|
||||||
|
this->sm2135_set_high_(this->data_pin_);
|
||||||
|
delayMicroseconds(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SM2135::write_buffer_(uint8_t *buffer, uint8_t size) {
|
||||||
|
this->sm2135_start_();
|
||||||
|
|
||||||
|
this->data_pin_->digital_write(false);
|
||||||
|
for (uint32_t i = 0; i < size; i++) {
|
||||||
|
this->write_byte_(buffer[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->sm2135_stop_();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SM2135::loop() {
|
void SM2135::loop() {
|
||||||
if (!this->update_)
|
if (!this->update_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
uint8_t data[6];
|
this->sm2135_start_();
|
||||||
|
this->write_byte_(SM2135_ADDR_MC);
|
||||||
|
this->write_byte_(current_mask_);
|
||||||
|
|
||||||
if (this->update_channel_ == 3 || this->update_channel_ == 4) {
|
if (this->update_channel_ == 3 || this->update_channel_ == 4) {
|
||||||
// No color so must be Cold/Warm
|
// No color so must be Cold/Warm
|
||||||
data[0] = SM2135_ADDR_MC;
|
|
||||||
data[1] = SM2135_CURRENT;
|
this->write_byte_(SM2135_CW);
|
||||||
data[2] = SM2135_CW;
|
this->sm2135_stop_();
|
||||||
this->write_buffer_(data, 3);
|
|
||||||
delay(1);
|
delay(1);
|
||||||
data[0] = SM2135_ADDR_C;
|
this->sm2135_start_();
|
||||||
data[1] = this->pwm_amounts_[4]; // Warm
|
this->write_byte_(SM2135_ADDR_C);
|
||||||
data[2] = this->pwm_amounts_[3]; // Cold
|
this->write_byte_(this->pwm_amounts_[4]); // Warm
|
||||||
this->write_buffer_(data, 3);
|
this->write_byte_(this->pwm_amounts_[3]); // Cold
|
||||||
} else {
|
} else {
|
||||||
// Color
|
// Color
|
||||||
data[0] = SM2135_ADDR_MC;
|
|
||||||
data[1] = SM2135_CURRENT;
|
this->write_byte_(SM2135_RGB);
|
||||||
data[2] = SM2135_RGB;
|
this->write_byte_(this->pwm_amounts_[1]); // Green
|
||||||
data[3] = this->pwm_amounts_[1]; // Green
|
this->write_byte_(this->pwm_amounts_[0]); // Red
|
||||||
data[4] = this->pwm_amounts_[0]; // Red
|
this->write_byte_(this->pwm_amounts_[2]); // Blue
|
||||||
data[5] = this->pwm_amounts_[2]; // Blue
|
|
||||||
this->write_buffer_(data, 6);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->sm2135_stop_();
|
||||||
|
|
||||||
this->update_ = false;
|
this->update_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SM2135::set_channel_value_(uint8_t channel, uint8_t value) {
|
||||||
|
if (this->pwm_amounts_[channel] != value) {
|
||||||
|
this->update_ = true;
|
||||||
|
this->update_channel_ = channel;
|
||||||
|
}
|
||||||
|
this->pwm_amounts_[channel] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SM2135::sm2135_set_low_(GPIOPin *pin) {
|
||||||
|
pin->digital_write(false);
|
||||||
|
pin->pin_mode(gpio::FLAG_OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SM2135::sm2135_set_high_(GPIOPin *pin) {
|
||||||
|
pin->digital_write(true);
|
||||||
|
pin->pin_mode(gpio::FLAG_PULLUP);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sm2135
|
} // namespace sm2135
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -1,19 +1,43 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "esphome/components/output/float_output.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/components/output/float_output.h"
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace sm2135 {
|
namespace sm2135 {
|
||||||
|
|
||||||
|
enum SM2135Current : uint8_t {
|
||||||
|
SM2135_CURRENT_10MA = 0x00,
|
||||||
|
SM2135_CURRENT_15MA = 0x01,
|
||||||
|
SM2135_CURRENT_20MA = 0x02,
|
||||||
|
SM2135_CURRENT_25MA = 0x03,
|
||||||
|
SM2135_CURRENT_30MA = 0x04,
|
||||||
|
SM2135_CURRENT_35MA = 0x05,
|
||||||
|
SM2135_CURRENT_40MA = 0x06,
|
||||||
|
SM2135_CURRENT_45MA = 0x07, // Max value for RGB
|
||||||
|
SM2135_CURRENT_50MA = 0x08,
|
||||||
|
SM2135_CURRENT_55MA = 0x09,
|
||||||
|
SM2135_CURRENT_60MA = 0x0A,
|
||||||
|
};
|
||||||
|
|
||||||
class SM2135 : public Component {
|
class SM2135 : public Component {
|
||||||
public:
|
public:
|
||||||
class Channel;
|
class Channel;
|
||||||
|
|
||||||
void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; }
|
void set_data_pin(GPIOPin *data_pin) { this->data_pin_ = data_pin; }
|
||||||
void set_clock_pin(GPIOPin *clock_pin) { clock_pin_ = clock_pin; }
|
void set_clock_pin(GPIOPin *clock_pin) { this->clock_pin_ = clock_pin; }
|
||||||
|
|
||||||
|
void set_rgb_current(SM2135Current rgb_current) {
|
||||||
|
this->rgb_current_ = rgb_current;
|
||||||
|
this->current_mask_ = (this->rgb_current_ << 4) | this->cw_current_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_cw_current(SM2135Current cw_current) {
|
||||||
|
this->cw_current_ = cw_current;
|
||||||
|
this->current_mask_ = (this->rgb_current_ << 4) | this->cw_current_;
|
||||||
|
}
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
|
||||||
|
@ -40,40 +64,20 @@ class SM2135 : public Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void set_channel_value_(uint8_t channel, uint8_t value) {
|
void set_channel_value_(uint8_t channel, uint8_t value);
|
||||||
if (this->pwm_amounts_[channel] != value) {
|
void sm2135_set_low_(GPIOPin *pin);
|
||||||
this->update_ = true;
|
void sm2135_set_high_(GPIOPin *pin);
|
||||||
this->update_channel_ = channel;
|
|
||||||
}
|
|
||||||
this->pwm_amounts_[channel] = value;
|
|
||||||
}
|
|
||||||
void write_bit_(bool value) {
|
|
||||||
this->clock_pin_->digital_write(false);
|
|
||||||
this->data_pin_->digital_write(value);
|
|
||||||
this->clock_pin_->digital_write(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_byte_(uint8_t data) {
|
void sm2135_start_();
|
||||||
for (uint8_t mask = 0x80; mask; mask >>= 1) {
|
void sm2135_stop_();
|
||||||
this->write_bit_(data & mask);
|
void write_byte_(uint8_t data);
|
||||||
}
|
void write_buffer_(uint8_t *buffer, uint8_t size);
|
||||||
this->clock_pin_->digital_write(false);
|
|
||||||
this->data_pin_->digital_write(true);
|
|
||||||
this->clock_pin_->digital_write(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_buffer_(uint8_t *buffer, uint8_t size) {
|
|
||||||
this->data_pin_->digital_write(false);
|
|
||||||
for (uint32_t i = 0; i < size; i++) {
|
|
||||||
this->write_byte_(buffer[i]);
|
|
||||||
}
|
|
||||||
this->clock_pin_->digital_write(false);
|
|
||||||
this->clock_pin_->digital_write(true);
|
|
||||||
this->data_pin_->digital_write(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
GPIOPin *data_pin_;
|
GPIOPin *data_pin_;
|
||||||
GPIOPin *clock_pin_;
|
GPIOPin *clock_pin_;
|
||||||
|
uint8_t current_mask_;
|
||||||
|
SM2135Current rgb_current_;
|
||||||
|
SM2135Current cw_current_;
|
||||||
uint8_t update_channel_;
|
uint8_t update_channel_;
|
||||||
std::vector<uint8_t> pwm_amounts_;
|
std::vector<uint8_t> pwm_amounts_;
|
||||||
bool update_{true};
|
bool update_{true};
|
||||||
|
|
|
@ -14,6 +14,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
esp8266=IMPLEMENTATION_LWIP_TCP,
|
esp8266=IMPLEMENTATION_LWIP_TCP,
|
||||||
esp32=IMPLEMENTATION_BSD_SOCKETS,
|
esp32=IMPLEMENTATION_BSD_SOCKETS,
|
||||||
rp2040=IMPLEMENTATION_LWIP_TCP,
|
rp2040=IMPLEMENTATION_LWIP_TCP,
|
||||||
|
host=IMPLEMENTATION_BSD_SOCKETS,
|
||||||
): cv.one_of(
|
): cv.one_of(
|
||||||
IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_"
|
IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_"
|
||||||
),
|
),
|
||||||
|
|
|
@ -130,6 +130,13 @@ struct iovec {
|
||||||
#include <sys/uio.h>
|
#include <sys/uio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#ifdef USE_HOST
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/ip.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#endif // USE_HOST
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
#ifdef USE_ARDUINO
|
||||||
// arduino-esp32 declares a global var called INADDR_NONE which is replaced
|
// arduino-esp32 declares a global var called INADDR_NONE which is replaced
|
||||||
// by the define
|
// by the define
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace socket {
|
namespace socket {
|
||||||
|
|
||||||
|
Socket::~Socket() {}
|
||||||
|
|
||||||
std::unique_ptr<Socket> socket_ip(int type, int protocol) {
|
std::unique_ptr<Socket> socket_ip(int type, int protocol) {
|
||||||
#if LWIP_IPV6
|
#if LWIP_IPV6
|
||||||
return socket(AF_INET6, type, protocol);
|
return socket(AF_INET6, type, protocol);
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace socket {
|
||||||
class Socket {
|
class Socket {
|
||||||
public:
|
public:
|
||||||
Socket() = default;
|
Socket() = default;
|
||||||
virtual ~Socket() = default;
|
virtual ~Socket();
|
||||||
Socket(const Socket &) = delete;
|
Socket(const Socket &) = delete;
|
||||||
Socket &operator=(const Socket &) = delete;
|
Socket &operator=(const Socket &) = delete;
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ class Socket {
|
||||||
virtual ssize_t readv(const struct iovec *iov, int iovcnt) = 0;
|
virtual ssize_t readv(const struct iovec *iov, int iovcnt) = 0;
|
||||||
virtual ssize_t write(const void *buf, size_t len) = 0;
|
virtual ssize_t write(const void *buf, size_t len) = 0;
|
||||||
virtual ssize_t writev(const struct iovec *iov, int iovcnt) = 0;
|
virtual ssize_t writev(const struct iovec *iov, int iovcnt) = 0;
|
||||||
virtual ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
|
virtual ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) = 0;
|
||||||
|
|
||||||
virtual int setblocking(bool blocking) = 0;
|
virtual int setblocking(bool blocking) = 0;
|
||||||
virtual int loop() { return 0; };
|
virtual int loop() { return 0; };
|
||||||
|
|
87
esphome/components/speaker/__init__.py
Normal file
87
esphome/components/speaker/__init__.py
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
from esphome import automation
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
import esphome.codegen as cg
|
||||||
|
|
||||||
|
from esphome.automation import maybe_simple_id
|
||||||
|
from esphome.const import CONF_ID, CONF_DATA
|
||||||
|
from esphome.core import CORE
|
||||||
|
from esphome.coroutine import coroutine_with_priority
|
||||||
|
|
||||||
|
|
||||||
|
CODEOWNERS = ["@jesserockz"]
|
||||||
|
|
||||||
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
speaker_ns = cg.esphome_ns.namespace("speaker")
|
||||||
|
|
||||||
|
Speaker = speaker_ns.class_("Speaker")
|
||||||
|
|
||||||
|
PlayAction = speaker_ns.class_(
|
||||||
|
"PlayAction", automation.Action, cg.Parented.template(Speaker)
|
||||||
|
)
|
||||||
|
StopAction = speaker_ns.class_(
|
||||||
|
"StopAction", automation.Action, cg.Parented.template(Speaker)
|
||||||
|
)
|
||||||
|
|
||||||
|
IsPlayingCondition = speaker_ns.class_("IsPlayingCondition", automation.Condition)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup_speaker_core_(var, config):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def register_speaker(var, config):
|
||||||
|
if not CORE.has_id(config[CONF_ID]):
|
||||||
|
var = cg.Pvariable(config[CONF_ID], var)
|
||||||
|
await setup_speaker_core_(var, config)
|
||||||
|
|
||||||
|
|
||||||
|
SPEAKER_SCHEMA = cv.Schema({})
|
||||||
|
|
||||||
|
|
||||||
|
SPEAKER_AUTOMATION_SCHEMA = maybe_simple_id({cv.GenerateID(): cv.use_id(Speaker)})
|
||||||
|
|
||||||
|
|
||||||
|
async def speaker_action(config, action_id, template_arg, args):
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
|
await cg.register_parented(var, config[CONF_ID])
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"speaker.play",
|
||||||
|
PlayAction,
|
||||||
|
cv.maybe_simple_value(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.use_id(Speaker),
|
||||||
|
cv.Required(CONF_DATA): cv.templatable(cv.ensure_list(cv.hex_uint8_t)),
|
||||||
|
},
|
||||||
|
key=CONF_DATA,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def speaker_play_action(config, action_id, template_arg, args):
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
|
await cg.register_parented(var, config[CONF_ID])
|
||||||
|
data = config[CONF_DATA]
|
||||||
|
|
||||||
|
if cg.is_template(data):
|
||||||
|
templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
|
||||||
|
cg.add(var.set_data_template(templ))
|
||||||
|
else:
|
||||||
|
cg.add(var.set_data_static(data))
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
automation.register_action("speaker.stop", StopAction, SPEAKER_AUTOMATION_SCHEMA)(
|
||||||
|
speaker_action
|
||||||
|
)
|
||||||
|
|
||||||
|
automation.register_condition(
|
||||||
|
"speaker.is_playing", IsPlayingCondition, SPEAKER_AUTOMATION_SCHEMA
|
||||||
|
)(speaker_action)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(100.0)
|
||||||
|
async def to_code(config):
|
||||||
|
cg.add_global(speaker_ns.using)
|
||||||
|
cg.add_define("USE_SPEAKER")
|
48
esphome/components/speaker/automation.h
Normal file
48
esphome/components/speaker/automation.h
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "speaker.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace speaker {
|
||||||
|
|
||||||
|
template<typename... Ts> class PlayAction : public Action<Ts...>, public Parented<Speaker> {
|
||||||
|
public:
|
||||||
|
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||||
|
this->data_func_ = func;
|
||||||
|
this->static_ = false;
|
||||||
|
}
|
||||||
|
void set_data_static(const std::vector<uint8_t> &data) {
|
||||||
|
this->data_static_ = data;
|
||||||
|
this->static_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void play(Ts... x) override {
|
||||||
|
if (this->static_) {
|
||||||
|
this->parent_->play(this->data_static_);
|
||||||
|
} else {
|
||||||
|
auto val = this->data_func_(x...);
|
||||||
|
this->parent_->play(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool static_{false};
|
||||||
|
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
|
||||||
|
std::vector<uint8_t> data_static_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class StopAction : public Action<Ts...>, public Parented<Speaker> {
|
||||||
|
public:
|
||||||
|
void play(Ts... x) override { this->parent_->stop(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class IsPlayingCondition : public Condition<Ts...>, public Parented<Speaker> {
|
||||||
|
public:
|
||||||
|
bool check(Ts... x) override { return this->parent_->is_running(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace speaker
|
||||||
|
} // namespace esphome
|
27
esphome/components/speaker/speaker.h
Normal file
27
esphome/components/speaker/speaker.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace speaker {
|
||||||
|
|
||||||
|
enum State : uint8_t {
|
||||||
|
STATE_STOPPED = 0,
|
||||||
|
STATE_STARTING,
|
||||||
|
STATE_RUNNING,
|
||||||
|
STATE_STOPPING,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Speaker {
|
||||||
|
public:
|
||||||
|
virtual bool play(const uint8_t *data, size_t length) = 0;
|
||||||
|
virtual bool play(const std::vector<uint8_t> &data) { return this->play(data.data(), data.size()); }
|
||||||
|
|
||||||
|
virtual void stop() = 0;
|
||||||
|
|
||||||
|
bool is_running() const { return this->state_ == STATE_RUNNING; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
State state_{STATE_STOPPED};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace speaker
|
||||||
|
} // namespace esphome
|
|
@ -286,7 +286,9 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_ENABLE_SWITCH): cv.maybe_simple_value(
|
cv.Optional(CONF_ENABLE_SWITCH): cv.maybe_simple_value(
|
||||||
switch.switch_schema(
|
switch.switch_schema(
|
||||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
SprinklerControllerSwitch,
|
||||||
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
|
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||||
),
|
),
|
||||||
key=CONF_NAME,
|
key=CONF_NAME,
|
||||||
),
|
),
|
||||||
|
@ -333,7 +335,9 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema(
|
||||||
cv.Optional(CONF_NAME): cv.string,
|
cv.Optional(CONF_NAME): cv.string,
|
||||||
cv.Optional(CONF_AUTO_ADVANCE_SWITCH): cv.maybe_simple_value(
|
cv.Optional(CONF_AUTO_ADVANCE_SWITCH): cv.maybe_simple_value(
|
||||||
switch.switch_schema(
|
switch.switch_schema(
|
||||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
SprinklerControllerSwitch,
|
||||||
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
|
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||||
),
|
),
|
||||||
key=CONF_NAME,
|
key=CONF_NAME,
|
||||||
),
|
),
|
||||||
|
@ -343,19 +347,25 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema(
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_QUEUE_ENABLE_SWITCH): cv.maybe_simple_value(
|
cv.Optional(CONF_QUEUE_ENABLE_SWITCH): cv.maybe_simple_value(
|
||||||
switch.switch_schema(
|
switch.switch_schema(
|
||||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
SprinklerControllerSwitch,
|
||||||
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
|
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||||
),
|
),
|
||||||
key=CONF_NAME,
|
key=CONF_NAME,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_REVERSE_SWITCH): cv.maybe_simple_value(
|
cv.Optional(CONF_REVERSE_SWITCH): cv.maybe_simple_value(
|
||||||
switch.switch_schema(
|
switch.switch_schema(
|
||||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
SprinklerControllerSwitch,
|
||||||
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
|
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||||
),
|
),
|
||||||
key=CONF_NAME,
|
key=CONF_NAME,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_STANDBY_SWITCH): cv.maybe_simple_value(
|
cv.Optional(CONF_STANDBY_SWITCH): cv.maybe_simple_value(
|
||||||
switch.switch_schema(
|
switch.switch_schema(
|
||||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
SprinklerControllerSwitch,
|
||||||
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
|
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||||
),
|
),
|
||||||
key=CONF_NAME,
|
key=CONF_NAME,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1176,6 +1176,21 @@ optional<uint32_t> Sprinkler::time_remaining_current_operation() {
|
||||||
return nullopt;
|
return nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Sprinkler::any_controller_is_active() {
|
||||||
|
if (this->state_ != IDLE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &controller : this->other_controllers_) {
|
||||||
|
if (controller != this) { // dummy check
|
||||||
|
if (controller->controller_state() != IDLE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
SprinklerControllerSwitch *Sprinkler::control_switch(size_t valve_number) {
|
SprinklerControllerSwitch *Sprinkler::control_switch(size_t valve_number) {
|
||||||
if (this->is_a_valid_valve(valve_number)) {
|
if (this->is_a_valid_valve(valve_number)) {
|
||||||
return this->valve_[valve_number].controller_switch;
|
return this->valve_[valve_number].controller_switch;
|
||||||
|
|
|
@ -406,6 +406,12 @@ class Sprinkler : public Component {
|
||||||
/// returns the amount of time remaining in seconds for all valves remaining, including the active valve, if any
|
/// returns the amount of time remaining in seconds for all valves remaining, including the active valve, if any
|
||||||
optional<uint32_t> time_remaining_current_operation();
|
optional<uint32_t> time_remaining_current_operation();
|
||||||
|
|
||||||
|
/// returns true if this or any sprinkler controller this controller knows about is active
|
||||||
|
bool any_controller_is_active();
|
||||||
|
|
||||||
|
/// returns the current state of the sprinkler controller
|
||||||
|
SprinklerState controller_state() { return this->state_; };
|
||||||
|
|
||||||
/// returns a pointer to a valve's control switch object
|
/// returns a pointer to a valve's control switch object
|
||||||
SprinklerControllerSwitch *control_switch(size_t valve_number);
|
SprinklerControllerSwitch *control_switch(size_t valve_number);
|
||||||
|
|
||||||
|
@ -503,7 +509,6 @@ class Sprinkler : public Component {
|
||||||
/// callback functions for timers
|
/// callback functions for timers
|
||||||
void valve_selection_callback_();
|
void valve_selection_callback_();
|
||||||
void sm_timer_callback_();
|
void sm_timer_callback_();
|
||||||
void pump_stop_delay_callback_();
|
|
||||||
|
|
||||||
/// Maximum allowed queue size
|
/// Maximum allowed queue size
|
||||||
const uint8_t max_queue_size_{100};
|
const uint8_t max_queue_size_{100};
|
||||||
|
|
|
@ -8,13 +8,12 @@ namespace template_ {
|
||||||
static const char *const TAG = "template.sensor";
|
static const char *const TAG = "template.sensor";
|
||||||
|
|
||||||
void TemplateSensor::update() {
|
void TemplateSensor::update() {
|
||||||
if (this->f_.has_value()) {
|
if (!this->f_.has_value())
|
||||||
auto val = (*this->f_)();
|
return;
|
||||||
if (val.has_value()) {
|
|
||||||
this->publish_state(*val);
|
auto val = (*this->f_)();
|
||||||
}
|
if (val.has_value()) {
|
||||||
} else if (!std::isnan(this->get_raw_state())) {
|
this->publish_state(*val);
|
||||||
this->publish_state(this->get_raw_state());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
|
|
|
@ -7,13 +7,12 @@ namespace template_ {
|
||||||
static const char *const TAG = "template.text_sensor";
|
static const char *const TAG = "template.text_sensor";
|
||||||
|
|
||||||
void TemplateTextSensor::update() {
|
void TemplateTextSensor::update() {
|
||||||
if (this->f_.has_value()) {
|
if (!this->f_.has_value())
|
||||||
auto val = (*this->f_)();
|
return;
|
||||||
if (val.has_value()) {
|
|
||||||
this->publish_state(*val);
|
auto val = (*this->f_)();
|
||||||
}
|
if (val.has_value()) {
|
||||||
} else if (this->has_state()) {
|
this->publish_state(*val);
|
||||||
this->publish_state(this->state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
|
||||||
from esphome.const import CONF_ID, CONF_MICROPHONE
|
from esphome.const import CONF_ID, CONF_MICROPHONE, CONF_SPEAKER
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.automation import register_action
|
from esphome.automation import register_action
|
||||||
from esphome.components import microphone
|
from esphome.components import microphone, speaker
|
||||||
|
|
||||||
AUTO_LOAD = ["socket"]
|
AUTO_LOAD = ["socket"]
|
||||||
DEPENDENCIES = ["api", "microphone"]
|
DEPENDENCIES = ["api", "microphone"]
|
||||||
|
@ -34,6 +34,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(VoiceAssistant),
|
cv.GenerateID(): cv.declare_id(VoiceAssistant),
|
||||||
cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone),
|
cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone),
|
||||||
|
cv.Optional(CONF_SPEAKER): cv.use_id(speaker.Speaker),
|
||||||
cv.Optional(CONF_ON_START): automation.validate_automation(single=True),
|
cv.Optional(CONF_ON_START): automation.validate_automation(single=True),
|
||||||
cv.Optional(CONF_ON_STT_END): automation.validate_automation(single=True),
|
cv.Optional(CONF_ON_STT_END): automation.validate_automation(single=True),
|
||||||
cv.Optional(CONF_ON_TTS_START): automation.validate_automation(single=True),
|
cv.Optional(CONF_ON_TTS_START): automation.validate_automation(single=True),
|
||||||
|
@ -51,6 +52,10 @@ async def to_code(config):
|
||||||
mic = await cg.get_variable(config[CONF_MICROPHONE])
|
mic = await cg.get_variable(config[CONF_MICROPHONE])
|
||||||
cg.add(var.set_microphone(mic))
|
cg.add(var.set_microphone(mic))
|
||||||
|
|
||||||
|
if CONF_SPEAKER in config:
|
||||||
|
spkr = await cg.get_variable(config[CONF_SPEAKER])
|
||||||
|
cg.add(var.set_speaker(spkr))
|
||||||
|
|
||||||
if CONF_ON_START in config:
|
if CONF_ON_START in config:
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
var.get_start_trigger(), [], config[CONF_ON_START]
|
var.get_start_trigger(), [], config[CONF_ON_START]
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
#include "voice_assistant.h"
|
#include "voice_assistant.h"
|
||||||
|
|
||||||
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace voice_assistant {
|
namespace voice_assistant {
|
||||||
|
|
||||||
|
@ -33,6 +37,27 @@ void VoiceAssistant::setup() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_SPEAKER
|
||||||
|
if (this->speaker_ != nullptr) {
|
||||||
|
struct sockaddr_storage server;
|
||||||
|
|
||||||
|
socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), 6055);
|
||||||
|
if (sl == 0) {
|
||||||
|
ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
server.ss_family = AF_INET;
|
||||||
|
|
||||||
|
err = socket_->bind((struct sockaddr *) &server, sizeof(server));
|
||||||
|
if (err != 0) {
|
||||||
|
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
this->mic_->add_data_callback([this](const std::vector<uint8_t> &data) {
|
this->mic_->add_data_callback([this](const std::vector<uint8_t> &data) {
|
||||||
if (!this->running_) {
|
if (!this->running_) {
|
||||||
return;
|
return;
|
||||||
|
@ -41,6 +66,21 @@ void VoiceAssistant::setup() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VoiceAssistant::loop() {
|
||||||
|
#ifdef USE_SPEAKER
|
||||||
|
if (this->speaker_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t buf[1024];
|
||||||
|
auto len = this->socket_->read(buf, sizeof(buf));
|
||||||
|
if (len == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->speaker_->play(buf, len);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void VoiceAssistant::start(struct sockaddr_storage *addr, uint16_t port) {
|
void VoiceAssistant::start(struct sockaddr_storage *addr, uint16_t port) {
|
||||||
ESP_LOGD(TAG, "Starting...");
|
ESP_LOGD(TAG, "Starting...");
|
||||||
|
|
||||||
|
@ -154,3 +194,5 @@ VoiceAssistant *global_voice_assistant = nullptr; // NOLINT(cppcoreguidelines-a
|
||||||
|
|
||||||
} // namespace voice_assistant
|
} // namespace voice_assistant
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_VOICE_ASSISTANT
|
||||||
|
|
|
@ -1,24 +1,49 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
|
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
#include "esphome/components/api/api_pb2.h"
|
#include "esphome/components/api/api_pb2.h"
|
||||||
#include "esphome/components/api/api_server.h"
|
#include "esphome/components/api/api_server.h"
|
||||||
#include "esphome/components/microphone/microphone.h"
|
#include "esphome/components/microphone/microphone.h"
|
||||||
|
#ifdef USE_SPEAKER
|
||||||
|
#include "esphome/components/speaker/speaker.h"
|
||||||
|
#endif
|
||||||
#include "esphome/components/socket/socket.h"
|
#include "esphome/components/socket/socket.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace voice_assistant {
|
namespace voice_assistant {
|
||||||
|
|
||||||
|
// Version 1: Initial version
|
||||||
|
// Version 2: Adds raw speaker support
|
||||||
|
static const uint32_t INITIAL_VERSION = 1;
|
||||||
|
static const uint32_t SPEAKER_SUPPORT = 2;
|
||||||
|
|
||||||
class VoiceAssistant : public Component {
|
class VoiceAssistant : public Component {
|
||||||
public:
|
public:
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
void loop() override;
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
void start(struct sockaddr_storage *addr, uint16_t port);
|
void start(struct sockaddr_storage *addr, uint16_t port);
|
||||||
|
|
||||||
void set_microphone(microphone::Microphone *mic) { this->mic_ = mic; }
|
void set_microphone(microphone::Microphone *mic) { this->mic_ = mic; }
|
||||||
|
#ifdef USE_SPEAKER
|
||||||
|
void set_speaker(speaker::Speaker *speaker) { this->speaker_ = speaker; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint32_t get_version() const {
|
||||||
|
#ifdef USE_SPEAKER
|
||||||
|
if (this->speaker_ != nullptr)
|
||||||
|
return SPEAKER_SUPPORT;
|
||||||
|
#endif
|
||||||
|
return INITIAL_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
void request_start();
|
void request_start();
|
||||||
void signal_stop();
|
void signal_stop();
|
||||||
|
@ -44,6 +69,9 @@ class VoiceAssistant : public Component {
|
||||||
Trigger<std::string, std::string> *error_trigger_ = new Trigger<std::string, std::string>();
|
Trigger<std::string, std::string> *error_trigger_ = new Trigger<std::string, std::string>();
|
||||||
|
|
||||||
microphone::Microphone *mic_{nullptr};
|
microphone::Microphone *mic_{nullptr};
|
||||||
|
#ifdef USE_SPEAKER
|
||||||
|
speaker::Speaker *speaker_{nullptr};
|
||||||
|
#endif
|
||||||
|
|
||||||
bool running_{false};
|
bool running_{false};
|
||||||
};
|
};
|
||||||
|
@ -62,3 +90,5 @@ extern VoiceAssistant *global_voice_assistant; // NOLINT(cppcoreguidelines-avoi
|
||||||
|
|
||||||
} // namespace voice_assistant
|
} // namespace voice_assistant
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_VOICE_ASSISTANT
|
||||||
|
|
|
@ -39,6 +39,9 @@ WaveshareEPaper4P2InBV2 = waveshare_epaper_ns.class_(
|
||||||
WaveshareEPaper5P8In = waveshare_epaper_ns.class_(
|
WaveshareEPaper5P8In = waveshare_epaper_ns.class_(
|
||||||
"WaveshareEPaper5P8In", WaveshareEPaper
|
"WaveshareEPaper5P8In", WaveshareEPaper
|
||||||
)
|
)
|
||||||
|
WaveshareEPaper5P8InV2 = waveshare_epaper_ns.class_(
|
||||||
|
"WaveshareEPaper5P8InV2", WaveshareEPaper
|
||||||
|
)
|
||||||
WaveshareEPaper7P5In = waveshare_epaper_ns.class_(
|
WaveshareEPaper7P5In = waveshare_epaper_ns.class_(
|
||||||
"WaveshareEPaper7P5In", WaveshareEPaper
|
"WaveshareEPaper7P5In", WaveshareEPaper
|
||||||
)
|
)
|
||||||
|
@ -80,6 +83,7 @@ MODELS = {
|
||||||
"4.20in": ("b", WaveshareEPaper4P2In),
|
"4.20in": ("b", WaveshareEPaper4P2In),
|
||||||
"4.20in-bv2": ("b", WaveshareEPaper4P2InBV2),
|
"4.20in-bv2": ("b", WaveshareEPaper4P2InBV2),
|
||||||
"5.83in": ("b", WaveshareEPaper5P8In),
|
"5.83in": ("b", WaveshareEPaper5P8In),
|
||||||
|
"5.83inv2": ("b", WaveshareEPaper5P8InV2),
|
||||||
"7.50in": ("b", WaveshareEPaper7P5In),
|
"7.50in": ("b", WaveshareEPaper7P5In),
|
||||||
"7.50in-bv2": ("b", WaveshareEPaper7P5InBV2),
|
"7.50in-bv2": ("b", WaveshareEPaper7P5InBV2),
|
||||||
"7.50in-bc": ("b", WaveshareEPaper7P5InBC),
|
"7.50in-bc": ("b", WaveshareEPaper7P5InBC),
|
||||||
|
|
|
@ -1037,6 +1037,88 @@ void WaveshareEPaper5P8In::dump_config() {
|
||||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================================
|
||||||
|
// 5.83in V2
|
||||||
|
// Datasheet/Specification/Reference:
|
||||||
|
// - https://www.waveshare.com/w/upload/3/37/5.83inch_e-Paper_V2_Specification.pdf
|
||||||
|
// - https://github.com/waveshare/e-Paper/blob/master/Arduino/epd5in83_V2/epd5in83_V2.cpp
|
||||||
|
// ========================================================
|
||||||
|
void WaveshareEPaper5P8InV2::initialize() {
|
||||||
|
// COMMAND POWER SETTING
|
||||||
|
this->command(0x01);
|
||||||
|
this->data(0x07);
|
||||||
|
this->data(0x07);
|
||||||
|
this->data(0x3f);
|
||||||
|
this->data(0x3f);
|
||||||
|
|
||||||
|
// COMMAND POWER ON
|
||||||
|
this->command(0x04);
|
||||||
|
delay(10);
|
||||||
|
this->wait_until_idle_();
|
||||||
|
|
||||||
|
// PANNEL SETTING
|
||||||
|
this->command(0x00);
|
||||||
|
this->data(0x1F);
|
||||||
|
|
||||||
|
// COMMAND RESOLUTION SETTING
|
||||||
|
this->command(0x61);
|
||||||
|
this->data(0x02);
|
||||||
|
this->data(0x88);
|
||||||
|
this->data(0x01);
|
||||||
|
this->data(0xE0);
|
||||||
|
|
||||||
|
this->command(0x15);
|
||||||
|
this->data(0x00);
|
||||||
|
|
||||||
|
// COMMAND TCON SETTING
|
||||||
|
this->command(0x60);
|
||||||
|
this->data(0x22);
|
||||||
|
|
||||||
|
// Do we need this?
|
||||||
|
// COMMAND PLL CONTROL
|
||||||
|
this->command(0x30);
|
||||||
|
this->data(0x3C); // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ
|
||||||
|
}
|
||||||
|
void HOT WaveshareEPaper5P8InV2::display() {
|
||||||
|
// Reuse the code from WaveshareEPaper4P2In::display()
|
||||||
|
// COMMAND VCM DC SETTING REGISTER
|
||||||
|
this->command(0x82);
|
||||||
|
this->data(0x12);
|
||||||
|
|
||||||
|
// COMMAND VCOM AND DATA INTERVAL SETTING
|
||||||
|
this->command(0x50);
|
||||||
|
this->data(0x97);
|
||||||
|
|
||||||
|
// COMMAND DATA START TRANSMISSION 1
|
||||||
|
this->command(0x10);
|
||||||
|
delay(2);
|
||||||
|
this->start_data_();
|
||||||
|
this->write_array(this->buffer_, this->get_buffer_length_());
|
||||||
|
this->end_data_();
|
||||||
|
delay(2);
|
||||||
|
|
||||||
|
// COMMAND DATA START TRANSMISSION 2
|
||||||
|
this->command(0x13);
|
||||||
|
delay(2);
|
||||||
|
this->start_data_();
|
||||||
|
this->write_array(this->buffer_, this->get_buffer_length_());
|
||||||
|
this->end_data_();
|
||||||
|
|
||||||
|
// COMMAND DISPLAY REFRESH
|
||||||
|
this->command(0x12);
|
||||||
|
}
|
||||||
|
int WaveshareEPaper5P8InV2::get_width_internal() { return 648; }
|
||||||
|
int WaveshareEPaper5P8InV2::get_height_internal() { return 480; }
|
||||||
|
void WaveshareEPaper5P8InV2::dump_config() {
|
||||||
|
LOG_DISPLAY("", "Waveshare E-Paper", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Model: 5.83inv2");
|
||||||
|
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||||
|
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||||
|
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
|
||||||
void WaveshareEPaper7P5InBV2::initialize() {
|
void WaveshareEPaper7P5InBV2::initialize() {
|
||||||
// COMMAND POWER SETTING
|
// COMMAND POWER SETTING
|
||||||
this->command(0x01);
|
this->command(0x01);
|
||||||
|
|
|
@ -284,6 +284,49 @@ class WaveshareEPaper5P8In : public WaveshareEPaper {
|
||||||
int get_height_internal() override;
|
int get_height_internal() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class WaveshareEPaper5P8InV2 : public WaveshareEPaper {
|
||||||
|
public:
|
||||||
|
void initialize() override;
|
||||||
|
|
||||||
|
void display() override;
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
void deep_sleep() override {
|
||||||
|
// COMMAND VCOM AND DATA INTERVAL SETTING
|
||||||
|
this->command(0x50);
|
||||||
|
this->data(0x17); // border floating
|
||||||
|
|
||||||
|
// COMMAND VCM DC SETTING
|
||||||
|
this->command(0x82);
|
||||||
|
// COMMAND PANEL SETTING
|
||||||
|
this->command(0x00);
|
||||||
|
|
||||||
|
delay(100); // NOLINT
|
||||||
|
|
||||||
|
// COMMAND POWER SETTING
|
||||||
|
this->command(0x01);
|
||||||
|
this->data(0x00);
|
||||||
|
this->data(0x00);
|
||||||
|
this->data(0x00);
|
||||||
|
this->data(0x00);
|
||||||
|
this->data(0x00);
|
||||||
|
delay(100); // NOLINT
|
||||||
|
|
||||||
|
// COMMAND POWER OFF
|
||||||
|
this->command(0x02);
|
||||||
|
this->wait_until_idle_();
|
||||||
|
// COMMAND DEEP SLEEP
|
||||||
|
this->command(0x07);
|
||||||
|
this->data(0xA5); // check byte
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int get_width_internal() override;
|
||||||
|
|
||||||
|
int get_height_internal() override;
|
||||||
|
};
|
||||||
|
|
||||||
class WaveshareEPaper7P5In : public WaveshareEPaper {
|
class WaveshareEPaper7P5In : public WaveshareEPaper {
|
||||||
public:
|
public:
|
||||||
void initialize() override;
|
void initialize() override;
|
||||||
|
|
|
@ -428,6 +428,9 @@ void WebServer::on_switch_update(switch_::Switch *obj, bool state) {
|
||||||
std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) {
|
std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) {
|
||||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||||
set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
|
set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
|
||||||
|
if (start_config == DETAIL_ALL) {
|
||||||
|
root["assumed_state"] = obj->assumed_state();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||||
|
|
|
@ -706,6 +706,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct dhcps_lease lease {};
|
struct dhcps_lease lease {};
|
||||||
|
lease.enable = true;
|
||||||
network::IPAddress start_address = info.ip.addr;
|
network::IPAddress start_address = info.ip.addr;
|
||||||
start_address[3] += 99;
|
start_address[3] += 99;
|
||||||
lease.start_ip.addr = static_cast<uint32_t>(start_address);
|
lease.start_ip.addr = static_cast<uint32_t>(start_address);
|
||||||
|
|
|
@ -1455,6 +1455,7 @@ class SplitDefault(Optional):
|
||||||
esp32_arduino=vol.UNDEFINED,
|
esp32_arduino=vol.UNDEFINED,
|
||||||
esp32_idf=vol.UNDEFINED,
|
esp32_idf=vol.UNDEFINED,
|
||||||
rp2040=vol.UNDEFINED,
|
rp2040=vol.UNDEFINED,
|
||||||
|
host=vol.UNDEFINED,
|
||||||
):
|
):
|
||||||
super().__init__(key)
|
super().__init__(key)
|
||||||
self._esp8266_default = vol.default_factory(esp8266)
|
self._esp8266_default = vol.default_factory(esp8266)
|
||||||
|
@ -1465,6 +1466,7 @@ class SplitDefault(Optional):
|
||||||
esp32_idf if esp32 is vol.UNDEFINED else esp32
|
esp32_idf if esp32 is vol.UNDEFINED else esp32
|
||||||
)
|
)
|
||||||
self._rp2040_default = vol.default_factory(rp2040)
|
self._rp2040_default = vol.default_factory(rp2040)
|
||||||
|
self._host_default = vol.default_factory(host)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default(self):
|
def default(self):
|
||||||
|
@ -1476,6 +1478,8 @@ class SplitDefault(Optional):
|
||||||
return self._esp32_idf_default
|
return self._esp32_idf_default
|
||||||
if CORE.is_rp2040:
|
if CORE.is_rp2040:
|
||||||
return self._rp2040_default
|
return self._rp2040_default
|
||||||
|
if CORE.is_host:
|
||||||
|
return self._host_default
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@default.setter
|
@default.setter
|
||||||
|
|
|
@ -7,8 +7,9 @@ ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
PLATFORM_ESP32 = "esp32"
|
PLATFORM_ESP32 = "esp32"
|
||||||
PLATFORM_ESP8266 = "esp8266"
|
PLATFORM_ESP8266 = "esp8266"
|
||||||
PLATFORM_RP2040 = "rp2040"
|
PLATFORM_RP2040 = "rp2040"
|
||||||
|
PLATFORM_HOST = "host"
|
||||||
|
|
||||||
TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]
|
TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_HOST]
|
||||||
|
|
||||||
SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
|
SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
|
||||||
HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
|
HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
|
||||||
|
@ -659,6 +660,7 @@ CONF_SLEEP_WHEN_DONE = "sleep_when_done"
|
||||||
CONF_SONY = "sony"
|
CONF_SONY = "sony"
|
||||||
CONF_SOURCE = "source"
|
CONF_SOURCE = "source"
|
||||||
CONF_SOURCE_ID = "source_id"
|
CONF_SOURCE_ID = "source_id"
|
||||||
|
CONF_SPEAKER = "speaker"
|
||||||
CONF_SPEED = "speed"
|
CONF_SPEED = "speed"
|
||||||
CONF_SPEED_COMMAND_TOPIC = "speed_command_topic"
|
CONF_SPEED_COMMAND_TOPIC = "speed_command_topic"
|
||||||
CONF_SPEED_COUNT = "speed_count"
|
CONF_SPEED_COUNT = "speed_count"
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue