mirror of
https://github.com/esphome/esphome.git
synced 2024-11-27 17:27:59 +01:00
Merge branch 'esphome:dev' into daikin-enhancements
This commit is contained in:
commit
51a63bf968
32 changed files with 320 additions and 55 deletions
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
|
@ -42,7 +42,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.7.1
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Set up Docker Buildx
|
||||
|
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -40,7 +40,7 @@ jobs:
|
|||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.7.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
|
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
@ -45,7 +45,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.7.1
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Set up python environment
|
||||
|
@ -90,7 +90,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.7.1
|
||||
with:
|
||||
python-version: "3.9"
|
||||
|
||||
|
|
2
.github/workflows/sync-device-classes.yml
vendored
2
.github/workflows/sync-device-classes.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
|||
path: lib/home-assistant
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4.7.0
|
||||
uses: actions/setup-python@v4.7.1
|
||||
with:
|
||||
python-version: 3.11
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@ fi
|
|||
|
||||
mkdir -p "${pio_cache_base}"
|
||||
|
||||
mkdir -p /config/esphome
|
||||
|
||||
if bashio::fs.directory_exists '/config/esphome/.esphome'; then
|
||||
bashio::log.info "Migrating old .esphome directory..."
|
||||
if bashio::fs.file_exists '/config/esphome/.esphome/esphome.json'; then
|
||||
|
|
|
@ -11,6 +11,7 @@ from esphome.const import (
|
|||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE_ID,
|
||||
CONF_TIME,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
)
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.util import Registry
|
||||
|
@ -69,6 +70,8 @@ WhileAction = cg.esphome_ns.class_("WhileAction", Action)
|
|||
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
|
||||
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
|
||||
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
|
||||
SuspendComponentAction = cg.esphome_ns.class_("SuspendComponentAction", Action)
|
||||
ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action)
|
||||
Automation = cg.esphome_ns.class_("Automation")
|
||||
|
||||
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
|
||||
|
@ -138,6 +141,7 @@ AUTOMATION_SCHEMA = cv.Schema(
|
|||
AndCondition = cg.esphome_ns.class_("AndCondition", Condition)
|
||||
OrCondition = cg.esphome_ns.class_("OrCondition", Condition)
|
||||
NotCondition = cg.esphome_ns.class_("NotCondition", Condition)
|
||||
XorCondition = cg.esphome_ns.class_("XorCondition", Condition)
|
||||
|
||||
|
||||
@register_condition("and", AndCondition, validate_condition_list)
|
||||
|
@ -158,6 +162,12 @@ async def not_condition_to_code(config, condition_id, template_arg, args):
|
|||
return cg.new_Pvariable(condition_id, template_arg, condition)
|
||||
|
||||
|
||||
@register_condition("xor", XorCondition, validate_condition_list)
|
||||
async def xor_condition_to_code(config, condition_id, template_arg, args):
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
|
||||
|
||||
@register_condition("lambda", LambdaCondition, cv.returning_lambda)
|
||||
async def lambda_condition_to_code(config, condition_id, template_arg, args):
|
||||
lambda_ = await cg.process_lambda(config, args, return_type=bool)
|
||||
|
@ -303,6 +313,41 @@ async def component_update_action_to_code(config, action_id, template_arg, args)
|
|||
return cg.new_Pvariable(action_id, template_arg, comp)
|
||||
|
||||
|
||||
@register_action(
|
||||
"component.suspend",
|
||||
SuspendComponentAction,
|
||||
maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def component_suspend_action_to_code(config, action_id, template_arg, args):
|
||||
comp = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, comp)
|
||||
|
||||
|
||||
@register_action(
|
||||
"component.resume",
|
||||
ResumeComponentAction,
|
||||
maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
|
||||
cv.Optional(CONF_UPDATE_INTERVAL): cv.templatable(
|
||||
cv.positive_time_period_milliseconds
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def component_resume_action_to_code(config, action_id, template_arg, args):
|
||||
comp = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, comp)
|
||||
if CONF_UPDATE_INTERVAL in config:
|
||||
template_ = await cg.templatable(config[CONF_UPDATE_INTERVAL], args, int)
|
||||
cg.add(var.set_update_interval(template_))
|
||||
return var
|
||||
|
||||
|
||||
async def build_action(full_config, template_arg, args):
|
||||
registry_entry, config = cg.extract_registry_entry_config(
|
||||
ACTION_REGISTRY, full_config
|
||||
|
|
|
@ -39,10 +39,14 @@ void BP5758D::loop() {
|
|||
uint8_t data[17];
|
||||
if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
|
||||
this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) {
|
||||
// Off / Sleep
|
||||
data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_STANDBY;
|
||||
for (int i = 1; i < 16; i++)
|
||||
data[i] = 0;
|
||||
|
||||
// First turn all channels off
|
||||
data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_START_3CH;
|
||||
this->write_buffer_(data, 17);
|
||||
// Then sleep
|
||||
data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_STANDBY;
|
||||
this->write_buffer_(data, 17);
|
||||
} else if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
|
||||
(this->pwm_amounts_[3] > 0 || this->pwm_amounts_[4] > 0)) {
|
||||
|
|
|
@ -235,6 +235,7 @@ ESP32_BOARD_PINS = {
|
|||
"SDA": 5,
|
||||
"SS": 15,
|
||||
},
|
||||
"denky_d4": {"RX": 8, "LED": 14},
|
||||
"esp-wrover-kit": {},
|
||||
"esp32-devkitlipo": {},
|
||||
"esp32-evb": {
|
||||
|
|
|
@ -13,7 +13,6 @@ from esphome.const import (
|
|||
CONF_PAGES,
|
||||
CONF_RESET_PIN,
|
||||
CONF_DIMENSIONS,
|
||||
CONF_DATA_RATE,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["spi"]
|
||||
|
@ -100,11 +99,10 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list(
|
||||
cv.file_
|
||||
),
|
||||
cv.Optional(CONF_DATA_RATE, default="40MHz"): spi.SPI_DATA_RATE_SCHEMA,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("1s"))
|
||||
.extend(spi.spi_device_schema(False)),
|
||||
.extend(spi.spi_device_schema(False, "40MHz")),
|
||||
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
|
||||
_validate,
|
||||
)
|
||||
|
@ -177,4 +175,3 @@ async def to_code(config):
|
|||
if rhs is not None:
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
cg.add(var.set_palette(prog_arr))
|
||||
cg.add(var.set_data_rate(config[CONF_DATA_RATE]))
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace max6675 {
|
|||
class MAX6675Sensor : public sensor::Sensor,
|
||||
public PollingComponent,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_1KHZ> {
|
||||
spi::DATA_RATE_1MHZ> {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
|
|
@ -164,6 +164,10 @@ void MAX7219Component::send_to_all_(uint8_t a_register, uint8_t data) {
|
|||
this->disable();
|
||||
}
|
||||
void MAX7219Component::update() {
|
||||
if (this->intensity_changed_) {
|
||||
this->send_to_all_(MAX7219_REGISTER_INTENSITY, this->intensity_);
|
||||
this->intensity_changed_ = false;
|
||||
}
|
||||
for (uint8_t i = 0; i < this->num_chips_ * 8; i++)
|
||||
this->buffer_[i] = 0;
|
||||
if (this->writer_.has_value())
|
||||
|
@ -217,7 +221,13 @@ uint8_t MAX7219Component::printf(const char *format, ...) {
|
|||
return 0;
|
||||
}
|
||||
void MAX7219Component::set_writer(max7219_writer_t &&writer) { this->writer_ = writer; }
|
||||
void MAX7219Component::set_intensity(uint8_t intensity) { this->intensity_ = intensity; }
|
||||
void MAX7219Component::set_intensity(uint8_t intensity) {
|
||||
intensity &= 0xF;
|
||||
if (intensity != this->intensity_) {
|
||||
this->intensity_changed_ = true;
|
||||
this->intensity_ = intensity;
|
||||
}
|
||||
}
|
||||
void MAX7219Component::set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; }
|
||||
|
||||
uint8_t MAX7219Component::strftime(uint8_t pos, const char *format, ESPTime time) {
|
||||
|
|
|
@ -52,7 +52,8 @@ class MAX7219Component : public PollingComponent,
|
|||
void send_byte_(uint8_t a_register, uint8_t data);
|
||||
void send_to_all_(uint8_t a_register, uint8_t data);
|
||||
|
||||
uint8_t intensity_{15}; /// Intensity of the display from 0 to 15 (most)
|
||||
uint8_t intensity_{15}; // Intensity of the display from 0 to 15 (most)
|
||||
bool intensity_changed_{}; // True if we need to re-send the intensity
|
||||
uint8_t num_chips_{1};
|
||||
uint8_t *buffer_;
|
||||
bool reverse_{false};
|
||||
|
|
|
@ -38,7 +38,7 @@ CONFIG_SCHEMA = (
|
|||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HEATER_ENABLED, default=True): cv.boolean,
|
||||
cv.Optional(CONF_HEATER_ENABLED, default=False): cv.boolean,
|
||||
},
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
|
|
|
@ -25,6 +25,7 @@ from esphome.const import (
|
|||
KEY_CORE,
|
||||
KEY_TARGET_PLATFORM,
|
||||
KEY_VARIANT,
|
||||
CONF_DATA_RATE,
|
||||
)
|
||||
from esphome.core import coroutine_with_priority, CORE
|
||||
|
||||
|
@ -33,6 +34,7 @@ spi_ns = cg.esphome_ns.namespace("spi")
|
|||
SPIComponent = spi_ns.class_("SPIComponent", cg.Component)
|
||||
SPIDevice = spi_ns.class_("SPIDevice")
|
||||
SPIDataRate = spi_ns.enum("SPIDataRate")
|
||||
SPIMode = spi_ns.enum("SPIMode")
|
||||
|
||||
SPI_DATA_RATE_OPTIONS = {
|
||||
80e6: SPIDataRate.DATA_RATE_80MHZ,
|
||||
|
@ -50,10 +52,37 @@ SPI_DATA_RATE_OPTIONS = {
|
|||
}
|
||||
SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS))
|
||||
|
||||
SPI_MODE_OPTIONS = {
|
||||
"MODE0": SPIMode.MODE0,
|
||||
"MODE1": SPIMode.MODE1,
|
||||
"MODE2": SPIMode.MODE2,
|
||||
"MODE3": SPIMode.MODE3,
|
||||
0: SPIMode.MODE0,
|
||||
1: SPIMode.MODE1,
|
||||
2: SPIMode.MODE2,
|
||||
3: SPIMode.MODE3,
|
||||
}
|
||||
|
||||
CONF_SPI_MODE = "spi_mode"
|
||||
CONF_FORCE_SW = "force_sw"
|
||||
CONF_INTERFACE = "interface"
|
||||
CONF_INTERFACE_INDEX = "interface_index"
|
||||
|
||||
# RP2040 SPI pin assignments are complicated. Refer to https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
|
||||
|
||||
RP_SPI_PINSETS = [
|
||||
{
|
||||
CONF_MISO_PIN: [0, 4, 16, 20, -1],
|
||||
CONF_CLK_PIN: [2, 6, 18, 22],
|
||||
CONF_MOSI_PIN: [3, 7, 19, 23, -1],
|
||||
},
|
||||
{
|
||||
CONF_MISO_PIN: [8, 12, 24, 28, -1],
|
||||
CONF_CLK_PIN: [10, 14, 26],
|
||||
CONF_MOSI_PIN: [11, 23, 27, -1],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_target_platform():
|
||||
return (
|
||||
|
@ -85,7 +114,7 @@ def get_hw_interface_list():
|
|||
return [["spi", "spi2"]]
|
||||
return [["spi", "spi2"], ["spi3"]]
|
||||
if target_platform == "rp2040":
|
||||
return [["spi"]]
|
||||
return [["spi"], ["spi1"]]
|
||||
return []
|
||||
|
||||
|
||||
|
@ -99,8 +128,10 @@ def get_spi_index(name):
|
|||
|
||||
|
||||
# Check that pins are suitable for HW spi
|
||||
# \param spi the config data for the spi instance
|
||||
# \param index the selected hw interface number, -1 if not yet known
|
||||
# TODO verify that the pins are internal
|
||||
def validate_hw_pins(spi):
|
||||
def validate_hw_pins(spi, index=-1):
|
||||
clk_pin = spi[CONF_CLK_PIN]
|
||||
if clk_pin[CONF_INVERTED]:
|
||||
return False
|
||||
|
@ -129,9 +160,30 @@ def validate_hw_pins(spi):
|
|||
if target_platform == "esp32":
|
||||
return clk_pin_no >= 0
|
||||
|
||||
if target_platform == "rp2040":
|
||||
pin_set = (
|
||||
list(filter(lambda s: clk_pin_no in s[CONF_CLK_PIN], RP_SPI_PINSETS))[0]
|
||||
if index == -1
|
||||
else RP_SPI_PINSETS[index]
|
||||
)
|
||||
if pin_set is None:
|
||||
return False
|
||||
if sdo_pin_no not in pin_set[CONF_MOSI_PIN]:
|
||||
return False
|
||||
if sdi_pin_no not in pin_set[CONF_MISO_PIN]:
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_hw_spi(config, available):
|
||||
"""Get an available hardware spi interface suitable for this config"""
|
||||
matching = list(filter(lambda idx: validate_hw_pins(config, idx), available))
|
||||
if len(matching) != 0:
|
||||
return matching[0]
|
||||
return None
|
||||
|
||||
|
||||
def validate_spi_config(config):
|
||||
available = list(range(len(get_hw_interface_list())))
|
||||
for spi in config:
|
||||
|
@ -147,9 +199,10 @@ def validate_spi_config(config):
|
|||
if not validate_hw_pins(spi):
|
||||
spi[CONF_INTERFACE] = "software"
|
||||
elif interface == "hardware":
|
||||
if len(available) == 0:
|
||||
raise cv.Invalid("No hardware interface available")
|
||||
index = spi[CONF_INTERFACE_INDEX] = available[0]
|
||||
index = get_hw_spi(spi, available)
|
||||
if index is None:
|
||||
raise cv.Invalid("No suitable hardware interface available")
|
||||
spi[CONF_INTERFACE_INDEX] = index
|
||||
available.remove(index)
|
||||
else:
|
||||
# Must be a specific name
|
||||
|
@ -164,11 +217,14 @@ def validate_spi_config(config):
|
|||
# Any specific names and any 'hardware' requests will have already been filled,
|
||||
# so just need to assign remaining hardware to 'any' requests.
|
||||
for spi in config:
|
||||
if spi[CONF_INTERFACE] == "any" and len(available) != 0:
|
||||
index = available[0]
|
||||
spi[CONF_INTERFACE_INDEX] = index
|
||||
available.remove(index)
|
||||
if CONF_INTERFACE_INDEX in spi and not validate_hw_pins(spi):
|
||||
if spi[CONF_INTERFACE] == "any":
|
||||
index = get_hw_spi(spi, available)
|
||||
if index is not None:
|
||||
spi[CONF_INTERFACE_INDEX] = index
|
||||
available.remove(index)
|
||||
if CONF_INTERFACE_INDEX in spi and not validate_hw_pins(
|
||||
spi, spi[CONF_INTERFACE_INDEX]
|
||||
):
|
||||
raise cv.Invalid("Invalid pin selections for hardware SPI interface")
|
||||
|
||||
return config
|
||||
|
@ -181,13 +237,13 @@ def get_spi_interface(index):
|
|||
# Arduino code follows
|
||||
platform = get_target_platform()
|
||||
if platform == "rp2040":
|
||||
return "&spi1"
|
||||
return ["&SPI", "&SPI1"][index]
|
||||
if index == 0:
|
||||
return "&SPI"
|
||||
# Following code can't apply to C2, H2 or 8266 since they have only one SPI
|
||||
if get_target_variant() in (VARIANT_ESP32S3, VARIANT_ESP32S2):
|
||||
return "new SPIClass(FSPI)"
|
||||
return "return new SPIClass(HSPI)"
|
||||
return "new SPIClass(HSPI)"
|
||||
|
||||
|
||||
SPI_SCHEMA = cv.All(
|
||||
|
@ -244,13 +300,20 @@ async def to_code(configs):
|
|||
cg.add_library("SPI", None)
|
||||
|
||||
|
||||
def spi_device_schema(cs_pin_required=True):
|
||||
def spi_device_schema(
|
||||
cs_pin_required=True, default_data_rate=cv.UNDEFINED, default_mode=cv.UNDEFINED
|
||||
):
|
||||
"""Create a schema for an SPI device.
|
||||
:param cs_pin_required: If true, make the CS_PIN required in the config.
|
||||
:param default_data_rate: Optional data_rate to use as default
|
||||
:return: The SPI device schema, `extend` this in your config schema.
|
||||
"""
|
||||
schema = {
|
||||
cv.GenerateID(CONF_SPI_ID): cv.use_id(SPIComponent),
|
||||
cv.Optional(CONF_DATA_RATE, default=default_data_rate): SPI_DATA_RATE_SCHEMA,
|
||||
cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum(
|
||||
SPI_MODE_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
if cs_pin_required:
|
||||
schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema
|
||||
|
@ -265,6 +328,10 @@ async def register_spi_device(var, config):
|
|||
if CONF_CS_PIN in config:
|
||||
pin = await cg.gpio_pin_expression(config[CONF_CS_PIN])
|
||||
cg.add(var.set_cs_pin(pin))
|
||||
if CONF_DATA_RATE in config:
|
||||
cg.add(var.set_data_rate(config[CONF_DATA_RATE]))
|
||||
if CONF_SPI_MODE in config:
|
||||
cg.add(var.set_mode(config[CONF_SPI_MODE]))
|
||||
|
||||
|
||||
def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: bool):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import spi
|
||||
from esphome.const import CONF_ID, CONF_DATA_RATE, CONF_MODE
|
||||
from esphome.const import CONF_ID, CONF_MODE
|
||||
|
||||
DEPENDENCIES = ["spi"]
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
@ -33,17 +33,15 @@ CONF_BIT_ORDER = "bit_order"
|
|||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(spi_device),
|
||||
cv.Optional(CONF_DATA_RATE, default="1MHz"): spi.SPI_DATA_RATE_SCHEMA,
|
||||
cv.Optional(CONF_BIT_ORDER, default="msb_first"): cv.enum(ORDERS, lower=True),
|
||||
cv.Optional(CONF_MODE, default="0"): cv.enum(MODES, upper=True),
|
||||
}
|
||||
).extend(spi.spi_device_schema(False))
|
||||
).extend(spi.spi_device_schema(False, "1MHz"))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_data_rate(config[CONF_DATA_RATE]))
|
||||
cg.add(var.set_mode(config[CONF_MODE]))
|
||||
cg.add(var.set_bit_order(config[CONF_BIT_ORDER]))
|
||||
await spi.register_spi_device(var, config)
|
||||
|
|
|
@ -2,7 +2,7 @@ import esphome.codegen as cg
|
|||
import esphome.config_validation as cv
|
||||
from esphome.components import light
|
||||
from esphome.components import spi
|
||||
from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_DATA_RATE
|
||||
from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS
|
||||
|
||||
spi_led_strip_ns = cg.esphome_ns.namespace("spi_led_strip")
|
||||
SpiLedStrip = spi_led_strip_ns.class_(
|
||||
|
@ -13,14 +13,12 @@ CONFIG_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend(
|
|||
{
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpiLedStrip),
|
||||
cv.Optional(CONF_NUM_LEDS, default=1): cv.positive_not_null_int,
|
||||
cv.Optional(CONF_DATA_RATE, default="1MHz"): spi.SPI_DATA_RATE_SCHEMA,
|
||||
}
|
||||
).extend(spi.spi_device_schema(False))
|
||||
).extend(spi.spi_device_schema(False, "1MHz"))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
cg.add(var.set_data_rate(spi.SPI_DATA_RATE_OPTIONS[config[CONF_DATA_RATE]]))
|
||||
cg.add(var.set_num_leds(config[CONF_NUM_LEDS]))
|
||||
await light.register_light(var, config)
|
||||
await spi.register_spi_device(var, config)
|
||||
|
|
|
@ -20,7 +20,7 @@ from esphome.const import (
|
|||
DEVICE_CLASS_PM25,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||
UNIT_COUNTS_PER_CUBIC_METER,
|
||||
UNIT_COUNTS_PER_CUBIC_CENTIMETER,
|
||||
UNIT_MICROMETER,
|
||||
ICON_CHEMICAL_WEAPON,
|
||||
ICON_COUNTER,
|
||||
|
@ -73,31 +73,31 @@ CONFIG_SCHEMA = (
|
|||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PMC_0_5): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER,
|
||||
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER,
|
||||
icon=ICON_COUNTER,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PMC_1_0): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER,
|
||||
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER,
|
||||
icon=ICON_COUNTER,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PMC_2_5): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER,
|
||||
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER,
|
||||
icon=ICON_COUNTER,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PMC_4_0): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER,
|
||||
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER,
|
||||
icon=ICON_COUNTER,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PMC_10_0): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER,
|
||||
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER,
|
||||
icon=ICON_COUNTER,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
|
|
|
@ -112,6 +112,9 @@ void SSD1351::set_brightness(float brightness) {
|
|||
} else {
|
||||
this->brightness_ = brightness;
|
||||
}
|
||||
if (!this->is_ready()) {
|
||||
return; // Component is not yet setup skip the command
|
||||
}
|
||||
// now write the new brightness level to the display
|
||||
this->command(SSD1351_CONTRASTMASTER);
|
||||
this->data(int(SSD1351_MAX_CONTRAST * (this->brightness_)));
|
||||
|
|
|
@ -138,7 +138,10 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Required(CONF_MODEL): cv.one_of(*MODELS.keys(), upper=True, space="_"),
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_DC_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_BACKLIGHT_PIN): cv.Any(
|
||||
cv.boolean,
|
||||
pins.gpio_output_pin_schema,
|
||||
),
|
||||
cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply),
|
||||
cv.Optional(CONF_EIGHTBITCOLOR, default=False): cv.boolean,
|
||||
cv.Optional(CONF_HEIGHT): cv.int_,
|
||||
|
@ -174,7 +177,7 @@ async def to_code(config):
|
|||
reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
|
||||
cg.add(var.set_reset_pin(reset))
|
||||
|
||||
if CONF_BACKLIGHT_PIN in config:
|
||||
if CONF_BACKLIGHT_PIN in config and config[CONF_BACKLIGHT_PIN]:
|
||||
bl = await cg.gpio_pin_expression(config[CONF_BACKLIGHT_PIN])
|
||||
cg.add(var.set_backlight_pin(bl))
|
||||
|
||||
|
|
|
@ -133,6 +133,7 @@ void ST7789V::dump_config() {
|
|||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
LOG_PIN(" B/L Pin: ", this->backlight_pin_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
ESP_LOGCONFIG(TAG, " Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000));
|
||||
#ifdef USE_POWER_SUPPLY
|
||||
ESP_LOGCONFIG(TAG, " Power Supply Configured: yes");
|
||||
#endif
|
||||
|
|
|
@ -6,6 +6,7 @@ from esphome.const import (
|
|||
CONF_NUMBER_DATAPOINT,
|
||||
CONF_MAX_VALUE,
|
||||
CONF_MIN_VALUE,
|
||||
CONF_MULTIPLY,
|
||||
CONF_STEP,
|
||||
)
|
||||
from .. import tuya_ns, CONF_TUYA_ID, Tuya
|
||||
|
@ -31,6 +32,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Required(CONF_MAX_VALUE): cv.float_,
|
||||
cv.Required(CONF_MIN_VALUE): cv.float_,
|
||||
cv.Required(CONF_STEP): cv.positive_float,
|
||||
cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
|
@ -49,7 +51,8 @@ async def to_code(config):
|
|||
step=config[CONF_STEP],
|
||||
)
|
||||
|
||||
paren = await cg.get_variable(config[CONF_TUYA_ID])
|
||||
cg.add(var.set_tuya_parent(paren))
|
||||
cg.add(var.set_write_multiply(config[CONF_MULTIPLY]))
|
||||
parent = await cg.get_variable(config[CONF_TUYA_ID])
|
||||
cg.add(var.set_tuya_parent(parent))
|
||||
|
||||
cg.add(var.set_number_id(config[CONF_NUMBER_DATAPOINT]))
|
||||
|
|
|
@ -10,7 +10,7 @@ void TuyaNumber::setup() {
|
|||
this->parent_->register_listener(this->number_id_, [this](const TuyaDatapoint &datapoint) {
|
||||
if (datapoint.type == TuyaDatapointType::INTEGER) {
|
||||
ESP_LOGV(TAG, "MCU reported number %u is: %d", datapoint.id, datapoint.value_int);
|
||||
this->publish_state(datapoint.value_int * this->traits.get_step());
|
||||
this->publish_state(datapoint.value_int / multiply_by_);
|
||||
} else if (datapoint.type == TuyaDatapointType::ENUM) {
|
||||
ESP_LOGV(TAG, "MCU reported number %u is: %u", datapoint.id, datapoint.value_enum);
|
||||
this->publish_state(datapoint.value_enum);
|
||||
|
@ -22,7 +22,7 @@ void TuyaNumber::setup() {
|
|||
void TuyaNumber::control(float value) {
|
||||
ESP_LOGV(TAG, "Setting number %u: %f", this->number_id_, value);
|
||||
if (this->type_ == TuyaDatapointType::INTEGER) {
|
||||
int integer_value = lround(value / this->traits.get_step());
|
||||
int integer_value = lround(value * multiply_by_);
|
||||
this->parent_->set_integer_datapoint_value(this->number_id_, integer_value);
|
||||
} else if (this->type_ == TuyaDatapointType::ENUM) {
|
||||
this->parent_->set_enum_datapoint_value(this->number_id_, value);
|
||||
|
|
|
@ -12,6 +12,7 @@ class TuyaNumber : public number::Number, public Component {
|
|||
void setup() override;
|
||||
void dump_config() override;
|
||||
void set_number_id(uint8_t number_id) { this->number_id_ = number_id; }
|
||||
void set_write_multiply(float factor) { multiply_by_ = factor; }
|
||||
|
||||
void set_tuya_parent(Tuya *parent) { this->parent_ = parent; }
|
||||
|
||||
|
@ -20,6 +21,7 @@ class TuyaNumber : public number::Number, public Component {
|
|||
|
||||
Tuya *parent_;
|
||||
uint8_t number_id_{0};
|
||||
float multiply_by_{1.0};
|
||||
TuyaDatapointType type_{};
|
||||
};
|
||||
|
||||
|
|
|
@ -915,7 +915,7 @@ UNIT_BYTES = "B"
|
|||
UNIT_CELSIUS = "°C"
|
||||
UNIT_CENTIMETER = "cm"
|
||||
UNIT_COUNT_DECILITRE = "/dL"
|
||||
UNIT_COUNTS_PER_CUBIC_METER = "#/m³"
|
||||
UNIT_COUNTS_PER_CUBIC_CENTIMETER = "#/cm³"
|
||||
UNIT_CUBIC_METER = "m³"
|
||||
UNIT_CUBIC_METER_PER_HOUR = "m³/h"
|
||||
UNIT_DECIBEL = "dB"
|
||||
|
|
|
@ -48,6 +48,22 @@ template<typename... Ts> class NotCondition : public Condition<Ts...> {
|
|||
Condition<Ts...> *condition_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class XorCondition : public Condition<Ts...> {
|
||||
public:
|
||||
explicit XorCondition(const std::vector<Condition<Ts...> *> &conditions) : conditions_(conditions) {}
|
||||
bool check(Ts... x) override {
|
||||
bool xor_state = false;
|
||||
for (auto *condition : this->conditions_) {
|
||||
xor_state = xor_state ^ condition->check(x...);
|
||||
}
|
||||
|
||||
return xor_state;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<Condition<Ts...> *> conditions_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class LambdaCondition : public Condition<Ts...> {
|
||||
public:
|
||||
explicit LambdaCondition(std::function<bool(Ts...)> &&f) : f_(std::move(f)) {}
|
||||
|
@ -330,4 +346,38 @@ template<typename... Ts> class UpdateComponentAction : public Action<Ts...> {
|
|||
PollingComponent *component_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SuspendComponentAction : public Action<Ts...> {
|
||||
public:
|
||||
SuspendComponentAction(PollingComponent *component) : component_(component) {}
|
||||
|
||||
void play(Ts... x) override {
|
||||
if (!this->component_->is_ready())
|
||||
return;
|
||||
this->component_->stop_poller();
|
||||
}
|
||||
|
||||
protected:
|
||||
PollingComponent *component_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ResumeComponentAction : public Action<Ts...> {
|
||||
public:
|
||||
ResumeComponentAction(PollingComponent *component) : component_(component) {}
|
||||
TEMPLATABLE_VALUE(uint32_t, update_interval)
|
||||
|
||||
void play(Ts... x) override {
|
||||
if (!this->component_->is_ready()) {
|
||||
return;
|
||||
}
|
||||
optional<uint32_t> update_interval = this->update_interval_.optional_value(x...);
|
||||
if (update_interval.has_value()) {
|
||||
this->component_->set_update_interval(update_interval.value());
|
||||
}
|
||||
this->component_->start_poller();
|
||||
}
|
||||
|
||||
protected:
|
||||
PollingComponent *component_;
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
|
|
|
@ -188,10 +188,20 @@ void PollingComponent::call_setup() {
|
|||
// Let the polling component subclass setup their HW.
|
||||
this->setup();
|
||||
|
||||
// init the poller
|
||||
this->start_poller();
|
||||
}
|
||||
|
||||
void PollingComponent::start_poller() {
|
||||
// Register interval.
|
||||
this->set_interval("update", this->get_update_interval(), [this]() { this->update(); });
|
||||
}
|
||||
|
||||
void PollingComponent::stop_poller() {
|
||||
// Clear the interval to suspend component
|
||||
this->cancel_interval("update");
|
||||
}
|
||||
|
||||
uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; }
|
||||
void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
||||
|
||||
|
|
|
@ -308,6 +308,12 @@ class PollingComponent : public Component {
|
|||
/// Get the update interval in ms of this sensor
|
||||
virtual uint32_t get_update_interval() const;
|
||||
|
||||
// Start the poller, used for component.suspend
|
||||
void start_poller();
|
||||
|
||||
// Stop the poller, used for component.suspend
|
||||
void stop_poller();
|
||||
|
||||
protected:
|
||||
uint32_t update_interval_;
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ esptool==4.6.2
|
|||
click==8.1.7
|
||||
esphome-dashboard==20230904.0
|
||||
aioesphomeapi==15.0.0
|
||||
zeroconf==0.115.0
|
||||
zeroconf==0.115.1
|
||||
|
||||
# esp-idf requires this, but doesn't bundle it by default
|
||||
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24
|
||||
|
|
|
@ -2948,7 +2948,7 @@ display:
|
|||
cs_pin: GPIO5
|
||||
dc_pin: GPIO16
|
||||
reset_pin: GPIO23
|
||||
backlight_pin: GPIO4
|
||||
backlight_pin: no
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
- platform: st7920
|
||||
|
@ -3104,11 +3104,39 @@ time:
|
|||
- platform: ds1307
|
||||
id: ds1307_time
|
||||
update_interval: never
|
||||
on_time:
|
||||
seconds: 0
|
||||
then: ds1307.read_time
|
||||
i2c_id: i2c_bus
|
||||
|
||||
on_time:
|
||||
- seconds: 0
|
||||
then: ds1307.read_time
|
||||
- at: "16:00:00"
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
or:
|
||||
- binary_sensor.is_on: close_sensor
|
||||
- binary_sensor.is_on: open_sensor
|
||||
then:
|
||||
logger.log: "close_sensor or open_sensor is on"
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- binary_sensor.is_on: close_sensor
|
||||
- binary_sensor.is_on: open_sensor
|
||||
then:
|
||||
logger.log: "close_sensor and open_sensor are both on"
|
||||
- if:
|
||||
condition:
|
||||
xor:
|
||||
- binary_sensor.is_on: close_sensor
|
||||
- binary_sensor.is_on: open_sensor
|
||||
then:
|
||||
logger.log: "close_sensor or open_sensor is exclusively on"
|
||||
- if:
|
||||
condition:
|
||||
not:
|
||||
- binary_sensor.is_on: close_sensor
|
||||
then:
|
||||
logger.log: "close_sensor is not on"
|
||||
cover:
|
||||
- platform: template
|
||||
name: Template Cover
|
||||
|
@ -3566,6 +3594,22 @@ button:
|
|||
name: Midea Power Inverse
|
||||
on_press:
|
||||
midea_ac.power_toggle:
|
||||
- platform: template
|
||||
name: Poller component suspend test
|
||||
on_press:
|
||||
- component.suspend: myteleinfo
|
||||
- delay: 20s
|
||||
- component.update: myteleinfo
|
||||
- delay: 20s
|
||||
- component.resume: myteleinfo
|
||||
- delay: 20s
|
||||
- component.resume:
|
||||
id: myteleinfo
|
||||
update_interval: 2s
|
||||
- delay: 20s
|
||||
- component.resume:
|
||||
id: myteleinfo
|
||||
update_interval: !lambda return 2500;
|
||||
- platform: ld2410
|
||||
factory_reset:
|
||||
name: "factory reset"
|
||||
|
|
|
@ -724,6 +724,7 @@ interval:
|
|||
display:
|
||||
- platform: st7789v
|
||||
model: LILYGO_T-EMBED_170X320
|
||||
spi_mode: mode0
|
||||
height: 320
|
||||
width: 170
|
||||
offset_height: 35
|
||||
|
|
|
@ -32,10 +32,15 @@ i2c:
|
|||
scan: false
|
||||
|
||||
spi:
|
||||
- id: spi_id_1
|
||||
clk_pin: GPIO21
|
||||
mosi_pin: GPIO22
|
||||
miso_pin: GPIO23
|
||||
interface: hardware
|
||||
- id: spi_id_2
|
||||
clk_pin: GPIO32
|
||||
mosi_pin: GPIO33
|
||||
interface: hardware
|
||||
|
||||
uart:
|
||||
- id: uart115200
|
||||
|
@ -92,6 +97,7 @@ sx1509:
|
|||
address: 0x3E
|
||||
|
||||
mcp3204:
|
||||
spi_id: spi_id_1
|
||||
cs_pin: GPIO23
|
||||
|
||||
dac7678:
|
||||
|
@ -495,6 +501,7 @@ display:
|
|||
update_interval: 16ms
|
||||
|
||||
- platform: waveshare_epaper
|
||||
spi_id: spi_id_1
|
||||
cs_pin: GPIO23
|
||||
dc_pin: GPIO23
|
||||
busy_pin: GPIO23
|
||||
|
@ -504,6 +511,7 @@ display:
|
|||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
- platform: waveshare_epaper
|
||||
spi_id: spi_id_1
|
||||
cs_pin: GPIO23
|
||||
dc_pin: GPIO23
|
||||
busy_pin: GPIO23
|
||||
|
@ -514,6 +522,7 @@ display:
|
|||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
- platform: waveshare_epaper
|
||||
spi_id: spi_id_1
|
||||
cs_pin: GPIO23
|
||||
dc_pin: GPIO23
|
||||
busy_pin: GPIO23
|
||||
|
@ -523,6 +532,7 @@ display:
|
|||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
- platform: waveshare_epaper
|
||||
spi_id: spi_id_1
|
||||
cs_pin: GPIO23
|
||||
dc_pin: GPIO23
|
||||
busy_pin: GPIO23
|
||||
|
@ -673,6 +683,7 @@ touchscreen:
|
|||
|
||||
- platform: xpt2046
|
||||
id: xpt_touchscreen
|
||||
spi_id: spi_id_2
|
||||
cs_pin: 17
|
||||
interrupt_pin: 16
|
||||
display: inkplate_display
|
||||
|
|
|
@ -42,6 +42,14 @@ switch:
|
|||
output: pin_4
|
||||
id: pin_4_switch
|
||||
|
||||
|
||||
spi: # Pins are for SPI1 on the RP2040 Pico-W
|
||||
miso_pin: 8
|
||||
clk_pin: 10
|
||||
mosi_pin: 11
|
||||
id: spi_0
|
||||
interface: hardware
|
||||
|
||||
#light:
|
||||
# - platform: rp2040_pio_led_strip
|
||||
# id: led_strip
|
||||
|
|
Loading…
Reference in a new issue