Merge commit '8822b6c808881390b1bf77423ac78430bc0b6346' into optolink

This commit is contained in:
j0ta29 2023-05-10 20:11:03 +00:00
commit 25ee78d9b0
117 changed files with 4286 additions and 358 deletions

View file

@ -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]

View file

@ -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

View file

@ -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 =======================

View file

@ -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

View file

@ -1,133 +1,27 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins
from esphome.components import sensor, voltage_sampler from esphome.components import sensor, voltage_sampler
from esphome.components.esp32 import get_esp32_variant
from esphome.const import ( from esphome.const import (
CONF_ATTENUATION, CONF_ATTENUATION,
CONF_RAW,
CONF_ID, CONF_ID,
CONF_INPUT,
CONF_NUMBER, CONF_NUMBER,
CONF_PIN, CONF_PIN,
CONF_RAW,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_VOLT, UNIT_VOLT,
) )
from esphome.core import CORE from esphome.core import CORE
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32H2,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
from . import (
ATTENUATION_MODES,
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
validate_adc_pin,
)
AUTO_LOAD = ["voltage_sampler"] AUTO_LOAD = ["voltage_sampler"]
ATTENUATION_MODES = {
"0db": cg.global_ns.ADC_ATTEN_DB_0,
"2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
"6db": cg.global_ns.ADC_ATTEN_DB_6,
"11db": cg.global_ns.ADC_ATTEN_DB_11,
"auto": "auto",
}
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
# pin to adc1 channel mapping
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
VARIANT_ESP32: {
36: adc1_channel_t.ADC1_CHANNEL_0,
37: adc1_channel_t.ADC1_CHANNEL_1,
38: adc1_channel_t.ADC1_CHANNEL_2,
39: adc1_channel_t.ADC1_CHANNEL_3,
32: adc1_channel_t.ADC1_CHANNEL_4,
33: adc1_channel_t.ADC1_CHANNEL_5,
34: adc1_channel_t.ADC1_CHANNEL_6,
35: adc1_channel_t.ADC1_CHANNEL_7,
},
VARIANT_ESP32S2: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
3: adc1_channel_t.ADC1_CHANNEL_2,
4: adc1_channel_t.ADC1_CHANNEL_3,
5: adc1_channel_t.ADC1_CHANNEL_4,
6: adc1_channel_t.ADC1_CHANNEL_5,
7: adc1_channel_t.ADC1_CHANNEL_6,
8: adc1_channel_t.ADC1_CHANNEL_7,
9: adc1_channel_t.ADC1_CHANNEL_8,
10: adc1_channel_t.ADC1_CHANNEL_9,
},
VARIANT_ESP32S3: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
3: adc1_channel_t.ADC1_CHANNEL_2,
4: adc1_channel_t.ADC1_CHANNEL_3,
5: adc1_channel_t.ADC1_CHANNEL_4,
6: adc1_channel_t.ADC1_CHANNEL_5,
7: adc1_channel_t.ADC1_CHANNEL_6,
8: adc1_channel_t.ADC1_CHANNEL_7,
9: adc1_channel_t.ADC1_CHANNEL_8,
10: adc1_channel_t.ADC1_CHANNEL_9,
},
VARIANT_ESP32C3: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
VARIANT_ESP32H2: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
}
def validate_adc_pin(value):
if str(value).upper() == "VCC":
return cv.only_on_esp8266("VCC")
if str(value).upper() == "TEMPERATURE":
return cv.only_on_rp2040("TEMPERATURE")
if CORE.is_esp32:
value = pins.internal_gpio_input_pin_number(value)
variant = get_esp32_variant()
if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
return pins.internal_gpio_input_pin_schema(value)
if CORE.is_esp8266:
from esphome.components.esp8266.gpio import CONF_ANALOG
value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
value
)
if value != 17: # A0
raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
return pins.gpio_pin_schema(
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
)(value)
if CORE.is_rp2040:
value = pins.internal_gpio_input_pin_number(value)
if value not in (26, 27, 28, 29):
raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.")
return pins.internal_gpio_input_pin_schema(value)
raise NotImplementedError
def validate_config(config): def validate_config(config):
if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto": if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":

View file

@ -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;
} }

View file

@ -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")

View file

@ -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)

View file

@ -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

View file

@ -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; }

View file

@ -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,

View file

@ -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 {

View file

@ -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)

View file

@ -282,13 +282,17 @@ void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align
int scan_x1, scan_y1, scan_width, scan_height; int scan_x1, scan_y1, scan_width, scan_height;
glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
for (int glyph_x = scan_x1; glyph_x < scan_x1 + scan_width; glyph_x++) { {
for (int glyph_y = scan_y1; glyph_y < scan_y1 + scan_height; glyph_y++) { const int glyph_x_max = scan_x1 + scan_width;
const int glyph_y_max = scan_y1 + scan_height;
for (int glyph_x = scan_x1; glyph_x < glyph_x_max; glyph_x++) {
for (int glyph_y = scan_y1; glyph_y < glyph_y_max; glyph_y++) {
if (glyph.get_pixel(glyph_x, glyph_y)) { if (glyph.get_pixel(glyph_x, glyph_y)) {
this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color); this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
} }
} }
} }
}
x_at += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; x_at += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;

View file

@ -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)

View file

@ -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");
} }

View file

@ -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)

View 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

View 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

View 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]}")
)
)

View file

@ -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: {

View 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]))

View 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

View 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

View 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]))

View 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

View 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

View 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")

View file

@ -0,0 +1,5 @@
import esphome.codegen as cg
KEY_HOST = "host"
host_ns = cg.esphome_ns.namespace("host")

View 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

View 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

View 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

View 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

View 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

View file

@ -0,0 +1,13 @@
#pragma once
#ifdef USE_HOST
namespace esphome {
namespace host {
void setup_preferences();
} // namespace host
} // namespace esphome
#endif // USE_HOST

View file

@ -0,0 +1 @@
CODEOWNERS = ["@Philippe12"]

View 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

View 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

View 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))

View file

@ -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]))

View file

@ -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_{};
}; };

View file

@ -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")

View file

@ -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_;

View file

@ -2,8 +2,9 @@ import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome import pins from esphome import pins
from esphome.const import CONF_ID from esphome.const import CONF_ID, CONF_NUMBER
from esphome.components import microphone from esphome.components import microphone, esp32
from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin
from .. import ( from .. import (
i2s_audio_ns, i2s_audio_ns,
@ -16,26 +17,73 @@ from .. import (
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["i2s_audio"] DEPENDENCIES = ["i2s_audio"]
CONF_ADC_PIN = "adc_pin"
CONF_ADC_TYPE = "adc_type"
CONF_PDM = "pdm"
I2SAudioMicrophone = i2s_audio_ns.class_( I2SAudioMicrophone = i2s_audio_ns.class_(
"I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component
) )
CONFIG_SCHEMA = microphone.MICROPHONE_SCHEMA.extend( INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32]
PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3]
def validate_esp32_variant(config):
variant = esp32.get_esp32_variant()
if config[CONF_ADC_TYPE] == "external":
if config[CONF_PDM]:
if variant not in PDM_VARIANTS:
raise cv.Invalid(f"{variant} does not support PDM")
return config
if config[CONF_ADC_TYPE] == "internal":
if variant not in INTERNAL_ADC_VARIANTS:
raise cv.Invalid(f"{variant} does not have an internal ADC")
return config
raise NotImplementedError
BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(I2SAudioMicrophone), cv.GenerateID(): cv.declare_id(I2SAudioMicrophone),
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number,
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = cv.All(
cv.typed_schema(
{
"internal": BASE_SCHEMA.extend(
{
cv.Required(CONF_ADC_PIN): validate_adc_pin,
}
),
"external": BASE_SCHEMA.extend(
{
cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number,
cv.Required(CONF_PDM): cv.boolean,
}
),
},
key=CONF_ADC_TYPE,
),
validate_esp32_variant,
)
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
parent = await cg.get_variable(config[CONF_I2S_AUDIO_ID]) await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
cg.add(parent.register_audio_in(var))
if config[CONF_ADC_TYPE] == "internal":
variant = esp32.get_esp32_variant()
pin_num = config[CONF_ADC_PIN][CONF_NUMBER]
channel = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
cg.add(var.set_adc_channel(channel))
else:
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN])) cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
cg.add(var.set_pdm(config[CONF_PDM]))
await microphone.register_microphone(var, config) await microphone.register_microphone(var, config)

View file

@ -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,
}; };
#if SOC_I2S_SUPPORTS_ADC
if (this->adc_) {
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN);
i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_);
i2s_adc_enable(this->parent_->get_port());
} else {
#endif
if (this->pdm_)
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_PDM);
i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
i2s_pin_config_t pin_config = this->parent_->get_pin_config(); i2s_pin_config_t pin_config = this->parent_->get_pin_config();
pin_config.data_in_num = this->din_pin_; pin_config.data_in_num = this->din_pin_;
i2s_set_pin(this->parent_->get_port(), &pin_config); i2s_set_pin(this->parent_->get_port(), &pin_config);
#if SOC_I2S_SUPPORTS_ADC
}
#endif
this->state_ = microphone::STATE_RUNNING; this->state_ = microphone::STATE_RUNNING;
this->high_freq_.start(); this->high_freq_.start();
} }
void I2SAudioMicrophone::stop() { void I2SAudioMicrophone::stop() {
if (this->state_ == microphone::STATE_STOPPED) if (this->state_ == microphone::STATE_STOPPED || this->is_failed())
return; return;
this->state_ = microphone::STATE_STOPPING; this->state_ = microphone::STATE_STOPPING;
} }

View file

@ -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_;

View 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))

View 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

View 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

View file

@ -84,10 +84,19 @@ void ILI9XXXDisplay::fill(Color color) {
break; break;
case BITS_16: case BITS_16:
new_color = display::ColorUtil::color_to_565(color); new_color = display::ColorUtil::color_to_565(color);
for (uint32_t i = 0; i < this->get_buffer_length_() * 2; i = i + 2) { {
const uint32_t buffer_length_16_bits = this->get_buffer_length_() * 2;
if (((uint8_t) (new_color >> 8)) == ((uint8_t) new_color)) {
// Upper and lower is equal can use quicker memset operation. Takes ~20ms.
memset(this->buffer_, (uint8_t) new_color, buffer_length_16_bits);
} else {
// Slower set of both buffers. Takes ~30ms.
for (uint32_t i = 0; i < buffer_length_16_bits; i = i + 2) {
this->buffer_[i] = (uint8_t) (new_color >> 8); this->buffer_[i] = (uint8_t) (new_color >> 8);
this->buffer_[i + 1] = (uint8_t) new_color; this->buffer_[i + 1] = (uint8_t) new_color;
} }
}
}
return; return;
break; break;
default: default:

View file

@ -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(
{ {

View file

@ -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]);
} }

View file

@ -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

View 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

View 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

View 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

View 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 411 (data bits D0D7)
};
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

View 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))

View 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

View 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

View 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

View file

@ -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;
} }

View file

@ -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

View file

@ -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};
}; };

View 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

View 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, &reg_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

View 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

View file

@ -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();

View file

@ -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_;

View file

@ -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(

View 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

View 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

View file

@ -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_;

View file

@ -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

View file

@ -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]))

View file

@ -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

View file

@ -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};

View file

@ -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="_"
), ),

View file

@ -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

View file

@ -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);

View file

@ -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; };

View 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")

View 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

View 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

View file

@ -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,
), ),

View file

@ -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;

View file

@ -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};

View file

@ -8,14 +8,13 @@ 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())
return;
auto val = (*this->f_)(); auto val = (*this->f_)();
if (val.has_value()) { if (val.has_value()) {
this->publish_state(*val); this->publish_state(*val);
} }
} else if (!std::isnan(this->get_raw_state())) {
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; }
void TemplateSensor::set_template(std::function<optional<float>()> &&f) { this->f_ = f; } void TemplateSensor::set_template(std::function<optional<float>()> &&f) { this->f_ = f; }

View file

@ -7,14 +7,13 @@ 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())
return;
auto val = (*this->f_)(); auto val = (*this->f_)();
if (val.has_value()) { if (val.has_value()) {
this->publish_state(*val); this->publish_state(*val);
} }
} else if (this->has_state()) {
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; }
void TemplateTextSensor::set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; } void TemplateTextSensor::set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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),

View file

@ -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);

View file

@ -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;

View file

@ -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) {

View file

@ -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);

View file

@ -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

View file

@ -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