1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-10 03:51:52 +00:00

Compare commits

...

34 Commits

Author SHA1 Message Date
Jesse Hills
c536c976b7 Merge pull request #5758 from esphome/bump-2023.11.0
2023.11.0
2023-11-15 16:11:18 +13:00
Jesse Hills
0c18872888 Bump version to 2023.11.0 2023-11-15 14:13:39 +13:00
Jesse Hills
197b6b4275 Merge pull request #5756 from esphome/bump-2023.11.0b7
2023.11.0b7
2023-11-15 13:19:48 +13:00
Jesse Hills
4e8bdc2155 Bump version to 2023.11.0b7 2023-11-15 12:45:03 +13:00
Jesse Hills
f1e8622187 Dont dump wifi info when disabled (#5755) 2023-11-15 12:45:02 +13:00
Jesse Hills
e0c7a02fbc Allow setup to continue past mqtt if network/wifi is disabled (#5754) 2023-11-15 12:45:02 +13:00
Jesse Hills
2a20a5fc11 Merge pull request #5750 from esphome/bump-2023.11.0b6
2023.11.0b6
2023-11-14 16:16:11 +13:00
Jesse Hills
7100d073f8 Bump version to 2023.11.0b6 2023-11-14 14:32:41 +13:00
Keith Burzinski
1ac6cf2ff9 Generate partitions.csv based on flash size (#5697) 2023-11-14 14:32:41 +13:00
J. Nick Koston
2ee089c9d5 dashboard: Run get_serial_ports in the executor (#5740) 2023-11-14 14:32:41 +13:00
J. Nick Koston
bd568eecf5 dashboard: remove usage of codecs module (#5741) 2023-11-14 14:32:40 +13:00
Jesse Hills
3e2b83acb0 Merge pull request #5742 from esphome/bump-2023.11.0b5
2023.11.0b5
2023-11-13 16:37:28 +13:00
Jesse Hills
c1eb5bd675 Bump version to 2023.11.0b5 2023-11-13 15:26:04 +13:00
Jesse Hills
a9772ebf3f Handle wake word not set up internally (#5738) 2023-11-13 15:26:04 +13:00
Jesse Hills
a9a17ee89d Merge pull request #5737 from esphome/bump-2023.11.0b4
2023.11.0b4
2023-11-13 11:25:42 +13:00
Jesse Hills
f094702a16 Bump version to 2023.11.0b4 2023-11-13 10:23:28 +13:00
J. Nick Koston
908f56ff46 Bump zeroconf to 0.123.0 (#5736) 2023-11-13 10:23:28 +13:00
J. Nick Koston
bd5905c59a Migrate to using aioesphomeapi for the log runner to fix multiple issues (#5733) 2023-11-13 10:23:28 +13:00
dependabot[bot]
91299f05f7 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-13 10:23:27 +13:00
Mike La Spina
30e5ff9fff Missed ifdefs (#5727) 2023-11-13 10:23:27 +13:00
J. Nick Koston
163b38e153 Fix zeroconf name resolution refactoring error (#5725) 2023-11-13 10:23:27 +13:00
Jesse Hills
3b486084c8 Add resistance_sampler interface for config validation (#5718) 2023-11-13 10:23:27 +13:00
Jesse Hills
9d453f0ba2 Merge pull request #5714 from esphome/bump-2023.11.0b3
2023.11.0b3
2023-11-10 08:16:33 +13:00
dependabot[bot]
799851a83a Bump zeroconf from 0.120.0 to 0.122.3 (#5715) 2023-11-10 07:29:44 +13:00
Jesse Hills
7a9866f1b6 Bump version to 2023.11.0b3 2023-11-09 22:14:22 +13:00
J. Nick Koston
3d30f1f733 Update Dockerfile to use piwheels for armv7 (#5709) 2023-11-09 22:14:22 +13:00
J. Nick Koston
1e55764d52 Bump aioesphomeapi to 18.2.7 (#5706) 2023-11-09 22:14:22 +13:00
Jesse Hills
020da89b6a Merge pull request #5707 from esphome/bump-2023.11.0b2
2023.11.0b2
2023-11-09 14:16:27 +13:00
Jesse Hills
6932422104 Bump version to 2023.11.0b2 2023-11-09 12:46:57 +13:00
Rodrigo Martín
29aa15b253 fix: Fix broken bluetooth_proxy and ble_clients after BLE enable/disable (#5704) 2023-11-09 12:46:57 +13:00
J. Nick Koston
c40519ec6f Use piwheels for armv7 docker image builds (#5703) 2023-11-09 12:46:57 +13:00
J. Nick Koston
6c62c00963 Fix static assets cache logic (#5700) 2023-11-09 12:46:57 +13:00
Jesse Hills
1bd2e558d6 Fix esp32_rmt_led_strip custom timing units (#5696) 2023-11-09 12:46:57 +13:00
Jesse Hills
dbb1263a36 Handle nanoseconds in config (#5695) 2023-11-09 12:46:57 +13:00
25 changed files with 281 additions and 121 deletions

View File

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

View File

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

View File

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

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( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.Required(CONF_BOARD): cv.string_strict, 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_VARIANT): cv.one_of(*VARIANTS, upper=True),
cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA, cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA,
} }
@@ -401,6 +412,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
cg.add_platformio_option("board", config[CONF_BOARD]) 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_build_flag("-DUSE_ESP32")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}") cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
@@ -505,24 +517,46 @@ async def to_code(config):
) )
ARDUINO_PARTITIONS_CSV = """\ APP_PARTITION_SIZES = {
nvs, data, nvs, 0x009000, 0x005000, "4MB": 0x1C0000, # 1792 KB
otadata, data, ota, 0x00e000, 0x002000, "8MB": 0x3C0000, # 3840 KB
app0, app, ota_0, 0x010000, 0x1C0000, "16MB": 0x7C0000, # 7936 KB
app1, app, ota_1, 0x1D0000, 0x1C0000, "32MB": 0xFC0000, # 16128 KB
eeprom, data, 0x99, 0x390000, 0x001000, }
spiffs, data, spiffs, 0x391000, 0x00F000
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 = """\ def get_idf_partition_csv(flash_size):
# Name, Type, SubType, Offset, Size, Flags app_partition_size = APP_PARTITION_SIZES[flash_size]
partition_csv = f"""\
otadata, data, ota, , 0x2000, otadata, data, ota, , 0x2000,
phy_init, data, phy, , 0x1000, phy_init, data, phy, , 0x1000,
app0, app, ota_0, , 0x1C0000, app0, app, ota_0, , 0x{app_partition_size:X},
app1, app, ota_1, , 0x1C0000, app1, app, ota_1, , 0x{app_partition_size:X},
nvs, data, nvs, , 0x6d000, nvs, data, nvs, , 0x6D000,
""" """
return partition_csv
def _format_sdkconfig_val(value: SdkconfigValueType) -> str: def _format_sdkconfig_val(value: SdkconfigValueType) -> str:
@@ -565,13 +599,17 @@ def copy_files():
if CORE.using_arduino: if CORE.using_arduino:
write_file_if_changed( write_file_if_changed(
CORE.relative_build_path("partitions.csv"), 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: if CORE.using_esp_idf:
_write_sdkconfig() _write_sdkconfig()
write_file_if_changed( write_file_if_changed(
CORE.relative_build_path("partitions.csv"), 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. # IDF build scripts look for version string to put in the build.
# However, if the build path does not have an initialized git repo, # 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() { void BLEClientBase::setup() {
static uint8_t connection_index = 0; static uint8_t connection_index = 0;
this->connection_index_ = connection_index++; 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() { 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 // READY_TO_CONNECT means we have discovered the device
// and the scanner has been stopped by the tracker. // and the scanner has been stopped by the tracker.
if (this->state_ == espbt::ClientState::READY_TO_CONNECT) { if (this->state_ == espbt::ClientState::READY_TO_CONNECT) {

View File

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

View File

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

View File

@@ -147,7 +147,7 @@ void MQTTClientComponent::dump_config() {
ESP_LOGCONFIG(TAG, " Availability: '%s'", this->availability_.topic.c_str()); ESP_LOGCONFIG(TAG, " Availability: '%s'", this->availability_.topic.c_str());
} }
} }
bool MQTTClientComponent::can_proceed() { return this->is_connected(); } bool MQTTClientComponent::can_proceed() { return network::is_disabled() || this->is_connected(); }
void MQTTClientComponent::start_dnslookup_() { void MQTTClientComponent::start_dnslookup_() {
for (auto &subscription : this->subscriptions_) { for (auto &subscription : this->subscriptions_) {

View File

@@ -29,6 +29,14 @@ bool is_connected() {
return false; return false;
} }
bool is_disabled() {
#ifdef USE_WIFI
if (wifi::global_wifi_component != nullptr)
return wifi::global_wifi_component->is_disabled();
#endif
return false;
}
network::IPAddress get_ip_address() { network::IPAddress get_ip_address() {
#ifdef USE_ETHERNET #ifdef USE_ETHERNET
if (ethernet::global_eth_component != nullptr) if (ethernet::global_eth_component != nullptr)

View File

@@ -8,6 +8,8 @@ namespace network {
/// Return whether the node is connected to the network (through wifi, eth, ...) /// Return whether the node is connected to the network (through wifi, eth, ...)
bool is_connected(); bool is_connected();
/// Return whether the network is disabled (only wifi for now)
bool is_disabled();
/// Get the active network hostname /// Get the active network hostname
std::string get_use_address(); std::string get_use_address();
IPAddress get_ip_address(); IPAddress get_ip_address();

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import sensor from esphome.components import sensor, resistance_sampler
from esphome.const import ( from esphome.const import (
CONF_SENSOR, CONF_SENSOR,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
@@ -8,8 +8,15 @@ from esphome.const import (
ICON_FLASH, ICON_FLASH,
) )
AUTO_LOAD = ["resistance_sampler"]
resistance_ns = cg.esphome_ns.namespace("resistance") 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_REFERENCE_VOLTAGE = "reference_voltage"
CONF_CONFIGURATION = "configuration" 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

@@ -610,6 +610,11 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
if (code == "wake-word-timeout" || code == "wake_word_detection_aborted") { 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. // Don't change state here since either the "tts-end" or "run-end" events will do it.
return; 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()); ESP_LOGE(TAG, "Error: %s - %s", code.c_str(), message.c_str());
if (this->state_ != State::IDLE) { if (this->state_ != State::IDLE) {

View File

@@ -389,6 +389,10 @@ void WiFiComponent::print_connect_params_() {
bssid_t bssid = wifi_bssid(); bssid_t bssid = wifi_bssid();
ESP_LOGCONFIG(TAG, " Local MAC: %s", get_mac_address_pretty().c_str()); ESP_LOGCONFIG(TAG, " Local MAC: %s", get_mac_address_pretty().c_str());
if (this->is_disabled()) {
ESP_LOGCONFIG(TAG, " WiFi is disabled!");
return;
}
ESP_LOGCONFIG(TAG, " SSID: " LOG_SECRET("'%s'"), wifi_ssid().c_str()); ESP_LOGCONFIG(TAG, " SSID: " LOG_SECRET("'%s'"), wifi_ssid().c_str());
ESP_LOGCONFIG(TAG, " IP Address: %s", wifi_sta_ip().str().c_str()); ESP_LOGCONFIG(TAG, " IP Address: %s", wifi_sta_ip().str().c_str());
ESP_LOGCONFIG(TAG, " BSSID: " LOG_SECRET("%02X:%02X:%02X:%02X:%02X:%02X"), bssid[0], bssid[1], bssid[2], bssid[3], ESP_LOGCONFIG(TAG, " BSSID: " LOG_SECRET("%02X:%02X:%02X:%02X:%02X:%02X"), bssid[0], bssid[1], bssid[2], bssid[3],

View File

@@ -66,6 +66,7 @@ from esphome.core import (
TimePeriod, TimePeriod,
TimePeriodMicroseconds, TimePeriodMicroseconds,
TimePeriodMilliseconds, TimePeriodMilliseconds,
TimePeriodNanoseconds,
TimePeriodSeconds, TimePeriodSeconds,
TimePeriodMinutes, TimePeriodMinutes,
) )
@@ -718,6 +719,8 @@ def time_period_str_unit(value):
raise Invalid("Expected string for time period with unit.") raise Invalid("Expected string for time period with unit.")
unit_to_kwarg = { unit_to_kwarg = {
"ns": "nanoseconds",
"nanoseconds": "nanoseconds",
"us": "microseconds", "us": "microseconds",
"microseconds": "microseconds", "microseconds": "microseconds",
"ms": "milliseconds", "ms": "milliseconds",
@@ -739,7 +742,10 @@ def time_period_str_unit(value):
raise Invalid(f"Expected time period with unit, got {value}") raise Invalid(f"Expected time period with unit, got {value}")
kwarg = unit_to_kwarg[one_of(*unit_to_kwarg)(match.group(2))] 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): def time_period_in_milliseconds_(value):
@@ -749,10 +755,18 @@ def time_period_in_milliseconds_(value):
def time_period_in_microseconds_(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()) return TimePeriodMicroseconds(**value.as_dict())
def time_period_in_nanoseconds_(value):
return TimePeriodNanoseconds(**value.as_dict())
def time_period_in_seconds_(value): 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: if value.microseconds is not None and value.microseconds != 0:
raise Invalid("Maximum precision is seconds") raise Invalid("Maximum precision is seconds")
if value.milliseconds is not None and value.milliseconds != 0: 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): 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: if value.microseconds is not None and value.microseconds != 0:
raise Invalid("Maximum precision is minutes") raise Invalid("Maximum precision is minutes")
if value.milliseconds is not None and value.milliseconds != 0: 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_microseconds = All(
positive_time_period, time_period_in_microseconds_ 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( positive_not_null_time_period = All(
time_period, Range(min=TimePeriod(), min_included=False) time_period, Range(min=TimePeriod(), min_included=False)
) )

View File

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

View File

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

View File

@@ -17,6 +17,7 @@ from esphome.core import (
TimePeriodMicroseconds, TimePeriodMicroseconds,
TimePeriodMilliseconds, TimePeriodMilliseconds,
TimePeriodMinutes, TimePeriodMinutes,
TimePeriodNanoseconds,
TimePeriodSeconds, TimePeriodSeconds,
) )
from esphome.helpers import cpp_string_escape, indent_all_but_first_and_last 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) return IntLiteral(obj)
if isinstance(obj, float): if isinstance(obj, float):
return FloatLiteral(obj) return FloatLiteral(obj)
if isinstance(obj, TimePeriodNanoseconds):
return IntLiteral(int(obj.total_nanoseconds))
if isinstance(obj, TimePeriodMicroseconds): if isinstance(obj, TimePeriodMicroseconds):
return IntLiteral(int(obj.total_microseconds)) return IntLiteral(int(obj.total_microseconds))
if isinstance(obj, TimePeriodMilliseconds): if isinstance(obj, TimePeriodMilliseconds):

View File

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

View File

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

View File

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

View File

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