Merge branch 'esphome:dev' into daikin-enhancements

This commit is contained in:
James Nimmo 2023-10-06 12:09:25 +13:00 committed by GitHub
commit 51a63bf968
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 320 additions and 55 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -235,6 +235,7 @@ ESP32_BOARD_PINS = {
"SDA": 5,
"SS": 15,
},
"denky_d4": {"RX": 8, "LED": 14},
"esp-wrover-kit": {},
"esp32-devkitlipo": {},
"esp32-evb": {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -915,7 +915,7 @@ UNIT_BYTES = "B"
UNIT_CELSIUS = "°C"
UNIT_CENTIMETER = "cm"
UNIT_COUNT_DECILITRE = "/dL"
UNIT_COUNTS_PER_CUBIC_METER = "#/"
UNIT_COUNTS_PER_CUBIC_CENTIMETER = "#/c"
UNIT_CUBIC_METER = ""
UNIT_CUBIC_METER_PER_HOUR = "m³/h"
UNIT_DECIBEL = "dB"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -724,6 +724,7 @@ interval:
display:
- platform: st7789v
model: LILYGO_T-EMBED_170X320
spi_mode: mode0
height: 320
width: 170
offset_height: 35

View file

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

View file

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