mirror of
https://github.com/esphome/esphome.git
synced 2025-08-10 20:29:24 +00:00
Compare commits
64 Commits
jesserockz
...
2023.5.2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
70aa5d0f6c | ||
![]() |
8fcec8e2cb | ||
![]() |
2d3b48f86f | ||
![]() |
99c10bc6de | ||
![]() |
f30f20db86 | ||
![]() |
9bf946e196 | ||
![]() |
bfaad1f28d | ||
![]() |
f90e9ba871 | ||
![]() |
726bdd7be2 | ||
![]() |
52db40eb41 | ||
![]() |
3c371a0c59 | ||
![]() |
c941bc4109 | ||
![]() |
cc76e5353c | ||
![]() |
11bb46e393 | ||
![]() |
71c4714a6e | ||
![]() |
a4e63c5f86 | ||
![]() |
c71e7d0132 | ||
![]() |
daa966975e | ||
![]() |
2e5757a3f0 | ||
![]() |
f66024b37c | ||
![]() |
c16ca7be13 | ||
![]() |
e13e754bc4 | ||
![]() |
6ccea59f71 | ||
![]() |
71b28be3c8 | ||
![]() |
e25d92e1f5 | ||
![]() |
65cda10884 | ||
![]() |
625126df68 | ||
![]() |
e0ee8ca17c | ||
![]() |
af95e781f5 | ||
![]() |
9b230a7d93 | ||
![]() |
e2f3e7c3a6 | ||
![]() |
2fd2e5ceb2 | ||
![]() |
d88358be8e | ||
![]() |
d80885c7a8 | ||
![]() |
c7c9c49f4e | ||
![]() |
2c160a8a25 | ||
![]() |
cc576cf1a9 | ||
![]() |
97a71482a9 | ||
![]() |
8822b6c808 | ||
![]() |
5099595aee | ||
![]() |
39a650ee54 | ||
![]() |
b19c7d462b | ||
![]() |
a8b821c213 | ||
![]() |
535003014b | ||
![]() |
3c05ae4e1a | ||
![]() |
c835b67bac | ||
![]() |
8c32941428 | ||
![]() |
b5dac00dcb | ||
![]() |
ffdc721c79 | ||
![]() |
0828a9fc11 | ||
![]() |
4b664b6f09 | ||
![]() |
679633245d | ||
![]() |
ce8a77c765 | ||
![]() |
2f78c4acfa | ||
![]() |
72f6841aac | ||
![]() |
10bd9b14fc | ||
![]() |
3498aade85 | ||
![]() |
c4539e10fb | ||
![]() |
2b3052e9d7 | ||
![]() |
e725e15f7a | ||
![]() |
4d1113e265 | ||
![]() |
52352ac27a | ||
![]() |
f60b2b754d | ||
![]() |
4a3f9712b2 |
@@ -84,6 +84,7 @@ esphome/components/esp32_ble_server/* @jesserockz
|
||||
esphome/components/esp32_camera_web_server/* @ayufan
|
||||
esphome/components/esp32_can/* @Sympatron
|
||||
esphome/components/esp32_improv/* @jesserockz
|
||||
esphome/components/esp32_rmt_led_strip/* @jesserockz
|
||||
esphome/components/esp8266/* @esphome/core
|
||||
esphome/components/ethernet_info/* @gtjadsonsantos
|
||||
esphome/components/exposure_notifications/* @OttoWinter
|
||||
@@ -95,6 +96,7 @@ esphome/components/feedback/* @ianchi
|
||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
||||
esphome/components/fs3000/* @kahrendt
|
||||
esphome/components/globals/* @esphome/core
|
||||
esphome/components/gp8403/* @jesserockz
|
||||
esphome/components/gpio/* @esphome/core
|
||||
esphome/components/gps/* @coogle
|
||||
esphome/components/graph/* @synco
|
||||
@@ -107,6 +109,7 @@ esphome/components/heatpumpir/* @rob-deutsch
|
||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||
esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/honeywellabp/* @RubyBailey
|
||||
esphome/components/host/* @esphome/core
|
||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
esphome/components/hte501/* @Stock-M
|
||||
esphome/components/hydreon_rgxx/* @functionpointer
|
||||
@@ -115,6 +118,7 @@ esphome/components/i2c/* @esphome/core
|
||||
esphome/components/i2s_audio/* @jesserockz
|
||||
esphome/components/i2s_audio/media_player/* @jesserockz
|
||||
esphome/components/i2s_audio/microphone/* @jesserockz
|
||||
esphome/components/i2s_audio/speaker/* @jesserockz
|
||||
esphome/components/ili9xxx/* @nielsnl68
|
||||
esphome/components/improv_base/* @esphome/core
|
||||
esphome/components/improv_serial/* @esphome/core
|
||||
@@ -237,7 +241,7 @@ esphome/components/shutdown/* @esphome/core @jsuanet
|
||||
esphome/components/sigma_delta_output/* @Cat-Ion
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/sm10bit_base/* @Cossid
|
||||
esphome/components/sm2135/* @BoukeHaarsma23
|
||||
esphome/components/sm2135/* @BoukeHaarsma23 @dd32 @matika77
|
||||
esphome/components/sm2235/* @Cossid
|
||||
esphome/components/sm2335/* @Cossid
|
||||
esphome/components/sml/* @alengwenus
|
||||
@@ -245,6 +249,7 @@ esphome/components/smt100/* @piechade
|
||||
esphome/components/sn74hc165/* @jesserockz
|
||||
esphome/components/socket/* @esphome/core
|
||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
|
||||
esphome/components/speaker/* @jesserockz
|
||||
esphome/components/spi/* @esphome/core
|
||||
esphome/components/sprinkler/* @kbx81
|
||||
esphome/components/sps30/* @martgras
|
||||
|
@@ -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"]
|
||||
|
||||
ATTENUATION_MODES = {
|
||||
"0db": cg.global_ns.ADC_ATTEN_DB_0,
|
||||
"2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
|
||||
"6db": cg.global_ns.ADC_ATTEN_DB_6,
|
||||
"11db": cg.global_ns.ADC_ATTEN_DB_11,
|
||||
"auto": "auto",
|
||||
}
|
||||
|
||||
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
|
||||
|
||||
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
|
||||
# pin to adc1 channel mapping
|
||||
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
|
||||
VARIANT_ESP32: {
|
||||
36: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
37: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
38: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
39: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
32: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
33: adc1_channel_t.ADC1_CHANNEL_5,
|
||||
34: adc1_channel_t.ADC1_CHANNEL_6,
|
||||
35: adc1_channel_t.ADC1_CHANNEL_7,
|
||||
},
|
||||
VARIANT_ESP32S2: {
|
||||
1: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
5: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
6: adc1_channel_t.ADC1_CHANNEL_5,
|
||||
7: adc1_channel_t.ADC1_CHANNEL_6,
|
||||
8: adc1_channel_t.ADC1_CHANNEL_7,
|
||||
9: adc1_channel_t.ADC1_CHANNEL_8,
|
||||
10: adc1_channel_t.ADC1_CHANNEL_9,
|
||||
},
|
||||
VARIANT_ESP32S3: {
|
||||
1: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
5: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
6: adc1_channel_t.ADC1_CHANNEL_5,
|
||||
7: adc1_channel_t.ADC1_CHANNEL_6,
|
||||
8: adc1_channel_t.ADC1_CHANNEL_7,
|
||||
9: adc1_channel_t.ADC1_CHANNEL_8,
|
||||
10: adc1_channel_t.ADC1_CHANNEL_9,
|
||||
},
|
||||
VARIANT_ESP32C3: {
|
||||
0: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
1: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
},
|
||||
VARIANT_ESP32H2: {
|
||||
0: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
1: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def validate_adc_pin(value):
|
||||
if str(value).upper() == "VCC":
|
||||
return cv.only_on_esp8266("VCC")
|
||||
|
||||
if str(value).upper() == "TEMPERATURE":
|
||||
return cv.only_on_rp2040("TEMPERATURE")
|
||||
|
||||
if CORE.is_esp32:
|
||||
value = pins.internal_gpio_input_pin_number(value)
|
||||
variant = get_esp32_variant()
|
||||
if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
|
||||
raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
|
||||
|
||||
if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
|
||||
raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
|
||||
return pins.internal_gpio_input_pin_schema(value)
|
||||
|
||||
if CORE.is_esp8266:
|
||||
from esphome.components.esp8266.gpio import CONF_ANALOG
|
||||
|
||||
value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
|
||||
value
|
||||
)
|
||||
|
||||
if value != 17: # A0
|
||||
raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
|
||||
return pins.gpio_pin_schema(
|
||||
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
|
||||
)(value)
|
||||
|
||||
if CORE.is_rp2040:
|
||||
value = pins.internal_gpio_input_pin_number(value)
|
||||
if value not in (26, 27, 28, 29):
|
||||
raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.")
|
||||
return pins.internal_gpio_input_pin_schema(value)
|
||||
|
||||
raise NotImplementedError
|
||||
|
@@ -1,133 +1,27 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
from esphome.const import (
|
||||
CONF_ATTENUATION,
|
||||
CONF_RAW,
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_NUMBER,
|
||||
CONF_PIN,
|
||||
CONF_RAW,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
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"]
|
||||
|
||||
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):
|
||||
if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
|
||||
|
@@ -4,7 +4,6 @@ from esphome.components import i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensor", "binary_sensor"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_APDS9960_ID = "apds9960_id"
|
||||
|
@@ -116,8 +116,12 @@ void APDS9960::setup() {
|
||||
APDS9960_WRITE_BYTE(0x80, val);
|
||||
}
|
||||
bool APDS9960::is_color_enabled_() const {
|
||||
return this->red_channel_ != nullptr || this->green_channel_ != nullptr || this->blue_channel_ != nullptr ||
|
||||
this->clear_channel_ != nullptr;
|
||||
#ifdef USE_SENSOR
|
||||
return this->red_sensor_ != nullptr || this->green_sensor_ != nullptr || this->blue_sensor_ != nullptr ||
|
||||
this->clear_sensor_ != nullptr;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void APDS9960::dump_config() {
|
||||
@@ -125,6 +129,15 @@ void APDS9960::dump_config() {
|
||||
LOG_I2C_DEVICE(this);
|
||||
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "Red channel", this->red_sensor_);
|
||||
LOG_SENSOR(" ", "Green channel", this->green_sensor_);
|
||||
LOG_SENSOR(" ", "Blue channel", this->blue_sensor_);
|
||||
LOG_SENSOR(" ", "Clear channel", this->clear_sensor_);
|
||||
LOG_SENSOR(" ", "Proximity", this->proximity_sensor_);
|
||||
#endif
|
||||
|
||||
if (this->is_failed()) {
|
||||
switch (this->error_code_) {
|
||||
case COMMUNICATION_FAILED:
|
||||
@@ -181,17 +194,22 @@ void APDS9960::read_color_data_(uint8_t status) {
|
||||
float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f;
|
||||
|
||||
ESP_LOGD(TAG, "Got clear=%.1f%% red=%.1f%% green=%.1f%% blue=%.1f%%", clear_perc, red_perc, green_perc, blue_perc);
|
||||
if (this->clear_channel_ != nullptr)
|
||||
this->clear_channel_->publish_state(clear_perc);
|
||||
if (this->red_channel_ != nullptr)
|
||||
this->red_channel_->publish_state(red_perc);
|
||||
if (this->green_channel_ != nullptr)
|
||||
this->green_channel_->publish_state(green_perc);
|
||||
if (this->blue_channel_ != nullptr)
|
||||
this->blue_channel_->publish_state(blue_perc);
|
||||
#ifdef USE_SENSOR
|
||||
if (this->clear_sensor_ != nullptr)
|
||||
this->clear_sensor_->publish_state(clear_perc);
|
||||
if (this->red_sensor_ != nullptr)
|
||||
this->red_sensor_->publish_state(red_perc);
|
||||
if (this->green_sensor_ != nullptr)
|
||||
this->green_sensor_->publish_state(green_perc);
|
||||
if (this->blue_sensor_ != nullptr)
|
||||
this->blue_sensor_->publish_state(blue_perc);
|
||||
#endif
|
||||
}
|
||||
void APDS9960::read_proximity_data_(uint8_t status) {
|
||||
if (this->proximity_ == nullptr)
|
||||
#ifndef USE_SENSOR
|
||||
return;
|
||||
#else
|
||||
if (this->proximity_sensor_ == nullptr)
|
||||
return;
|
||||
|
||||
if ((status & 0b10) == 0x00) {
|
||||
@@ -204,7 +222,8 @@ void APDS9960::read_proximity_data_(uint8_t status) {
|
||||
|
||||
float prox_perc = (prox / float(UINT8_MAX)) * 100.0f;
|
||||
ESP_LOGD(TAG, "Got proximity=%.1f%%", prox_perc);
|
||||
this->proximity_->publish_state(prox_perc);
|
||||
this->proximity_sensor_->publish_state(prox_perc);
|
||||
#endif
|
||||
}
|
||||
void APDS9960::read_gesture_data_() {
|
||||
if (!this->is_gesture_enabled_())
|
||||
@@ -256,28 +275,29 @@ void APDS9960::read_gesture_data_() {
|
||||
}
|
||||
}
|
||||
void APDS9960::report_gesture_(int gesture) {
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
binary_sensor::BinarySensor *bin;
|
||||
switch (gesture) {
|
||||
case 1:
|
||||
bin = this->up_direction_;
|
||||
bin = this->up_direction_binary_sensor_;
|
||||
this->gesture_up_started_ = false;
|
||||
this->gesture_down_started_ = false;
|
||||
ESP_LOGD(TAG, "Got gesture UP");
|
||||
break;
|
||||
case 2:
|
||||
bin = this->down_direction_;
|
||||
bin = this->down_direction_binary_sensor_;
|
||||
this->gesture_up_started_ = false;
|
||||
this->gesture_down_started_ = false;
|
||||
ESP_LOGD(TAG, "Got gesture DOWN");
|
||||
break;
|
||||
case 3:
|
||||
bin = this->left_direction_;
|
||||
bin = this->left_direction_binary_sensor_;
|
||||
this->gesture_left_started_ = false;
|
||||
this->gesture_right_started_ = false;
|
||||
ESP_LOGD(TAG, "Got gesture LEFT");
|
||||
break;
|
||||
case 4:
|
||||
bin = this->right_direction_;
|
||||
bin = this->right_direction_binary_sensor_;
|
||||
this->gesture_left_started_ = false;
|
||||
this->gesture_right_started_ = false;
|
||||
ESP_LOGD(TAG, "Got gesture RIGHT");
|
||||
@@ -290,6 +310,7 @@ void APDS9960::report_gesture_(int gesture) {
|
||||
bin->publish_state(true);
|
||||
bin->publish_state(false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void APDS9960::process_dataset_(int up, int down, int left, int right) {
|
||||
/* Algorithm: (see Figure 11 in datasheet)
|
||||
@@ -365,10 +386,22 @@ void APDS9960::process_dataset_(int up, int down, int left, int right) {
|
||||
}
|
||||
}
|
||||
float APDS9960::get_setup_priority() const { return setup_priority::DATA; }
|
||||
bool APDS9960::is_proximity_enabled_() const { return this->proximity_ != nullptr || this->is_gesture_enabled_(); }
|
||||
bool APDS9960::is_proximity_enabled_() const {
|
||||
return
|
||||
#ifdef USE_SENSOR
|
||||
this->proximity_sensor_ != nullptr
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
|| this->is_gesture_enabled_();
|
||||
}
|
||||
bool APDS9960::is_gesture_enabled_() const {
|
||||
return this->up_direction_ != nullptr || this->left_direction_ != nullptr || this->down_direction_ != nullptr ||
|
||||
this->right_direction_ != nullptr;
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
return this->up_direction_binary_sensor_ != nullptr || this->left_direction_binary_sensor_ != nullptr ||
|
||||
this->down_direction_binary_sensor_ != nullptr || this->right_direction_binary_sensor_ != nullptr;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace apds9960
|
||||
|
@@ -1,14 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace apds9960 {
|
||||
|
||||
class APDS9960 : public PollingComponent, public i2c::I2CDevice {
|
||||
#ifdef USE_SENSOR
|
||||
SUB_SENSOR(red)
|
||||
SUB_SENSOR(green)
|
||||
SUB_SENSOR(blue)
|
||||
SUB_SENSOR(clear)
|
||||
SUB_SENSOR(proximity)
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
SUB_BINARY_SENSOR(up_direction)
|
||||
SUB_BINARY_SENSOR(right_direction)
|
||||
SUB_BINARY_SENSOR(down_direction)
|
||||
SUB_BINARY_SENSOR(left_direction)
|
||||
#endif
|
||||
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
@@ -23,16 +43,6 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice {
|
||||
void set_gesture_gain(uint8_t gain) { this->gesture_gain_ = gain; }
|
||||
void set_gesture_wait_time(uint8_t wait_time) { this->gesture_wait_time_ = wait_time; }
|
||||
|
||||
void set_red_channel(sensor::Sensor *red_channel) { red_channel_ = red_channel; }
|
||||
void set_green_channel(sensor::Sensor *green_channel) { green_channel_ = green_channel; }
|
||||
void set_blue_channel(sensor::Sensor *blue_channel) { blue_channel_ = blue_channel; }
|
||||
void set_clear_channel(sensor::Sensor *clear_channel) { clear_channel_ = clear_channel; }
|
||||
void set_up_direction(binary_sensor::BinarySensor *up_direction) { up_direction_ = up_direction; }
|
||||
void set_right_direction(binary_sensor::BinarySensor *right_direction) { right_direction_ = right_direction; }
|
||||
void set_down_direction(binary_sensor::BinarySensor *down_direction) { down_direction_ = down_direction; }
|
||||
void set_left_direction(binary_sensor::BinarySensor *left_direction) { left_direction_ = left_direction; }
|
||||
void set_proximity(sensor::Sensor *proximity) { proximity_ = proximity; }
|
||||
|
||||
protected:
|
||||
bool is_color_enabled_() const;
|
||||
bool is_proximity_enabled_() const;
|
||||
@@ -50,15 +60,6 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice {
|
||||
uint8_t gesture_gain_;
|
||||
uint8_t gesture_wait_time_;
|
||||
|
||||
sensor::Sensor *red_channel_{nullptr};
|
||||
sensor::Sensor *green_channel_{nullptr};
|
||||
sensor::Sensor *blue_channel_{nullptr};
|
||||
sensor::Sensor *clear_channel_{nullptr};
|
||||
binary_sensor::BinarySensor *up_direction_{nullptr};
|
||||
binary_sensor::BinarySensor *right_direction_{nullptr};
|
||||
binary_sensor::BinarySensor *down_direction_{nullptr};
|
||||
binary_sensor::BinarySensor *left_direction_{nullptr};
|
||||
sensor::Sensor *proximity_{nullptr};
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
COMMUNICATION_FAILED,
|
||||
|
@@ -6,19 +6,14 @@ from . import APDS9960, CONF_APDS9960_ID
|
||||
|
||||
DEPENDENCIES = ["apds9960"]
|
||||
|
||||
DIRECTIONS = {
|
||||
"UP": "set_up_direction",
|
||||
"DOWN": "set_down_direction",
|
||||
"LEFT": "set_left_direction",
|
||||
"RIGHT": "set_right_direction",
|
||||
}
|
||||
DIRECTIONS = ["up", "down", "left", "right"]
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_MOVING
|
||||
).extend(
|
||||
{
|
||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
||||
cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
|
||||
cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, lower=True),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -26,5 +21,5 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_APDS9960_ID])
|
||||
var = await binary_sensor.new_binary_sensor(config)
|
||||
func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]])
|
||||
func = getattr(hub, f"set_{config[CONF_DIRECTION]}_direction_binary_sensor")
|
||||
cg.add(func(var))
|
||||
|
@@ -11,13 +11,7 @@ from . import APDS9960, CONF_APDS9960_ID
|
||||
|
||||
DEPENDENCIES = ["apds9960"]
|
||||
|
||||
TYPES = {
|
||||
"CLEAR": "set_clear_channel",
|
||||
"RED": "set_red_channel",
|
||||
"GREEN": "set_green_channel",
|
||||
"BLUE": "set_blue_channel",
|
||||
"PROXIMITY": "set_proximity",
|
||||
}
|
||||
TYPES = ["clear", "red", "green", "blue", "proximity"]
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
@@ -26,7 +20,7 @@ CONFIG_SCHEMA = sensor.sensor_schema(
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
|
||||
cv.Required(CONF_TYPE): cv.one_of(*TYPES, lower=True),
|
||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
||||
}
|
||||
)
|
||||
@@ -35,5 +29,5 @@ CONFIG_SCHEMA = sensor.sensor_schema(
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_APDS9960_ID])
|
||||
var = await sensor.new_sensor(config)
|
||||
func = getattr(hub, TYPES[config[CONF_TYPE]])
|
||||
func = getattr(hub, f"set_{config[CONF_TYPE]}_sensor")
|
||||
cg.add(func(var))
|
||||
|
@@ -223,6 +223,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
|
||||
msg.assumed_state = traits.get_is_assumed_state();
|
||||
msg.supports_position = traits.get_supports_position();
|
||||
msg.supports_tilt = traits.get_supports_tilt();
|
||||
msg.supports_stop = traits.get_supports_stop();
|
||||
msg.device_class = cover->get_device_class();
|
||||
msg.disabled_by_default = cover->is_disabled_by_default();
|
||||
msg.icon = cover->get_icon();
|
||||
@@ -978,6 +979,8 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
resp.manufacturer = "Espressif";
|
||||
#elif defined(USE_RP2040)
|
||||
resp.manufacturer = "Raspberry Pi";
|
||||
#elif defined(USE_HOST)
|
||||
resp.manufacturer = "Host";
|
||||
#endif
|
||||
resp.model = ESPHOME_BOARD;
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
@@ -996,7 +999,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
: bluetooth_proxy::PASSIVE_ONLY_VERSION;
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
resp.voice_assistant_version = 1;
|
||||
resp.voice_assistant_version = voice_assistant::global_voice_assistant->get_version();
|
||||
#endif
|
||||
return resp;
|
||||
}
|
||||
|
@@ -7,9 +7,10 @@ import requests
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
from esphome import git
|
||||
from esphome.components.packages import validate_source_shorthand
|
||||
from esphome.const import CONF_REF, CONF_WIFI
|
||||
from esphome.const import CONF_REF, CONF_WIFI, CONF_ESPHOME, CONF_PROJECT
|
||||
from esphome.wizard import wizard_file
|
||||
from esphome.yaml_util import dump
|
||||
|
||||
@@ -52,6 +53,17 @@ CONFIG_SCHEMA = cv.All(
|
||||
validate_full_url,
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
full_config = fv.full_config.get()[CONF_ESPHOME]
|
||||
if CONF_PROJECT not in full_config:
|
||||
raise cv.Invalid(
|
||||
"Dashboard import requires the `esphome` -> `project` information to be provided."
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
WIFI_CONFIG = """
|
||||
|
||||
wifi:
|
||||
|
@@ -282,10 +282,14 @@ void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align
|
||||
int scan_x1, scan_y1, scan_width, scan_height;
|
||||
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++) {
|
||||
if (glyph.get_pixel(glyph_x, glyph_y)) {
|
||||
this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
|
||||
{
|
||||
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)) {
|
||||
this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
0
esphome/components/esp32_rmt_led_strip/__init__.py
Normal file
0
esphome/components/esp32_rmt_led_strip/__init__.py
Normal file
207
esphome/components/esp32_rmt_led_strip/led_strip.cpp
Normal file
207
esphome/components/esp32_rmt_led_strip/led_strip.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
#include "led_strip.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <esp_attr.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_rmt_led_strip {
|
||||
|
||||
static const char *const TAG = "esp32_rmt_led_strip";
|
||||
|
||||
static const uint8_t RMT_CLK_DIV = 2;
|
||||
|
||||
void ESP32RMTLEDStripLightOutput::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip...");
|
||||
|
||||
size_t buffer_size = this->get_buffer_size_();
|
||||
|
||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
this->buf_ = allocator.allocate(buffer_size);
|
||||
if (this->buf_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Cannot allocate LED buffer!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->effect_data_ = allocator.allocate(this->num_leds_);
|
||||
if (this->effect_data_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Cannot allocate effect data!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
ExternalRAMAllocator<rmt_item32_t> rmt_allocator(ExternalRAMAllocator<rmt_item32_t>::ALLOW_FAILURE);
|
||||
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8); // 8 bits per byte, 1 rmt_item32_t per bit
|
||||
|
||||
rmt_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.channel = this->channel_;
|
||||
config.rmt_mode = RMT_MODE_TX;
|
||||
config.gpio_num = gpio_num_t(this->pin_);
|
||||
config.mem_block_num = 1;
|
||||
config.clk_div = RMT_CLK_DIV;
|
||||
config.tx_config.loop_en = false;
|
||||
config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
|
||||
config.tx_config.carrier_en = false;
|
||||
config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;
|
||||
config.tx_config.idle_output_en = true;
|
||||
|
||||
if (rmt_config(&config) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Cannot initialize RMT!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (rmt_driver_install(config.channel, 0, 0) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Cannot install RMT driver!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high,
|
||||
uint32_t bit1_low) {
|
||||
float ratio = (float) APB_CLK_FREQ / RMT_CLK_DIV / 1e09f;
|
||||
|
||||
// 0-bit
|
||||
this->bit0_.duration0 = (uint32_t) (ratio * bit0_high);
|
||||
this->bit0_.level0 = 1;
|
||||
this->bit0_.duration1 = (uint32_t) (ratio * bit0_low);
|
||||
this->bit0_.level1 = 0;
|
||||
// 1-bit
|
||||
this->bit1_.duration0 = (uint32_t) (ratio * bit1_high);
|
||||
this->bit1_.level0 = 1;
|
||||
this->bit1_.duration1 = (uint32_t) (ratio * bit1_low);
|
||||
this->bit1_.level1 = 0;
|
||||
}
|
||||
|
||||
void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
|
||||
// protect from refreshing too often
|
||||
uint32_t now = micros();
|
||||
if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) {
|
||||
// try again next loop iteration, so that this change won't get lost
|
||||
this->schedule_show();
|
||||
return;
|
||||
}
|
||||
this->last_refresh_ = now;
|
||||
this->mark_shown_();
|
||||
|
||||
ESP_LOGVV(TAG, "Writing RGB values to bus...");
|
||||
|
||||
if (rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000)) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "RMT TX timeout");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
delayMicroseconds(50);
|
||||
|
||||
size_t buffer_size = this->get_buffer_size_();
|
||||
|
||||
size_t size = 0;
|
||||
size_t len = 0;
|
||||
uint8_t *psrc = this->buf_;
|
||||
rmt_item32_t *pdest = this->rmt_buf_;
|
||||
while (size < buffer_size) {
|
||||
uint8_t b = *psrc;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
pdest->val = b & (1 << (7 - i)) ? this->bit1_.val : this->bit0_.val;
|
||||
pdest++;
|
||||
len++;
|
||||
}
|
||||
size++;
|
||||
psrc++;
|
||||
}
|
||||
|
||||
if (rmt_write_items(this->channel_, this->rmt_buf_, len, false) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "RMT TX error");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index) const {
|
||||
int32_t r = 0, g = 0, b = 0;
|
||||
switch (this->rgb_order_) {
|
||||
case ORDER_RGB:
|
||||
r = 0;
|
||||
g = 1;
|
||||
b = 2;
|
||||
break;
|
||||
case ORDER_RBG:
|
||||
r = 0;
|
||||
g = 2;
|
||||
b = 1;
|
||||
break;
|
||||
case ORDER_GRB:
|
||||
r = 1;
|
||||
g = 0;
|
||||
b = 2;
|
||||
break;
|
||||
case ORDER_GBR:
|
||||
r = 2;
|
||||
g = 0;
|
||||
b = 1;
|
||||
break;
|
||||
case ORDER_BGR:
|
||||
r = 2;
|
||||
g = 1;
|
||||
b = 0;
|
||||
break;
|
||||
case ORDER_BRG:
|
||||
r = 1;
|
||||
g = 2;
|
||||
b = 0;
|
||||
break;
|
||||
}
|
||||
uint8_t multiplier = this->is_rgbw_ ? 4 : 3;
|
||||
return {this->buf_ + (index * multiplier) + r,
|
||||
this->buf_ + (index * multiplier) + g,
|
||||
this->buf_ + (index * multiplier) + b,
|
||||
this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr,
|
||||
&this->effect_data_[index],
|
||||
&this->correction_};
|
||||
}
|
||||
|
||||
void ESP32RMTLEDStripLightOutput::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:");
|
||||
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
|
||||
ESP_LOGCONFIG(TAG, " Channel: %u", this->channel_);
|
||||
const char *rgb_order;
|
||||
switch (this->rgb_order_) {
|
||||
case ORDER_RGB:
|
||||
rgb_order = "RGB";
|
||||
break;
|
||||
case ORDER_RBG:
|
||||
rgb_order = "RBG";
|
||||
break;
|
||||
case ORDER_GRB:
|
||||
rgb_order = "GRB";
|
||||
break;
|
||||
case ORDER_GBR:
|
||||
rgb_order = "GBR";
|
||||
break;
|
||||
case ORDER_BGR:
|
||||
rgb_order = "BGR";
|
||||
break;
|
||||
case ORDER_BRG:
|
||||
rgb_order = "BRG";
|
||||
break;
|
||||
default:
|
||||
rgb_order = "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " RGB Order: %s", rgb_order);
|
||||
ESP_LOGCONFIG(TAG, " Max refresh rate: %u", *this->max_refresh_rate_);
|
||||
ESP_LOGCONFIG(TAG, " Number of LEDs: %u", this->num_leds_);
|
||||
}
|
||||
|
||||
float ESP32RMTLEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
} // namespace esp32_rmt_led_strip
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
87
esphome/components/esp32_rmt_led_strip/led_strip.h
Normal file
87
esphome/components/esp32_rmt_led_strip/led_strip.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/light/addressable_light.h"
|
||||
#include "esphome/components/light/light_output.h"
|
||||
#include "esphome/core/color.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/rmt.h>
|
||||
#include <esp_err.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_rmt_led_strip {
|
||||
|
||||
enum RGBOrder : uint8_t {
|
||||
ORDER_RGB,
|
||||
ORDER_RBG,
|
||||
ORDER_GRB,
|
||||
ORDER_GBR,
|
||||
ORDER_BGR,
|
||||
ORDER_BRG,
|
||||
};
|
||||
|
||||
class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||
public:
|
||||
void setup() override;
|
||||
void write_state(light::LightState *state) override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
int32_t size() const override { return this->num_leds_; }
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
if (this->is_rgbw_) {
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::RGB_WHITE});
|
||||
} else {
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB});
|
||||
}
|
||||
return traits;
|
||||
}
|
||||
|
||||
void set_pin(uint8_t pin) { this->pin_ = pin; }
|
||||
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
|
||||
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
|
||||
|
||||
/// Set a maximum refresh rate in µs as some lights do not like being updated too often.
|
||||
void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
|
||||
|
||||
void set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low);
|
||||
|
||||
void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
|
||||
void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; }
|
||||
|
||||
void clear_effect_data() override {
|
||||
for (int i = 0; i < this->size(); i++)
|
||||
this->effect_data_[i] = 0;
|
||||
}
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
light::ESPColorView get_view_internal(int32_t index) const override;
|
||||
|
||||
size_t get_buffer_size_() const { return this->num_leds_ * (3 + this->is_rgbw_); }
|
||||
|
||||
uint8_t *buf_{nullptr};
|
||||
uint8_t *effect_data_{nullptr};
|
||||
rmt_item32_t *rmt_buf_{nullptr};
|
||||
|
||||
uint8_t pin_;
|
||||
uint16_t num_leds_;
|
||||
bool is_rgbw_;
|
||||
|
||||
rmt_item32_t bit0_, bit1_;
|
||||
RGBOrder rgb_order_;
|
||||
rmt_channel_t channel_;
|
||||
|
||||
uint32_t last_refresh_{0};
|
||||
optional<uint32_t> max_refresh_rate_{};
|
||||
};
|
||||
|
||||
} // namespace esp32_rmt_led_strip
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
151
esphome/components/esp32_rmt_led_strip/light.py
Normal file
151
esphome/components/esp32_rmt_led_strip/light.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import esp32, light
|
||||
from esphome.const import (
|
||||
CONF_CHIPSET,
|
||||
CONF_MAX_REFRESH_RATE,
|
||||
CONF_NUM_LEDS,
|
||||
CONF_OUTPUT_ID,
|
||||
CONF_PIN,
|
||||
CONF_RGB_ORDER,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
esp32_rmt_led_strip_ns = cg.esphome_ns.namespace("esp32_rmt_led_strip")
|
||||
ESP32RMTLEDStripLightOutput = esp32_rmt_led_strip_ns.class_(
|
||||
"ESP32RMTLEDStripLightOutput", light.AddressableLight
|
||||
)
|
||||
|
||||
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
|
||||
|
||||
RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder")
|
||||
|
||||
RGB_ORDERS = {
|
||||
"RGB": RGBOrder.ORDER_RGB,
|
||||
"RBG": RGBOrder.ORDER_RBG,
|
||||
"GRB": RGBOrder.ORDER_GRB,
|
||||
"GBR": RGBOrder.ORDER_GBR,
|
||||
"BGR": RGBOrder.ORDER_BGR,
|
||||
"BRG": RGBOrder.ORDER_BRG,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class LEDStripTimings:
|
||||
bit0_high: int
|
||||
bit0_low: int
|
||||
bit1_high: int
|
||||
bit1_low: int
|
||||
|
||||
|
||||
CHIPSETS = {
|
||||
"WS2812": LEDStripTimings(400, 1000, 1000, 400),
|
||||
"SK6812": LEDStripTimings(300, 900, 600, 600),
|
||||
"APA106": LEDStripTimings(350, 1360, 1360, 350),
|
||||
"SM16703": LEDStripTimings(300, 900, 1360, 350),
|
||||
}
|
||||
|
||||
|
||||
CONF_IS_RGBW = "is_rgbw"
|
||||
CONF_BIT0_HIGH = "bit0_high"
|
||||
CONF_BIT0_LOW = "bit0_low"
|
||||
CONF_BIT1_HIGH = "bit1_high"
|
||||
CONF_BIT1_LOW = "bit1_low"
|
||||
CONF_RMT_CHANNEL = "rmt_channel"
|
||||
|
||||
RMT_CHANNELS = {
|
||||
esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7],
|
||||
esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3],
|
||||
esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3],
|
||||
esp32.const.VARIANT_ESP32C3: [0, 1],
|
||||
}
|
||||
|
||||
|
||||
def _validate_rmt_channel(value):
|
||||
variant = esp32.get_esp32_variant()
|
||||
if variant not in RMT_CHANNELS:
|
||||
raise cv.Invalid(f"ESP32 variant {variant} does not support RMT.")
|
||||
if value not in RMT_CHANNELS[variant]:
|
||||
raise cv.Invalid(
|
||||
f"RMT channel {value} is not supported for ESP32 variant {variant}."
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
light.ADDRESSABLE_LIGHT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ESP32RMTLEDStripLightOutput),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
|
||||
cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True),
|
||||
cv.Required(CONF_RMT_CHANNEL): _validate_rmt_channel,
|
||||
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
|
||||
cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
|
||||
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
|
||||
cv.Inclusive(
|
||||
CONF_BIT0_HIGH,
|
||||
"custom",
|
||||
): cv.positive_time_period_microseconds,
|
||||
cv.Inclusive(
|
||||
CONF_BIT0_LOW,
|
||||
"custom",
|
||||
): cv.positive_time_period_microseconds,
|
||||
cv.Inclusive(
|
||||
CONF_BIT1_HIGH,
|
||||
"custom",
|
||||
): cv.positive_time_period_microseconds,
|
||||
cv.Inclusive(
|
||||
CONF_BIT1_LOW,
|
||||
"custom",
|
||||
): cv.positive_time_period_microseconds,
|
||||
}
|
||||
),
|
||||
cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
await light.register_light(var, config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_num_leds(config[CONF_NUM_LEDS]))
|
||||
cg.add(var.set_pin(config[CONF_PIN]))
|
||||
|
||||
if CONF_MAX_REFRESH_RATE in config:
|
||||
cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
|
||||
|
||||
if CONF_CHIPSET in config:
|
||||
chipset = CHIPSETS[config[CONF_CHIPSET]]
|
||||
cg.add(
|
||||
var.set_led_params(
|
||||
chipset.bit0_high,
|
||||
chipset.bit0_low,
|
||||
chipset.bit1_high,
|
||||
chipset.bit1_low,
|
||||
)
|
||||
)
|
||||
else:
|
||||
cg.add(
|
||||
var.set_led_params(
|
||||
config[CONF_BIT0_HIGH],
|
||||
config[CONF_BIT0_LOW],
|
||||
config[CONF_BIT1_HIGH],
|
||||
config[CONF_BIT1_LOW],
|
||||
)
|
||||
)
|
||||
|
||||
cg.add(var.set_rgb_order(config[CONF_RGB_ORDER]))
|
||||
cg.add(var.set_is_rgbw(config[CONF_IS_RGBW]))
|
||||
|
||||
cg.add(
|
||||
var.set_rmt_channel(
|
||||
getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}")
|
||||
)
|
||||
)
|
@@ -106,20 +106,18 @@ void EZOSensor::loop() {
|
||||
break;
|
||||
}
|
||||
|
||||
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
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
if (buf[0] == 1) {
|
||||
std::string payload = reinterpret_cast<char *>(&buf[1]);
|
||||
if (!payload.empty()) {
|
||||
switch (to_run->command_type) {
|
||||
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);
|
||||
if (!val.has_value()) {
|
||||
ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
|
||||
@@ -154,7 +152,10 @@ void EZOSensor::loop() {
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
case EzoCommandType::EZO_CUSTOM: {
|
||||
|
40
esphome/components/gp8403/__init__.py
Normal file
40
esphome/components/gp8403/__init__.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ID, CONF_VOLTAGE
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
|
||||
gp8403_ns = cg.esphome_ns.namespace("gp8403")
|
||||
GP8403 = gp8403_ns.class_("GP8403", cg.Component, i2c.I2CDevice)
|
||||
|
||||
GP8403Voltage = gp8403_ns.enum("GP8403Voltage")
|
||||
|
||||
CONF_GP8403_ID = "gp8403_id"
|
||||
|
||||
VOLTAGES = {
|
||||
"5V": GP8403Voltage.GP8403_VOLTAGE_5V,
|
||||
"10V": GP8403Voltage.GP8403_VOLTAGE_10V,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(GP8403),
|
||||
cv.Required(CONF_VOLTAGE): cv.enum(VOLTAGES, upper=True),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(i2c.i2c_device_schema(0x58))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_voltage(config[CONF_VOLTAGE]))
|
21
esphome/components/gp8403/gp8403.cpp
Normal file
21
esphome/components/gp8403/gp8403.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "gp8403.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace gp8403 {
|
||||
|
||||
static const char *const TAG = "gp8403";
|
||||
|
||||
static const uint8_t RANGE_REGISTER = 0x01;
|
||||
|
||||
void GP8403::setup() { this->write_register(RANGE_REGISTER, (uint8_t *) (&this->voltage_), 1); }
|
||||
|
||||
void GP8403::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "GP8403:");
|
||||
ESP_LOGCONFIG(TAG, " Voltage: %dV", this->voltage_ == GP8403_VOLTAGE_5V ? 5 : 10);
|
||||
LOG_I2C_DEVICE(this);
|
||||
}
|
||||
|
||||
} // namespace gp8403
|
||||
} // namespace esphome
|
27
esphome/components/gp8403/gp8403.h
Normal file
27
esphome/components/gp8403/gp8403.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace gp8403 {
|
||||
|
||||
enum GP8403Voltage {
|
||||
GP8403_VOLTAGE_5V = 0x00,
|
||||
GP8403_VOLTAGE_10V = 0x11,
|
||||
};
|
||||
|
||||
class GP8403 : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
void set_voltage(gp8403::GP8403Voltage voltage) { this->voltage_ = voltage; }
|
||||
|
||||
protected:
|
||||
GP8403Voltage voltage_;
|
||||
};
|
||||
|
||||
} // namespace gp8403
|
||||
} // namespace esphome
|
31
esphome/components/gp8403/output/__init__.py
Normal file
31
esphome/components/gp8403/output/__init__.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome.components import i2c, output
|
||||
from esphome.const import CONF_ID, CONF_CHANNEL
|
||||
|
||||
from .. import gp8403_ns, GP8403, CONF_GP8403_ID
|
||||
|
||||
DEPENDENCIES = ["gp8403"]
|
||||
|
||||
GP8403Output = gp8403_ns.class_(
|
||||
"GP8403Output", cg.Component, i2c.I2CDevice, output.FloatOutput
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(GP8403Output),
|
||||
cv.GenerateID(CONF_GP8403_ID): cv.use_id(GP8403),
|
||||
cv.Required(CONF_CHANNEL): cv.one_of(0, 1),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await output.register_output(var, config)
|
||||
|
||||
await cg.register_parented(var, config[CONF_GP8403_ID])
|
||||
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
26
esphome/components/gp8403/output/gp8403_output.cpp
Normal file
26
esphome/components/gp8403/output/gp8403_output.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "gp8403_output.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace gp8403 {
|
||||
|
||||
static const char *const TAG = "gp8403.output";
|
||||
|
||||
static const uint8_t OUTPUT_REGISTER = 0x02;
|
||||
|
||||
void GP8403Output::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "GP8403 Output:");
|
||||
ESP_LOGCONFIG(TAG, " Channel: %u", this->channel_);
|
||||
}
|
||||
|
||||
void GP8403Output::write_state(float state) {
|
||||
uint16_t value = ((uint16_t) (state * 4095)) << 4;
|
||||
i2c::ErrorCode err = this->parent_->write_register(OUTPUT_REGISTER + (2 * this->channel_), (uint8_t *) &value, 2);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Error writing to GP8403, code %d", err);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gp8403
|
||||
} // namespace esphome
|
25
esphome/components/gp8403/output/gp8403_output.h
Normal file
25
esphome/components/gp8403/output/gp8403_output.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include "esphome/components/gp8403/gp8403.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace gp8403 {
|
||||
|
||||
class GP8403Output : public Component, public output::FloatOutput, public Parented<GP8403> {
|
||||
public:
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA - 1; }
|
||||
|
||||
void set_channel(uint8_t channel) { this->channel_ = channel; }
|
||||
|
||||
void write_state(float state) override;
|
||||
|
||||
protected:
|
||||
uint8_t channel_;
|
||||
};
|
||||
|
||||
} // namespace gp8403
|
||||
} // namespace esphome
|
38
esphome/components/host/__init__.py
Normal file
38
esphome/components/host/__init__.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from esphome.const import (
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from .const import KEY_HOST
|
||||
|
||||
# force import gpio to register pin schema
|
||||
from .gpio import host_pin_to_code # noqa
|
||||
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["network"]
|
||||
|
||||
|
||||
def set_core_data(config):
|
||||
CORE.data[KEY_HOST] = {}
|
||||
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "host"
|
||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "host"
|
||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version(1, 0, 0)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema({}),
|
||||
set_core_data,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_build_flag("-DUSE_HOST")
|
||||
cg.add_define("ESPHOME_BOARD", "host")
|
||||
cg.add_platformio_option("platform", "platformio/native")
|
5
esphome/components/host/const.py
Normal file
5
esphome/components/host/const.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
KEY_HOST = "host"
|
||||
|
||||
host_ns = cg.esphome_ns.namespace("host")
|
77
esphome/components/host/core.cpp
Normal file
77
esphome/components/host/core.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "preferences.h"
|
||||
|
||||
#include <sched.h>
|
||||
#include <time.h>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void IRAM_ATTR HOT yield() { ::sched_yield(); }
|
||||
uint32_t IRAM_ATTR HOT millis() {
|
||||
struct timespec spec;
|
||||
clock_gettime(CLOCK_MONOTONIC, &spec);
|
||||
time_t seconds = spec.tv_sec;
|
||||
uint32_t ms = round(spec.tv_nsec / 1e6);
|
||||
return ((uint32_t) seconds) * 1000U + ms;
|
||||
}
|
||||
void IRAM_ATTR HOT delay(uint32_t ms) {
|
||||
struct timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
ts.tv_nsec = (ms % 1000) * 1000000;
|
||||
int res;
|
||||
do {
|
||||
res = nanosleep(&ts, &ts);
|
||||
} while (res != 0 && errno == EINTR);
|
||||
}
|
||||
uint32_t IRAM_ATTR HOT micros() {
|
||||
struct timespec spec;
|
||||
clock_gettime(CLOCK_MONOTONIC, &spec);
|
||||
time_t seconds = spec.tv_sec;
|
||||
uint32_t us = round(spec.tv_nsec / 1e3);
|
||||
return ((uint32_t) seconds) * 1000000U + us;
|
||||
}
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) {
|
||||
struct timespec ts;
|
||||
ts.tv_sec = us / 1000000U;
|
||||
ts.tv_nsec = (us % 1000000U) * 1000U;
|
||||
int res;
|
||||
do {
|
||||
res = nanosleep(&ts, &ts);
|
||||
} while (res != 0 && errno == EINTR);
|
||||
}
|
||||
void arch_restart() { exit(0); }
|
||||
void arch_init() {
|
||||
// pass
|
||||
}
|
||||
void IRAM_ATTR HOT arch_feed_wdt() {
|
||||
// pass
|
||||
}
|
||||
|
||||
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
|
||||
uint32_t arch_get_cpu_cycle_count() {
|
||||
struct timespec spec;
|
||||
clock_gettime(CLOCK_MONOTONIC, &spec);
|
||||
time_t seconds = spec.tv_sec;
|
||||
uint32_t us = spec.tv_nsec;
|
||||
return ((uint32_t) seconds) * 1000000000U + us;
|
||||
}
|
||||
uint32_t arch_get_cpu_freq_hz() { return 1000000000U; }
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
void setup();
|
||||
void loop();
|
||||
int main() {
|
||||
esphome::host::setup_preferences();
|
||||
setup();
|
||||
while (true) {
|
||||
loop();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // USE_HOST
|
59
esphome/components/host/gpio.cpp
Normal file
59
esphome/components/host/gpio.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include "gpio.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace host {
|
||||
|
||||
static const char *const TAG = "host";
|
||||
|
||||
struct ISRPinArg {
|
||||
uint8_t pin;
|
||||
bool inverted;
|
||||
};
|
||||
|
||||
ISRInternalGPIOPin HostGPIOPin::to_isr() const {
|
||||
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
arg->pin = pin_;
|
||||
arg->inverted = inverted_;
|
||||
return ISRInternalGPIOPin((void *) arg);
|
||||
}
|
||||
|
||||
void HostGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
|
||||
ESP_LOGD(TAG, "Attaching interrupt %p to pin %d and mode %d", func, pin_, (uint32_t) type);
|
||||
}
|
||||
void HostGPIOPin::pin_mode(gpio::Flags flags) { ESP_LOGD(TAG, "Setting pin %d mode to %02X", pin_, (uint32_t) flags); }
|
||||
|
||||
std::string HostGPIOPin::dump_summary() const {
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool HostGPIOPin::digital_read() { return inverted_; }
|
||||
void HostGPIOPin::digital_write(bool value) {
|
||||
// pass
|
||||
ESP_LOGD(TAG, "Setting pin %d to %s", pin_, value != inverted_ ? "HIGH" : "LOW");
|
||||
}
|
||||
void HostGPIOPin::detach_interrupt() const {}
|
||||
|
||||
} // namespace host
|
||||
|
||||
using namespace host;
|
||||
|
||||
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
return arg->inverted;
|
||||
}
|
||||
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
|
||||
// pass
|
||||
}
|
||||
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
ESP_LOGD(TAG, "Clearing interrupt for pin %d", arg->pin);
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_HOST
|
37
esphome/components/host/gpio.h
Normal file
37
esphome/components/host/gpio.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace host {
|
||||
|
||||
class HostGPIOPin : public InternalGPIOPin {
|
||||
public:
|
||||
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||
|
||||
void setup() override { pin_mode(flags_); }
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
bool digital_read() override;
|
||||
void digital_write(bool value) override;
|
||||
std::string dump_summary() const override;
|
||||
void detach_interrupt() const override;
|
||||
ISRInternalGPIOPin to_isr() const override;
|
||||
uint8_t get_pin() const override { return pin_; }
|
||||
bool is_inverted() const override { return inverted_; }
|
||||
|
||||
protected:
|
||||
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||
|
||||
uint8_t pin_;
|
||||
bool inverted_;
|
||||
gpio::Flags flags_;
|
||||
};
|
||||
|
||||
} // namespace host
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_HOST
|
73
esphome/components/host/gpio.py
Normal file
73
esphome/components/host/gpio.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import logging
|
||||
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_INVERTED,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OPEN_DRAIN,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
from esphome import pins
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from .const import host_ns
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
HostGPIOPin = host_ns.class_("HostGPIOPin", cg.InternalGPIOPin)
|
||||
|
||||
|
||||
def _translate_pin(value):
|
||||
if isinstance(value, dict) or value is None:
|
||||
raise cv.Invalid(
|
||||
"This variable only supports pin numbers, not full pin schemas "
|
||||
"(with inverted and mode)."
|
||||
)
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
pass
|
||||
if value.startswith("GPIO"):
|
||||
return cv.int_(value[len("GPIO") :].strip())
|
||||
return value
|
||||
|
||||
|
||||
def validate_gpio_pin(value):
|
||||
return _translate_pin(value)
|
||||
|
||||
|
||||
HOST_PIN_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HostGPIOPin),
|
||||
cv.Required(CONF_NUMBER): validate_gpio_pin,
|
||||
cv.Optional(CONF_MODE, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_INPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULLDOWN, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register("host", HOST_PIN_SCHEMA)
|
||||
async def host_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
36
esphome/components/host/preferences.cpp
Normal file
36
esphome/components/host/preferences.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include "preferences.h"
|
||||
#include <cstring>
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace host {
|
||||
|
||||
static const char *const TAG = "host.preferences";
|
||||
|
||||
class HostPreferences : public ESPPreferences {
|
||||
public:
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { return {}; }
|
||||
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type) override { return {}; }
|
||||
|
||||
bool sync() override { return true; }
|
||||
bool reset() override { return true; }
|
||||
};
|
||||
|
||||
void setup_preferences() {
|
||||
auto *pref = new HostPreferences(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
global_preferences = pref;
|
||||
}
|
||||
|
||||
} // namespace host
|
||||
|
||||
ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_HOST
|
13
esphome/components/host/preferences.h
Normal file
13
esphome/components/host/preferences.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_HOST
|
||||
|
||||
namespace esphome {
|
||||
namespace host {
|
||||
|
||||
void setup_preferences();
|
||||
|
||||
} // namespace host
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_HOST
|
@@ -42,8 +42,8 @@ I2S_PORTS = {
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(I2SAudioComponent),
|
||||
cv.Required(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
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])
|
||||
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]))
|
||||
if CONF_I2S_BCLK_PIN in config:
|
||||
cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN]))
|
||||
|
@@ -19,15 +19,6 @@ class I2SAudioComponent : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
void register_audio_in(I2SAudioIn *in) {
|
||||
this->audio_in_ = in;
|
||||
in->set_parent(this);
|
||||
}
|
||||
void register_audio_out(I2SAudioOut *out) {
|
||||
this->audio_out_ = out;
|
||||
out->set_parent(this);
|
||||
}
|
||||
|
||||
i2s_pin_config_t get_pin_config() const {
|
||||
return {
|
||||
.mck_io_num = I2S_PIN_NO_CHANGE,
|
||||
@@ -38,8 +29,8 @@ class I2SAudioComponent : public Component {
|
||||
};
|
||||
}
|
||||
|
||||
void set_bclk_pin(uint8_t pin) { this->bclk_pin_ = pin; }
|
||||
void set_lrclk_pin(uint8_t pin) { this->lrclk_pin_ = pin; }
|
||||
void set_bclk_pin(int pin) { this->bclk_pin_ = pin; }
|
||||
void set_lrclk_pin(int pin) { this->lrclk_pin_ = pin; }
|
||||
|
||||
void lock() { this->lock_.lock(); }
|
||||
bool try_lock() { return this->lock_.try_lock(); }
|
||||
@@ -53,8 +44,8 @@ class I2SAudioComponent : public Component {
|
||||
I2SAudioIn *audio_in_{nullptr};
|
||||
I2SAudioOut *audio_out_{nullptr};
|
||||
|
||||
uint8_t bclk_pin_;
|
||||
uint8_t lrclk_pin_;
|
||||
int bclk_pin_{I2S_PIN_NO_CHANGE};
|
||||
int lrclk_pin_;
|
||||
i2s_port_t port_{};
|
||||
};
|
||||
|
||||
|
@@ -84,8 +84,7 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
await media_player.register_media_player(var, config)
|
||||
|
||||
parent = await cg.get_variable(config[CONF_I2S_AUDIO_ID])
|
||||
cg.add(parent.register_audio_out(var))
|
||||
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]))
|
||||
@@ -98,5 +97,5 @@ async def to_code(config):
|
||||
|
||||
cg.add_library("WiFiClientSecure", 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")
|
||||
|
@@ -22,14 +22,14 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
||||
this->start();
|
||||
}
|
||||
}
|
||||
if (this->i2s_state_ != I2S_STATE_RUNNING) {
|
||||
return;
|
||||
}
|
||||
if (call.get_volume().has_value()) {
|
||||
this->volume = call.get_volume().value();
|
||||
this->set_volume_(volume);
|
||||
this->unmute_();
|
||||
}
|
||||
if (this->i2s_state_ != I2S_STATE_RUNNING) {
|
||||
return;
|
||||
}
|
||||
if (call.get_command().has_value()) {
|
||||
switch (call.get_command().value()) {
|
||||
case media_player::MEDIA_PLAYER_COMMAND_PLAY:
|
||||
@@ -97,7 +97,8 @@ void I2SAudioMediaPlayer::unmute_() {
|
||||
this->muted_ = false;
|
||||
}
|
||||
void I2SAudioMediaPlayer::set_volume_(float volume, bool publish) {
|
||||
this->audio_->setVolume(remap<uint8_t, float>(volume, 0.0f, 1.0f, 0, 21));
|
||||
if (this->audio_ != nullptr)
|
||||
this->audio_->setVolume(remap<uint8_t, float>(volume, 0.0f, 1.0f, 0, 21));
|
||||
if (publish)
|
||||
this->volume = volume;
|
||||
}
|
||||
@@ -132,7 +133,7 @@ void I2SAudioMediaPlayer::play_() {
|
||||
|
||||
void I2SAudioMediaPlayer::start() { this->i2s_state_ = I2S_STATE_STARTING; }
|
||||
void I2SAudioMediaPlayer::start_() {
|
||||
if (this->parent_->try_lock()) {
|
||||
if (!this->parent_->try_lock()) {
|
||||
return; // Waiting for another i2s to return lock
|
||||
}
|
||||
|
||||
@@ -141,7 +142,7 @@ void I2SAudioMediaPlayer::start_() {
|
||||
this->audio_ = make_unique<Audio>(true, this->internal_dac_mode_, this->parent_->get_port());
|
||||
} else {
|
||||
#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();
|
||||
pin_config.data_out_num = this->dout_pin_;
|
||||
@@ -155,15 +156,26 @@ void I2SAudioMediaPlayer::start_() {
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
}
|
||||
#endif
|
||||
|
||||
this->i2s_state_ = I2S_STATE_RUNNING;
|
||||
this->high_freq_.start();
|
||||
this->audio_->setVolume(remap<uint8_t, float>(this->volume, 0.0f, 1.0f, 0, 21));
|
||||
if (this->current_url_.has_value()) {
|
||||
this->audio_->connecttohost(this->current_url_.value().c_str());
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
void I2SAudioMediaPlayer::stop() { this->i2s_state_ = I2S_STATE_STOPPING; }
|
||||
void I2SAudioMediaPlayer::stop() {
|
||||
if (this->i2s_state_ == I2S_STATE_STOPPED) {
|
||||
return;
|
||||
}
|
||||
if (this->i2s_state_ == I2S_STATE_STARTING) {
|
||||
this->i2s_state_ = I2S_STATE_STOPPED;
|
||||
return;
|
||||
}
|
||||
this->i2s_state_ = I2S_STATE_STOPPING;
|
||||
}
|
||||
void I2SAudioMediaPlayer::stop_() {
|
||||
if (this->audio_->isRunning()) {
|
||||
this->audio_->stopSong();
|
||||
@@ -207,6 +219,12 @@ void I2SAudioMediaPlayer::dump_config() {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, " External DAC channels: %d", this->external_dac_channels_);
|
||||
ESP_LOGCONFIG(TAG, " I2S DOUT Pin: %d", this->dout_pin_);
|
||||
LOG_PIN(" Mute Pin: ", this->mute_pin_);
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@@ -2,8 +2,9 @@ import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.components import microphone
|
||||
from esphome.const import CONF_ID, CONF_NUMBER
|
||||
from esphome.components import microphone, esp32
|
||||
from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin
|
||||
|
||||
from .. import (
|
||||
i2s_audio_ns,
|
||||
@@ -16,26 +17,73 @@ from .. import (
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["i2s_audio"]
|
||||
|
||||
CONF_ADC_PIN = "adc_pin"
|
||||
CONF_ADC_TYPE = "adc_type"
|
||||
CONF_PDM = "pdm"
|
||||
|
||||
I2SAudioMicrophone = i2s_audio_ns.class_(
|
||||
"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(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
|
||||
cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number,
|
||||
}
|
||||
).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):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
parent = await cg.get_variable(config[CONF_I2S_AUDIO_ID])
|
||||
cg.add(parent.register_audio_in(var))
|
||||
await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
|
||||
|
||||
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
|
||||
if config[CONF_ADC_TYPE] == "internal":
|
||||
variant = esp32.get_esp32_variant()
|
||||
pin_num = config[CONF_ADC_PIN][CONF_NUMBER]
|
||||
channel = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
|
||||
cg.add(var.set_adc_channel(channel))
|
||||
else:
|
||||
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
|
||||
cg.add(var.set_pdm(config[CONF_PDM]))
|
||||
|
||||
await microphone.register_microphone(var, config)
|
||||
|
@@ -17,15 +17,36 @@ static const char *const TAG = "i2s_audio.microphone";
|
||||
void I2SAudioMicrophone::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone...");
|
||||
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_() {
|
||||
if (!this->parent_->try_lock()) {
|
||||
return; // Waiting for another i2s to return lock
|
||||
}
|
||||
i2s_driver_config_t config = {
|
||||
.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM),
|
||||
.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX),
|
||||
.sample_rate = 16000,
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
|
||||
@@ -40,19 +61,38 @@ void I2SAudioMicrophone::start_() {
|
||||
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
||||
};
|
||||
|
||||
i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
if (this->adc_) {
|
||||
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN);
|
||||
i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
|
||||
|
||||
i2s_pin_config_t pin_config = this->parent_->get_pin_config();
|
||||
pin_config.data_in_num = this->din_pin_;
|
||||
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_set_pin(this->parent_->get_port(), &pin_config);
|
||||
i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
|
||||
|
||||
i2s_pin_config_t pin_config = this->parent_->get_pin_config();
|
||||
pin_config.data_in_num = this->din_pin_;
|
||||
|
||||
i2s_set_pin(this->parent_->get_port(), &pin_config);
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
}
|
||||
#endif
|
||||
this->state_ = microphone::STATE_RUNNING;
|
||||
this->high_freq_.start();
|
||||
}
|
||||
|
||||
void I2SAudioMicrophone::stop() {
|
||||
if (this->state_ == microphone::STATE_STOPPED)
|
||||
if (this->state_ == microphone::STATE_STOPPED || this->is_failed())
|
||||
return;
|
||||
if (this->state_ == microphone::STATE_STARTING) {
|
||||
this->state_ = microphone::STATE_STOPPED;
|
||||
return;
|
||||
}
|
||||
this->state_ = microphone::STATE_STOPPING;
|
||||
}
|
||||
|
||||
|
@@ -18,14 +18,27 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||
|
||||
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:
|
||||
void start_();
|
||||
void stop_();
|
||||
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_;
|
||||
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
|
87
esphome/components/i2s_audio/speaker/__init__.py
Normal file
87
esphome/components/i2s_audio/speaker/__init__.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_ID, CONF_MODE
|
||||
from esphome.components import esp32, speaker
|
||||
|
||||
from .. import (
|
||||
CONF_I2S_AUDIO_ID,
|
||||
CONF_I2S_DOUT_PIN,
|
||||
I2SAudioComponent,
|
||||
I2SAudioOut,
|
||||
i2s_audio_ns,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["i2s_audio"]
|
||||
|
||||
I2SAudioSpeaker = i2s_audio_ns.class_(
|
||||
"I2SAudioSpeaker", cg.Component, speaker.Speaker, I2SAudioOut
|
||||
)
|
||||
|
||||
i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t")
|
||||
|
||||
CONF_MUTE_PIN = "mute_pin"
|
||||
CONF_DAC_TYPE = "dac_type"
|
||||
|
||||
INTERNAL_DAC_OPTIONS = {
|
||||
"left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
|
||||
"right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
|
||||
"stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
|
||||
}
|
||||
|
||||
EXTERNAL_DAC_OPTIONS = ["mono", "stereo"]
|
||||
|
||||
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
|
||||
|
||||
|
||||
def validate_esp32_variant(config):
|
||||
if config[CONF_DAC_TYPE] != "internal":
|
||||
return config
|
||||
variant = esp32.get_esp32_variant()
|
||||
if variant in NO_INTERNAL_DAC_VARIANTS:
|
||||
raise cv.Invalid(f"{variant} does not have an internal DAC")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.typed_schema(
|
||||
{
|
||||
"internal": speaker.SPEAKER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(I2SAudioSpeaker),
|
||||
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
|
||||
cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
"external": speaker.SPEAKER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(I2SAudioSpeaker),
|
||||
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
|
||||
cv.Required(
|
||||
CONF_I2S_DOUT_PIN
|
||||
): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_MODE, default="mono"): cv.one_of(
|
||||
*EXTERNAL_DAC_OPTIONS, lower=True
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
},
|
||||
key=CONF_DAC_TYPE,
|
||||
),
|
||||
validate_esp32_variant,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await speaker.register_speaker(var, config)
|
||||
|
||||
await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
|
||||
|
||||
if config[CONF_DAC_TYPE] == "internal":
|
||||
cg.add(var.set_internal_dac_mode(config[CONF_MODE]))
|
||||
else:
|
||||
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
|
||||
cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1))
|
212
esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp
Normal file
212
esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp
Normal file
@@ -0,0 +1,212 @@
|
||||
#include "i2s_audio_speaker.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <driver/i2s.h>
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace i2s_audio {
|
||||
|
||||
static const size_t BUFFER_COUNT = 10;
|
||||
|
||||
static const char *const TAG = "i2s_audio.speaker";
|
||||
|
||||
void I2SAudioSpeaker::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up I2S Audio Speaker...");
|
||||
|
||||
this->buffer_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(DataEvent));
|
||||
this->event_queue_ = xQueueCreate(20, sizeof(TaskEvent));
|
||||
}
|
||||
|
||||
void I2SAudioSpeaker::start() { this->state_ = speaker::STATE_STARTING; }
|
||||
void I2SAudioSpeaker::start_() {
|
||||
if (!this->parent_->try_lock()) {
|
||||
return; // Waiting for another i2s component to return lock
|
||||
}
|
||||
this->state_ = speaker::STATE_RUNNING;
|
||||
|
||||
xTaskCreate(I2SAudioSpeaker::player_task, "speaker_task", 8192, (void *) this, 0, &this->player_task_handle_);
|
||||
}
|
||||
|
||||
void I2SAudioSpeaker::player_task(void *params) {
|
||||
I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params;
|
||||
|
||||
TaskEvent event;
|
||||
event.type = TaskEventType::STARTING;
|
||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
||||
|
||||
i2s_driver_config_t config = {
|
||||
.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX),
|
||||
.sample_rate = 16000,
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
|
||||
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = 1024,
|
||||
.use_apll = false,
|
||||
.tx_desc_auto_clear = true,
|
||||
.fixed_mclk = I2S_PIN_NO_CHANGE,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
|
||||
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
||||
};
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
|
||||
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_DAC_BUILT_IN);
|
||||
}
|
||||
#endif
|
||||
|
||||
i2s_driver_install(this_speaker->parent_->get_port(), &config, 0, nullptr);
|
||||
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
if (this_speaker->internal_dac_mode_ == I2S_DAC_CHANNEL_DISABLE) {
|
||||
#endif
|
||||
i2s_pin_config_t pin_config = this_speaker->parent_->get_pin_config();
|
||||
pin_config.data_out_num = this_speaker->dout_pin_;
|
||||
|
||||
i2s_set_pin(this_speaker->parent_->get_port(), &pin_config);
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
} else {
|
||||
i2s_set_dac_mode(this_speaker->internal_dac_mode_);
|
||||
}
|
||||
#endif
|
||||
|
||||
DataEvent data_event;
|
||||
|
||||
event.type = TaskEventType::STARTED;
|
||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
||||
|
||||
int16_t buffer[BUFFER_SIZE / 2];
|
||||
|
||||
while (true) {
|
||||
if (xQueueReceive(this_speaker->buffer_queue_, &data_event, 100 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||
break; // End of audio from main thread
|
||||
}
|
||||
if (data_event.stop) {
|
||||
// Stop signal from main thread
|
||||
while (xQueueReceive(this_speaker->buffer_queue_, &data_event, 0) == pdTRUE) {
|
||||
// Flush queue
|
||||
}
|
||||
break;
|
||||
}
|
||||
size_t bytes_written;
|
||||
|
||||
memmove(buffer, data_event.data, data_event.len);
|
||||
size_t remaining = data_event.len / 2;
|
||||
size_t current = 0;
|
||||
|
||||
while (remaining > 0) {
|
||||
uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF);
|
||||
|
||||
esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written,
|
||||
(100 / portTICK_PERIOD_MS));
|
||||
if (err != ESP_OK) {
|
||||
event = {.type = TaskEventType::WARNING, .err = err};
|
||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
||||
continue;
|
||||
}
|
||||
remaining--;
|
||||
current++;
|
||||
}
|
||||
|
||||
event.type = TaskEventType::PLAYING;
|
||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
||||
}
|
||||
|
||||
i2s_zero_dma_buffer(this_speaker->parent_->get_port());
|
||||
|
||||
event.type = TaskEventType::STOPPING;
|
||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
||||
|
||||
i2s_stop(this_speaker->parent_->get_port());
|
||||
i2s_driver_uninstall(this_speaker->parent_->get_port());
|
||||
|
||||
event.type = TaskEventType::STOPPED;
|
||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
||||
|
||||
while (true) {
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
void I2SAudioSpeaker::stop() {
|
||||
if (this->state_ == speaker::STATE_STOPPED)
|
||||
return;
|
||||
if (this->state_ == speaker::STATE_STARTING) {
|
||||
this->state_ = speaker::STATE_STOPPED;
|
||||
return;
|
||||
}
|
||||
this->state_ = speaker::STATE_STOPPING;
|
||||
DataEvent data;
|
||||
data.stop = true;
|
||||
xQueueSendToFront(this->buffer_queue_, &data, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void I2SAudioSpeaker::watch_() {
|
||||
TaskEvent event;
|
||||
if (xQueueReceive(this->event_queue_, &event, 0) == pdTRUE) {
|
||||
switch (event.type) {
|
||||
case TaskEventType::STARTING:
|
||||
case TaskEventType::STARTED:
|
||||
case TaskEventType::STOPPING:
|
||||
break;
|
||||
case TaskEventType::PLAYING:
|
||||
this->status_clear_warning();
|
||||
break;
|
||||
case TaskEventType::STOPPED:
|
||||
this->parent_->unlock();
|
||||
this->state_ = speaker::STATE_STOPPED;
|
||||
vTaskDelete(this->player_task_handle_);
|
||||
this->player_task_handle_ = nullptr;
|
||||
break;
|
||||
case TaskEventType::WARNING:
|
||||
ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(event.err));
|
||||
this->status_set_warning();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void I2SAudioSpeaker::loop() {
|
||||
switch (this->state_) {
|
||||
case speaker::STATE_STARTING:
|
||||
this->start_();
|
||||
break;
|
||||
case speaker::STATE_RUNNING:
|
||||
this->watch_();
|
||||
break;
|
||||
case speaker::STATE_STOPPING:
|
||||
case speaker::STATE_STOPPED:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool I2SAudioSpeaker::play(const uint8_t *data, size_t length) {
|
||||
if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) {
|
||||
this->start();
|
||||
}
|
||||
size_t remaining = length;
|
||||
size_t index = 0;
|
||||
while (remaining > 0) {
|
||||
DataEvent event;
|
||||
event.stop = false;
|
||||
size_t to_send_length = std::min(remaining, BUFFER_SIZE);
|
||||
event.len = to_send_length;
|
||||
memcpy(event.data, data + index, to_send_length);
|
||||
if (xQueueSend(this->buffer_queue_, &event, 100 / portTICK_PERIOD_MS) == pdTRUE) {
|
||||
remaining -= to_send_length;
|
||||
index += to_send_length;
|
||||
}
|
||||
App.feed_wdt();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace i2s_audio
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
81
esphome/components/i2s_audio/speaker/i2s_audio_speaker.h
Normal file
81
esphome/components/i2s_audio/speaker/i2s_audio_speaker.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "../i2s_audio.h"
|
||||
|
||||
#include <driver/i2s.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
|
||||
#include "esphome/components/speaker/speaker.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace i2s_audio {
|
||||
|
||||
static const size_t BUFFER_SIZE = 1024;
|
||||
|
||||
enum class TaskEventType : uint8_t {
|
||||
STARTING = 0,
|
||||
STARTED,
|
||||
PLAYING,
|
||||
STOPPING,
|
||||
STOPPED,
|
||||
WARNING = 255,
|
||||
};
|
||||
|
||||
struct TaskEvent {
|
||||
TaskEventType type;
|
||||
esp_err_t err;
|
||||
};
|
||||
|
||||
struct DataEvent {
|
||||
bool stop;
|
||||
size_t len;
|
||||
uint8_t data[BUFFER_SIZE];
|
||||
};
|
||||
|
||||
class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAudioOut {
|
||||
public:
|
||||
float get_setup_priority() const override { return esphome::setup_priority::LATE; }
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
|
||||
void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; }
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; }
|
||||
#endif
|
||||
void set_external_dac_channels(uint8_t channels) { this->external_dac_channels_ = channels; }
|
||||
|
||||
void start();
|
||||
void stop() override;
|
||||
|
||||
bool play(const uint8_t *data, size_t length) override;
|
||||
|
||||
protected:
|
||||
void start_();
|
||||
// void stop_();
|
||||
void watch_();
|
||||
|
||||
static void player_task(void *params);
|
||||
|
||||
TaskHandle_t player_task_handle_{nullptr};
|
||||
QueueHandle_t buffer_queue_;
|
||||
QueueHandle_t event_queue_;
|
||||
|
||||
uint8_t dout_pin_{0};
|
||||
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
|
||||
#endif
|
||||
uint8_t external_dac_channels_;
|
||||
};
|
||||
|
||||
} // namespace i2s_audio
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
@@ -84,9 +84,18 @@ void ILI9XXXDisplay::fill(Color color) {
|
||||
break;
|
||||
case BITS_16:
|
||||
new_color = display::ColorUtil::color_to_565(color);
|
||||
for (uint32_t i = 0; i < this->get_buffer_length_() * 2; i = i + 2) {
|
||||
this->buffer_[i] = (uint8_t) (new_color >> 8);
|
||||
this->buffer_[i + 1] = (uint8_t) new_color;
|
||||
{
|
||||
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 + 1] = (uint8_t) new_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
break;
|
||||
|
@@ -33,7 +33,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(KeyCollector),
|
||||
cv.GenerateID(CONF_SOURCE_ID): cv.use_id(key_provider.KeyProvider),
|
||||
cv.Optional(CONF_SOURCE_ID): cv.use_id(key_provider.KeyProvider),
|
||||
cv.Optional(CONF_MIN_LENGTH): cv.int_,
|
||||
cv.Optional(CONF_MAX_LENGTH): cv.int_,
|
||||
cv.Optional(CONF_START_KEYS): cv.string,
|
||||
@@ -55,8 +55,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
source = await cg.get_variable(config[CONF_SOURCE_ID])
|
||||
cg.add(var.set_provider(source))
|
||||
if CONF_SOURCE_ID in config:
|
||||
source = await cg.get_variable(config[CONF_SOURCE_ID])
|
||||
cg.add(var.set_provider(source))
|
||||
if CONF_MIN_LENGTH in config:
|
||||
cg.add(var.set_min_length(config[CONF_MIN_LENGTH]))
|
||||
if CONF_MAX_LENGTH in config:
|
||||
|
@@ -52,6 +52,8 @@ void KeyCollector::clear(bool progress_update) {
|
||||
this->progress_trigger_->trigger(this->result_, 0);
|
||||
}
|
||||
|
||||
void KeyCollector::send_key(uint8_t key) { this->key_pressed_(key); }
|
||||
|
||||
void KeyCollector::key_pressed_(uint8_t key) {
|
||||
this->last_key_time_ = millis();
|
||||
if (!this->start_keys_.empty() && !this->start_key_) {
|
||||
|
@@ -27,6 +27,7 @@ class KeyCollector : public Component {
|
||||
void set_timeout(int timeout) { this->timeout_ = timeout; };
|
||||
|
||||
void clear(bool progress_update = true);
|
||||
void send_key(uint8_t key);
|
||||
|
||||
protected:
|
||||
void key_pressed_(uint8_t key);
|
||||
|
@@ -25,7 +25,7 @@ class PulseLightEffect : public LightEffect {
|
||||
return;
|
||||
}
|
||||
auto call = this->state_->turn_on();
|
||||
float out = this->on_ ? 1.0 : 0.0;
|
||||
float out = this->on_ ? this->max_brightness : this->min_brightness;
|
||||
call.set_brightness_if_supported(out);
|
||||
this->on_ = !this->on_;
|
||||
call.set_transition_length_if_supported(this->transition_length_);
|
||||
@@ -41,11 +41,18 @@ class PulseLightEffect : public LightEffect {
|
||||
|
||||
void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
||||
|
||||
void set_min_max_brightness(float min, float max) {
|
||||
this->min_brightness = min;
|
||||
this->max_brightness = max;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool on_ = false;
|
||||
uint32_t last_color_change_{0};
|
||||
uint32_t transition_length_{};
|
||||
uint32_t update_interval_{};
|
||||
float min_brightness{0.0};
|
||||
float max_brightness{1.0};
|
||||
};
|
||||
|
||||
/// Random effect. Sets random colors every 10 seconds and slowly transitions between them.
|
||||
|
@@ -28,6 +28,8 @@ from esphome.const import (
|
||||
CONF_NUM_LEDS,
|
||||
CONF_RANDOM,
|
||||
CONF_SEQUENCE,
|
||||
CONF_MAX_BRIGHTNESS,
|
||||
CONF_MIN_BRIGHTNESS,
|
||||
)
|
||||
from esphome.util import Registry
|
||||
from .types import (
|
||||
@@ -174,12 +176,19 @@ async def automation_effect_to_code(config, effect_id):
|
||||
cv.Optional(
|
||||
CONF_UPDATE_INTERVAL, default="1s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_MIN_BRIGHTNESS, default="0%"): cv.percentage,
|
||||
cv.Optional(CONF_MAX_BRIGHTNESS, default="100%"): cv.percentage,
|
||||
},
|
||||
)
|
||||
async def pulse_effect_to_code(config, effect_id):
|
||||
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||
cg.add(effect.set_transition_length(config[CONF_TRANSITION_LENGTH]))
|
||||
cg.add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||
cg.add(
|
||||
effect.set_min_max_brightness(
|
||||
config[CONF_MIN_BRIGHTNESS], config[CONF_MAX_BRIGHTNESS]
|
||||
)
|
||||
)
|
||||
return effect
|
||||
|
||||
|
||||
|
@@ -17,6 +17,9 @@ from esphome.const import (
|
||||
CONF_TAG,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TX_BUFFER_SIZE,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
|
||||
@@ -141,7 +144,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
esp8266=UART0,
|
||||
esp32=UART0,
|
||||
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_LOGS, default={}): cv.Schema(
|
||||
{
|
||||
|
@@ -145,6 +145,9 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
|
||||
if (xPortGetFreeHeapSize() < 2048)
|
||||
return;
|
||||
#endif
|
||||
#ifdef USE_HOST
|
||||
puts(msg);
|
||||
#endif
|
||||
|
||||
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) {
|
||||
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_; }
|
||||
#endif
|
||||
|
||||
void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) {
|
||||
this->log_callback_.add(std::move(callback));
|
||||
}
|
||||
@@ -294,7 +301,10 @@ void Logger::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Logger:");
|
||||
ESP_LOGCONFIG(TAG, " Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]);
|
||||
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_]);
|
||||
#endif
|
||||
|
||||
for (auto &it : this->log_levels_) {
|
||||
ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.tag.c_str(), LOG_LEVELS[it.level]);
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ namespace esphome {
|
||||
|
||||
namespace logger {
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
|
||||
/** Enum for logging UART selection
|
||||
*
|
||||
* Advanced configuration (pin selection, etc) is not supported.
|
||||
@@ -52,6 +53,7 @@ enum UARTSelection {
|
||||
UART_SELECTION_USB_CDC,
|
||||
#endif // USE_RP2040
|
||||
};
|
||||
#endif // USE_ESP32 || USE_ESP8266
|
||||
|
||||
class Logger : public Component {
|
||||
public:
|
||||
@@ -66,10 +68,11 @@ class Logger : public Component {
|
||||
#ifdef USE_ESP_IDF
|
||||
uart_port_t get_uart_num() const { return uart_num_; }
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
|
||||
void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
|
||||
/// Get the UART used by the logger.
|
||||
UARTSelection get_uart() const;
|
||||
#endif
|
||||
|
||||
/// Set the log level of the specified tag.
|
||||
void set_log_level(const std::string &tag, int log_level);
|
||||
@@ -139,7 +142,9 @@ class Logger : public Component {
|
||||
char *tx_buffer_{nullptr};
|
||||
int tx_buffer_at_{0};
|
||||
int tx_buffer_size_{0};
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
|
||||
UARTSelection uart_{UART_SELECTION_UART0};
|
||||
#endif
|
||||
#ifdef USE_ARDUINO
|
||||
Stream *hw_serial_{nullptr};
|
||||
#endif
|
||||
|
18
esphome/components/mdns/mdns_host.cpp
Normal file
18
esphome/components/mdns/mdns_host.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "mdns_component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mdns {
|
||||
|
||||
void MDNSComponent::setup() { this->compile_records_(); }
|
||||
|
||||
void MDNSComponent::on_shutdown() {}
|
||||
|
||||
} // namespace mdns
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -23,6 +23,9 @@ bool is_connected() {
|
||||
return wifi::global_wifi_component->is_connected();
|
||||
#endif
|
||||
|
||||
#ifdef USE_HOST
|
||||
return true; // Assume its connected
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -57,6 +57,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_SULPHUR_DIOXIDE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_VOLUME,
|
||||
DEVICE_CLASS_VOLUME_STORAGE,
|
||||
@@ -109,6 +110,7 @@ DEVICE_CLASSES = [
|
||||
DEVICE_CLASS_SULPHUR_DIOXIDE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_VOLUME,
|
||||
DEVICE_CLASS_VOLUME_STORAGE,
|
||||
|
@@ -6,15 +6,5 @@ namespace 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 esphome
|
||||
|
@@ -12,7 +12,7 @@ enum NumberMode : uint8_t {
|
||||
NUMBER_MODE_SLIDER = 2,
|
||||
};
|
||||
|
||||
class NumberTraits : public EntityBase_DeviceClass {
|
||||
class NumberTraits : public EntityBase_DeviceClass, public EntityBase_UnitOfMeasurement {
|
||||
public:
|
||||
// Set/get the number value boundaries.
|
||||
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; }
|
||||
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.
|
||||
void set_mode(NumberMode mode) { this->mode_ = mode; }
|
||||
NumberMode get_mode() const { return this->mode_; }
|
||||
@@ -37,7 +32,6 @@ class NumberTraits : public EntityBase_DeviceClass {
|
||||
float min_value_ = NAN;
|
||||
float max_value_ = NAN;
|
||||
float step_ = NAN;
|
||||
optional<std::string> unit_of_measurement_; ///< Unit of measurement override
|
||||
NumberMode mode_{NUMBER_MODE_AUTO};
|
||||
};
|
||||
|
||||
|
@@ -252,7 +252,7 @@ void SEN5XComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Firmware version: %d", this->firmware_version_);
|
||||
ESP_LOGCONFIG(TAG, " Serial number %02d.%02d.%02d", serial_number_[0], serial_number_[1], serial_number_[2]);
|
||||
if (this->auto_cleaning_interval_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Auto auto cleaning interval %d seconds", auto_cleaning_interval_.value());
|
||||
ESP_LOGCONFIG(TAG, " Auto cleaning interval %d seconds", auto_cleaning_interval_.value());
|
||||
}
|
||||
if (this->acceleration_mode_.has_value()) {
|
||||
switch (this->acceleration_mode_.value()) {
|
||||
|
@@ -119,7 +119,7 @@ CONFIG_SCHEMA = (
|
||||
device_class=DEVICE_CLASS_PM10,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_AUTO_CLEANING_INTERVAL): cv.time_period_in_seconds_,
|
||||
cv.Optional(CONF_AUTO_CLEANING_INTERVAL): cv.update_interval,
|
||||
cv.Optional(CONF_VOC): sensor.sensor_schema(
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
|
@@ -73,6 +73,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_TIMESTAMP,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_VOLUME,
|
||||
DEVICE_CLASS_VOLUME_STORAGE,
|
||||
@@ -129,6 +130,7 @@ DEVICE_CLASSES = [
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_TIMESTAMP,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_VOLUME,
|
||||
DEVICE_CLASS_VOLUME_STORAGE,
|
||||
|
@@ -22,15 +22,6 @@ std::string state_class_to_string(StateClass state_class) {
|
||||
|
||||
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() {
|
||||
if (this->accuracy_decimals_.has_value())
|
||||
return *this->accuracy_decimals_;
|
||||
|
@@ -54,15 +54,10 @@ std::string state_class_to_string(StateClass state_class);
|
||||
*
|
||||
* A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy.
|
||||
*/
|
||||
class Sensor : public EntityBase, public EntityBase_DeviceClass {
|
||||
class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBase_UnitOfMeasurement {
|
||||
public:
|
||||
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.
|
||||
int8_t get_accuracy_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.
|
||||
|
||||
optional<std::string> unit_of_measurement_; ///< Unit of measurement override
|
||||
optional<int8_t> accuracy_decimals_; ///< Accuracy in decimals override
|
||||
optional<StateClass> state_class_{STATE_CLASS_NONE}; ///< State class override
|
||||
bool force_update_{false}; ///< Force update mode
|
||||
|
@@ -23,6 +23,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
CONF_MIN_BRIGHTNESS,
|
||||
CONF_MAX_BRIGHTNESS,
|
||||
)
|
||||
from esphome.core import HexInt, CORE
|
||||
|
||||
@@ -41,8 +43,7 @@ CONF_UPDATE = "update"
|
||||
CONF_LEADING_EDGE = "leading_edge"
|
||||
CONF_WARMUP_BRIGHTNESS = "warmup_brightness"
|
||||
# CONF_WARMUP_TIME = "warmup_time"
|
||||
CONF_MIN_BRIGHTNESS = "min_brightness"
|
||||
CONF_MAX_BRIGHTNESS = "max_brightness"
|
||||
|
||||
|
||||
CONF_NRST_PIN = "nrst_pin"
|
||||
CONF_BOOT0_PIN = "boot0_pin"
|
||||
|
@@ -8,17 +8,49 @@ from esphome.const import (
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["output"]
|
||||
CODEOWNERS = ["@BoukeHaarsma23"]
|
||||
CODEOWNERS = ["@BoukeHaarsma23", "@matika77", "@dd32"]
|
||||
|
||||
sm2135_ns = cg.esphome_ns.namespace("sm2135")
|
||||
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
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SM2135),
|
||||
cv.Required(CONF_DATA_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)
|
||||
|
||||
@@ -31,3 +63,6 @@ async def to_code(config):
|
||||
cg.add(var.set_data_pin(data))
|
||||
clock = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN])
|
||||
cg.add(var.set_clock_pin(clock))
|
||||
|
||||
cg.add(var.set_rgb_current(config[CONF_RGB_CURRENT]))
|
||||
cg.add(var.set_cw_current(config[CONF_CW_CURRENT]))
|
||||
|
@@ -19,63 +19,125 @@ static const uint8_t SM2135_ADDR_W = 0xC6; // Warm
|
||||
static const uint8_t SM2135_RGB = 0x00; // RGB channel
|
||||
static const uint8_t SM2135_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() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up SM2135OutputComponent...");
|
||||
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_->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);
|
||||
}
|
||||
|
||||
void SM2135::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "SM2135:");
|
||||
LOG_PIN(" Data Pin: ", this->data_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() {
|
||||
if (!this->update_)
|
||||
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) {
|
||||
// No color so must be Cold/Warm
|
||||
data[0] = SM2135_ADDR_MC;
|
||||
data[1] = SM2135_CURRENT;
|
||||
data[2] = SM2135_CW;
|
||||
this->write_buffer_(data, 3);
|
||||
|
||||
this->write_byte_(SM2135_CW);
|
||||
this->sm2135_stop_();
|
||||
delay(1);
|
||||
data[0] = SM2135_ADDR_C;
|
||||
data[1] = this->pwm_amounts_[4]; // Warm
|
||||
data[2] = this->pwm_amounts_[3]; // Cold
|
||||
this->write_buffer_(data, 3);
|
||||
this->sm2135_start_();
|
||||
this->write_byte_(SM2135_ADDR_C);
|
||||
this->write_byte_(this->pwm_amounts_[4]); // Warm
|
||||
this->write_byte_(this->pwm_amounts_[3]); // Cold
|
||||
} else {
|
||||
// Color
|
||||
data[0] = SM2135_ADDR_MC;
|
||||
data[1] = SM2135_CURRENT;
|
||||
data[2] = SM2135_RGB;
|
||||
data[3] = this->pwm_amounts_[1]; // Green
|
||||
data[4] = this->pwm_amounts_[0]; // Red
|
||||
data[5] = this->pwm_amounts_[2]; // Blue
|
||||
this->write_buffer_(data, 6);
|
||||
|
||||
this->write_byte_(SM2135_RGB);
|
||||
this->write_byte_(this->pwm_amounts_[1]); // Green
|
||||
this->write_byte_(this->pwm_amounts_[0]); // Red
|
||||
this->write_byte_(this->pwm_amounts_[2]); // Blue
|
||||
}
|
||||
|
||||
this->sm2135_stop_();
|
||||
|
||||
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 esphome
|
||||
|
@@ -1,19 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
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 {
|
||||
public:
|
||||
class Channel;
|
||||
|
||||
void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; }
|
||||
void set_clock_pin(GPIOPin *clock_pin) { clock_pin_ = clock_pin; }
|
||||
void set_data_pin(GPIOPin *data_pin) { this->data_pin_ = data_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;
|
||||
|
||||
@@ -40,40 +64,20 @@ class SM2135 : public Component {
|
||||
};
|
||||
|
||||
protected:
|
||||
void 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 write_bit_(bool value) {
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->data_pin_->digital_write(value);
|
||||
this->clock_pin_->digital_write(true);
|
||||
}
|
||||
void set_channel_value_(uint8_t channel, uint8_t value);
|
||||
void sm2135_set_low_(GPIOPin *pin);
|
||||
void sm2135_set_high_(GPIOPin *pin);
|
||||
|
||||
void write_byte_(uint8_t data) {
|
||||
for (uint8_t mask = 0x80; mask; mask >>= 1) {
|
||||
this->write_bit_(data & mask);
|
||||
}
|
||||
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);
|
||||
}
|
||||
void sm2135_start_();
|
||||
void sm2135_stop_();
|
||||
void write_byte_(uint8_t data);
|
||||
void write_buffer_(uint8_t *buffer, uint8_t size);
|
||||
|
||||
GPIOPin *data_pin_;
|
||||
GPIOPin *clock_pin_;
|
||||
uint8_t current_mask_;
|
||||
SM2135Current rgb_current_;
|
||||
SM2135Current cw_current_;
|
||||
uint8_t update_channel_;
|
||||
std::vector<uint8_t> pwm_amounts_;
|
||||
bool update_{true};
|
||||
|
@@ -14,6 +14,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
esp8266=IMPLEMENTATION_LWIP_TCP,
|
||||
esp32=IMPLEMENTATION_BSD_SOCKETS,
|
||||
rp2040=IMPLEMENTATION_LWIP_TCP,
|
||||
host=IMPLEMENTATION_BSD_SOCKETS,
|
||||
): cv.one_of(
|
||||
IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_"
|
||||
),
|
||||
|
@@ -130,6 +130,13 @@ struct iovec {
|
||||
#include <sys/uio.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
|
||||
// arduino-esp32 declares a global var called INADDR_NONE which is replaced
|
||||
// by the define
|
||||
|
@@ -7,6 +7,8 @@
|
||||
namespace esphome {
|
||||
namespace socket {
|
||||
|
||||
Socket::~Socket() {}
|
||||
|
||||
std::unique_ptr<Socket> socket_ip(int type, int protocol) {
|
||||
#if LWIP_IPV6
|
||||
return socket(AF_INET6, type, protocol);
|
||||
|
@@ -11,7 +11,7 @@ namespace socket {
|
||||
class Socket {
|
||||
public:
|
||||
Socket() = default;
|
||||
virtual ~Socket() = default;
|
||||
virtual ~Socket();
|
||||
Socket(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 write(const void *buf, size_t len) = 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 loop() { return 0; };
|
||||
|
87
esphome/components/speaker/__init__.py
Normal file
87
esphome/components/speaker/__init__.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from esphome import automation
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.const import CONF_ID, CONF_DATA
|
||||
from esphome.core import CORE
|
||||
from esphome.coroutine import coroutine_with_priority
|
||||
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
speaker_ns = cg.esphome_ns.namespace("speaker")
|
||||
|
||||
Speaker = speaker_ns.class_("Speaker")
|
||||
|
||||
PlayAction = speaker_ns.class_(
|
||||
"PlayAction", automation.Action, cg.Parented.template(Speaker)
|
||||
)
|
||||
StopAction = speaker_ns.class_(
|
||||
"StopAction", automation.Action, cg.Parented.template(Speaker)
|
||||
)
|
||||
|
||||
IsPlayingCondition = speaker_ns.class_("IsPlayingCondition", automation.Condition)
|
||||
|
||||
|
||||
async def setup_speaker_core_(var, config):
|
||||
pass
|
||||
|
||||
|
||||
async def register_speaker(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
await setup_speaker_core_(var, config)
|
||||
|
||||
|
||||
SPEAKER_SCHEMA = cv.Schema({})
|
||||
|
||||
|
||||
SPEAKER_AUTOMATION_SCHEMA = maybe_simple_id({cv.GenerateID(): cv.use_id(Speaker)})
|
||||
|
||||
|
||||
async def speaker_action(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"speaker.play",
|
||||
PlayAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(Speaker),
|
||||
cv.Required(CONF_DATA): cv.templatable(cv.ensure_list(cv.hex_uint8_t)),
|
||||
},
|
||||
key=CONF_DATA,
|
||||
),
|
||||
)
|
||||
async def speaker_play_action(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
data = config[CONF_DATA]
|
||||
|
||||
if cg.is_template(data):
|
||||
templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
|
||||
cg.add(var.set_data_template(templ))
|
||||
else:
|
||||
cg.add(var.set_data_static(data))
|
||||
return var
|
||||
|
||||
|
||||
automation.register_action("speaker.stop", StopAction, SPEAKER_AUTOMATION_SCHEMA)(
|
||||
speaker_action
|
||||
)
|
||||
|
||||
automation.register_condition(
|
||||
"speaker.is_playing", IsPlayingCondition, SPEAKER_AUTOMATION_SCHEMA
|
||||
)(speaker_action)
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
async def to_code(config):
|
||||
cg.add_global(speaker_ns.using)
|
||||
cg.add_define("USE_SPEAKER")
|
48
esphome/components/speaker/automation.h
Normal file
48
esphome/components/speaker/automation.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "speaker.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace speaker {
|
||||
|
||||
template<typename... Ts> class PlayAction : public Action<Ts...>, public Parented<Speaker> {
|
||||
public:
|
||||
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||
this->data_func_ = func;
|
||||
this->static_ = false;
|
||||
}
|
||||
void set_data_static(const std::vector<uint8_t> &data) {
|
||||
this->data_static_ = data;
|
||||
this->static_ = true;
|
||||
}
|
||||
|
||||
void play(Ts... x) override {
|
||||
if (this->static_) {
|
||||
this->parent_->play(this->data_static_);
|
||||
} else {
|
||||
auto val = this->data_func_(x...);
|
||||
this->parent_->play(val);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
bool static_{false};
|
||||
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
|
||||
std::vector<uint8_t> data_static_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class StopAction : public Action<Ts...>, public Parented<Speaker> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->stop(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsPlayingCondition : public Condition<Ts...>, public Parented<Speaker> {
|
||||
public:
|
||||
bool check(Ts... x) override { return this->parent_->is_running(); }
|
||||
};
|
||||
|
||||
} // namespace speaker
|
||||
} // namespace esphome
|
27
esphome/components/speaker/speaker.h
Normal file
27
esphome/components/speaker/speaker.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
namespace speaker {
|
||||
|
||||
enum State : uint8_t {
|
||||
STATE_STOPPED = 0,
|
||||
STATE_STARTING,
|
||||
STATE_RUNNING,
|
||||
STATE_STOPPING,
|
||||
};
|
||||
|
||||
class Speaker {
|
||||
public:
|
||||
virtual bool play(const uint8_t *data, size_t length) = 0;
|
||||
virtual bool play(const std::vector<uint8_t> &data) { return this->play(data.data(), data.size()); }
|
||||
|
||||
virtual void stop() = 0;
|
||||
|
||||
bool is_running() const { return this->state_ == STATE_RUNNING; }
|
||||
|
||||
protected:
|
||||
State state_{STATE_STOPPED};
|
||||
};
|
||||
|
||||
} // namespace speaker
|
||||
} // namespace esphome
|
@@ -275,7 +275,7 @@ SPRINKLER_ACTION_SET_RUN_DURATION_SCHEMA = cv.Schema(
|
||||
SPRINKLER_ACTION_QUEUE_VALVE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Sprinkler),
|
||||
cv.Optional(CONF_RUN_DURATION, default=0): cv.templatable(
|
||||
cv.Optional(CONF_RUN_DURATION, default="0s"): cv.templatable(
|
||||
cv.positive_time_period_seconds
|
||||
),
|
||||
cv.Required(CONF_VALVE_NUMBER): cv.templatable(cv.positive_int),
|
||||
@@ -599,15 +599,6 @@ async def to_code(config):
|
||||
)
|
||||
cg.add(var.set_controller_auto_adv_switch(sw_aa_var))
|
||||
|
||||
if CONF_QUEUE_ENABLE_SWITCH in sprinkler_controller:
|
||||
sw_qen_var = await switch.new_switch(
|
||||
sprinkler_controller[CONF_QUEUE_ENABLE_SWITCH]
|
||||
)
|
||||
await cg.register_component(
|
||||
sw_qen_var, sprinkler_controller[CONF_QUEUE_ENABLE_SWITCH]
|
||||
)
|
||||
cg.add(var.set_controller_queue_enable_switch(sw_qen_var))
|
||||
|
||||
if CONF_REVERSE_SWITCH in sprinkler_controller:
|
||||
sw_rev_var = await switch.new_switch(
|
||||
sprinkler_controller[CONF_REVERSE_SWITCH]
|
||||
@@ -617,78 +608,83 @@ async def to_code(config):
|
||||
)
|
||||
cg.add(var.set_controller_reverse_switch(sw_rev_var))
|
||||
|
||||
if CONF_STANDBY_SWITCH in sprinkler_controller:
|
||||
sw_stb_var = await switch.new_switch(
|
||||
sprinkler_controller[CONF_STANDBY_SWITCH]
|
||||
)
|
||||
await cg.register_component(
|
||||
sw_stb_var, sprinkler_controller[CONF_STANDBY_SWITCH]
|
||||
)
|
||||
cg.add(var.set_controller_standby_switch(sw_stb_var))
|
||||
if CONF_STANDBY_SWITCH in sprinkler_controller:
|
||||
sw_stb_var = await switch.new_switch(
|
||||
sprinkler_controller[CONF_STANDBY_SWITCH]
|
||||
)
|
||||
await cg.register_component(
|
||||
sw_stb_var, sprinkler_controller[CONF_STANDBY_SWITCH]
|
||||
)
|
||||
cg.add(var.set_controller_standby_switch(sw_stb_var))
|
||||
|
||||
if CONF_MULTIPLIER_NUMBER in sprinkler_controller:
|
||||
num_mult_var = await number.new_number(
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER],
|
||||
min_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][
|
||||
CONF_MIN_VALUE
|
||||
],
|
||||
max_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][
|
||||
CONF_MAX_VALUE
|
||||
],
|
||||
step=sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_STEP],
|
||||
if CONF_QUEUE_ENABLE_SWITCH in sprinkler_controller:
|
||||
sw_qen_var = await switch.new_switch(
|
||||
sprinkler_controller[CONF_QUEUE_ENABLE_SWITCH]
|
||||
)
|
||||
await cg.register_component(
|
||||
sw_qen_var, sprinkler_controller[CONF_QUEUE_ENABLE_SWITCH]
|
||||
)
|
||||
cg.add(var.set_controller_queue_enable_switch(sw_qen_var))
|
||||
|
||||
if CONF_MULTIPLIER_NUMBER in sprinkler_controller:
|
||||
num_mult_var = await number.new_number(
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER],
|
||||
min_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_MIN_VALUE],
|
||||
max_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_MAX_VALUE],
|
||||
step=sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_STEP],
|
||||
)
|
||||
await cg.register_component(
|
||||
num_mult_var, sprinkler_controller[CONF_MULTIPLIER_NUMBER]
|
||||
)
|
||||
cg.add(
|
||||
num_mult_var.set_initial_value(
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_INITIAL_VALUE]
|
||||
)
|
||||
await cg.register_component(
|
||||
num_mult_var, sprinkler_controller[CONF_MULTIPLIER_NUMBER]
|
||||
)
|
||||
cg.add(
|
||||
num_mult_var.set_restore_value(
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_RESTORE_VALUE]
|
||||
)
|
||||
cg.add(
|
||||
num_mult_var.set_initial_value(
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_INITIAL_VALUE]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
num_mult_var.set_restore_value(
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_RESTORE_VALUE]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_SET_ACTION in sprinkler_controller[CONF_MULTIPLIER_NUMBER]:
|
||||
await automation.build_automation(
|
||||
num_mult_var.get_set_trigger(),
|
||||
[(float, "x")],
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_SET_ACTION],
|
||||
)
|
||||
|
||||
if CONF_SET_ACTION in sprinkler_controller[CONF_MULTIPLIER_NUMBER]:
|
||||
await automation.build_automation(
|
||||
num_mult_var.get_set_trigger(),
|
||||
[(float, "x")],
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_SET_ACTION],
|
||||
)
|
||||
cg.add(var.set_controller_multiplier_number(num_mult_var))
|
||||
|
||||
cg.add(var.set_controller_multiplier_number(num_mult_var))
|
||||
if CONF_REPEAT_NUMBER in sprinkler_controller:
|
||||
num_repeat_var = await number.new_number(
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER],
|
||||
min_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MIN_VALUE],
|
||||
max_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MAX_VALUE],
|
||||
step=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_STEP],
|
||||
)
|
||||
await cg.register_component(
|
||||
num_repeat_var, sprinkler_controller[CONF_REPEAT_NUMBER]
|
||||
)
|
||||
cg.add(
|
||||
num_repeat_var.set_initial_value(
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER][CONF_INITIAL_VALUE]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
num_repeat_var.set_restore_value(
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER][CONF_RESTORE_VALUE]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_REPEAT_NUMBER in sprinkler_controller:
|
||||
num_repeat_var = await number.new_number(
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER],
|
||||
min_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MIN_VALUE],
|
||||
max_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MAX_VALUE],
|
||||
step=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_STEP],
|
||||
)
|
||||
await cg.register_component(
|
||||
num_repeat_var, sprinkler_controller[CONF_REPEAT_NUMBER]
|
||||
)
|
||||
cg.add(
|
||||
num_repeat_var.set_initial_value(
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER][CONF_INITIAL_VALUE]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
num_repeat_var.set_restore_value(
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER][CONF_RESTORE_VALUE]
|
||||
)
|
||||
if CONF_SET_ACTION in sprinkler_controller[CONF_REPEAT_NUMBER]:
|
||||
await automation.build_automation(
|
||||
num_repeat_var.get_set_trigger(),
|
||||
[(float, "x")],
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER][CONF_SET_ACTION],
|
||||
)
|
||||
|
||||
if CONF_SET_ACTION in sprinkler_controller[CONF_REPEAT_NUMBER]:
|
||||
await automation.build_automation(
|
||||
num_repeat_var.get_set_trigger(),
|
||||
[(float, "x")],
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER][CONF_SET_ACTION],
|
||||
)
|
||||
|
||||
cg.add(var.set_controller_repeat_number(num_repeat_var))
|
||||
cg.add(var.set_controller_repeat_number(num_repeat_var))
|
||||
|
||||
for valve in sprinkler_controller[CONF_VALVES]:
|
||||
sw_valve_var = await switch.new_switch(valve[CONF_VALVE_SWITCH])
|
||||
|
@@ -147,22 +147,22 @@ SprinklerValveOperator::SprinklerValveOperator(SprinklerValve *valve, Sprinkler
|
||||
: controller_(controller), valve_(valve) {}
|
||||
|
||||
void SprinklerValveOperator::loop() {
|
||||
if (millis() >= this->pinned_millis_) { // dummy check
|
||||
if (millis() >= this->start_millis_) { // dummy check
|
||||
switch (this->state_) {
|
||||
case STARTING:
|
||||
if (millis() > (this->pinned_millis_ + this->start_delay_)) {
|
||||
if (millis() > (this->start_millis_ + this->start_delay_)) {
|
||||
this->run_(); // start_delay_ has been exceeded, so ensure both valves are on and update the state
|
||||
}
|
||||
break;
|
||||
|
||||
case ACTIVE:
|
||||
if (millis() > (this->pinned_millis_ + this->start_delay_ + this->run_duration_)) {
|
||||
if (millis() > (this->start_millis_ + this->start_delay_ + this->run_duration_)) {
|
||||
this->stop(); // start_delay_ + run_duration_ has been exceeded, start shutting down
|
||||
}
|
||||
break;
|
||||
|
||||
case STOPPING:
|
||||
if (millis() > (this->pinned_millis_ + this->stop_delay_)) {
|
||||
if (millis() > (this->stop_millis_ + this->stop_delay_)) {
|
||||
this->kill_(); // stop_delay_has been exceeded, ensure all valves are off
|
||||
}
|
||||
break;
|
||||
@@ -183,11 +183,12 @@ void SprinklerValveOperator::set_controller(Sprinkler *controller) {
|
||||
|
||||
void SprinklerValveOperator::set_valve(SprinklerValve *valve) {
|
||||
if (valve != nullptr) {
|
||||
this->state_ = IDLE; // reset state
|
||||
this->run_duration_ = 0; // reset to ensure the valve isn't started without updating it
|
||||
this->pinned_millis_ = 0; // reset because (new) valve has not been started yet
|
||||
this->kill_(); // ensure everything is off before we let go!
|
||||
this->valve_ = valve; // finally, set the pointer to the new valve
|
||||
this->state_ = IDLE; // reset state
|
||||
this->run_duration_ = 0; // reset to ensure the valve isn't started without updating it
|
||||
this->start_millis_ = 0; // reset because (new) valve has not been started yet
|
||||
this->stop_millis_ = 0; // reset because (new) valve has not been started yet
|
||||
this->kill_(); // ensure everything is off before we let go!
|
||||
this->valve_ = valve; // finally, set the pointer to the new valve
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +222,8 @@ void SprinklerValveOperator::start() {
|
||||
} else {
|
||||
this->run_(); // there is no start_delay_, so just start the pump and valve
|
||||
}
|
||||
this->pinned_millis_ = millis(); // save the time the start request was made
|
||||
this->stop_millis_ = 0;
|
||||
this->start_millis_ = millis(); // save the time the start request was made
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::stop() {
|
||||
@@ -238,19 +240,33 @@ void SprinklerValveOperator::stop() {
|
||||
if (this->pump_switch()->state()) { // if the pump is still on at this point, it may be in use...
|
||||
this->valve_off_(); // ...so just switch the valve off now to ensure consistent run time
|
||||
}
|
||||
this->pinned_millis_ = millis(); // save the time the stop request was made
|
||||
} else {
|
||||
this->kill_(); // there is no stop_delay_, so just stop the pump and valve
|
||||
}
|
||||
this->stop_millis_ = millis(); // save the time the stop request was made
|
||||
}
|
||||
|
||||
uint32_t SprinklerValveOperator::run_duration() { return this->run_duration_; }
|
||||
uint32_t SprinklerValveOperator::run_duration() { return this->run_duration_ / 1000; }
|
||||
|
||||
uint32_t SprinklerValveOperator::time_remaining() {
|
||||
if ((this->state_ == STARTING) || (this->state_ == ACTIVE)) {
|
||||
return (this->pinned_millis_ + this->start_delay_ + this->run_duration_ - millis()) / 1000;
|
||||
if (this->start_millis_ == 0) {
|
||||
return this->run_duration(); // hasn't been started yet
|
||||
}
|
||||
return 0;
|
||||
|
||||
if (this->stop_millis_) {
|
||||
if (this->stop_millis_ - this->start_millis_ >= this->start_delay_ + this->run_duration_) {
|
||||
return 0; // valve was active for more than its configured duration, so we are done
|
||||
} else {
|
||||
// we're stopped; return time remaining
|
||||
return (this->run_duration_ - (this->stop_millis_ - this->start_millis_)) / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
auto completed_millis = this->start_millis_ + this->start_delay_ + this->run_duration_;
|
||||
if (completed_millis > millis()) {
|
||||
return (completed_millis - millis()) / 1000; // running now
|
||||
}
|
||||
return 0; // run completed
|
||||
}
|
||||
|
||||
SprinklerState SprinklerValveOperator::state() { return this->state_; }
|
||||
@@ -386,6 +402,9 @@ void Sprinkler::loop() {
|
||||
for (auto &vo : this->valve_op_) {
|
||||
vo.loop();
|
||||
}
|
||||
if (this->prev_req_.has_request() && this->prev_req_.valve_operator()->state() == IDLE) {
|
||||
this->prev_req_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControllerSwitch *enable_sw) {
|
||||
@@ -732,7 +751,7 @@ bool Sprinkler::auto_advance() {
|
||||
if (this->auto_adv_sw_ != nullptr) {
|
||||
return this->auto_adv_sw_->state;
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
float Sprinkler::multiplier() {
|
||||
@@ -972,7 +991,14 @@ optional<SprinklerValveRunRequestOrigin> Sprinkler::active_valve_request_is_from
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<size_t> Sprinkler::active_valve() { return this->active_req_.valve_as_opt(); }
|
||||
optional<size_t> Sprinkler::active_valve() {
|
||||
if (!this->valve_overlap_ && this->prev_req_.has_request() &&
|
||||
(this->prev_req_.valve_operator()->state() == STARTING || this->prev_req_.valve_operator()->state() == ACTIVE)) {
|
||||
return this->prev_req_.valve_as_opt();
|
||||
}
|
||||
return this->active_req_.valve_as_opt();
|
||||
}
|
||||
|
||||
optional<size_t> Sprinkler::paused_valve() { return this->paused_valve_; }
|
||||
|
||||
optional<size_t> Sprinkler::queued_valve() {
|
||||
@@ -1097,22 +1123,35 @@ uint32_t Sprinkler::total_cycle_time_enabled_valves() {
|
||||
|
||||
uint32_t Sprinkler::total_cycle_time_enabled_incomplete_valves() {
|
||||
uint32_t total_time_remaining = 0;
|
||||
uint32_t valve_count = 0;
|
||||
uint32_t enabled_valve_count = 0;
|
||||
uint32_t incomplete_valve_count = 0;
|
||||
|
||||
for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
|
||||
if (this->valve_is_enabled_(valve) && !this->valve_cycle_complete_(valve)) {
|
||||
if (!this->active_valve().has_value() || (valve != this->active_valve().value())) {
|
||||
total_time_remaining += this->valve_run_duration_adjusted(valve);
|
||||
valve_count++;
|
||||
if (this->valve_is_enabled_(valve)) {
|
||||
enabled_valve_count++;
|
||||
if (!this->valve_cycle_complete_(valve)) {
|
||||
if (!this->active_valve().has_value() || (valve != this->active_valve().value())) {
|
||||
total_time_remaining += this->valve_run_duration_adjusted(valve);
|
||||
incomplete_valve_count++;
|
||||
} else {
|
||||
// to get here, there must be an active valve and this valve must be equal to 'valve'
|
||||
if (this->active_req_.valve_operator() == nullptr) { // no SVO has been assigned yet so it hasn't started
|
||||
total_time_remaining += this->valve_run_duration_adjusted(valve);
|
||||
incomplete_valve_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (valve_count) {
|
||||
if (incomplete_valve_count >= enabled_valve_count) {
|
||||
incomplete_valve_count--;
|
||||
}
|
||||
if (incomplete_valve_count) {
|
||||
if (this->valve_overlap_) {
|
||||
total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
|
||||
total_time_remaining -= this->switching_delay_.value_or(0) * incomplete_valve_count;
|
||||
} else {
|
||||
total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
|
||||
total_time_remaining += this->switching_delay_.value_or(0) * incomplete_valve_count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1149,31 +1188,32 @@ optional<uint32_t> Sprinkler::time_remaining_active_valve() {
|
||||
return this->active_req_.valve_operator()->time_remaining();
|
||||
}
|
||||
}
|
||||
for (auto &vo : this->valve_op_) { // ...else return the value from the first non-IDLE SprinklerValveOperator
|
||||
if (vo.state() != IDLE) {
|
||||
return vo.time_remaining();
|
||||
if (this->prev_req_.has_request()) { // try to return the value based on prev_req_...
|
||||
if (this->prev_req_.valve_operator() != nullptr) {
|
||||
return this->prev_req_.valve_operator()->time_remaining();
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<uint32_t> Sprinkler::time_remaining_current_operation() {
|
||||
auto total_time_remaining = this->time_remaining_active_valve();
|
||||
if (!this->time_remaining_active_valve().has_value() && this->state_ == IDLE) {
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
if (total_time_remaining.has_value()) {
|
||||
if (this->auto_advance()) {
|
||||
total_time_remaining = total_time_remaining.value() + this->total_cycle_time_enabled_incomplete_valves();
|
||||
total_time_remaining =
|
||||
total_time_remaining.value() +
|
||||
auto total_time_remaining = this->time_remaining_active_valve().value_or(0);
|
||||
if (this->auto_advance()) {
|
||||
total_time_remaining += this->total_cycle_time_enabled_incomplete_valves();
|
||||
if (this->repeat().value_or(0) > 0) {
|
||||
total_time_remaining +=
|
||||
(this->total_cycle_time_enabled_valves() * (this->repeat().value_or(0) - this->repeat_count().value_or(0)));
|
||||
}
|
||||
|
||||
if (this->queue_enabled()) {
|
||||
total_time_remaining = total_time_remaining.value() + this->total_queue_time();
|
||||
}
|
||||
return total_time_remaining;
|
||||
}
|
||||
return nullopt;
|
||||
|
||||
if (this->queue_enabled()) {
|
||||
total_time_remaining += this->total_queue_time();
|
||||
}
|
||||
return total_time_remaining;
|
||||
}
|
||||
|
||||
bool Sprinkler::any_controller_is_active() {
|
||||
@@ -1305,6 +1345,12 @@ optional<size_t> Sprinkler::next_valve_number_in_cycle_(const optional<size_t> f
|
||||
}
|
||||
|
||||
void Sprinkler::load_next_valve_run_request_(const optional<size_t> first_valve) {
|
||||
if (this->active_req_.has_request()) {
|
||||
this->prev_req_ = this->active_req_;
|
||||
} else {
|
||||
this->prev_req_.reset();
|
||||
}
|
||||
|
||||
if (this->next_req_.has_request()) {
|
||||
if (!this->next_req_.run_duration()) { // ensure the run duration is set correctly for consumption later on
|
||||
this->next_req_.set_run_duration(this->valve_run_duration_adjusted(this->next_req_.valve()));
|
||||
|
@@ -170,7 +170,8 @@ class SprinklerValveOperator {
|
||||
uint32_t start_delay_{0};
|
||||
uint32_t stop_delay_{0};
|
||||
uint32_t run_duration_{0};
|
||||
uint64_t pinned_millis_{0};
|
||||
uint64_t start_millis_{0};
|
||||
uint64_t stop_millis_{0};
|
||||
Sprinkler *controller_{nullptr};
|
||||
SprinklerValve *valve_{nullptr};
|
||||
SprinklerState state_{IDLE};
|
||||
@@ -538,15 +539,18 @@ class Sprinkler : public Component {
|
||||
/// The valve run request that is currently active
|
||||
SprinklerValveRunRequest active_req_;
|
||||
|
||||
/// The next run request for the controller to consume after active_req_ is complete
|
||||
SprinklerValveRunRequest next_req_;
|
||||
|
||||
/// The previous run request the controller processed
|
||||
SprinklerValveRunRequest prev_req_;
|
||||
|
||||
/// The number of the manually selected valve currently selected
|
||||
optional<size_t> manual_valve_;
|
||||
|
||||
/// The number of the valve to resume from (if paused)
|
||||
optional<size_t> paused_valve_;
|
||||
|
||||
/// The next run request for the controller to consume after active_req_ is complete
|
||||
SprinklerValveRunRequest next_req_;
|
||||
|
||||
/// Set the number of times to repeat a full cycle
|
||||
optional<uint32_t> target_repeats_;
|
||||
|
||||
|
@@ -8,13 +8,12 @@ namespace template_ {
|
||||
static const char *const TAG = "template.sensor";
|
||||
|
||||
void TemplateSensor::update() {
|
||||
if (this->f_.has_value()) {
|
||||
auto val = (*this->f_)();
|
||||
if (val.has_value()) {
|
||||
this->publish_state(*val);
|
||||
}
|
||||
} else if (!std::isnan(this->get_raw_state())) {
|
||||
this->publish_state(this->get_raw_state());
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto val = (*this->f_)();
|
||||
if (val.has_value()) {
|
||||
this->publish_state(*val);
|
||||
}
|
||||
}
|
||||
float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
@@ -7,13 +7,12 @@ namespace template_ {
|
||||
static const char *const TAG = "template.text_sensor";
|
||||
|
||||
void TemplateTextSensor::update() {
|
||||
if (this->f_.has_value()) {
|
||||
auto val = (*this->f_)();
|
||||
if (val.has_value()) {
|
||||
this->publish_state(*val);
|
||||
}
|
||||
} else if (this->has_state()) {
|
||||
this->publish_state(this->state);
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto val = (*this->f_)();
|
||||
if (val.has_value()) {
|
||||
this->publish_state(*val);
|
||||
}
|
||||
}
|
||||
float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
@@ -3,7 +3,6 @@ import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_ID, CONF_SDO_PIN, CONF_SCL_PIN
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["binary_sensor"]
|
||||
|
||||
CONF_TTP229_ID = "ttp229_id"
|
||||
|
@@ -57,37 +57,43 @@ void TuyaLight::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
float red, green, blue;
|
||||
switch (*this->color_type_) {
|
||||
case TuyaColorType::RGBHSV:
|
||||
case TuyaColorType::RGB: {
|
||||
auto red = parse_hex<uint8_t>(datapoint.value_string.substr(0, 2));
|
||||
auto green = parse_hex<uint8_t>(datapoint.value_string.substr(2, 2));
|
||||
auto blue = parse_hex<uint8_t>(datapoint.value_string.substr(4, 2));
|
||||
if (red.has_value() && green.has_value() && blue.has_value()) {
|
||||
auto rgb_call = this->state_->make_call();
|
||||
rgb_call.set_rgb(float(*red) / 255, float(*green) / 255, float(*blue) / 255);
|
||||
rgb_call.perform();
|
||||
}
|
||||
auto rgb = parse_hex<uint32_t>(datapoint.value_string.substr(0, 6));
|
||||
if (!rgb.has_value())
|
||||
return;
|
||||
|
||||
red = (*rgb >> 16) / 255.0f;
|
||||
green = ((*rgb >> 8) & 0xff) / 255.0f;
|
||||
blue = (*rgb & 0xff) / 255.0f;
|
||||
break;
|
||||
}
|
||||
case TuyaColorType::HSV: {
|
||||
auto hue = parse_hex<uint16_t>(datapoint.value_string.substr(0, 4));
|
||||
auto saturation = parse_hex<uint16_t>(datapoint.value_string.substr(4, 4));
|
||||
auto value = parse_hex<uint16_t>(datapoint.value_string.substr(8, 4));
|
||||
if (hue.has_value() && saturation.has_value() && value.has_value()) {
|
||||
float red, green, blue;
|
||||
hsv_to_rgb(*hue, float(*saturation) / 1000, float(*value) / 1000, red, green, blue);
|
||||
auto rgb_call = this->state_->make_call();
|
||||
rgb_call.set_rgb(red, green, blue);
|
||||
rgb_call.perform();
|
||||
}
|
||||
if (!hue.has_value() || !saturation.has_value() || !value.has_value())
|
||||
return;
|
||||
|
||||
hsv_to_rgb(*hue, float(*saturation) / 1000, float(*value) / 1000, red, green, blue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float current_red, current_green, current_blue;
|
||||
this->state_->current_values_as_rgb(¤t_red, ¤t_green, ¤t_blue);
|
||||
if (red == current_red && green == current_green && blue == current_blue)
|
||||
return;
|
||||
auto rgb_call = this->state_->make_call();
|
||||
rgb_call.set_rgb(red, green, blue);
|
||||
rgb_call.perform();
|
||||
});
|
||||
}
|
||||
|
||||
if (min_value_datapoint_id_.has_value()) {
|
||||
parent_->set_integer_datapoint_value(*this->min_value_datapoint_id_, this->min_value_);
|
||||
this->parent_->set_integer_datapoint_value(*this->min_value_datapoint_id_, this->min_value_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +162,7 @@ void TuyaLight::write_state(light::LightState *state) {
|
||||
}
|
||||
|
||||
if (!state->current_values.is_on() && this->switch_id_.has_value()) {
|
||||
parent_->set_boolean_datapoint_value(*this->switch_id_, false);
|
||||
this->parent_->set_boolean_datapoint_value(*this->switch_id_, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -166,14 +172,14 @@ void TuyaLight::write_state(light::LightState *state) {
|
||||
if (this->color_temperature_invert_) {
|
||||
color_temp_int = this->color_temperature_max_value_ - color_temp_int;
|
||||
}
|
||||
parent_->set_integer_datapoint_value(*this->color_temperature_id_, color_temp_int);
|
||||
this->parent_->set_integer_datapoint_value(*this->color_temperature_id_, color_temp_int);
|
||||
}
|
||||
|
||||
if (this->dimmer_id_.has_value()) {
|
||||
auto brightness_int = static_cast<uint32_t>(brightness * this->max_value_);
|
||||
brightness_int = std::max(brightness_int, this->min_value_);
|
||||
|
||||
parent_->set_integer_datapoint_value(*this->dimmer_id_, brightness_int);
|
||||
this->parent_->set_integer_datapoint_value(*this->dimmer_id_, brightness_int);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +216,7 @@ void TuyaLight::write_state(light::LightState *state) {
|
||||
}
|
||||
|
||||
if (this->switch_id_.has_value()) {
|
||||
parent_->set_boolean_datapoint_value(*this->switch_id_, true);
|
||||
this->parent_->set_boolean_datapoint_value(*this->switch_id_, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -86,10 +86,26 @@ void ESP32ArduinoUARTComponent::setup() {
|
||||
is_default_tx = tx_pin_ == nullptr || tx_pin_->get_pin() == 1;
|
||||
is_default_rx = rx_pin_ == nullptr || rx_pin_->get_pin() == 3;
|
||||
#endif
|
||||
if (is_default_tx && is_default_rx) {
|
||||
static uint8_t next_uart_num = 0;
|
||||
if (is_default_tx && is_default_rx && next_uart_num == 0) {
|
||||
this->hw_serial_ = &Serial;
|
||||
next_uart_num++;
|
||||
} else {
|
||||
static uint8_t next_uart_num = 1;
|
||||
#ifdef USE_LOGGER
|
||||
// The logger doesn't use this UART component, instead it targets the UARTs
|
||||
// directly (i.e. Serial/Serial0, Serial1, and Serial2). If the logger is
|
||||
// enabled, skip the UART that it is configured to use.
|
||||
if (logger::global_logger->get_baud_rate() > 0 && logger::global_logger->get_uart() == next_uart_num) {
|
||||
next_uart_num++;
|
||||
}
|
||||
#endif // USE_LOGGER
|
||||
|
||||
if (next_uart_num >= UART_NUM_MAX) {
|
||||
ESP_LOGW(TAG, "Maximum number of UART components created already.");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->number_ = next_uart_num;
|
||||
this->hw_serial_ = new HardwareSerial(next_uart_num++); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
|
||||
#include <driver/uart.h>
|
||||
#include <HardwareSerial.h>
|
||||
#include <vector>
|
||||
#include "esphome/core/component.h"
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import esphome.config_validation as cv
|
||||
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.automation import register_action
|
||||
from esphome.components import microphone
|
||||
from esphome.components import microphone, speaker
|
||||
|
||||
AUTO_LOAD = ["socket"]
|
||||
DEPENDENCIES = ["api", "microphone"]
|
||||
@@ -34,6 +34,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(VoiceAssistant),
|
||||
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_STT_END): 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])
|
||||
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:
|
||||
await automation.build_automation(
|
||||
var.get_start_trigger(), [], config[CONF_ON_START]
|
||||
|
@@ -1,7 +1,11 @@
|
||||
#include "voice_assistant.h"
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace esphome {
|
||||
namespace voice_assistant {
|
||||
|
||||
@@ -33,6 +37,27 @@ void VoiceAssistant::setup() {
|
||||
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) {
|
||||
if (!this->running_) {
|
||||
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) {
|
||||
ESP_LOGD(TAG, "Starting...");
|
||||
|
||||
@@ -154,3 +194,5 @@ VoiceAssistant *global_voice_assistant = nullptr; // NOLINT(cppcoreguidelines-a
|
||||
|
||||
} // namespace voice_assistant
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_VOICE_ASSISTANT
|
||||
|
@@ -1,24 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "esphome/components/api/api_pb2.h"
|
||||
#include "esphome/components/api/api_server.h"
|
||||
#include "esphome/components/microphone/microphone.h"
|
||||
#ifdef USE_SPEAKER
|
||||
#include "esphome/components/speaker/speaker.h"
|
||||
#endif
|
||||
#include "esphome/components/socket/socket.h"
|
||||
|
||||
namespace esphome {
|
||||
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 {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
void start(struct sockaddr_storage *addr, uint16_t port);
|
||||
|
||||
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 signal_stop();
|
||||
@@ -44,6 +69,9 @@ class VoiceAssistant : public Component {
|
||||
Trigger<std::string, std::string> *error_trigger_ = new Trigger<std::string, std::string>();
|
||||
|
||||
microphone::Microphone *mic_{nullptr};
|
||||
#ifdef USE_SPEAKER
|
||||
speaker::Speaker *speaker_{nullptr};
|
||||
#endif
|
||||
|
||||
bool running_{false};
|
||||
};
|
||||
@@ -62,3 +90,5 @@ extern VoiceAssistant *global_voice_assistant; // NOLINT(cppcoreguidelines-avoi
|
||||
|
||||
} // namespace voice_assistant
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_VOICE_ASSISTANT
|
||||
|
@@ -102,6 +102,16 @@ void Wiegand::loop() {
|
||||
uint8_t key = KEYS[value];
|
||||
this->send_key_(key);
|
||||
}
|
||||
} else if (count == 8) {
|
||||
if ((value ^ 0xf0) >> 4 == (value & 0xf)) {
|
||||
value &= 0xf;
|
||||
for (auto *trigger : this->key_triggers_)
|
||||
trigger->trigger(value);
|
||||
if (value < 12) {
|
||||
uint8_t key = KEYS[value];
|
||||
this->send_key_(key);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "received unknown %d-bit value: %llx", count, value);
|
||||
}
|
||||
|
@@ -706,6 +706,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
|
||||
#endif
|
||||
|
||||
struct dhcps_lease lease {};
|
||||
lease.enable = true;
|
||||
network::IPAddress start_address = info.ip.addr;
|
||||
start_address[3] += 99;
|
||||
lease.start_ip.addr = static_cast<uint32_t>(start_address);
|
||||
|
@@ -767,11 +767,10 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
|
||||
info.gw.addr = static_cast<uint32_t>(network::IPAddress(192, 168, 4, 1));
|
||||
info.netmask.addr = static_cast<uint32_t>(network::IPAddress(255, 255, 255, 0));
|
||||
}
|
||||
esp_netif_dhcp_status_t dhcp_status;
|
||||
esp_netif_dhcps_get_status(s_sta_netif, &dhcp_status);
|
||||
err = esp_netif_dhcps_stop(s_sta_netif);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGV(TAG, "esp_netif_dhcps_stop failed! %d", err);
|
||||
|
||||
err = esp_netif_dhcpc_stop(s_sta_netif);
|
||||
if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) {
|
||||
ESP_LOGV(TAG, "esp_netif_dhcpc_stop failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -1455,6 +1455,7 @@ class SplitDefault(Optional):
|
||||
esp32_arduino=vol.UNDEFINED,
|
||||
esp32_idf=vol.UNDEFINED,
|
||||
rp2040=vol.UNDEFINED,
|
||||
host=vol.UNDEFINED,
|
||||
):
|
||||
super().__init__(key)
|
||||
self._esp8266_default = vol.default_factory(esp8266)
|
||||
@@ -1465,6 +1466,7 @@ class SplitDefault(Optional):
|
||||
esp32_idf if esp32 is vol.UNDEFINED else esp32
|
||||
)
|
||||
self._rp2040_default = vol.default_factory(rp2040)
|
||||
self._host_default = vol.default_factory(host)
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
@@ -1476,6 +1478,8 @@ class SplitDefault(Optional):
|
||||
return self._esp32_idf_default
|
||||
if CORE.is_rp2040:
|
||||
return self._rp2040_default
|
||||
if CORE.is_host:
|
||||
return self._host_default
|
||||
raise NotImplementedError
|
||||
|
||||
@default.setter
|
||||
|
@@ -1,14 +1,15 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2023.5.0-dev"
|
||||
__version__ = "2023.5.2"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
|
||||
PLATFORM_ESP32 = "esp32"
|
||||
PLATFORM_ESP8266 = "esp8266"
|
||||
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"}
|
||||
HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
|
||||
@@ -376,6 +377,7 @@ CONF_MAKE_ID = "make_id"
|
||||
CONF_MANUAL_IP = "manual_ip"
|
||||
CONF_MANUFACTURER_ID = "manufacturer_id"
|
||||
CONF_MASK_DISTURBER = "mask_disturber"
|
||||
CONF_MAX_BRIGHTNESS = "max_brightness"
|
||||
CONF_MAX_COOLING_RUN_TIME = "max_cooling_run_time"
|
||||
CONF_MAX_CURRENT = "max_current"
|
||||
CONF_MAX_DURATION = "max_duration"
|
||||
@@ -395,6 +397,7 @@ CONF_MEDIUM = "medium"
|
||||
CONF_MEMORY_BLOCKS = "memory_blocks"
|
||||
CONF_METHOD = "method"
|
||||
CONF_MICROPHONE = "microphone"
|
||||
CONF_MIN_BRIGHTNESS = "min_brightness"
|
||||
CONF_MIN_COOLING_OFF_TIME = "min_cooling_off_time"
|
||||
CONF_MIN_COOLING_RUN_TIME = "min_cooling_run_time"
|
||||
CONF_MIN_FAN_MODE_SWITCHING_TIME = "min_fan_mode_switching_time"
|
||||
@@ -659,6 +662,7 @@ CONF_SLEEP_WHEN_DONE = "sleep_when_done"
|
||||
CONF_SONY = "sony"
|
||||
CONF_SOURCE = "source"
|
||||
CONF_SOURCE_ID = "source_id"
|
||||
CONF_SPEAKER = "speaker"
|
||||
CONF_SPEED = "speed"
|
||||
CONF_SPEED_COMMAND_TOPIC = "speed_command_topic"
|
||||
CONF_SPEED_COUNT = "speed_count"
|
||||
@@ -1000,6 +1004,7 @@ DEVICE_CLASS_TIMESTAMP = "timestamp"
|
||||
DEVICE_CLASS_UPDATE = "update"
|
||||
DEVICE_CLASS_VIBRATION = "vibration"
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds"
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS = "volatile_organic_compounds_parts"
|
||||
DEVICE_CLASS_VOLTAGE = "voltage"
|
||||
DEVICE_CLASS_VOLUME = "volume"
|
||||
DEVICE_CLASS_VOLUME_STORAGE = "volume_storage"
|
||||
|
@@ -602,6 +602,10 @@ class EsphomeCore:
|
||||
def is_rp2040(self):
|
||||
return self.target_platform == "rp2040"
|
||||
|
||||
@property
|
||||
def is_host(self):
|
||||
return self.target_platform == "host"
|
||||
|
||||
@property
|
||||
def target_framework(self):
|
||||
return self.data[KEY_CORE][KEY_TARGET_FRAMEWORK]
|
||||
|
@@ -73,6 +73,8 @@
|
||||
#define USE_WIFI_11KV_SUPPORT
|
||||
#define USE_BLUETOOTH_PROXY
|
||||
#define USE_VOICE_ASSISTANT
|
||||
#define USE_MICROPHONE
|
||||
#define USE_SPEAKER
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#define USE_ARDUINO_VERSION_CODE VERSION_CODE(2, 0, 5)
|
||||
@@ -100,6 +102,10 @@
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef USE_HOST
|
||||
#define USE_SOCKET_IMPL_BSD_SOCKETS
|
||||
#endif
|
||||
|
||||
// Disabled feature flags
|
||||
//#define USE_BSEC // Requires a library with proprietary license.
|
||||
|
||||
|
@@ -84,4 +84,13 @@ std::string EntityBase_DeviceClass::get_device_class() {
|
||||
|
||||
void EntityBase_DeviceClass::set_device_class(const char *device_class) { this->device_class_ = device_class; }
|
||||
|
||||
std::string EntityBase_UnitOfMeasurement::get_unit_of_measurement() {
|
||||
if (this->unit_of_measurement_ == nullptr)
|
||||
return "";
|
||||
return this->unit_of_measurement_;
|
||||
}
|
||||
void EntityBase_UnitOfMeasurement::set_unit_of_measurement(const char *unit_of_measurement) {
|
||||
this->unit_of_measurement_ = unit_of_measurement;
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
@@ -74,4 +74,15 @@ class EntityBase_DeviceClass {
|
||||
const char *device_class_{nullptr}; ///< Device class override
|
||||
};
|
||||
|
||||
class EntityBase_UnitOfMeasurement {
|
||||
public:
|
||||
/// 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 char *unit_of_measurement);
|
||||
|
||||
protected:
|
||||
const char *unit_of_measurement_{nullptr}; ///< Unit of measurement override
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
|
@@ -2,13 +2,14 @@
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#if defined(USE_ESP8266)
|
||||
#include <osapi.h>
|
||||
@@ -18,17 +19,20 @@
|
||||
#elif defined(USE_ESP32_FRAMEWORK_ARDUINO)
|
||||
#include <Esp.h>
|
||||
#elif defined(USE_ESP_IDF)
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/portmacro.h>
|
||||
#include "esp_mac.h"
|
||||
#include "esp_random.h"
|
||||
#include "esp_system.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/portmacro.h>
|
||||
#elif defined(USE_RP2040)
|
||||
#if defined(USE_WIFI)
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
#include <hardware/structs/rosc.h>
|
||||
#include <hardware/sync.h>
|
||||
#elif defined(USE_HOST)
|
||||
#include <limits>
|
||||
#include <random>
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC
|
||||
@@ -38,6 +42,8 @@
|
||||
|
||||
namespace esphome {
|
||||
|
||||
static const char *const TAG = "helpers";
|
||||
|
||||
// STL backports
|
||||
|
||||
#if _GLIBCXX_RELEASE < 7
|
||||
@@ -106,6 +112,11 @@ uint32_t random_uint32() {
|
||||
result |= rosc_hw->randombit;
|
||||
}
|
||||
return result;
|
||||
#elif defined(USE_HOST)
|
||||
std::random_device dev;
|
||||
std::mt19937 rng(dev());
|
||||
std::uniform_int_distribution<uint32_t> dist(0, std::numeric_limits<uint32_t>::max());
|
||||
return dist(rng);
|
||||
#else
|
||||
#error "No random source available for this configuration."
|
||||
#endif
|
||||
@@ -127,6 +138,19 @@ bool random_bytes(uint8_t *data, size_t len) {
|
||||
*data++ = result;
|
||||
}
|
||||
return true;
|
||||
#elif defined(USE_HOST)
|
||||
FILE *fp = fopen("/dev/urandom", "r");
|
||||
if (fp == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not open /dev/urandom, errno=%d", errno);
|
||||
exit(1);
|
||||
}
|
||||
size_t read = fread(data, 1, len, fp);
|
||||
if (read != len) {
|
||||
ESP_LOGW(TAG, "Not enough data from /dev/urandom");
|
||||
exit(1);
|
||||
}
|
||||
fclose(fp);
|
||||
return true;
|
||||
#else
|
||||
#error "No random source available for this configuration."
|
||||
#endif
|
||||
@@ -145,7 +169,7 @@ std::string str_truncate(const std::string &str, size_t length) {
|
||||
return str.length() > length ? str.substr(0, length) : str;
|
||||
}
|
||||
std::string str_until(const char *str, char ch) {
|
||||
char *pos = strchr(str, ch);
|
||||
const char *pos = strchr(str, ch);
|
||||
return pos == nullptr ? std::string(str) : std::string(str, pos - str);
|
||||
}
|
||||
std::string str_until(const std::string &str, char ch) { return str.substr(0, str.find(ch)); }
|
||||
@@ -395,7 +419,7 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green
|
||||
}
|
||||
|
||||
// System APIs
|
||||
#if defined(USE_ESP8266) || defined(USE_RP2040)
|
||||
#if defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_HOST)
|
||||
// ESP8266 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS.
|
||||
Mutex::Mutex() {}
|
||||
void Mutex::lock() {}
|
||||
@@ -469,6 +493,7 @@ void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); }
|
||||
|
||||
void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability
|
||||
uint32_t start = micros();
|
||||
|
||||
const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop.
|
||||
// it must be larger than the worst-case duration of a delay(1) call (hardware tasks)
|
||||
// 5ms is conservative, it could be reduced when exact BT/WiFi stack delays are known
|
||||
|
@@ -1,8 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <iterator>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_JSON
|
||||
|
@@ -108,7 +108,6 @@ platform_packages =
|
||||
platformio/framework-arduinoespressif32@~3.20005.0
|
||||
|
||||
framework = arduino
|
||||
board = nodemcu-32s
|
||||
lib_deps =
|
||||
; order matters with lib-deps; some of the libs in common:arduino.lib_deps
|
||||
; don't declare built-in libraries as dependencies, so they have to be declared first
|
||||
@@ -121,7 +120,7 @@ lib_deps =
|
||||
HTTPClient ; http_request,nextion (Arduino built-in)
|
||||
ESPmDNS ; mdns (Arduino built-in)
|
||||
DNSServer ; captive_portal (Arduino built-in)
|
||||
esphome/ESP32-audioI2S@2.0.6 ; i2s_audio
|
||||
esphome/ESP32-audioI2S@2.0.7 ; i2s_audio
|
||||
crankyoldgit/IRremoteESP8266@2.7.12 ; heatpumpir
|
||||
build_flags =
|
||||
${common:arduino.build_flags}
|
||||
@@ -168,6 +167,9 @@ build_flags =
|
||||
-DUSE_RP2040_FRAMEWORK_ARDUINO
|
||||
|
||||
; All the actual environments are defined below.
|
||||
|
||||
;;;;;;;; ESP8266 ;;;;;;;;
|
||||
|
||||
[env:esp8266-arduino]
|
||||
extends = common:esp8266-arduino
|
||||
board = nodemcuv2
|
||||
@@ -182,6 +184,8 @@ build_flags =
|
||||
${common:esp8266-arduino.build_flags}
|
||||
${flags:clangtidy.build_flags}
|
||||
|
||||
;;;;;;;; ESP32 ;;;;;;;;
|
||||
|
||||
[env:esp32-arduino]
|
||||
extends = common:esp32-arduino
|
||||
board = esp32dev
|
||||
@@ -189,6 +193,7 @@ board_build.partitions = huge_app.csv
|
||||
build_flags =
|
||||
${common:esp32-arduino.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32
|
||||
|
||||
[env:esp32-arduino-tidy]
|
||||
extends = common:esp32-arduino
|
||||
@@ -196,6 +201,7 @@ board = esp32dev
|
||||
build_flags =
|
||||
${common:esp32-arduino.build_flags}
|
||||
${flags:clangtidy.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32
|
||||
|
||||
[env:esp32-idf]
|
||||
extends = common:esp32-idf
|
||||
@@ -204,6 +210,7 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32-idf
|
||||
build_flags =
|
||||
${common:esp32-idf.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32
|
||||
|
||||
[env:esp32-idf-tidy]
|
||||
extends = common:esp32-idf
|
||||
@@ -212,6 +219,25 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32-idf-tidy
|
||||
build_flags =
|
||||
${common:esp32-idf.build_flags}
|
||||
${flags:clangtidy.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32
|
||||
|
||||
;;;;;;;; ESP32-C3 ;;;;;;;;
|
||||
|
||||
[env:esp32c3-arduino]
|
||||
extends = common:esp32-arduino
|
||||
board = esp32-c3-devkitm-1
|
||||
build_flags =
|
||||
${common:esp32-arduino.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32C3
|
||||
|
||||
[env:esp32c3-arduino-tidy]
|
||||
extends = common:esp32-arduino
|
||||
board = esp32-c3-devkitm-1
|
||||
build_flags =
|
||||
${common:esp32-arduino.build_flags}
|
||||
${flags:clangtidy.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32C3
|
||||
|
||||
[env:esp32c3-idf]
|
||||
extends = common:esp32-idf
|
||||
@@ -220,6 +246,7 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32c3-idf
|
||||
build_flags =
|
||||
${common:esp32-idf.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32C3
|
||||
|
||||
[env:esp32c3-idf-tidy]
|
||||
extends = common:esp32-idf
|
||||
@@ -228,6 +255,25 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32c3-idf-tidy
|
||||
build_flags =
|
||||
${common:esp32-idf.build_flags}
|
||||
${flags:clangtidy.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32C3
|
||||
|
||||
;;;;;;;; ESP32-S2 ;;;;;;;;
|
||||
|
||||
[env:esp32s2-arduino]
|
||||
extends = common:esp32-arduino
|
||||
board = esp32-s2-kaluga-1
|
||||
build_flags =
|
||||
${common:esp32-arduino.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32S2
|
||||
|
||||
[env:esp32s2-arduino-tidy]
|
||||
extends = common:esp32-arduino
|
||||
board = esp32-s2-kaluga-1
|
||||
build_flags =
|
||||
${common:esp32-arduino.build_flags}
|
||||
${flags:clangtidy.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32S2
|
||||
|
||||
[env:esp32s2-idf]
|
||||
extends = common:esp32-idf
|
||||
@@ -236,6 +282,7 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s2-idf
|
||||
build_flags =
|
||||
${common:esp32-idf.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32S2
|
||||
|
||||
[env:esp32s2-idf-tidy]
|
||||
extends = common:esp32-idf
|
||||
@@ -244,6 +291,45 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s2-idf-tidy
|
||||
build_flags =
|
||||
${common:esp32-idf.build_flags}
|
||||
${flags:clangtidy.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32S2
|
||||
|
||||
;;;;;;;; ESP32-S3 ;;;;;;;;
|
||||
|
||||
[env:esp32s3-arduino]
|
||||
extends = common:esp32-arduino
|
||||
board = esp32-s3-devkitc-1
|
||||
build_flags =
|
||||
${common:esp32-arduino.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32S3
|
||||
|
||||
[env:esp32s3-arduino-tidy]
|
||||
extends = common:esp32-arduino
|
||||
board = esp32-s3-devkitc-1
|
||||
build_flags =
|
||||
${common:esp32-arduino.build_flags}
|
||||
${flags:clangtidy.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32S3
|
||||
|
||||
[env:esp32s3-idf]
|
||||
extends = common:esp32-idf
|
||||
board = esp32-s3-devkitc-1
|
||||
board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s3-idf
|
||||
build_flags =
|
||||
${common:esp32-idf.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32S3
|
||||
|
||||
[env:esp32s3-idf-tidy]
|
||||
extends = common:esp32-idf
|
||||
board = esp32-s3-devkitc-1
|
||||
board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s3-idf-tidy
|
||||
build_flags =
|
||||
${common:esp32-idf.build_flags}
|
||||
${flags:clangtidy.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32S3
|
||||
|
||||
;;;;;;;; RP2040 ;;;;;;;;
|
||||
|
||||
[env:rp2040-pico-arduino]
|
||||
extends = common:rp2040-arduino
|
||||
@@ -251,3 +337,12 @@ board = rpipico
|
||||
build_flags =
|
||||
${common:rp2040-arduino.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
|
||||
[env:host]
|
||||
extends = common
|
||||
platform = platformio/native
|
||||
lib_deps =
|
||||
esphome/noise-c@0.1.1 ; used by api
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-DUSE_HOST
|
||||
|
@@ -3,14 +3,14 @@ PyYAML==6.0
|
||||
paho-mqtt==1.6.1
|
||||
colorama==0.4.6
|
||||
tornado==6.3.1
|
||||
tzlocal==4.2 # from time
|
||||
tzlocal==5.0.1 # from time
|
||||
tzdata>=2021.1 # from time
|
||||
pyserial==3.5
|
||||
platformio==6.1.6 # When updating platformio, also update Dockerfile
|
||||
esptool==4.5.1
|
||||
click==8.1.3
|
||||
esphome-dashboard==20230214.0
|
||||
aioesphomeapi==13.7.2
|
||||
esphome-dashboard==20230516.0
|
||||
aioesphomeapi==13.7.5
|
||||
zeroconf==0.60.0
|
||||
|
||||
# esp-idf requires this, but doesn't bundle it by default
|
||||
|
@@ -63,7 +63,7 @@ solve_registry = []
|
||||
def get_component_names():
|
||||
from esphome.loader import CORE_COMPONENTS_PATH
|
||||
|
||||
component_names = ["esphome", "sensor"]
|
||||
component_names = ["esphome", "sensor", "esp32", "esp8266"]
|
||||
|
||||
for d in os.listdir(CORE_COMPONENTS_PATH):
|
||||
if not d.startswith("__") and os.path.isdir(
|
||||
@@ -109,6 +109,13 @@ def write_file(name, obj):
|
||||
print(f"Wrote {full_path}")
|
||||
|
||||
|
||||
def delete_extra_files(keep_names):
|
||||
for d in os.listdir(args.output_path):
|
||||
if d.endswith(".json") and not d[:-5] in keep_names:
|
||||
os.remove(os.path.join(args.output_path, d))
|
||||
print(f"Deleted {d}")
|
||||
|
||||
|
||||
def register_module_schemas(key, module, manifest=None):
|
||||
for name, schema in module_schemas(module):
|
||||
register_known_schema(key, name, schema)
|
||||
@@ -150,7 +157,7 @@ def module_schemas(module):
|
||||
schemas = {}
|
||||
for m_attr_name in dir(module):
|
||||
m_attr_obj = getattr(module, m_attr_name)
|
||||
if isConvertibleSchema(m_attr_obj):
|
||||
if is_convertible_schema(m_attr_obj):
|
||||
schemas[module_str.find(m_attr_name)] = [m_attr_name, m_attr_obj]
|
||||
|
||||
for pos in sorted(schemas.keys()):
|
||||
@@ -240,25 +247,34 @@ def do_pins():
|
||||
pins_providers.append(pin_registry)
|
||||
|
||||
|
||||
def setBoards(obj, boards):
|
||||
obj[S_TYPE] = "enum"
|
||||
obj["values"] = {}
|
||||
for k, v in boards.items():
|
||||
obj["values"][k] = {"docs": v["name"]}
|
||||
|
||||
|
||||
def do_esp32():
|
||||
import esphome.components.esp32.boards as esp32_boards
|
||||
|
||||
setEnum(
|
||||
setBoards(
|
||||
output["esp32"]["schemas"]["CONFIG_SCHEMA"]["schema"]["config_vars"]["board"],
|
||||
list(esp32_boards.BOARDS.keys()),
|
||||
esp32_boards.BOARDS,
|
||||
)
|
||||
|
||||
|
||||
def do_esp8266():
|
||||
import esphome.components.esp8266.boards as esp8266_boards
|
||||
|
||||
setEnum(
|
||||
setBoards(
|
||||
output["esp8266"]["schemas"]["CONFIG_SCHEMA"]["schema"]["config_vars"]["board"],
|
||||
list(esp8266_boards.ESP8266_BOARD_PINS.keys()),
|
||||
esp8266_boards.BOARDS,
|
||||
)
|
||||
|
||||
|
||||
def fix_remote_receiver():
|
||||
if "remote_receiver.binary_sensor" not in output:
|
||||
return
|
||||
remote_receiver_schema = output["remote_receiver.binary_sensor"]["schemas"]
|
||||
remote_receiver_schema["CONFIG_SCHEMA"] = {
|
||||
"type": "schema",
|
||||
@@ -275,6 +291,8 @@ def fix_remote_receiver():
|
||||
|
||||
|
||||
def fix_script():
|
||||
if "script" not in output:
|
||||
return
|
||||
output["script"][S_SCHEMAS][S_CONFIG_SCHEMA][S_TYPE] = S_SCHEMA
|
||||
config_schema = output["script"][S_SCHEMAS][S_CONFIG_SCHEMA]
|
||||
config_schema[S_SCHEMA][S_CONFIG_VARS]["id"]["id_type"] = {
|
||||
@@ -283,7 +301,17 @@ def fix_script():
|
||||
config_schema["is_list"] = True
|
||||
|
||||
|
||||
def fix_font():
|
||||
if "font" not in output:
|
||||
return
|
||||
output["font"][S_SCHEMAS]["FILE_SCHEMA"] = output["font"][S_SCHEMAS].pop(
|
||||
"TYPED_FILE_SCHEMA"
|
||||
)
|
||||
|
||||
|
||||
def fix_menu():
|
||||
if "display_menu_base" not in output:
|
||||
return
|
||||
# # Menu has a recursive schema which is not kept properly
|
||||
schemas = output["display_menu_base"][S_SCHEMAS]
|
||||
# 1. Move items to a new schema
|
||||
@@ -330,6 +358,8 @@ def get_logger_tags():
|
||||
|
||||
|
||||
def add_logger_tags():
|
||||
if "logger" not in output or "schemas" not in output["logger"]:
|
||||
return
|
||||
tags = get_logger_tags()
|
||||
logs = output["logger"]["schemas"]["CONFIG_SCHEMA"]["schema"]["config_vars"][
|
||||
"logs"
|
||||
@@ -429,6 +459,12 @@ def merge(source, destination):
|
||||
return destination
|
||||
|
||||
|
||||
def is_platform_schema(schema_name):
|
||||
# added mostly because of schema_name == "microphone.MICROPHONE_SCHEMA"
|
||||
# which is shrunk because there is only one component of the schema (i2s_audio)
|
||||
return schema_name == "microphone.MICROPHONE_SCHEMA"
|
||||
|
||||
|
||||
def shrink():
|
||||
"""Shrink the extending schemas which has just an end type, e.g. at this point
|
||||
ota / port is type schema with extended pointing to core.port, this should instead be
|
||||
@@ -454,7 +490,7 @@ def shrink():
|
||||
add_referenced_recursive(referenced_schemas, vvv, [k, kv, kvv])
|
||||
|
||||
for x, paths in referenced_schemas.items():
|
||||
if len(paths) == 1:
|
||||
if len(paths) == 1 and not is_platform_schema(x):
|
||||
key_s = get_str_path_schema(x)
|
||||
arr_s = get_arr_path_schema(paths[0])
|
||||
# key_s |= arr_s
|
||||
@@ -508,6 +544,7 @@ def shrink():
|
||||
if (
|
||||
not s.endswith("." + S_CONFIG_SCHEMA)
|
||||
and s not in referenced_schemas.keys()
|
||||
and not is_platform_schema(s)
|
||||
):
|
||||
print(f"Removing {s}")
|
||||
output[domain][S_SCHEMAS].pop(schema_name)
|
||||
@@ -589,6 +626,7 @@ def build_schema():
|
||||
do_esp32()
|
||||
fix_remote_receiver()
|
||||
fix_script()
|
||||
fix_font()
|
||||
add_logger_tags()
|
||||
shrink()
|
||||
fix_menu()
|
||||
@@ -611,17 +649,13 @@ def build_schema():
|
||||
|
||||
for c, s in data.items():
|
||||
write_file(c, s)
|
||||
delete_extra_files(data.keys())
|
||||
|
||||
|
||||
def setEnum(obj, items):
|
||||
obj[S_TYPE] = "enum"
|
||||
obj["values"] = items
|
||||
|
||||
|
||||
def isConvertibleSchema(schema):
|
||||
def is_convertible_schema(schema):
|
||||
if schema is None:
|
||||
return False
|
||||
if isinstance(schema, (cv.Schema, cv.All)):
|
||||
if isinstance(schema, (cv.Schema, cv.All, cv.Any)):
|
||||
return True
|
||||
if repr(schema) in ejs.hidden_schemas:
|
||||
return True
|
||||
@@ -640,20 +674,23 @@ def isConvertibleSchema(schema):
|
||||
|
||||
def convert_config(schema, path):
|
||||
converted = {}
|
||||
convert_1(schema, converted, path)
|
||||
convert(schema, converted, path)
|
||||
return converted
|
||||
|
||||
|
||||
def convert_1(schema, config_var, path):
|
||||
def convert(schema, config_var, path):
|
||||
"""config_var can be a config_var or a schema: both are dicts
|
||||
config_var has a S_TYPE property, if this is S_SCHEMA, then it has a S_SCHEMA property
|
||||
schema does not have a type property, schema can have optionally both S_CONFIG_VARS and S_EXTENDS
|
||||
"""
|
||||
repr_schema = repr(schema)
|
||||
|
||||
if path.startswith("ads1115.sensor") and path.endswith("gain"):
|
||||
print(path)
|
||||
|
||||
if repr_schema in known_schemas:
|
||||
schema_info = known_schemas[(repr_schema)]
|
||||
for (schema_instance, name) in schema_info:
|
||||
for schema_instance, name in schema_info:
|
||||
if schema_instance is schema:
|
||||
assert S_CONFIG_VARS not in config_var
|
||||
assert S_EXTENDS not in config_var
|
||||
@@ -665,7 +702,7 @@ def convert_1(schema, config_var, path):
|
||||
config_var[S_SCHEMA] = {}
|
||||
if S_EXTENDS not in config_var[S_SCHEMA]:
|
||||
config_var[S_SCHEMA][S_EXTENDS] = [name]
|
||||
else:
|
||||
elif name not in config_var[S_SCHEMA][S_EXTENDS]:
|
||||
config_var[S_SCHEMA][S_EXTENDS].append(name)
|
||||
return
|
||||
|
||||
@@ -679,25 +716,25 @@ def convert_1(schema, config_var, path):
|
||||
return
|
||||
|
||||
assert len(extended) == 2
|
||||
convert_1(extended[0], config_var, path + "/extL")
|
||||
convert_1(extended[1], config_var, path + "/extR")
|
||||
convert(extended[0], config_var, path + "/extL")
|
||||
convert(extended[1], config_var, path + "/extR")
|
||||
return
|
||||
|
||||
if isinstance(schema, cv.All):
|
||||
i = 0
|
||||
for inner in schema.validators:
|
||||
i = i + 1
|
||||
convert_1(inner, config_var, path + f"/val {i}")
|
||||
convert(inner, config_var, path + f"/val {i}")
|
||||
return
|
||||
|
||||
if hasattr(schema, "validators"):
|
||||
i = 0
|
||||
for inner in schema.validators:
|
||||
i = i + 1
|
||||
convert_1(inner, config_var, path + f"/val {i}")
|
||||
convert(inner, config_var, path + f"/val {i}")
|
||||
|
||||
if isinstance(schema, cv.Schema):
|
||||
convert_1(schema.schema, config_var, path + "/all")
|
||||
convert(schema.schema, config_var, path + "/all")
|
||||
return
|
||||
|
||||
if isinstance(schema, dict):
|
||||
@@ -707,7 +744,7 @@ def convert_1(schema, config_var, path):
|
||||
if repr_schema in ejs.list_schemas:
|
||||
config_var["is_list"] = True
|
||||
items_schema = ejs.list_schemas[repr_schema][0]
|
||||
convert_1(items_schema, config_var, path + "/list")
|
||||
convert(items_schema, config_var, path + "/list")
|
||||
return
|
||||
|
||||
if DUMP_RAW:
|
||||
@@ -741,10 +778,10 @@ def convert_1(schema, config_var, path):
|
||||
# enums, e.g. esp32/variant
|
||||
if schema_type == "one_of":
|
||||
config_var[S_TYPE] = "enum"
|
||||
config_var["values"] = list(data)
|
||||
config_var["values"] = dict.fromkeys(list(data))
|
||||
elif schema_type == "enum":
|
||||
config_var[S_TYPE] = "enum"
|
||||
config_var["values"] = list(data.keys())
|
||||
config_var["values"] = dict.fromkeys(list(data.keys()))
|
||||
elif schema_type == "maybe":
|
||||
config_var[S_TYPE] = S_SCHEMA
|
||||
config_var["maybe"] = data[1]
|
||||
@@ -785,13 +822,13 @@ def convert_1(schema, config_var, path):
|
||||
config_var["filter"] = data[0]
|
||||
elif schema_type == "templatable":
|
||||
config_var["templatable"] = True
|
||||
convert_1(data, config_var, path + "/templat")
|
||||
convert(data, config_var, path + "/templat")
|
||||
elif schema_type == "triggers":
|
||||
# remote base
|
||||
convert_1(data, config_var, path + "/trigger")
|
||||
convert(data, config_var, path + "/trigger")
|
||||
elif schema_type == "sensor":
|
||||
schema = data
|
||||
convert_1(data, config_var, path + "/trigger")
|
||||
convert(data, config_var, path + "/trigger")
|
||||
elif schema_type == "declare_id":
|
||||
# pylint: disable=protected-access
|
||||
parents = data._parents
|
||||
@@ -856,6 +893,9 @@ def convert_1(schema, config_var, path):
|
||||
|
||||
if DUMP_PATH:
|
||||
config_var["path"] = path
|
||||
if S_TYPE not in config_var:
|
||||
pass
|
||||
# print(path)
|
||||
|
||||
|
||||
def get_overridden_config(key, converted):
|
||||
@@ -921,7 +961,7 @@ def convert_keys(converted, schema, path):
|
||||
result["default"] = str(default_value)
|
||||
|
||||
# Do value
|
||||
convert_1(v, result, path + f"/{str(k)}")
|
||||
convert(v, result, path + f"/{str(k)}")
|
||||
if "schema" not in converted:
|
||||
converted[S_TYPE] = "schema"
|
||||
converted["schema"] = {S_CONFIG_VARS: {}}
|
||||
|
@@ -535,6 +535,7 @@ def lint_relative_py_import(fname):
|
||||
"esphome/components/esp32/core.cpp",
|
||||
"esphome/components/esp8266/core.cpp",
|
||||
"esphome/components/rp2040/core.cpp",
|
||||
"esphome/components/host/core.cpp",
|
||||
],
|
||||
)
|
||||
def lint_namespace(fname, content):
|
||||
|
@@ -300,6 +300,8 @@ text_sensor:
|
||||
sm2135:
|
||||
data_pin: GPIO12
|
||||
clock_pin: GPIO14
|
||||
rgb_current: 20mA
|
||||
cw_current: 60mA
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
|
@@ -700,12 +700,26 @@ prometheus:
|
||||
|
||||
microphone:
|
||||
- platform: i2s_audio
|
||||
id: mic_id
|
||||
id: mic_id_adc
|
||||
adc_pin: GPIO35
|
||||
adc_type: internal
|
||||
|
||||
- platform: i2s_audio
|
||||
id: mic_id_external
|
||||
i2s_din_pin: GPIO23
|
||||
adc_type: external
|
||||
pdm: false
|
||||
|
||||
speaker:
|
||||
- platform: i2s_audio
|
||||
id: speaker_id
|
||||
dac_type: external
|
||||
i2s_dout_pin: GPIO25
|
||||
mode: mono
|
||||
|
||||
|
||||
voice_assistant:
|
||||
microphone: mic_id
|
||||
microphone: mic_id_external
|
||||
on_start:
|
||||
- logger.log: "Voice assistant started"
|
||||
on_stt_end:
|
||||
|
@@ -208,6 +208,12 @@ tlc5947:
|
||||
clock_pin: GPIO14
|
||||
lat_pin: GPIO15
|
||||
|
||||
gp8403:
|
||||
- id: gp8403_5v
|
||||
voltage: 5V
|
||||
- id: gp8403_10v
|
||||
voltage: 10V
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
pin: GPIO2
|
||||
@@ -245,6 +251,15 @@ output:
|
||||
id: Led7
|
||||
led: 7
|
||||
|
||||
- platform: gp8403
|
||||
id: gp8403_output_0
|
||||
gp8403_id: gp8403_5v
|
||||
channel: 0
|
||||
- platform: gp8403
|
||||
gp8403_id: gp8403_10v
|
||||
id: gp8403_output_1
|
||||
channel: 1
|
||||
|
||||
demo:
|
||||
|
||||
esp32_ble:
|
||||
@@ -474,6 +489,7 @@ sensor:
|
||||
offset: 0
|
||||
normalized_offset_slope: 0
|
||||
time_constant: 0
|
||||
auto_cleaning_interval: 604800s
|
||||
acceleration_mode: low
|
||||
store_baseline: true
|
||||
address: 0x69
|
||||
@@ -629,3 +645,22 @@ key_collector:
|
||||
source_id: keypad
|
||||
min_length: 4
|
||||
max_length: 4
|
||||
|
||||
light:
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led_strip
|
||||
pin: 13
|
||||
num_leds: 60
|
||||
rmt_channel: 6
|
||||
rgb_order: GRB
|
||||
chipset: ws2812
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led_strip2
|
||||
pin: 15
|
||||
num_leds: 60
|
||||
rmt_channel: 2
|
||||
rgb_order: RGB
|
||||
bit0_high: 100us
|
||||
bit0_low: 100us
|
||||
bit1_high: 100us
|
||||
bit1_low: 100us
|
||||
|
Reference in New Issue
Block a user