1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-03 16:41:50 +00:00

Compare commits

...

28 Commits

Author SHA1 Message Date
Jesse Hills
83f9d88314 Add ESP32-C6 I2S count 2023-11-14 17:11:22 +13:00
Keith Burzinski
ae0e481cff Generate partitions.csv based on flash size (#5697) 2023-11-14 13:47:29 +13:00
J. Nick Koston
f198be39d7 dashboard: Run get_serial_ports in the executor (#5740) 2023-11-14 13:46:51 +13:00
J. Nick Koston
08fc96b890 dashboard: remove usage of codecs module (#5741) 2023-11-14 13:44:49 +13:00
dependabot[bot]
8c28bea5b1 Bump zeroconf from 0.123.0 to 0.126.0 (#5748)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-13 16:19:15 -06:00
Sergey Dudanov
00eedeb8b3 remote_base: added helper class and schemas (#5169)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-11-14 06:55:36 +13:00
Jimmy Hedman
0a4853ba7b Correct url for Arduino platform (#5744) 2023-11-14 06:38:08 +13:00
Jesse Hills
45276cc244 Handle wake word not set up internally (#5738) 2023-11-13 11:48:26 +13:00
Jimmy Hedman
684cf10230 Bump Arduino Pico Framework to 3.6.0 and Platform to 1.10.0 (#5731) 2023-11-13 07:28:02 +13:00
J. Nick Koston
63a277ba80 Bump zeroconf to 0.123.0 (#5736) 2023-11-13 07:20:34 +13:00
J. Nick Koston
53f3385c49 Migrate to using aioesphomeapi for the log runner to fix multiple issues (#5733) 2023-11-12 14:36:56 +13:00
dependabot[bot]
51930a0243 Bump aioesphomeapi from 18.2.7 to 18.4.0 (#5735)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-11 21:59:36 +00:00
Mike La Spina
6a5cea171e Missed ifdefs (#5727) 2023-11-10 18:37:39 -06:00
J. Nick Koston
3363c8f434 Fix zeroconf name resolution refactoring error (#5725) 2023-11-10 22:55:21 +13:00
J. Nick Koston
3b891bc146 Speed up YAML by using YAML C loader when available (#5721) 2023-11-10 22:17:40 +13:00
dependabot[bot]
0f19450ab4 Bump black from 23.10.1 to 23.11.0 (#5702)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2023-11-09 21:18:22 -06:00
Samuel Sieb
98ec798bfc fix pin range for xl9535 (#5722)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2023-11-10 01:53:35 +00:00
Jesse Hills
01d28ce3fc Add resistance_sampler interface for config validation (#5718) 2023-11-10 11:40:07 +13:00
dependabot[bot]
bc7519f645 Bump zeroconf from 0.120.0 to 0.122.3 (#5715) 2023-11-09 12:26:05 -06:00
J. Nick Koston
28513a0502 Update Dockerfile to use piwheels for armv7 (#5709) 2023-11-09 21:04:39 +13:00
J. Nick Koston
3e3266fa74 Bump aioesphomeapi to 18.2.7 (#5706) 2023-11-09 15:52:08 +13:00
Rodrigo Martín
ce020b1f9f fix: Fix broken bluetooth_proxy and ble_clients after BLE enable/disable (#5704) 2023-11-08 23:35:37 +00:00
J. Nick Koston
d394b957d1 Use piwheels for armv7 docker image builds (#5703) 2023-11-09 11:50:08 +13:00
J. Nick Koston
cf22c55430 Fix static assets cache logic (#5700) 2023-11-08 21:04:01 +00:00
Jesse Hills
511348974e Fix esp32_rmt_led_strip custom timing units (#5696) 2023-11-08 09:01:26 +00:00
Jesse Hills
972598a698 Handle nanoseconds in config (#5695) 2023-11-08 21:34:44 +13:00
Edward Firmo
d81bec860b Nextion support to esp-idf (#5667)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-11-07 19:50:45 -06:00
Jesse Hills
fde7a04ee7 Bump version to 2023.12.0-dev 2023-11-08 13:13:56 +13:00
44 changed files with 743 additions and 246 deletions

View File

@@ -3,7 +3,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.10.1
rev: 23.11.0
hooks:
- id: black
args:

View File

@@ -246,6 +246,7 @@ esphome/components/radon_eye_rd200/* @jeffeb3
esphome/components/rc522/* @glmnet
esphome/components/rc522_i2c/* @glmnet
esphome/components/rc522_spi/* @glmnet
esphome/components/resistance_sampler/* @jesserockz
esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz

View File

@@ -5,6 +5,7 @@
# One of "docker", "hassio"
ARG BASEIMGTYPE=docker
# https://github.com/hassio-addons/addon-debian-base/releases
FROM ghcr.io/hassio-addons/debian-base:7.2.0 AS base-hassio
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bookworm
@@ -12,9 +13,11 @@ FROM debian:12.2-slim AS base-docker
FROM base-${BASEIMGTYPE} AS base
ARG TARGETARCH
ARG TARGETVARIANT
# Note that --break-system-packages is used below because
# https://peps.python.org/pep-0668/ added a safety check that prevents
# installing packages with the same name as a system package. This is
@@ -46,7 +49,7 @@ RUN \
libssl-dev=3.0.11-1~deb12u2 \
libffi-dev=3.4.4-1 \
cargo=0.66.0+ds1-1 \
pkg-config=1.8.1-1; \
pkg-config=1.8.1-1 \
gcc-arm-linux-gnueabihf=4:12.2.0-3; \
fi; \
rm -rf \
@@ -60,7 +63,6 @@ ENV \
# Store globally installed pio libs in /piolibs
PLATFORMIO_GLOBALLIB_DIR=/piolibs
# Support legacy binaries on Debian multiarch system. There is no "correct" way
# to do this, other than using properly built toolchains...
# See: https://unix.stackexchange.com/questions/553743/correct-way-to-add-lib-ld-linux-so-3-in-debian
@@ -71,8 +73,12 @@ RUN \
RUN \
# Ubuntu python3-pip is missing wheel
pip3 install --break-system-packages --no-cache-dir \
platformio==6.1.11 \
if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \
pip3 install \
--break-system-packages --no-cache-dir \
platformio==6.1.11 \
# Change some platformio settings
&& platformio settings set enable_telemetry No \
&& platformio settings set check_platformio_interval 1000000 \
@@ -83,8 +89,12 @@ RUN \
# tmpfs is for https://github.com/rust-lang/cargo/issues/8719
COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini /
RUN --mount=type=tmpfs,target=/root/.cargo CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo \
pip3 install --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \
CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo \
pip3 install \
--break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
&& /platformio_install_deps.py /platformio.ini --libraries
@@ -93,7 +103,11 @@ FROM base AS docker
# Copy esphome and install
COPY . /esphome
RUN pip3 install --break-system-packages --no-cache-dir --no-use-pep517 -e /esphome
RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \
pip3 install \
--break-system-packages --no-cache-dir --no-use-pep517 -e /esphome
# Settings for dashboard
ENV USERNAME="" PASSWORD=""
@@ -139,7 +153,11 @@ COPY docker/ha-addon-rootfs/ /
# Copy esphome and install
COPY . /esphome
RUN pip3 install --break-system-packages --no-cache-dir --no-use-pep517 -e /esphome
RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \
pip3 install \
--break-system-packages --no-cache-dir --no-use-pep517 -e /esphome
# Labels
LABEL \
@@ -175,7 +193,11 @@ RUN \
/var/lib/apt/lists/*
COPY requirements_test.txt /
RUN pip3 install --break-system-packages --no-cache-dir -r /requirements_test.txt
RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \
pip3 install \
--break-system-packages --no-cache-dir -r /requirements_test.txt
VOLUME ["/esphome"]
WORKDIR /esphome

View File

@@ -1,71 +1,65 @@
from __future__ import annotations
import asyncio
import logging
from datetime import datetime
from typing import Optional
from typing import Any
from aioesphomeapi import APIClient, ReconnectLogic, APIConnectionError, LogLevel
import zeroconf
from aioesphomeapi import APIClient
from aioesphomeapi.api_pb2 import SubscribeLogsResponse
from aioesphomeapi.log_runner import async_run
from zeroconf.asyncio import AsyncZeroconf
from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__
from esphome.core import CORE
from esphome.const import CONF_KEY, CONF_PORT, CONF_PASSWORD, __version__
from esphome.util import safe_print
from . import CONF_ENCRYPTION
_LOGGER = logging.getLogger(__name__)
async def async_run_logs(config, address):
"""Run the logs command in the event loop."""
conf = config["api"]
port: int = int(conf[CONF_PORT])
password: str = conf[CONF_PASSWORD]
noise_psk: Optional[str] = None
noise_psk: str | None = None
if CONF_ENCRYPTION in conf:
noise_psk = conf[CONF_ENCRYPTION][CONF_KEY]
_LOGGER.info("Starting log output from %s using esphome API", address)
aiozc = AsyncZeroconf()
cli = APIClient(
address,
port,
password,
client_info=f"ESPHome Logs {__version__}",
noise_psk=noise_psk,
zeroconf_instance=aiozc.zeroconf,
)
first_connect = True
dashboard = CORE.dashboard
def on_log(msg):
time_ = datetime.now().time().strftime("[%H:%M:%S]")
text = msg.message.decode("utf8", "backslashreplace")
safe_print(time_ + text)
async def on_connect():
nonlocal first_connect
try:
await cli.subscribe_logs(
on_log,
log_level=LogLevel.LOG_LEVEL_VERY_VERBOSE,
dump_config=first_connect,
)
first_connect = False
except APIConnectionError:
cli.disconnect()
async def on_disconnect(expected_disconnect: bool) -> None:
_LOGGER.warning("Disconnected from API")
zc = zeroconf.Zeroconf()
reconnect = ReconnectLogic(
client=cli,
on_connect=on_connect,
on_disconnect=on_disconnect,
zeroconf_instance=zc,
)
await reconnect.start()
def on_log(msg: SubscribeLogsResponse) -> None:
"""Handle a new log message."""
time_ = datetime.now()
message: bytes = msg.message
text = message.decode("utf8", "backslashreplace")
if dashboard:
text = text.replace("\033", "\\033")
print(f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}]{text}")
stop = await async_run(cli, on_log, aio_zeroconf_instance=aiozc)
try:
while True:
await asyncio.sleep(60)
finally:
await aiozc.async_close()
await stop()
def run_logs(config: dict[str, Any], address: str) -> None:
"""Run the logs command."""
try:
asyncio.run(async_run_logs(config, address))
except KeyboardInterrupt:
await reconnect.stop()
zc.close()
def run_logs(config, address):
asyncio.run(async_run_logs(config, address))
pass

View File

@@ -1,38 +1,37 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import (
climate,
remote_transmitter,
remote_receiver,
sensor,
remote_base,
)
from esphome.components.remote_base import CONF_RECEIVER_ID, CONF_TRANSMITTER_ID
from esphome.components import climate, sensor, remote_base
from esphome.const import CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, CONF_SENSOR
DEPENDENCIES = ["remote_transmitter"]
AUTO_LOAD = ["sensor", "remote_base"]
CODEOWNERS = ["@glmnet"]
climate_ir_ns = cg.esphome_ns.namespace("climate_ir")
ClimateIR = climate_ir_ns.class_(
"ClimateIR", climate.Climate, cg.Component, remote_base.RemoteReceiverListener
"ClimateIR",
climate.Climate,
cg.Component,
remote_base.RemoteReceiverListener,
remote_base.RemoteTransmittable,
)
CLIMATE_IR_SCHEMA = climate.CLIMATE_SCHEMA.extend(
{
cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(
remote_transmitter.RemoteTransmitterComponent
),
cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean,
cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean,
cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
}
).extend(cv.COMPONENT_SCHEMA)
CLIMATE_IR_SCHEMA = (
climate.CLIMATE_SCHEMA.extend(
{
cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean,
cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean,
cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(remote_base.REMOTE_TRANSMITTABLE_SCHEMA)
)
CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend(
{
cv.Optional(CONF_RECEIVER_ID): cv.use_id(
remote_receiver.RemoteReceiverComponent
cv.Optional(remote_base.CONF_RECEIVER_ID): cv.use_id(
remote_base.RemoteReceiverBase
),
}
)
@@ -41,15 +40,11 @@ CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend(
async def register_climate_ir(var, config):
await cg.register_component(var, config)
await climate.register_climate(var, config)
await remote_base.register_transmittable(var, config)
cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL]))
cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT]))
if remote_base.CONF_RECEIVER_ID in config:
await remote_base.register_listener(var, config)
if sensor_id := config.get(CONF_SENSOR):
sens = await cg.get_variable(sensor_id)
cg.add(var.set_sensor(sens))
if receiver_id := config.get(CONF_RECEIVER_ID):
receiver = await cg.get_variable(receiver_id)
cg.add(receiver.register_listener(var))
transmitter = await cg.get_variable(config[CONF_TRANSMITTER_ID])
cg.add(var.set_transmitter(transmitter))

View File

@@ -18,7 +18,10 @@ namespace climate_ir {
Likewise to decode a IR into the AC state, implement
bool RemoteReceiverListener::on_receive(remote_base::RemoteReceiveData data) and return true
*/
class ClimateIR : public climate::Climate, public Component, public remote_base::RemoteReceiverListener {
class ClimateIR : public Component,
public climate::Climate,
public remote_base::RemoteReceiverListener,
public remote_base::RemoteTransmittable {
public:
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {},
@@ -35,9 +38,6 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
void setup() override;
void dump_config() override;
void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) {
this->transmitter_ = transmitter;
}
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
@@ -64,7 +64,6 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
std::set<climate::ClimateSwingMode> swing_modes_ = {};
std::set<climate::ClimatePreset> presets_ = {};
remote_transmitter::RemoteTransmitterComponent *transmitter_;
sensor::Sensor *sensor_{nullptr};
};

View File

@@ -102,11 +102,7 @@ void CoolixClimate::transmit_state() {
}
}
ESP_LOGV(TAG, "Sending coolix code: 0x%06" PRIX32, remote_state);
auto transmit = this->transmitter_->transmit();
auto *data = transmit.get_data();
remote_base::CoolixProtocol().encode(data, remote_state);
transmit.perform();
this->transmit_<remote_base::CoolixProtocol>(remote_state);
}
bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data) {

View File

@@ -386,10 +386,21 @@ FRAMEWORK_SCHEMA = cv.typed_schema(
)
FLASH_SIZES = [
"4MB",
"8MB",
"16MB",
"32MB",
]
CONF_FLASH_SIZE = "flash_size"
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.Required(CONF_BOARD): cv.string_strict,
cv.Optional(CONF_FLASH_SIZE, default="4MB"): cv.one_of(
*FLASH_SIZES, upper=True
),
cv.Optional(CONF_VARIANT): cv.one_of(*VARIANTS, upper=True),
cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA,
}
@@ -401,6 +412,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
cg.add_platformio_option("board", config[CONF_BOARD])
cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE])
cg.add_build_flag("-DUSE_ESP32")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
@@ -505,24 +517,46 @@ async def to_code(config):
)
ARDUINO_PARTITIONS_CSV = """\
nvs, data, nvs, 0x009000, 0x005000,
otadata, data, ota, 0x00e000, 0x002000,
app0, app, ota_0, 0x010000, 0x1C0000,
app1, app, ota_1, 0x1D0000, 0x1C0000,
eeprom, data, 0x99, 0x390000, 0x001000,
spiffs, data, spiffs, 0x391000, 0x00F000
APP_PARTITION_SIZES = {
"4MB": 0x1C0000, # 1792 KB
"8MB": 0x3C0000, # 3840 KB
"16MB": 0x7C0000, # 7936 KB
"32MB": 0xFC0000, # 16128 KB
}
def get_arduino_partition_csv(flash_size):
app_partition_size = APP_PARTITION_SIZES[flash_size]
eeprom_partition_size = 0x1000 # 4 KB
spiffs_partition_size = 0xF000 # 60 KB
app0_partition_start = 0x010000 # 64 KB
app1_partition_start = app0_partition_start + app_partition_size
eeprom_partition_start = app1_partition_start + app_partition_size
spiffs_partition_start = eeprom_partition_start + eeprom_partition_size
partition_csv = f"""\
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xE000, 0x2000,
app0, app, ota_0, 0x{app0_partition_start:X}, 0x{app_partition_size:X},
app1, app, ota_1, 0x{app1_partition_start:X}, 0x{app_partition_size:X},
eeprom, data, 0x99, 0x{eeprom_partition_start:X}, 0x{eeprom_partition_size:X},
spiffs, data, spiffs, 0x{spiffs_partition_start:X}, 0x{spiffs_partition_size:X}
"""
return partition_csv
IDF_PARTITIONS_CSV = """\
# Name, Type, SubType, Offset, Size, Flags
def get_idf_partition_csv(flash_size):
app_partition_size = APP_PARTITION_SIZES[flash_size]
partition_csv = f"""\
otadata, data, ota, , 0x2000,
phy_init, data, phy, , 0x1000,
app0, app, ota_0, , 0x1C0000,
app1, app, ota_1, , 0x1C0000,
nvs, data, nvs, , 0x6d000,
app0, app, ota_0, , 0x{app_partition_size:X},
app1, app, ota_1, , 0x{app_partition_size:X},
nvs, data, nvs, , 0x6D000,
"""
return partition_csv
def _format_sdkconfig_val(value: SdkconfigValueType) -> str:
@@ -565,13 +599,17 @@ def copy_files():
if CORE.using_arduino:
write_file_if_changed(
CORE.relative_build_path("partitions.csv"),
ARDUINO_PARTITIONS_CSV,
get_arduino_partition_csv(
CORE.platformio_options.get("board_upload.flash_size")
),
)
if CORE.using_esp_idf:
_write_sdkconfig()
write_file_if_changed(
CORE.relative_build_path("partitions.csv"),
IDF_PARTITIONS_CSV,
get_idf_partition_csv(
CORE.platformio_options.get("board_upload.flash_size")
),
)
# IDF build scripts look for version string to put in the build.
# However, if the build path does not have an initialized git repo,

View File

@@ -20,16 +20,21 @@ static const esp_bt_uuid_t NOTIFY_DESC_UUID = {
void BLEClientBase::setup() {
static uint8_t connection_index = 0;
this->connection_index_ = connection_index++;
auto ret = esp_ble_gattc_app_register(this->app_id);
if (ret) {
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
this->mark_failed();
}
this->set_state(espbt::ClientState::IDLE);
}
void BLEClientBase::loop() {
if (!esp32_ble::global_ble->is_active()) {
this->set_state(espbt::ClientState::INIT);
return;
}
if (this->state_ == espbt::ClientState::INIT) {
auto ret = esp_ble_gattc_app_register(this->app_id);
if (ret) {
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
this->mark_failed();
}
this->set_state(espbt::ClientState::IDLE);
}
// READY_TO_CONNECT means we have discovered the device
// and the scanner has been stopped by the tracker.
if (this->state_ == espbt::ClientState::READY_TO_CONNECT) {

View File

@@ -93,19 +93,19 @@ CONFIG_SCHEMA = cv.All(
cv.Inclusive(
CONF_BIT0_HIGH,
"custom",
): cv.positive_time_period_microseconds,
): cv.positive_time_period_nanoseconds,
cv.Inclusive(
CONF_BIT0_LOW,
"custom",
): cv.positive_time_period_microseconds,
): cv.positive_time_period_nanoseconds,
cv.Inclusive(
CONF_BIT1_HIGH,
"custom",
): cv.positive_time_period_microseconds,
): cv.positive_time_period_nanoseconds,
cv.Inclusive(
CONF_BIT1_LOW,
"custom",
): cv.positive_time_period_microseconds,
): cv.positive_time_period_nanoseconds,
}
),
cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH),

View File

@@ -3,7 +3,6 @@
#ifdef USE_ARDUINO
#include "esphome/components/remote_base/remote_base.h"
#include "esphome/components/remote_transmitter/remote_transmitter.h"
#include <IRSender.h> // arduino-heatpump library
namespace esphome {
@@ -11,14 +10,13 @@ namespace heatpumpir {
class IRSenderESPHome : public IRSender {
public:
IRSenderESPHome(remote_transmitter::RemoteTransmitterComponent *transmitter)
: IRSender(0), transmit_(transmitter->transmit()){};
IRSenderESPHome(remote_base::RemoteTransmitterBase *transmitter) : IRSender(0), transmit_(transmitter->transmit()){};
void setFrequency(int frequency) override; // NOLINT(readability-identifier-naming)
void space(int space_length) override;
void mark(int mark_length) override;
protected:
remote_transmitter::RemoteTransmitterComponent::TransmitCall transmit_;
remote_base::RemoteTransmitterBase::TransmitCall transmit_;
};
} // namespace heatpumpir

View File

@@ -10,6 +10,7 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2,
VARIANT_ESP32S3,
VARIANT_ESP32C3,
VARIANT_ESP32C6,
)
CODEOWNERS = ["@jesserockz"]
@@ -38,6 +39,7 @@ I2S_PORTS = {
VARIANT_ESP32S2: 1,
VARIANT_ESP32S3: 2,
VARIANT_ESP32C3: 1,
VARIANT_ESP32C6: 1,
}
CONFIG_SCHEMA = cv.Schema(

View File

@@ -68,6 +68,7 @@ void LD2420Component::dump_config() {
ESP_LOGCONFIG(TAG, "LD2420:");
ESP_LOGCONFIG(TAG, " Firmware Version : %7s", this->ld2420_firmware_ver_);
ESP_LOGCONFIG(TAG, "LD2420 Number:");
#ifdef USE_NUMBER
LOG_NUMBER(TAG, " Gate Timeout:", this->gate_timeout_number_);
LOG_NUMBER(TAG, " Gate Max Distance:", this->max_gate_distance_number_);
LOG_NUMBER(TAG, " Gate Min Distance:", this->min_gate_distance_number_);
@@ -76,10 +77,13 @@ void LD2420Component::dump_config() {
LOG_NUMBER(TAG, " Gate Move Threshold:", this->gate_move_threshold_numbers_[gate]);
LOG_NUMBER(TAG, " Gate Still Threshold::", this->gate_still_threshold_numbers_[gate]);
}
#endif
#ifdef USE_BUTTON
LOG_BUTTON(TAG, " Apply Config:", this->apply_config_button_);
LOG_BUTTON(TAG, " Revert Edits:", this->revert_config_button_);
LOG_BUTTON(TAG, " Factory Reset:", this->factory_reset_button_);
LOG_BUTTON(TAG, " Restart Module:", this->restart_module_button_);
#endif
ESP_LOGCONFIG(TAG, "LD2420 Select:");
LOG_SELECT(TAG, " Operating Mode", this->operating_selector_);
if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
@@ -183,9 +187,11 @@ void LD2420Component::factory_reset_action() {
return;
}
this->set_min_max_distances_timeout(FACTORY_MAX_GATE, FACTORY_MIN_GATE, FACTORY_TIMEOUT);
#ifdef USE_NUMBER
this->gate_timeout_number_->state = FACTORY_TIMEOUT;
this->min_gate_distance_number_->state = FACTORY_MIN_GATE;
this->max_gate_distance_number_->state = FACTORY_MAX_GATE;
#endif
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
this->new_config.move_thresh[gate] = FACTORY_MOVE_THRESH[gate];
this->new_config.still_thresh[gate] = FACTORY_STILL_THRESH[gate];

View File

@@ -36,7 +36,7 @@ CONFIG_SCHEMA = (
display.BASIC_DISPLAY_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(Nextion),
cv.Optional(CONF_TFT_URL): cv.All(cv.string, cv.only_with_arduino),
cv.Optional(CONF_TFT_URL): cv.url,
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
cv.Optional(CONF_ON_SETUP): automation.validate_automation(
{
@@ -85,10 +85,10 @@ async def to_code(config):
if CONF_TFT_URL in config:
cg.add_define("USE_NEXTION_TFT_UPLOAD")
cg.add(var.set_tft_url(config[CONF_TFT_URL]))
if CORE.is_esp32:
if CORE.is_esp32 and CORE.using_arduino:
cg.add_library("WiFiClientSecure", None)
cg.add_library("HTTPClient", None)
if CORE.is_esp8266:
elif CORE.is_esp8266 and CORE.using_arduino:
cg.add_library("ESP8266HTTPClient", None)
if CONF_TOUCH_SLEEP_TIMEOUT in config:

View File

@@ -128,7 +128,7 @@ void Nextion::dump_config() {
ESP_LOGCONFIG(TAG, " Wake On Touch: %s", this->auto_wake_on_touch_ ? "True" : "False");
if (this->touch_sleep_timeout_ != 0) {
ESP_LOGCONFIG(TAG, " Touch Timeout: %d", this->touch_sleep_timeout_);
ESP_LOGCONFIG(TAG, " Touch Timeout: %" PRIu32, this->touch_sleep_timeout_);
}
if (this->wake_up_page_ != -1) {
@@ -868,6 +868,12 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool
start = millis();
while ((timeout == 0 && this->available()) || millis() - start <= timeout) {
if (!this->available()) {
App.feed_wdt();
delay(1);
continue;
}
this->read_byte(&c);
if (c == 0xFF) {
nr_of_ff_bytes++;
@@ -886,7 +892,7 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool
}
}
App.feed_wdt();
delay(1);
delay(2);
if (exit_flag || ff_flag) {
break;

View File

@@ -12,14 +12,18 @@
#include "esphome/components/display/display_color_utils.h"
#ifdef USE_NEXTION_TFT_UPLOAD
#ifdef ARDUINO
#ifdef USE_ESP32
#include <HTTPClient.h>
#endif
#endif // USE_ESP32
#ifdef USE_ESP8266
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#endif
#endif
#endif // USE_ESP8266
#elif defined(USE_ESP_IDF)
#include <esp_http_client.h>
#endif // ARDUINO vs ESP-IDF
#endif // USE_NEXTION_TFT_UPLOAD
namespace esphome {
namespace nextion {
@@ -685,16 +689,18 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
#ifdef USE_NEXTION_TFT_UPLOAD
/**
* Set the tft file URL. https seems problamtic with arduino..
* Set the tft file URL. https seems problematic with arduino..
*/
void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; }
#endif
/**
* Upload the tft file and softreset the Nextion
* Upload the tft file and soft reset Nextion
* @return bool True: Transfer completed successfuly, False: Transfer failed.
*/
void upload_tft();
bool upload_tft();
void dump_config() override;
/**
@@ -817,16 +823,16 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr};
WiFiClient *get_wifi_client_();
#endif
int content_length_ = 0;
int tft_size_ = 0;
#ifdef ARDUINO
/**
* will request chunk_size chunks from the web server
* and send each to the nextion
* @param int contentLength Total size of the file
* @param uint32_t chunk_size
* @return true if success, false for failure.
* @param HTTPClient http HTTP client handler.
* @param int range_start Position of next byte to transfer.
* @return position of last byte transferred, -1 for failure.
*/
int content_length_ = 0;
int tft_size_ = 0;
int upload_by_chunks_(HTTPClient *http, int range_start);
bool upload_with_range_(uint32_t range_start, uint32_t range_end);
@@ -839,7 +845,30 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
* @return true if success, false for failure.
*/
bool upload_from_buffer_(const uint8_t *file_buf, size_t buf_size);
void upload_end_();
/**
* Ends the upload process, restart Nextion and, if successful,
* restarts ESP
* @param bool url successful True: Transfer completed successfuly, False: Transfer failed.
* @return bool True: Transfer completed successfuly, False: Transfer failed.
*/
bool upload_end_(bool successful);
#elif defined(USE_ESP_IDF)
/**
* will request 4096 bytes chunks from the web server
* and send each to Nextion
* @param std::string url Full url for download.
* @param int range_start Position of next byte to transfer.
* @return position of last byte transferred, -1 for failure.
*/
int upload_range(const std::string &url, int range_start);
/**
* Ends the upload process, restart Nextion and, if successful,
* restarts ESP
* @param bool url successful True: Transfer completed successfuly, False: Transfer failed.
* @return bool True: Transfer completed successfuly, False: Transfer failed.
*/
bool upload_end(bool successful);
#endif // ARDUINO vs ESP-IDF
#endif // USE_NEXTION_TFT_UPLOAD

View File

@@ -55,7 +55,7 @@ void Nextion::set_protocol_reparse_mode(bool active_mode) {
// Set Colors
void Nextion::set_component_background_color(const char *component, uint32_t color) {
this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component, color);
this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%" PRIu32, component, color);
}
void Nextion::set_component_background_color(const char *component, const char *color) {
@@ -68,7 +68,8 @@ void Nextion::set_component_background_color(const char *component, Color color)
}
void Nextion::set_component_pressed_background_color(const char *component, uint32_t color) {
this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component, color);
this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%" PRIu32, component,
color);
}
void Nextion::set_component_pressed_background_color(const char *component, const char *color) {
@@ -89,7 +90,7 @@ void Nextion::set_component_picc(const char *component, uint8_t pic_id) {
}
void Nextion::set_component_font_color(const char *component, uint32_t color) {
this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component, color);
this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%" PRIu32, component, color);
}
void Nextion::set_component_font_color(const char *component, const char *color) {
@@ -102,7 +103,7 @@ void Nextion::set_component_font_color(const char *component, Color color) {
}
void Nextion::set_component_pressed_font_color(const char *component, uint32_t color) {
this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component, color);
this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%" PRIu32, component, color);
}
void Nextion::set_component_pressed_font_color(const char *component, const char *color) {

View File

@@ -1,5 +1,6 @@
#include "nextion.h"
#ifdef ARDUINO
#ifdef USE_NEXTION_TFT_UPLOAD
#include "esphome/core/application.h"
@@ -128,15 +129,15 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) {
return range_end + 1;
}
void Nextion::upload_tft() {
bool Nextion::upload_tft() {
if (this->is_updating_) {
ESP_LOGD(TAG, "Currently updating");
return;
return false;
}
if (!network::is_connected()) {
ESP_LOGD(TAG, "network is not connected");
return;
return false;
}
this->is_updating_ = true;
@@ -164,7 +165,7 @@ void Nextion::upload_tft() {
ESP_LOGD(TAG, "connection failed");
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
allocator.deallocate(this->transfer_buffer_, this->transfer_buffer_size_);
return;
return false;
} else {
ESP_LOGD(TAG, "Connected");
}
@@ -192,7 +193,7 @@ void Nextion::upload_tft() {
}
if ((code != 200 && code != 206) || tries > 5) {
this->upload_end_();
return this->upload_end_(false);
}
String content_range_string = http.header("Content-Range");
@@ -203,7 +204,7 @@ void Nextion::upload_tft() {
if (this->content_length_ < 4096) {
ESP_LOGE(TAG, "Failed to get file size");
this->upload_end_();
return this->upload_end_(false);
}
ESP_LOGD(TAG, "Updating Nextion %s...", this->device_model_.c_str());
@@ -246,7 +247,7 @@ void Nextion::upload_tft() {
ESP_LOGD(TAG, "preparation for tft update done");
} else {
ESP_LOGD(TAG, "preparation for tft update failed %d \"%s\"", response[0], response.c_str());
this->upload_end_();
return this->upload_end_(false);
}
// Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096
@@ -280,7 +281,7 @@ void Nextion::upload_tft() {
this->transfer_buffer_ = allocator.allocate(chunk_size);
if (!this->transfer_buffer_)
this->upload_end_();
return this->upload_end_(false);
}
this->transfer_buffer_size_ = chunk_size;
@@ -295,7 +296,7 @@ void Nextion::upload_tft() {
result = this->upload_by_chunks_(&http, result);
if (result < 0) {
ESP_LOGD(TAG, "Error updating Nextion!");
this->upload_end_();
return this->upload_end_(false);
}
App.feed_wdt();
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
@@ -303,15 +304,19 @@ void Nextion::upload_tft() {
}
ESP_LOGD(TAG, "Successfully updated Nextion!");
this->upload_end_();
return this->upload_end_(true);
}
void Nextion::upload_end_() {
bool Nextion::upload_end_(bool successful) {
this->is_updating_ = false;
ESP_LOGD(TAG, "Restarting Nextion");
this->soft_reset();
delay(1500); // NOLINT
ESP_LOGD(TAG, "Restarting esphome");
ESP.restart(); // NOLINT(readability-static-accessed-through-instance)
if (successful) {
delay(1500); // NOLINT
ESP_LOGD(TAG, "Restarting esphome");
ESP.restart(); // NOLINT(readability-static-accessed-through-instance)
}
return successful;
}
#ifdef USE_ESP8266
@@ -337,3 +342,4 @@ WiFiClient *Nextion::get_wifi_client_() {
} // namespace esphome
#endif // USE_NEXTION_TFT_UPLOAD
#endif // ARDUINO

View File

@@ -0,0 +1,268 @@
#include "nextion.h"
#ifdef USE_ESP_IDF
#ifdef USE_NEXTION_TFT_UPLOAD
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/util.h"
#include "esphome/core/log.h"
#include "esphome/components/network/util.h"
#include <esp_heap_caps.h>
#include <esp_http_client.h>
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion_upload";
// Followed guide
// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
int Nextion::upload_range(const std::string &url, int range_start) {
ESP_LOGVV(TAG, "url: %s", url.c_str());
uint range_size = this->tft_size_ - range_start;
ESP_LOGVV(TAG, "tft_size_: %i", this->tft_size_);
ESP_LOGV(TAG, "Available heap: %u", esp_get_free_heap_size());
int range_end = (range_start == 0) ? std::min(this->tft_size_, 16383) : this->tft_size_;
if (range_size <= 0 or range_end <= range_start) {
ESP_LOGE(TAG, "Invalid range");
ESP_LOGD(TAG, "Range start: %i", range_start);
ESP_LOGD(TAG, "Range end: %i", range_end);
ESP_LOGD(TAG, "Range size: %i", range_size);
return -1;
}
esp_http_client_config_t config = {
.url = url.c_str(),
.cert_pem = nullptr,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
char range_header[64];
sprintf(range_header, "bytes=%d-%d", range_start, range_end);
ESP_LOGV(TAG, "Requesting range: %s", range_header);
esp_http_client_set_header(client, "Range", range_header);
ESP_LOGVV(TAG, "Available heap: %u", esp_get_free_heap_size());
ESP_LOGV(TAG, "Opening http connetion");
esp_err_t err;
if ((err = esp_http_client_open(client, 0)) != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
esp_http_client_cleanup(client);
return -1;
}
ESP_LOGV(TAG, "Fetch content length");
int content_length = esp_http_client_fetch_headers(client);
ESP_LOGV(TAG, "content_length = %d", content_length);
if (content_length <= 0) {
ESP_LOGE(TAG, "Failed to get content length: %d", content_length);
esp_http_client_cleanup(client);
return -1;
}
int total_read_len = 0, read_len;
ESP_LOGV(TAG, "Allocate buffer");
uint8_t *buffer = new uint8_t[4096];
std::string recv_string;
if (buffer == nullptr) {
ESP_LOGE(TAG, "Failed to allocate memory for buffer");
ESP_LOGV(TAG, "Available heap: %u", esp_get_free_heap_size());
} else {
ESP_LOGV(TAG, "Memory for buffer allocated successfully");
while (true) {
App.feed_wdt();
ESP_LOGVV(TAG, "Available heap: %u", esp_get_free_heap_size());
int read_len = esp_http_client_read(client, reinterpret_cast<char *>(buffer), 4096);
ESP_LOGVV(TAG, "Read %d bytes from HTTP client, writing to UART", read_len);
if (read_len > 0) {
this->write_array(buffer, read_len);
ESP_LOGVV(TAG, "Write to UART successful");
this->recv_ret_string_(recv_string, 5000, true);
this->content_length_ -= read_len;
ESP_LOGD(TAG, "Uploaded %0.2f %%, remaining %d bytes",
100.0 * (this->tft_size_ - this->content_length_) / this->tft_size_, this->content_length_);
if (recv_string[0] != 0x05) { // 0x05 == "ok"
ESP_LOGD(
TAG, "recv_string [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
}
// handle partial upload request
if (recv_string[0] == 0x08 && recv_string.size() == 5) {
uint32_t result = 0;
for (int j = 0; j < 4; ++j) {
result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
}
if (result > 0) {
ESP_LOGI(TAG, "Nextion reported new range %" PRIu32, result);
this->content_length_ = this->tft_size_ - result;
// Deallocate the buffer when done
delete[] buffer;
ESP_LOGVV(TAG, "Memory for buffer deallocated");
esp_http_client_cleanup(client);
esp_http_client_close(client);
return result;
}
}
recv_string.clear();
} else if (read_len == 0) {
ESP_LOGV(TAG, "End of HTTP response reached");
break; // Exit the loop if there is no more data to read
} else {
ESP_LOGE(TAG, "Failed to read from HTTP client, error code: %d", read_len);
break; // Exit the loop on error
}
}
// Deallocate the buffer when done
delete[] buffer;
ESP_LOGVV(TAG, "Memory for buffer deallocated");
}
esp_http_client_cleanup(client);
esp_http_client_close(client);
return range_end + 1;
}
bool Nextion::upload_tft() {
ESP_LOGD(TAG, "Nextion TFT upload requested");
ESP_LOGD(TAG, "url: %s", this->tft_url_.c_str());
if (this->is_updating_) {
ESP_LOGW(TAG, "Currently updating");
return false;
}
if (!network::is_connected()) {
ESP_LOGE(TAG, "Network is not connected");
return false;
}
this->is_updating_ = true;
// Define the configuration for the HTTP client
ESP_LOGV(TAG, "Establishing connection to HTTP server");
ESP_LOGVV(TAG, "Available heap: %u", esp_get_free_heap_size());
esp_http_client_config_t config = {
.url = this->tft_url_.c_str(),
.cert_pem = nullptr,
.method = HTTP_METHOD_HEAD,
.timeout_ms = 15000,
};
// Initialize the HTTP client with the configuration
ESP_LOGV(TAG, "Initializing HTTP client");
ESP_LOGV(TAG, "Available heap: %u", esp_get_free_heap_size());
esp_http_client_handle_t http = esp_http_client_init(&config);
if (!http) {
ESP_LOGE(TAG, "Failed to initialize HTTP client.");
return this->upload_end(false);
}
// Perform the HTTP request
ESP_LOGV(TAG, "Check if the client could connect");
ESP_LOGV(TAG, "Available heap: %u", esp_get_free_heap_size());
esp_err_t err = esp_http_client_perform(http);
if (err != ESP_OK) {
ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err));
esp_http_client_cleanup(http);
return this->upload_end(false);
}
// Check the HTTP Status Code
int status_code = esp_http_client_get_status_code(http);
ESP_LOGV(TAG, "HTTP Status Code: %d", status_code);
size_t tft_file_size = esp_http_client_get_content_length(http);
ESP_LOGD(TAG, "TFT file size: %zu", tft_file_size);
if (tft_file_size < 4096) {
ESP_LOGE(TAG, "File size check failed. Size: %zu", tft_file_size);
esp_http_client_cleanup(http);
return this->upload_end(false);
} else {
ESP_LOGV(TAG, "File size check passed. Proceeding...");
}
this->content_length_ = tft_file_size;
this->tft_size_ = tft_file_size;
ESP_LOGD(TAG, "Updating Nextion");
// The Nextion will ignore the update command if it is sleeping
this->send_command_("sleep=0");
this->set_backlight_brightness(1.0);
vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT
App.feed_wdt();
char command[128];
// Tells the Nextion the content length of the tft file and baud rate it will be sent at
// Once the Nextion accepts the command it will wait until the file is successfully uploaded
// If it fails for any reason a power cycle of the display will be needed
sprintf(command, "whmi-wris %d,%" PRIu32 ",1", this->content_length_, this->parent_->get_baud_rate());
// Clear serial receive buffer
uint8_t d;
while (this->available()) {
this->read_byte(&d);
};
this->send_command_(command);
std::string response;
ESP_LOGV(TAG, "Waiting for upgrade response");
this->recv_ret_string_(response, 2048, true); // This can take some time to return
// The Nextion display will, if it's ready to accept data, send a 0x05 byte.
ESP_LOGD(TAG, "Upgrade response is [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str());
if (response.find(0x05) != std::string::npos) {
ESP_LOGV(TAG, "Preparation for tft update done");
} else {
ESP_LOGE(TAG, "Preparation for tft update failed %d \"%s\"", response[0], response.c_str());
esp_http_client_cleanup(http);
return this->upload_end(false);
}
ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d, Heap Size %" PRIu32, this->tft_url_.c_str(),
content_length_, esp_get_free_heap_size());
ESP_LOGV(TAG, "Starting transfer by chunks loop");
int result = 0;
while (content_length_ > 0) {
result = upload_range(this->tft_url_.c_str(), result);
if (result < 0) {
ESP_LOGE(TAG, "Error updating Nextion!");
esp_http_client_cleanup(http);
return this->upload_end(false);
}
App.feed_wdt();
ESP_LOGV(TAG, "Heap Size %" PRIu32 ", Bytes left %d", esp_get_free_heap_size(), content_length_);
}
ESP_LOGD(TAG, "Successfully updated Nextion!");
ESP_LOGD(TAG, "Close HTTP connection");
esp_http_client_close(http);
esp_http_client_cleanup(http);
return upload_end(true);
}
bool Nextion::upload_end(bool successful) {
this->is_updating_ = false;
ESP_LOGD(TAG, "Restarting Nextion");
this->soft_reset();
vTaskDelay(pdMS_TO_TICKS(1500)); // NOLINT
if (successful) {
ESP_LOGD(TAG, "Restarting esphome");
esp_restart(); // NOLINT(readability-static-accessed-through-instance)
}
return successful;
}
} // namespace nextion
} // namespace esphome
#endif // USE_NEXTION_TFT_UPLOAD
#endif // USE_ESP_IDF

View File

@@ -2,7 +2,7 @@ from math import log
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.components import sensor
from esphome.components import sensor, resistance_sampler
from esphome.const import (
CONF_CALIBRATION,
CONF_REFERENCE_RESISTANCE,
@@ -15,6 +15,8 @@ from esphome.const import (
UNIT_CELSIUS,
)
AUTO_LOAD = ["resistance_sampler"]
ntc_ns = cg.esphome_ns.namespace("ntc")
NTC = ntc_ns.class_("NTC", cg.Component, sensor.Sensor)
@@ -124,7 +126,7 @@ CONFIG_SCHEMA = (
)
.extend(
{
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
cv.Required(CONF_SENSOR): cv.use_id(resistance_sampler.ResistanceSampler),
cv.Required(CONF_CALIBRATION): process_calibration,
}
)

View File

@@ -52,8 +52,9 @@ RemoteReceiverTrigger = ns.class_(
"RemoteReceiverTrigger", automation.Trigger, RemoteReceiverListener
)
RemoteTransmitterDumper = ns.class_("RemoteTransmitterDumper")
RemoteTransmittable = ns.class_("RemoteTransmittable")
RemoteTransmitterActionBase = ns.class_(
"RemoteTransmitterActionBase", automation.Action
"RemoteTransmitterActionBase", RemoteTransmittable, automation.Action
)
RemoteReceiverBase = ns.class_("RemoteReceiverBase")
RemoteTransmitterBase = ns.class_("RemoteTransmitterBase")
@@ -68,11 +69,30 @@ def templatize(value):
return cv.Schema(ret)
REMOTE_LISTENER_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase),
}
)
REMOTE_TRANSMITTABLE_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterBase),
}
)
async def register_listener(var, config):
receiver = await cg.get_variable(config[CONF_RECEIVER_ID])
cg.add(receiver.register_listener(var))
async def register_transmittable(var, config):
transmitter_ = await cg.get_variable(config[CONF_TRANSMITTER_ID])
cg.add(var.set_transmitter(transmitter_))
def register_binary_sensor(name, type, schema):
return BINARY_SENSOR_REGISTRY.register(name, type, schema)
@@ -129,10 +149,9 @@ def validate_repeat(value):
BASE_REMOTE_TRANSMITTER_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterBase),
cv.Optional(CONF_REPEAT): validate_repeat,
}
)
).extend(REMOTE_TRANSMITTABLE_SCHEMA)
def register_action(name, type_, schema):
@@ -143,9 +162,8 @@ def register_action(name, type_, schema):
def decorator(func):
async def new_func(config, action_id, template_arg, args):
transmitter = await cg.get_variable(config[CONF_TRANSMITTER_ID])
var = cg.new_Pvariable(action_id, template_arg)
cg.add(var.set_parent(transmitter))
await register_transmittable(var, config)
if CONF_REPEAT in config:
conf = config[CONF_REPEAT]
template_ = await cg.templatable(conf[CONF_TIMES], args, cg.uint32)
@@ -1539,7 +1557,7 @@ MIDEA_SCHEMA = cv.Schema(
@register_binary_sensor("midea", MideaBinarySensor, MIDEA_SCHEMA)
def midea_binary_sensor(var, config):
cg.add(var.set_code(config[CONF_CODE]))
cg.add(var.set_data(config[CONF_CODE]))
@register_trigger("midea", MideaTrigger, MideaData)

View File

@@ -67,20 +67,7 @@ class MideaProtocol : public RemoteProtocol<MideaData> {
void dump(const MideaData &data) override;
};
class MideaBinarySensor : public RemoteReceiverBinarySensorBase {
public:
bool matches(RemoteReceiveData src) override {
auto data = MideaProtocol().decode(src);
return data.has_value() && data.value() == this->data_;
}
void set_code(const std::vector<uint8_t> &code) { this->data_ = code; }
protected:
MideaData data_;
};
using MideaTrigger = RemoteReceiverTrigger<MideaProtocol, MideaData>;
using MideaDumper = RemoteReceiverDumper<MideaProtocol, MideaData>;
DECLARE_REMOTE_PROTOCOL(Midea)
template<typename... Ts> class MideaAction : public RemoteTransmitterActionBase<Ts...> {
TEMPLATABLE_VALUE(std::vector<uint8_t>, code)

View File

@@ -15,6 +15,8 @@ struct RCSwitchData {
class RCSwitchBase {
public:
using ProtocolData = RCSwitchData;
RCSwitchBase() = default;
RCSwitchBase(uint32_t sync_high, uint32_t sync_low, uint32_t zero_high, uint32_t zero_low, uint32_t one_high,
uint32_t one_low, bool inverted);
@@ -213,7 +215,7 @@ class RCSwitchDumper : public RemoteReceiverDumperBase {
bool dump(RemoteReceiveData src) override;
};
using RCSwitchTrigger = RemoteReceiverTrigger<RCSwitchBase, RCSwitchData>;
using RCSwitchTrigger = RemoteReceiverTrigger<RCSwitchBase>;
} // namespace remote_base
} // namespace esphome

View File

@@ -127,6 +127,14 @@ class RemoteTransmitterBase : public RemoteComponentBase {
this->temp_.reset();
return TransmitCall(this);
}
template<typename Protocol>
void transmit(const typename Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) {
auto call = this->transmit();
Protocol().encode(call.get_data(), data);
call.set_send_times(send_times);
call.set_send_wait(send_wait);
call.perform();
}
protected:
void send_(uint32_t send_times, uint32_t send_wait);
@@ -184,12 +192,13 @@ class RemoteReceiverBinarySensorBase : public binary_sensor::BinarySensorInitial
template<typename T> class RemoteProtocol {
public:
virtual void encode(RemoteTransmitData *dst, const T &data) = 0;
virtual optional<T> decode(RemoteReceiveData src) = 0;
virtual void dump(const T &data) = 0;
using ProtocolData = T;
virtual void encode(RemoteTransmitData *dst, const ProtocolData &data) = 0;
virtual optional<ProtocolData> decode(RemoteReceiveData src) = 0;
virtual void dump(const ProtocolData &data) = 0;
};
template<typename T, typename D> class RemoteReceiverBinarySensor : public RemoteReceiverBinarySensorBase {
template<typename T> class RemoteReceiverBinarySensor : public RemoteReceiverBinarySensorBase {
public:
RemoteReceiverBinarySensor() : RemoteReceiverBinarySensorBase() {}
@@ -201,13 +210,14 @@ template<typename T, typename D> class RemoteReceiverBinarySensor : public Remot
}
public:
void set_data(D data) { data_ = data; }
void set_data(typename T::ProtocolData data) { data_ = data; }
protected:
D data_;
typename T::ProtocolData data_;
};
template<typename T, typename D> class RemoteReceiverTrigger : public Trigger<D>, public RemoteReceiverListener {
template<typename T>
class RemoteReceiverTrigger : public Trigger<typename T::ProtocolData>, public RemoteReceiverListener {
protected:
bool on_receive(RemoteReceiveData src) override {
auto proto = T();
@@ -220,28 +230,36 @@ template<typename T, typename D> class RemoteReceiverTrigger : public Trigger<D>
}
};
template<typename... Ts> class RemoteTransmitterActionBase : public Action<Ts...> {
class RemoteTransmittable {
public:
void set_parent(RemoteTransmitterBase *parent) { this->parent_ = parent; }
RemoteTransmittable() {}
RemoteTransmittable(RemoteTransmitterBase *transmitter) : transmitter_(transmitter) {}
void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; }
TEMPLATABLE_VALUE(uint32_t, send_times);
TEMPLATABLE_VALUE(uint32_t, send_wait);
protected:
template<typename Protocol>
void transmit_(const typename Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) {
this->transmitter_->transmit<Protocol>(data, send_times, send_wait);
}
RemoteTransmitterBase *transmitter_;
};
template<typename... Ts> class RemoteTransmitterActionBase : public RemoteTransmittable, public Action<Ts...> {
TEMPLATABLE_VALUE(uint32_t, send_times)
TEMPLATABLE_VALUE(uint32_t, send_wait)
protected:
void play(Ts... x) override {
auto call = this->parent_->transmit();
auto call = this->transmitter_->transmit();
this->encode(call.get_data(), x...);
call.set_send_times(this->send_times_.value_or(x..., 1));
call.set_send_wait(this->send_wait_.value_or(x..., 0));
call.perform();
}
protected:
virtual void encode(RemoteTransmitData *dst, Ts... x) = 0;
RemoteTransmitterBase *parent_{};
};
template<typename T, typename D> class RemoteReceiverDumper : public RemoteReceiverDumperBase {
template<typename T> class RemoteReceiverDumper : public RemoteReceiverDumperBase {
public:
bool dump(RemoteReceiveData src) override {
auto proto = T();
@@ -254,9 +272,9 @@ template<typename T, typename D> class RemoteReceiverDumper : public RemoteRecei
};
#define DECLARE_REMOTE_PROTOCOL_(prefix) \
using prefix##BinarySensor = RemoteReceiverBinarySensor<prefix##Protocol, prefix##Data>; \
using prefix##Trigger = RemoteReceiverTrigger<prefix##Protocol, prefix##Data>; \
using prefix##Dumper = RemoteReceiverDumper<prefix##Protocol, prefix##Data>;
using prefix##BinarySensor = RemoteReceiverBinarySensor<prefix##Protocol>; \
using prefix##Trigger = RemoteReceiverTrigger<prefix##Protocol>; \
using prefix##Dumper = RemoteReceiverDumper<prefix##Protocol>;
#define DECLARE_REMOTE_PROTOCOL(prefix) DECLARE_REMOTE_PROTOCOL_(prefix)
} // namespace remote_base

View File

@@ -1,7 +1,8 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/resistance_sampler/resistance_sampler.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
namespace esphome {
namespace resistance {
@@ -11,7 +12,7 @@ enum ResistanceConfiguration {
DOWNSTREAM,
};
class ResistanceSensor : public Component, public sensor::Sensor {
class ResistanceSensor : public Component, public sensor::Sensor, resistance_sampler::ResistanceSampler {
public:
void set_sensor(Sensor *sensor) { sensor_ = sensor; }
void set_configuration(ResistanceConfiguration configuration) { configuration_ = configuration; }

View File

@@ -1,6 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.components import sensor, resistance_sampler
from esphome.const import (
CONF_SENSOR,
STATE_CLASS_MEASUREMENT,
@@ -8,8 +8,15 @@ from esphome.const import (
ICON_FLASH,
)
AUTO_LOAD = ["resistance_sampler"]
resistance_ns = cg.esphome_ns.namespace("resistance")
ResistanceSensor = resistance_ns.class_("ResistanceSensor", cg.Component, sensor.Sensor)
ResistanceSensor = resistance_ns.class_(
"ResistanceSensor",
cg.Component,
sensor.Sensor,
resistance_sampler.ResistanceSampler,
)
CONF_REFERENCE_VOLTAGE = "reference_voltage"
CONF_CONFIGURATION = "configuration"

View File

@@ -0,0 +1,6 @@
import esphome.codegen as cg
resistance_sampler_ns = cg.esphome_ns.namespace("resistance_sampler")
ResistanceSampler = resistance_sampler_ns.class_("ResistanceSampler")
CODEOWNERS = ["@jesserockz"]

View File

@@ -0,0 +1,10 @@
#pragma once
namespace esphome {
namespace resistance_sampler {
/// Abstract interface to mark components that provide resistance values.
class ResistanceSampler {};
} // namespace resistance_sampler
} // namespace esphome

View File

@@ -74,12 +74,12 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
# The default/recommended arduino framework version
# - https://github.com/earlephilhower/arduino-pico/releases
# - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 4, 0)
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 6, 0)
# The platformio/raspberrypi version to use for arduino frameworks
# - https://github.com/platformio/platform-raspberrypi/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/raspberrypi
ARDUINO_PLATFORM_VERSION = cv.Version(1, 9, 0)
ARDUINO_PLATFORM_VERSION = cv.Version(1, 10, 0)
def _arduino_check_versions(value):

View File

@@ -610,6 +610,11 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
if (code == "wake-word-timeout" || code == "wake_word_detection_aborted") {
// Don't change state here since either the "tts-end" or "run-end" events will do it.
return;
} else if (code == "wake-provider-missing" || code == "wake-engine-missing") {
// Wake word is not set up or not ready on Home Assistant so stop and do not retry until user starts again.
this->request_stop();
this->error_trigger_->trigger(code, message);
return;
}
ESP_LOGE(TAG, "Error: %s - %s", code.c_str(), message.c_str());
if (this->state_ != State::IDLE) {

View File

@@ -43,11 +43,17 @@ def validate_mode(mode):
return mode
def validate_pin(pin):
if pin in (8, 9):
raise cv.Invalid(f"pin {pin} doesn't exist")
return pin
XL9535_PIN_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(XL9535GPIOPin),
cv.Required(CONF_XL9535): cv.use_id(XL9535Component),
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15),
cv.Required(CONF_NUMBER): cv.All(cv.int_range(min=0, max=17), validate_pin),
cv.Optional(CONF_MODE, default={}): cv.All(
{
cv.Optional(CONF_INPUT, default=False): cv.boolean,

View File

@@ -66,6 +66,7 @@ from esphome.core import (
TimePeriod,
TimePeriodMicroseconds,
TimePeriodMilliseconds,
TimePeriodNanoseconds,
TimePeriodSeconds,
TimePeriodMinutes,
)
@@ -718,6 +719,8 @@ def time_period_str_unit(value):
raise Invalid("Expected string for time period with unit.")
unit_to_kwarg = {
"ns": "nanoseconds",
"nanoseconds": "nanoseconds",
"us": "microseconds",
"microseconds": "microseconds",
"ms": "milliseconds",
@@ -739,7 +742,10 @@ def time_period_str_unit(value):
raise Invalid(f"Expected time period with unit, got {value}")
kwarg = unit_to_kwarg[one_of(*unit_to_kwarg)(match.group(2))]
return TimePeriod(**{kwarg: float(match.group(1))})
try:
return TimePeriod(**{kwarg: float(match.group(1))})
except ValueError as e:
raise Invalid(e) from e
def time_period_in_milliseconds_(value):
@@ -749,10 +755,18 @@ def time_period_in_milliseconds_(value):
def time_period_in_microseconds_(value):
if value.nanoseconds is not None and value.nanoseconds != 0:
raise Invalid("Maximum precision is microseconds")
return TimePeriodMicroseconds(**value.as_dict())
def time_period_in_nanoseconds_(value):
return TimePeriodNanoseconds(**value.as_dict())
def time_period_in_seconds_(value):
if value.nanoseconds is not None and value.nanoseconds != 0:
raise Invalid("Maximum precision is seconds")
if value.microseconds is not None and value.microseconds != 0:
raise Invalid("Maximum precision is seconds")
if value.milliseconds is not None and value.milliseconds != 0:
@@ -761,6 +775,8 @@ def time_period_in_seconds_(value):
def time_period_in_minutes_(value):
if value.nanoseconds is not None and value.nanoseconds != 0:
raise Invalid("Maximum precision is minutes")
if value.microseconds is not None and value.microseconds != 0:
raise Invalid("Maximum precision is minutes")
if value.milliseconds is not None and value.milliseconds != 0:
@@ -787,6 +803,9 @@ time_period_microseconds = All(time_period, time_period_in_microseconds_)
positive_time_period_microseconds = All(
positive_time_period, time_period_in_microseconds_
)
positive_time_period_nanoseconds = All(
positive_time_period, time_period_in_nanoseconds_
)
positive_not_null_time_period = All(
time_period, Range(min=TimePeriod(), min_included=False)
)

View File

@@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2023.11.0-dev"
__version__ = "2023.12.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (

View File

@@ -87,6 +87,7 @@ def is_approximately_integer(value):
class TimePeriod:
def __init__(
self,
nanoseconds=None,
microseconds=None,
milliseconds=None,
seconds=None,
@@ -136,13 +137,23 @@ class TimePeriod:
if microseconds is not None:
if not is_approximately_integer(microseconds):
raise ValueError("Maximum precision is microseconds")
frac_microseconds, microseconds = math.modf(microseconds)
nanoseconds = (nanoseconds or 0) + frac_microseconds * 1000
self.microseconds = int(round(microseconds))
else:
self.microseconds = None
if nanoseconds is not None:
if not is_approximately_integer(nanoseconds):
raise ValueError("Maximum precision is nanoseconds")
self.nanoseconds = int(round(nanoseconds))
else:
self.nanoseconds = None
def as_dict(self):
out = OrderedDict()
if self.nanoseconds is not None:
out["nanoseconds"] = self.nanoseconds
if self.microseconds is not None:
out["microseconds"] = self.microseconds
if self.milliseconds is not None:
@@ -158,6 +169,8 @@ class TimePeriod:
return out
def __str__(self):
if self.nanoseconds is not None:
return f"{self.total_nanoseconds}ns"
if self.microseconds is not None:
return f"{self.total_microseconds}us"
if self.milliseconds is not None:
@@ -173,7 +186,11 @@ class TimePeriod:
return "0s"
def __repr__(self):
return f"TimePeriod<{self.total_microseconds}>"
return f"TimePeriod<{self.total_nanoseconds}ns>"
@property
def total_nanoseconds(self):
return self.total_microseconds * 1000 + (self.nanoseconds or 0)
@property
def total_microseconds(self):
@@ -201,35 +218,39 @@ class TimePeriod:
def __eq__(self, other):
if isinstance(other, TimePeriod):
return self.total_microseconds == other.total_microseconds
return self.total_nanoseconds == other.total_nanoseconds
return NotImplemented
def __ne__(self, other):
if isinstance(other, TimePeriod):
return self.total_microseconds != other.total_microseconds
return self.total_nanoseconds != other.total_nanoseconds
return NotImplemented
def __lt__(self, other):
if isinstance(other, TimePeriod):
return self.total_microseconds < other.total_microseconds
return self.total_nanoseconds < other.total_nanoseconds
return NotImplemented
def __gt__(self, other):
if isinstance(other, TimePeriod):
return self.total_microseconds > other.total_microseconds
return self.total_nanoseconds > other.total_nanoseconds
return NotImplemented
def __le__(self, other):
if isinstance(other, TimePeriod):
return self.total_microseconds <= other.total_microseconds
return self.total_nanoseconds <= other.total_nanoseconds
return NotImplemented
def __ge__(self, other):
if isinstance(other, TimePeriod):
return self.total_microseconds >= other.total_microseconds
return self.total_nanoseconds >= other.total_nanoseconds
return NotImplemented
class TimePeriodNanoseconds(TimePeriod):
pass
class TimePeriodMicroseconds(TimePeriod):
pass

View File

@@ -17,6 +17,7 @@ from esphome.core import (
TimePeriodMicroseconds,
TimePeriodMilliseconds,
TimePeriodMinutes,
TimePeriodNanoseconds,
TimePeriodSeconds,
)
from esphome.helpers import cpp_string_escape, indent_all_but_first_and_last
@@ -351,6 +352,8 @@ def safe_exp(obj: SafeExpType) -> Expression:
return IntLiteral(obj)
if isinstance(obj, float):
return FloatLiteral(obj)
if isinstance(obj, TimePeriodNanoseconds):
return IntLiteral(int(obj.total_nanoseconds))
if isinstance(obj, TimePeriodMicroseconds):
return IntLiteral(int(obj.total_microseconds))
if isinstance(obj, TimePeriodMilliseconds):

View File

@@ -1,9 +1,10 @@
from __future__ import annotations
import asyncio
import base64
import binascii
import codecs
import collections
import datetime
import functools
import gzip
import hashlib
@@ -338,8 +339,8 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
def handle_stdin(self, json_message):
if not self.is_process_active:
return
data = json_message["data"]
data = codecs.encode(data, "utf8", "replace")
text: str = json_message["data"]
data = text.encode("utf-8", "replace")
_LOGGER.debug("< stdin: %s", data)
self._proc.stdin.write(data)
@@ -350,18 +351,18 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
while True:
try:
if self._use_popen:
data = yield self._queue.get()
data: bytes = yield self._queue.get()
if data is None:
self._proc_on_exit(self._proc.poll())
break
else:
data = yield self._proc.stdout.read_until_regex(reg)
data: bytes = yield self._proc.stdout.read_until_regex(reg)
except tornado.iostream.StreamClosedError:
break
data = codecs.decode(data, "utf8", "replace")
_LOGGER.debug("> stdout: %s", data)
self.write_message({"event": "line", "data": data})
text = data.decode("utf-8", "replace")
_LOGGER.debug("> stdout: %s", text)
self.write_message({"event": "line", "data": text})
def _stdout_thread(self):
if not self._use_popen:
@@ -508,8 +509,8 @@ class EsphomeUpdateAllHandler(EsphomeCommandWebSocket):
class SerialPortRequestHandler(BaseHandler):
@authenticated
def get(self):
ports = get_serial_ports()
async def get(self):
ports = await asyncio.get_running_loop().run_in_executor(None, get_serial_ports)
data = []
for port in ports:
desc = port.description
@@ -1406,15 +1407,17 @@ def make_app(debug=get_bool_env(ENV_DEV)):
)
class StaticFileHandler(tornado.web.StaticFileHandler):
def set_extra_headers(self, path):
if "favicon.ico" in path:
self.set_header("Cache-Control", "max-age=84600, public")
else:
if debug:
self.set_header(
"Cache-Control",
"no-store, no-cache, must-revalidate, max-age=0",
)
def get_cache_time(
self, path: str, modified: datetime.datetime | None, mime_type: str
) -> int:
"""Override to customize cache control behavior."""
if debug:
return 0
# Assets that are hashed have ?hash= in the URL, all javascript
# filenames hashed so we can cache them for a long time
if "hash" in self.request.arguments or "/javascript" in mime_type:
return self.CACHE_MAX_AGE
return super().get_cache_time(path, modified, mime_type)
app_settings = {
"debug": debug,

View File

@@ -23,6 +23,14 @@ from esphome.core import (
from esphome.helpers import add_class_to_obj
from esphome.util import OrderedDict, filter_yaml_files
try:
from yaml import CSafeLoader as FastestAvailableSafeLoader
except ImportError:
from yaml import ( # type: ignore[assignment]
SafeLoader as FastestAvailableSafeLoader,
)
_LOGGER = logging.getLogger(__name__)
# Mostly copied from Home Assistant because that code works fine and
@@ -89,7 +97,7 @@ def _add_data_ref(fn):
return wrapped
class ESPHomeLoader(yaml.SafeLoader):
class ESPHomeLoader(FastestAvailableSafeLoader):
"""Loader class that keeps track of line numbers."""
@_add_data_ref

View File

@@ -147,12 +147,13 @@ class DashboardImportDiscovery:
class EsphomeZeroconf(Zeroconf):
def resolve_host(self, host: str, timeout=3.0):
def resolve_host(self, host: str, timeout: float = 3.0) -> str | None:
"""Resolve a host name to an IP address."""
name = host.partition(".")[0]
info = HostResolver(f"{name}.{ESPHOME_SERVICE_TYPE}", ESPHOME_SERVICE_TYPE)
if (info.load_from_cache(self) or info.request(self, timeout * 1000)) and (
addresses := info.ip_addresses_by_version(IPVersion.V4Only)
):
info = HostResolver(ESPHOME_SERVICE_TYPE, f"{name}.{ESPHOME_SERVICE_TYPE}")
if (
info.load_from_cache(self)
or (timeout and info.request(self, timeout * 1000))
) and (addresses := info.ip_addresses_by_version(IPVersion.V4Only)):
return str(addresses[0])
return None

View File

@@ -159,7 +159,7 @@ board_build.filesystem_size = 0.5m
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
platform_packages =
; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted
earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.4.0/rp2040-3.4.0.zip
earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.6.0/rp2040-3.6.0.zip
framework = arduino
lib_deps =

View File

@@ -10,8 +10,8 @@ platformio==6.1.11 # When updating platformio, also update Dockerfile
esptool==4.6.2
click==8.1.7
esphome-dashboard==20231107.0
aioesphomeapi==18.2.4
zeroconf==0.120.0
aioesphomeapi==18.4.0
zeroconf==0.126.0
# esp-idf requires this, but doesn't bundle it by default
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24

View File

@@ -1,6 +1,6 @@
pylint==2.17.6
flake8==6.1.0 # also change in .pre-commit-config.yaml when updating
black==23.10.1 # also change in .pre-commit-config.yaml when updating
black==23.11.0 # also change in .pre-commit-config.yaml when updating
pyupgrade==3.15.0 # also change in .pre-commit-config.yaml when updating
pre-commit

View File

@@ -3050,6 +3050,9 @@ remote_receiver:
on_coolix:
then:
delay: !lambda "return x.first + x.second;"
on_rc_switch:
then:
delay: !lambda "return uint32_t(x.code) + x.protocol;"
status_led:
pin: GPIO2

View File

@@ -425,6 +425,15 @@ binary_sensor:
input: true
inverted: false
- platform: gpio
name: XL9535 Pin 17
pin:
xl9535: xl9535_hub
number: 17
mode:
input: true
inverted: false
climate:
- platform: tuya
id: tuya_climate

View File

@@ -116,14 +116,16 @@ class TestTimePeriod:
assert actual == expected
def test_init__microseconds_with_fraction(self):
with pytest.raises(ValueError, match="Maximum precision is microseconds"):
core.TimePeriod(microseconds=1.1)
def test_init__nanoseconds_with_fraction(self):
with pytest.raises(ValueError, match="Maximum precision is nanoseconds"):
core.TimePeriod(nanoseconds=1.1)
@pytest.mark.parametrize(
"kwargs, expected",
(
({}, "0s"),
({"nanoseconds": 1}, "1ns"),
({"nanoseconds": 1.0001}, "1ns"),
({"microseconds": 1}, "1us"),
({"microseconds": 1.0001}, "1us"),
({"milliseconds": 2}, "2ms"),