mirror of
https://github.com/esphome/esphome.git
synced 2026-10-26 16:02:13 +00:00
Compare commits
92
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8badf748e | ||
|
|
93b6388cb9 | ||
|
|
be42f9147e | ||
|
|
8677844629 | ||
|
|
c1a9c8f2f1 | ||
|
|
6b7c9e492d | ||
|
|
f35e45febf | ||
|
|
1063e87b36 | ||
|
|
9f47b5c7f8 | ||
|
|
58b6779dbe | ||
|
|
bd881b025f | ||
|
|
c41b1f8f0f | ||
|
|
87b20ffdbb | ||
|
|
a3f38a9d4d | ||
|
|
76491691f5 | ||
|
|
6791dc7750 | ||
|
|
db76f0b1cd | ||
|
|
09f7b71efd | ||
|
|
fd54bf7a07 | ||
|
|
011497c447 | ||
|
|
31bfdcb316 | ||
|
|
42f50ac3bc | ||
|
|
3edbfb47b9 | ||
|
|
b622a59d74 | ||
|
|
0bfdbb948e | ||
|
|
97d4ff5316 | ||
|
|
840f3216b8 | ||
|
|
e257b279d5 | ||
|
|
125bd8ccd0 | ||
|
|
cce3511112 | ||
|
|
836b3220a2 | ||
|
|
49f2610595 | ||
|
|
b49f3953f9 | ||
|
|
de9420a625 | ||
|
|
ae127f7db8 | ||
|
|
26ba2cc234 | ||
|
|
068fe3b7ce | ||
|
|
d8b525a401 | ||
|
|
db6c956e55 | ||
|
|
c95ce587c0 | ||
|
|
3540ac4d1f | ||
|
|
180d7b31d8 | ||
|
|
3cf79df27c | ||
|
|
2beb2de8a1 | ||
|
|
9e457b11ba | ||
|
|
ee41748744 | ||
|
|
08d4d59b1f | ||
|
|
e4c0ef7c95 | ||
|
|
26ee68652f | ||
|
|
fa88c9218b | ||
|
|
44e27c08d8 | ||
|
|
f431cdfb0c | ||
|
|
ba488eab6e | ||
|
|
f21183c160 | ||
|
|
60ca75baad | ||
|
|
b668cb1e50 | ||
|
|
ab7d0ca17d | ||
|
|
43f10a7cfe | ||
|
|
d5e755e407 | ||
|
|
cf67610d43 | ||
|
|
bd55bdcd6c | ||
|
|
f42d1ac645 | ||
|
|
5198c37cd9 | ||
|
|
d9fcc292e2 | ||
|
|
8f8e615ee7 | ||
|
|
f91ad8d43c | ||
|
|
465a8b0386 | ||
|
|
9e692b6772 | ||
|
|
1fd4025a0e | ||
|
|
03b8bd7941 | ||
|
|
0e2c906fce | ||
|
|
d260265cf5 | ||
|
|
2388c3313d | ||
|
|
e717a21bce | ||
|
|
d47a510b84 | ||
|
|
a16d0c48a0 | ||
|
|
8541104d87 | ||
|
|
579c5a5226 | ||
|
|
a027785fbf | ||
|
|
d7f5976525 | ||
|
|
4c8de264fd | ||
|
|
a6dc3130ae | ||
|
|
16fd3e198b | ||
|
|
27bb7dfdc5 | ||
|
|
facd81ead8 | ||
|
|
07c33a25c9 | ||
|
|
ace0f54a81 | ||
|
|
8671d82120 | ||
|
|
93d5b8d3ab | ||
|
|
9e48ac2d31 | ||
|
|
f0699650ab | ||
|
|
8d4efd0975 |
No files matched your search
@@ -217,9 +217,10 @@ file does, and it is the authority when they disagree. The most useful starting
|
||||
the helper's arguments instead of writing a loop. 7-byte BCD RTC calendars use `time::decode_bcd_calendar<LAYOUT>`
|
||||
and `encode_bcd_calendar<LAYOUT>` with `EPSON_BCD_CALENDAR`, `DS_BCD_CALENDAR` or `PCF_BCD_CALENDAR`.
|
||||
* **Bus helpers:** UART drains with `read_available(on_byte)` and `discard_available()`, text lines with
|
||||
`LineReader<N>`; I2C has `update_register`, `write_registers` (PROGMEM `I2CRegValue` table), `check_chip_id`
|
||||
and `read_bytes_16_le`. `if (!this->status_comm_result(ok)) return;` sets or clears the warning.
|
||||
A child sensor that skips repeated values is `SUB_SENSOR_WITH_DEDUP(name, type)`.
|
||||
`LineReader<N>`, fixed-length binary frames with `FrameReader<N, HEADER...>`; I2C has `update_register`,
|
||||
`write_registers` (PROGMEM `I2CRegValue` table), `check_chip_id` and `read_bytes_16_le`.
|
||||
`if (!this->status_comm_result(ok)) return;` sets or clears the warning. A child sensor that skips repeated
|
||||
values is `SUB_SENSOR_WITH_DEDUP(name, type)`.
|
||||
* **An unset float is `NAN`**, not `optional<float>`; the optional costs 4 to 8 bytes of padding per member.
|
||||
* **`PollingComponent` first runs `update()`** after a random share of the interval; call `this->update()` at the
|
||||
end of `setup()` when the entities need a state at boot.
|
||||
@@ -443,12 +444,16 @@ file does, and it is the authority when they disagree. The most useful starting
|
||||
uses `button.ApplyButton` (`switch.ApplySwitch`, `number.ApplyNumber`, `select.ApplySelect`) in its schema
|
||||
and `apply=` on the `sub_*` call. The body is a call on the hub with `entity` and `state`, `value` or
|
||||
`index`/`option` in scope; on a switch, number or select `publish="before"` or `"after"` publishes the state.
|
||||
`publish="momentary"` is only for legacy action switches (restart, factory reset): it publishes off and runs
|
||||
the body only when turned on. Never use it for a new entity; an action is a button.
|
||||
```python
|
||||
switches = switch.sub_switches(config, parent=hub)
|
||||
await switches(CONF_BLUETOOTH, None, apply="set_bluetooth(state)", publish="after")
|
||||
```
|
||||
Restore an owned or standalone switch in `setup()` with `sw->restore()`, or `restore(default)`
|
||||
to still drive a state when the restore mode is disabled.
|
||||
A hub that applies its switches' restored states itself reads `sw->get_initial_write_state()`, which
|
||||
already applies `inverted`.
|
||||
|
||||
- **Other codegen helpers:** `uart.new_uart_component(config, *ctor_args)` (and `i2c.new_i2c_component`,
|
||||
`spi.new_spi_component`, `modbus.new_modbus_client_component`, `ble_client.new_ble_node_component`)
|
||||
|
||||
+30
-1
@@ -1,15 +1,18 @@
|
||||
from collections.abc import Awaitable, Callable, Sequence
|
||||
from dataclasses import dataclass, field
|
||||
import logging
|
||||
import math
|
||||
import string
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ABOVE,
|
||||
CONF_ALL,
|
||||
CONF_ANY,
|
||||
CONF_AUTOMATION_ID,
|
||||
CONF_BELOW,
|
||||
CONF_CONDITION,
|
||||
CONF_COUNT,
|
||||
CONF_ELSE,
|
||||
@@ -363,6 +366,25 @@ class ApplyField:
|
||||
return ApplyCall(target, ((self.conf_key, self.type_, self.const_fn),))
|
||||
|
||||
|
||||
def _in_range_low(config: ConfigType, value: float) -> str:
|
||||
return "-INFINITY" if math.isnan(value) else str(cg.safe_exp(value))
|
||||
|
||||
|
||||
def _in_range_high(config: ConfigType, value: float) -> str:
|
||||
# With no lower bound, an unset upper bound stays NaN so the check is false, as before.
|
||||
if math.isnan(value):
|
||||
return "NAN" if math.isnan(config[CONF_ABOVE]) else "INFINITY"
|
||||
return str(cg.safe_exp(value))
|
||||
|
||||
|
||||
# ``above <= state <= below`` on the parent's float ``state``; register with ``IN_RANGE_DEFAULTS``.
|
||||
IN_RANGE_CHECK = ApplyCall(
|
||||
"state >= {} && {parent}->state <= {}",
|
||||
((CONF_ABOVE, cg.float_, _in_range_low), (CONF_BELOW, cg.float_, _in_range_high)),
|
||||
)
|
||||
IN_RANGE_DEFAULTS = {CONF_ABOVE: math.nan, CONF_BELOW: math.nan}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ApplyBytes:
|
||||
"""A byte payload key forwarded as ``target``, whose ``{}`` stands for ``data, len``.
|
||||
@@ -691,7 +713,11 @@ async def _render_check(
|
||||
|
||||
|
||||
def register_apply_condition(
|
||||
name: str, schema: cv.Schema, check: str | ApplyCall, id_key: str = CONF_ID
|
||||
name: str,
|
||||
schema: cv.Schema,
|
||||
check: str | ApplyCall,
|
||||
id_key: str = CONF_ID,
|
||||
defaults: ConfigType | None = None,
|
||||
) -> None:
|
||||
"""Register a condition that is one expression on its parent, with no C++ class.
|
||||
|
||||
@@ -700,6 +726,7 @@ def register_apply_condition(
|
||||
against config values, all of which must be present. Write ``== false`` to negate.
|
||||
A ``cg.std_string`` constant stays in flash on ESP8266 and supports only ``==`` or ``!=``
|
||||
against a ``std::string`` or ``StringRef`` member; other string types are plain literals.
|
||||
``defaults`` fills absent keys at code generation only, so the validated config is unchanged.
|
||||
Generates one static predicate and an ``ApplyCondition`` templated on it.
|
||||
"""
|
||||
call = check if isinstance(check, ApplyCall) else ApplyCall(check)
|
||||
@@ -714,6 +741,8 @@ def register_apply_condition(
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
if defaults:
|
||||
config = {**defaults, **config}
|
||||
parent = await _apply_parent(config, id_key)
|
||||
lambda_args = _apply_lambda_args(args)
|
||||
values = _apply_values(config, members)
|
||||
|
||||
+30
-13
@@ -7,6 +7,7 @@ import re
|
||||
import textwrap
|
||||
|
||||
from esphome.build_helpers import pch
|
||||
from esphome.build_helpers.ccache import CCACHE_PCH_MIN_VERSION
|
||||
from esphome.components.esp32 import (
|
||||
KEY_SRC_CMAKE,
|
||||
get_esp32_variant,
|
||||
@@ -515,7 +516,7 @@ target_link_options(${{COMPONENT_LIB}} PUBLIC
|
||||
# Where CMake puts the .gch of the src component; ccache reads the checksum
|
||||
# next to it in place of the .gch
|
||||
_PCH_SUM_PATH = "build/esp-idf/src/CMakeFiles/__idf_src.dir/cmake_pch.hxx.gch.sum"
|
||||
# Where the Windows gate records its choice
|
||||
# Where the CMake gate records its choice
|
||||
_PCH_CHOICE_VAR = "ESPHOME_PCH"
|
||||
|
||||
|
||||
@@ -531,16 +532,32 @@ def _pch_cmake_block() -> str:
|
||||
block = f"""target_precompile_headers(${{COMPONENT_LIB}} PRIVATE
|
||||
{headers}
|
||||
)"""
|
||||
if not pch.pch_needs_gcc_check():
|
||||
if pch.pch_forced():
|
||||
return f"\n# ESPHome precompiled header\n{block}\n"
|
||||
# Before the first configure only CMake knows the compiler version
|
||||
return f"""
|
||||
# ESPHome precompiled header, unless GCC bug 14940 keeps it from loading
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND ({pch.PCH_WINDOWS_CMAKE_OLD_GCC}))
|
||||
# Only CMake knows the compiler version before the first configure, and
|
||||
# the ccache IDF found is the one the build runs
|
||||
gcc_gate = ""
|
||||
if pch.pch_needs_gcc_check():
|
||||
gcc_gate = f"""if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND ({pch.PCH_WINDOWS_CMAKE_OLD_GCC}))
|
||||
message(STATUS "ESPHome: GCC ${{CMAKE_CXX_COMPILER_VERSION}} cannot load a precompiled header on Windows; compiling without it")
|
||||
set({_PCH_CHOICE_VAR} OFF CACHE BOOL "ESPHome precompiled header in use" FORCE)
|
||||
else()
|
||||
set({_PCH_CHOICE_VAR} ON CACHE BOOL "ESPHome precompiled header in use" FORCE)
|
||||
set(_esphome_pch OFF)
|
||||
endif()
|
||||
"""
|
||||
min_ccache = ".".join(map(str, CCACHE_PCH_MIN_VERSION))
|
||||
return f"""
|
||||
# ESPHome precompiled header, unless a check below rules it out
|
||||
set(_esphome_pch ON)
|
||||
{gcc_gate}if(CCACHE_ENABLE AND CCACHE_FOUND)
|
||||
execute_process(COMMAND "${{CCACHE_FOUND}}" --version OUTPUT_VARIABLE _esphome_ccache ERROR_QUIET)
|
||||
if(_esphome_ccache MATCHES "ccache version ([0-9.]+)")
|
||||
if(CMAKE_MATCH_1 VERSION_LESS {min_ccache})
|
||||
message(STATUS "ESPHome: ccache ${{CMAKE_MATCH_1}} can reuse stale objects with a precompiled header; compiling without it (upgrade ccache to {min_ccache} or newer, or set ESPHOME_PCH_ENABLE=1 to force)")
|
||||
set(_esphome_pch OFF)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
set({_PCH_CHOICE_VAR} ${{_esphome_pch}} CACHE BOOL "ESPHome precompiled header in use" FORCE)
|
||||
if(_esphome_pch)
|
||||
{textwrap.indent(block, " ")}
|
||||
endif()
|
||||
"""
|
||||
@@ -557,10 +574,10 @@ def write_pch_checksum() -> None:
|
||||
|
||||
if not pch.pch_enabled():
|
||||
return
|
||||
# The gate's choice, cached by configure
|
||||
if pch.pch_needs_gcc_check() and get_cmake_cache_value(_PCH_CHOICE_VAR) != "ON":
|
||||
return
|
||||
pch.log_pch_in_use()
|
||||
# Written whatever the gate chose last time: a build tree without that
|
||||
# choice, or a reconfigure that turns the pch on, must not see a stale sum
|
||||
if pch.pch_forced() or get_cmake_cache_value(_PCH_CHOICE_VAR) == "ON":
|
||||
pch.log_pch_in_use()
|
||||
checksum = pch.pch_checksum(
|
||||
CORE.relative_src_path(),
|
||||
pch.PCH_DEFAULT_HEADERS,
|
||||
|
||||
@@ -3,21 +3,68 @@ resolution, and default ``CCACHE_*`` values."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from esphome.framework_helpers import strip_win_long_path_prefix, tool_version_runs
|
||||
from esphome.framework_helpers import (
|
||||
strip_win_long_path_prefix,
|
||||
tool_version_probe,
|
||||
tool_version_runs,
|
||||
)
|
||||
from esphome.helpers import FALSY_ENV_STRINGS, TRUTHY_ENV_STRINGS
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Before 4.7 ccache stores a pch consumer whose header is newer than one
|
||||
# second without that header, so a later build reuses it after the header
|
||||
# changed (#21621)
|
||||
CCACHE_PCH_MIN_VERSION = (4, 7)
|
||||
|
||||
_VERSION_RE = re.compile(r"ccache version (\d+(?:\.\d+)*)")
|
||||
|
||||
|
||||
@functools.cache
|
||||
def _ccache_probe(ccache: str) -> str | Exception:
|
||||
"""``ccache --version``, run once per binary per process."""
|
||||
return tool_version_probe(ccache)
|
||||
|
||||
|
||||
def _ccache_runs(ccache: str) -> bool:
|
||||
"""Return True when the ``ccache`` found on PATH actually runs."""
|
||||
return tool_version_runs(
|
||||
ccache,
|
||||
"Ignoring ccache at %s because it failed to run; compiling without ccache",
|
||||
probe=_ccache_probe,
|
||||
)
|
||||
|
||||
|
||||
@functools.cache
|
||||
def _ccache_too_old_for_pch(ccache: str) -> bool:
|
||||
output = _ccache_probe(ccache)
|
||||
if not isinstance(output, str) or (found := _VERSION_RE.search(output)) is None:
|
||||
return False
|
||||
if tuple(map(int, found[1].split("."))) >= CCACHE_PCH_MIN_VERSION:
|
||||
return False
|
||||
_LOGGER.info(
|
||||
"ccache %s at %s can reuse stale objects with a precompiled header; "
|
||||
"compiling without it (upgrade ccache to %s or newer, or set "
|
||||
"ESPHOME_PCH_ENABLE=1 to force)",
|
||||
found[1],
|
||||
ccache,
|
||||
".".join(map(str, CCACHE_PCH_MIN_VERSION)),
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def ccache_breaks_pch() -> bool:
|
||||
"""Whether the ccache this build uses is too old for a precompiled header.
|
||||
ESP-IDF builds check the ccache IDF finds in CMake instead (build_gen.espidf)."""
|
||||
return (ccache := resolve_ccache_path()) is not None and _ccache_too_old_for_pch(
|
||||
ccache
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,11 @@ import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from esphome.build_helpers.ccache import effective_ccache_basedir, parse_enable_env
|
||||
from esphome.build_helpers.ccache import (
|
||||
ccache_breaks_pch,
|
||||
effective_ccache_basedir,
|
||||
parse_enable_env,
|
||||
)
|
||||
from esphome.const import PLATFORM_NRF52
|
||||
from esphome.helpers import write_file_if_changed
|
||||
|
||||
@@ -117,9 +121,16 @@ def pch_needs_gcc_check() -> bool:
|
||||
return sys.platform == "win32" and parse_enable_env("ESPHOME_PCH_ENABLE") is None
|
||||
|
||||
|
||||
def _ccache_rules_out_pch() -> bool:
|
||||
"""An old ccache, unless ``ESPHOME_PCH_ENABLE=1`` forces the pch on.
|
||||
ESP-IDF runs the same check in CMake (build_gen.espidf)."""
|
||||
return not pch_forced() and ccache_breaks_pch()
|
||||
|
||||
|
||||
def pch_usable(cxx: Sequence[Path | str]) -> bool:
|
||||
"""The knob plus the host rule; ``ESPHOME_PCH_ENABLE=1`` skips the rule."""
|
||||
if not pch_enabled():
|
||||
"""The knob plus the ccache and host rules; ``ESPHOME_PCH_ENABLE=1``
|
||||
skips the rules."""
|
||||
if not pch_enabled() or _ccache_rules_out_pch():
|
||||
return False
|
||||
if not pch_needs_gcc_check():
|
||||
return True
|
||||
@@ -162,7 +173,11 @@ def pch_script_enabled() -> bool:
|
||||
"""Whether this PlatformIO build takes the pch script."""
|
||||
from esphome.core import CORE
|
||||
|
||||
return pch_enabled() and CORE.target_platform not in PCH_SCRIPT_EXCLUDED_PLATFORMS
|
||||
return (
|
||||
pch_enabled()
|
||||
and CORE.target_platform not in PCH_SCRIPT_EXCLUDED_PLATFORMS
|
||||
and not _ccache_rules_out_pch()
|
||||
)
|
||||
|
||||
|
||||
def pch_header_text(include_headers: Iterable[str]) -> str:
|
||||
|
||||
@@ -90,6 +90,10 @@ class AirtonClimate : public climate_ir::ClimateIR {
|
||||
bool set_display_state(bool state);
|
||||
bool set_vertical_direction_state(VerticalDirection direction);
|
||||
void send_settings();
|
||||
void control_vertical_direction(VerticalDirection direction) {
|
||||
if (this->set_vertical_direction_state(direction))
|
||||
this->send_settings();
|
||||
}
|
||||
void set_setting(AirtonSetting setting, bool state) {
|
||||
const bool changed = setting == AirtonSetting::AIRTON_SETTING_DISPLAY ? this->set_display_state(state)
|
||||
: this->set_sleep_mode_state(state);
|
||||
|
||||
@@ -9,18 +9,16 @@ from ..climate import (
|
||||
CONF_VERTICAL_DIRECTION,
|
||||
VERTICAL_DIRECTIONS,
|
||||
AirtonClimate,
|
||||
airton_ns,
|
||||
VerticalDirection,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@Procsiab"]
|
||||
|
||||
VerticalDirectionSelect = airton_ns.class_("VerticalDirectionSelect", select.Select)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_AIRTON_ID): cv.use_id(AirtonClimate),
|
||||
cv.Optional(CONF_VERTICAL_DIRECTION): select.select_schema(
|
||||
VerticalDirectionSelect,
|
||||
select.ApplySelect,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
),
|
||||
}
|
||||
@@ -34,4 +32,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
CONF_VERTICAL_DIRECTION,
|
||||
hub.set_vertical_direction_select,
|
||||
options=list(VERTICAL_DIRECTIONS),
|
||||
# The options are in the order of the enum; the hub publishes the state
|
||||
apply=f"control_vertical_direction(static_cast<{VerticalDirection}>(index))",
|
||||
)
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/select/select.h"
|
||||
#include "../airton.h"
|
||||
|
||||
namespace esphome::airton {
|
||||
|
||||
class VerticalDirectionSelect : public select::Select, public Parented<AirtonClimate> {
|
||||
protected:
|
||||
// The options are in the order of the enum, so the index is the direction; the parent publishes the state
|
||||
void control(size_t index) override {
|
||||
if (this->parent_->set_vertical_direction_state(static_cast<VerticalDirection>(index)))
|
||||
this->parent_->send_settings();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::airton
|
||||
@@ -1,7 +1,6 @@
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_CODE,
|
||||
@@ -10,11 +9,11 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MQTT_ID,
|
||||
CONF_ON_STATE,
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority, entity_helpers
|
||||
from esphome.core.entity_helpers import (
|
||||
entity_duplicate_validator,
|
||||
mqtt_component_class,
|
||||
queue_entity_register,
|
||||
setup_entity,
|
||||
)
|
||||
@@ -44,12 +43,12 @@ AlarmControlPanelState = alarm_control_panel_ns.enum("AlarmControlPanelState")
|
||||
|
||||
|
||||
_ALARM_CONTROL_PANEL_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
cv.ENTITY_BASE_SCHEMA.extend(entity_helpers.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt.MQTTAlarmControlPanelComponent
|
||||
mqtt_component_class("MQTTAlarmControlPanelComponent")
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation({}),
|
||||
@@ -166,11 +165,7 @@ _CALLBACK_AUTOMATIONS = (
|
||||
@setup_entity("alarm_control_panel")
|
||||
async def setup_alarm_control_panel_core_(var, config):
|
||||
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
|
||||
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||
await web_server.add_entity_config(var, web_server_config)
|
||||
if mqtt_id := config.get(CONF_MQTT_ID):
|
||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
await entity_helpers.setup_entity_integrations(var, config)
|
||||
|
||||
|
||||
async def register_alarm_control_panel(var, config):
|
||||
|
||||
@@ -11,7 +11,52 @@ ESPHOME_LOG_TAG(TAG, "apds9960");
|
||||
this->mark_failed(LOG_STR("Setting up APDS9960 registers failed")); \
|
||||
return; \
|
||||
}
|
||||
#define APDS9960_WRITE_BYTE(reg, value) APDS9960_ERROR_CHECK(this->write_byte(reg, value));
|
||||
|
||||
static constexpr i2c::I2CRegValue APDS9960_TIMING[] PROGMEM = {
|
||||
// ATime (ADC integration time, 2.78ms increments, 0x81) -> 0xDB (103ms)
|
||||
{0x81, 0xDB},
|
||||
// WTime (Wait time, 0x83) -> 0xF6 (27ms)
|
||||
{0x83, 0xF6},
|
||||
// PPulse (0x8E) -> 0x87 (16us, 8 pulses)
|
||||
{0x8E, 0x87},
|
||||
// POffset UR (0x9D) -> 0 (no offset)
|
||||
{0x9D, 0x00},
|
||||
// POffset DL (0x9E) -> 0 (no offset)
|
||||
{0x9E, 0x00},
|
||||
// Config 1 (0x8D) -> 0x60 (no wtime factor)
|
||||
{0x8D, 0x60},
|
||||
};
|
||||
|
||||
static constexpr i2c::I2CRegValue APDS9960_INTERRUPT_GESTURE[] PROGMEM = {
|
||||
// Pers (0x8C) -> 0x11 (2 consecutive proximity or ALS for interrupt)
|
||||
{0x8C, 0x11},
|
||||
// Config 2 (0x90) -> 0x01 (no saturation interrupts or LED boost)
|
||||
{0x90, 0x01},
|
||||
// Config 3 (0x9F) -> 0x00 (enable all photodiodes, no SAI)
|
||||
{0x9F, 0x00},
|
||||
// GPenTh (0xA0, gesture enter threshold) -> 0x28 (also 0x32)
|
||||
{0xA0, 0x28},
|
||||
// GPexTh (0xA1, gesture exit threshold) -> 0x1E
|
||||
{0xA1, 0x1E},
|
||||
// GConf 1 (0xA2, gesture config 1) -> 0x40 (4 gesture events for interrupt (GFIFO 3), 1 for exit)
|
||||
{0xA2, 0x40},
|
||||
};
|
||||
|
||||
static constexpr i2c::I2CRegValue APDS9960_GESTURE_OFFSETS[] PROGMEM = {
|
||||
// GOffsetU (0xA4) -> 0x00 (no offset)
|
||||
{0xA4, 0x00},
|
||||
// GOffsetD (0xA5) -> 0x00 (no offset)
|
||||
{0xA5, 0x00},
|
||||
// GOffsetL (0xA7) -> 0x00 (no offset)
|
||||
{0xA7, 0x00},
|
||||
// GOffsetR (0xA9) -> 0x00 (no offset)
|
||||
{0xA9, 0x00},
|
||||
// GPulse (0xA6) -> 0xC9 (32 µs, 10 pulses)
|
||||
{0xA6, 0xC9},
|
||||
// GConf 3 (0xAA, gesture config 3) -> 0x00 (all photodiodes active during gesture, all gesture dimensions enabled)
|
||||
// 0x00 -> all dimensions, 0x01 -> up down, 0x02 -> left right
|
||||
{0xAA, 0x00},
|
||||
};
|
||||
|
||||
void APDS9960::setup() {
|
||||
uint8_t id;
|
||||
@@ -25,83 +70,27 @@ void APDS9960::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
// ATime (ADC integration time, 2.78ms increments, 0x81) -> 0xDB (103ms)
|
||||
APDS9960_WRITE_BYTE(0x81, 0xDB);
|
||||
// WTime (Wait time, 0x83) -> 0xF6 (27ms)
|
||||
APDS9960_WRITE_BYTE(0x83, 0xF6);
|
||||
// PPulse (0x8E) -> 0x87 (16us, 8 pulses)
|
||||
APDS9960_WRITE_BYTE(0x8E, 0x87);
|
||||
// POffset UR (0x9D) -> 0 (no offset)
|
||||
APDS9960_WRITE_BYTE(0x9D, 0x00);
|
||||
// POffset DL (0x9E) -> 0 (no offset)
|
||||
APDS9960_WRITE_BYTE(0x9E, 0x00);
|
||||
// Config 1 (0x8D) -> 0x60 (no wtime factor)
|
||||
APDS9960_WRITE_BYTE(0x8D, 0x60);
|
||||
APDS9960_ERROR_CHECK(this->write_registers(APDS9960_TIMING));
|
||||
|
||||
// Control (0x8F) ->
|
||||
uint8_t val = 0;
|
||||
APDS9960_ERROR_CHECK(this->read_byte(0x8F, &val));
|
||||
val &= 0b00111111;
|
||||
// led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
|
||||
val |= (this->led_drive_ & 0b11) << 6;
|
||||
// Control (0x8F): led drive (0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA),
|
||||
// proximity gain (0 -> 1x, 1 -> 2X, 2 -> 4X, 3 -> 8X), ambient light gain (0 -> 1x, 1 -> 4x, 2 -> 16x, 3 -> 64x)
|
||||
APDS9960_ERROR_CHECK(this->update_register(
|
||||
0x8F, 0b11001111,
|
||||
(this->led_drive_ & 0b11) << 6 | (this->proximity_gain_ & 0b11) << 2 | (this->ambient_gain_ & 0b11)));
|
||||
|
||||
val &= 0b11110011;
|
||||
// proximity gain, 0 -> 1x, 1 -> 2X, 2 -> 4X, 3 -> 8X
|
||||
val |= (this->proximity_gain_ & 0b11) << 2;
|
||||
APDS9960_ERROR_CHECK(this->write_registers(APDS9960_INTERRUPT_GESTURE));
|
||||
|
||||
val &= 0b11111100;
|
||||
// ambient light gain, 0 -> 1x, 1 -> 4x, 2 -> 16x, 3 -> 64x
|
||||
val |= (this->ambient_gain_ & 0b11) << 0;
|
||||
APDS9960_WRITE_BYTE(0x8F, val);
|
||||
// GConf 2 (0xA3): gesture gain (0 -> 1x, 1 -> 2x, 2 -> 4x, 3 -> 8x),
|
||||
// gesture led drive (0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA), gesture wait time
|
||||
// (0 -> 0ms, 1 -> 2.8ms, 2 -> 5.6ms, 3 -> 8.4ms, 4 -> 14.0ms, 5 -> 22.4 ms, 6 -> 30.8ms, 7 -> 39.2 ms)
|
||||
APDS9960_ERROR_CHECK(this->update_register(
|
||||
0xA3, 0b01111111,
|
||||
(this->gesture_gain_ & 0b11) << 5 | (this->gesture_led_drive_ & 0b11) << 3 | (this->gesture_wait_time_ & 0b111)));
|
||||
|
||||
// Pers (0x8C) -> 0x11 (2 consecutive proximity or ALS for interrupt)
|
||||
APDS9960_WRITE_BYTE(0x8C, 0x11);
|
||||
// Config 2 (0x90) -> 0x01 (no saturation interrupts or LED boost)
|
||||
APDS9960_WRITE_BYTE(0x90, 0x01);
|
||||
// Config 3 (0x9F) -> 0x00 (enable all photodiodes, no SAI)
|
||||
APDS9960_WRITE_BYTE(0x9F, 0x00);
|
||||
// GPenTh (0xA0, gesture enter threshold) -> 0x28 (also 0x32)
|
||||
APDS9960_WRITE_BYTE(0xA0, 0x28);
|
||||
// GPexTh (0xA1, gesture exit threshold) -> 0x1E
|
||||
APDS9960_WRITE_BYTE(0xA1, 0x1E);
|
||||
|
||||
// GConf 1 (0xA2, gesture config 1) -> 0x40 (4 gesture events for interrupt (GFIFO 3), 1 for exit)
|
||||
APDS9960_WRITE_BYTE(0xA2, 0x40);
|
||||
|
||||
// GConf 2 (0xA3, gesture config 2) ->
|
||||
APDS9960_ERROR_CHECK(this->read_byte(0xA3, &val));
|
||||
val &= 0b10011111;
|
||||
// gesture gain, 0 -> 1x, 1 -> 2x, 2 -> 4x, 3 -> 8x
|
||||
val |= (this->gesture_gain_ & 0b11) << 5;
|
||||
|
||||
val &= 0b11100111;
|
||||
// gesture led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
|
||||
val |= (this->gesture_led_drive_ & 0b11) << 3;
|
||||
|
||||
val &= 0b11111000;
|
||||
// gesture wait time
|
||||
// 0 -> 0ms, 1 -> 2.8ms, 2 -> 5.6ms, 3 -> 8.4ms
|
||||
// 4 -> 14.0ms, 5 -> 22.4 ms, 6 -> 30.8ms, 7 -> 39.2 ms
|
||||
val |= (this->gesture_wait_time_ & 0b111) << 0;
|
||||
APDS9960_WRITE_BYTE(0xA3, val);
|
||||
|
||||
// GOffsetU (0xA4) -> 0x00 (no offset)
|
||||
APDS9960_WRITE_BYTE(0xA4, 0x00);
|
||||
// GOffsetD (0xA5) -> 0x00 (no offset)
|
||||
APDS9960_WRITE_BYTE(0xA5, 0x00);
|
||||
// GOffsetL (0xA7) -> 0x00 (no offset)
|
||||
APDS9960_WRITE_BYTE(0xA7, 0x00);
|
||||
// GOffsetR (0xA9) -> 0x00 (no offset)
|
||||
APDS9960_WRITE_BYTE(0xA9, 0x00);
|
||||
// GPulse (0xA6) -> 0xC9 (32 µs, 10 pulses)
|
||||
APDS9960_WRITE_BYTE(0xA6, 0xC9);
|
||||
|
||||
// GConf 3 (0xAA, gesture config 3) -> 0x00 (all photodiodes active during gesture, all gesture dimensions enabled)
|
||||
// 0x00 -> all dimensions, 0x01 -> up down, 0x02 -> left right
|
||||
APDS9960_WRITE_BYTE(0xAA, 0x00);
|
||||
APDS9960_ERROR_CHECK(this->write_registers(APDS9960_GESTURE_OFFSETS));
|
||||
|
||||
// Enable (0x80) ->
|
||||
val = 0;
|
||||
uint8_t val = 0;
|
||||
val |= (0b1) << 0; // power on
|
||||
val |= (this->is_color_enabled_() & 0b1) << 1;
|
||||
val |= (this->is_proximity_enabled_() & 0b1) << 2;
|
||||
@@ -109,7 +98,7 @@ void APDS9960::setup() {
|
||||
val |= 0b0 << 4; // color interrupt disabled
|
||||
val |= 0b0 << 5; // proximity interrupt disabled
|
||||
val |= (this->is_gesture_enabled_() & 0b1) << 6; // proximity is required for gestures
|
||||
APDS9960_WRITE_BYTE(0x80, val);
|
||||
APDS9960_ERROR_CHECK(this->write_byte(0x80, val));
|
||||
}
|
||||
bool APDS9960::is_color_enabled_() const {
|
||||
#ifdef USE_SENSOR
|
||||
|
||||
@@ -6,19 +6,11 @@ namespace esphome::as3935_i2c {
|
||||
ESPHOME_LOG_TAG(TAG, "as3935_i2c");
|
||||
|
||||
void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_pos) {
|
||||
uint8_t write_reg;
|
||||
if (!this->read_byte(reg, &write_reg)) {
|
||||
this->mark_failed();
|
||||
ESP_LOGW(TAG, "read_byte failed - increase log level for more details!");
|
||||
return;
|
||||
}
|
||||
|
||||
write_reg &= (~mask);
|
||||
write_reg |= (bits << start_pos);
|
||||
|
||||
if (!this->write_byte(reg, write_reg)) {
|
||||
ESP_LOGW(TAG, "write_byte failed - increase log level for more details!");
|
||||
return;
|
||||
const uint8_t value = bits << start_pos;
|
||||
// Bits of value outside mask were ORed in before, so they stay part of the update
|
||||
// Also called at runtime, so a transient bus error only warns, like read_register()
|
||||
if (!this->update_register(reg, mask | value, value)) {
|
||||
ESP_LOGW(TAG, "Failed to update register 0x%02X", reg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -240,17 +240,11 @@ bool AS7341Component::write_register_bit(uint8_t address, bool value, uint8_t bi
|
||||
}
|
||||
|
||||
bool AS7341Component::set_register_bit(uint8_t address, uint8_t bit_position) {
|
||||
uint8_t data;
|
||||
this->read_byte(address, &data);
|
||||
data |= (1 << bit_position);
|
||||
return this->write_byte(address, data);
|
||||
return this->update_register(address, 1 << bit_position, 1 << bit_position);
|
||||
}
|
||||
|
||||
bool AS7341Component::clear_register_bit(uint8_t address, uint8_t bit_position) {
|
||||
uint8_t data;
|
||||
this->read_byte(address, &data);
|
||||
data &= ~(1 << bit_position);
|
||||
return this->write_byte(address, data);
|
||||
return this->update_register(address, 1 << bit_position, 0);
|
||||
}
|
||||
|
||||
} // namespace esphome::as7341
|
||||
@@ -264,12 +264,7 @@ bool AS734XComponent::select_bank_(uint8_t reg) {
|
||||
}
|
||||
|
||||
bool AS734XComponent::update_bits_(uint8_t reg, uint8_t mask, bool value) {
|
||||
uint8_t data = 0;
|
||||
if (!this->select_bank_(reg) || !this->read_byte(reg, &data)) {
|
||||
return false;
|
||||
}
|
||||
data = value ? (data | mask) : (data & ~mask);
|
||||
return this->write_byte(reg, data);
|
||||
return this->select_bank_(reg) && this->update_register(reg, mask, value ? mask : 0);
|
||||
}
|
||||
|
||||
// The exposure is applied before every measurement, so a gain, ATIME or ASTEP changed at runtime
|
||||
|
||||
@@ -108,8 +108,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
full = fv.full_config.get()
|
||||
hub = full.get_config_for_path(full.get_path_for_id(config[CONF_AS734X_ID])[:-1])
|
||||
hub = fv.get_declaring_config(config[CONF_AS734X_ID])
|
||||
if hub[CONF_TYPE] != "AS7341":
|
||||
return config
|
||||
for key in _BAND_KEYS:
|
||||
|
||||
@@ -4,14 +4,13 @@ import esphome.config_validation as cv
|
||||
from esphome.const import DEVICE_CLASS_SWITCH, ICON_WIFI
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_AT581X_ID, AT581XComponent, at581x_ns
|
||||
from .. import CONF_AT581X_ID, AT581XComponent
|
||||
|
||||
DEPENDENCIES = ["at581x"]
|
||||
|
||||
RFSwitch = at581x_ns.class_("RFSwitch", switch.Switch)
|
||||
|
||||
CONFIG_SCHEMA = switch.switch_schema(
|
||||
RFSwitch,
|
||||
switch.ApplySwitch,
|
||||
device_class=DEVICE_CLASS_SWITCH,
|
||||
icon=ICON_WIFI,
|
||||
).extend(
|
||||
@@ -24,7 +23,7 @@ CONFIG_SCHEMA = switch.switch_schema(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
at581x_component = await cg.get_variable(config[CONF_AT581X_ID])
|
||||
s = await switch.new_switch(config)
|
||||
await cg.register_parented(s, config[CONF_AT581X_ID])
|
||||
cg.add(at581x_component.set_rf_power_switch(s))
|
||||
hub = await cg.get_variable(config[CONF_AT581X_ID])
|
||||
await switch.sub_switches(config, parent=hub).create(
|
||||
config, hub.set_rf_power_switch, apply="set_rf_mode(state)", publish="before"
|
||||
)
|
||||
@@ -1,10 +0,0 @@
|
||||
#include "rf_switch.h"
|
||||
|
||||
namespace esphome::at581x {
|
||||
|
||||
void RFSwitch::write_state(bool state) {
|
||||
this->publish_state(state);
|
||||
this->parent_->set_rf_mode(state);
|
||||
}
|
||||
|
||||
} // namespace esphome::at581x
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#include "../at581x.h"
|
||||
|
||||
namespace esphome::at581x {
|
||||
|
||||
class RFSwitch final : public switch_::Switch, public Parented<AT581XComponent> {
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
} // namespace esphome::at581x
|
||||
@@ -58,7 +58,7 @@ optional<ParseResult> ATCMiThermometer::parse_header_(const ble_device_base::Ser
|
||||
return {};
|
||||
}
|
||||
|
||||
auto raw = service_data.data;
|
||||
const auto &raw = service_data.data;
|
||||
if (raw.size() < 13) {
|
||||
ESP_LOGVV(TAG, "parse_header_(): service data too short (%zu).", raw.size());
|
||||
return {};
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#include "bedjet_child.h"
|
||||
#include "bedjet_const.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome::bedjet {
|
||||
|
||||
@@ -40,7 +42,7 @@ void BedJetHub::upgrade_firmware() {
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] MAGIC_UPDATE button failed, status=%d", this->get_name().c_str(), status);
|
||||
ESP_LOGW(TAG, "[%s] MAGIC_UPDATE button failed, status=%d", this->get_name(), status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +66,7 @@ bool BedJetHub::set_fan_index(uint8_t fan_speed_index) {
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] writing fan speed failed, status=%d", this->get_name().c_str(), status);
|
||||
ESP_LOGW(TAG, "[%s] writing fan speed failed, status=%d", this->get_name(), status);
|
||||
}
|
||||
return status == 0;
|
||||
}
|
||||
@@ -82,7 +84,7 @@ bool BedJetHub::set_target_temp(float temp_c) {
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] writing target temp failed, status=%d", this->get_name().c_str(), status);
|
||||
ESP_LOGW(TAG, "[%s] writing target temp failed, status=%d", this->get_name(), status);
|
||||
}
|
||||
return status == 0;
|
||||
}
|
||||
@@ -93,7 +95,7 @@ bool BedJetHub::set_time_remaining(uint8_t hours, uint8_t mins) {
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] writing remaining runtime failed, status=%d", this->get_name().c_str(), status);
|
||||
ESP_LOGW(TAG, "[%s] writing remaining runtime failed, status=%d", this->get_name(), status);
|
||||
}
|
||||
return status == 0;
|
||||
}
|
||||
@@ -103,11 +105,10 @@ bool BedJetHub::send_button(BedjetButton button) {
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] writing button %s failed, status=%d", this->get_name().c_str(),
|
||||
ESP_LOGW(TAG, "[%s] writing button %s failed, status=%d", this->get_name(),
|
||||
LOG_STR_ARG(bedjet_button_to_string(button)), status);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "[%s] writing button %s success", this->get_name().c_str(),
|
||||
LOG_STR_ARG(bedjet_button_to_string(button)));
|
||||
ESP_LOGD(TAG, "[%s] writing button %s success", this->get_name(), LOG_STR_ARG(bedjet_button_to_string(button)));
|
||||
}
|
||||
return status == 0;
|
||||
}
|
||||
@@ -125,9 +126,9 @@ uint16_t BedJetHub::get_time_remaining() {
|
||||
uint8_t BedJetHub::write_bedjet_packet_(BedjetPacket *pkt) {
|
||||
if (!this->is_connected()) {
|
||||
if (!this->parent_->enabled) {
|
||||
ESP_LOGI(TAG, "[%s] Cannot write packet: Not connected, enabled=false", this->get_name().c_str());
|
||||
ESP_LOGI(TAG, "[%s] Cannot write packet: Not connected, enabled=false", this->get_name());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%s] Cannot write packet: Not connected", this->get_name().c_str());
|
||||
ESP_LOGW(TAG, "[%s] Cannot write packet: Not connected", this->get_name());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -144,16 +145,16 @@ uint8_t BedJetHub::set_notify_(const bool enable) {
|
||||
status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
|
||||
this->char_handle_status_);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name(), status);
|
||||
}
|
||||
} else {
|
||||
status = esp_ble_gattc_unregister_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
|
||||
this->char_handle_status_);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_unregister_for_notify failed, status=%d", this->get_name().c_str(), status);
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_unregister_for_notify failed, status=%d", this->get_name(), status);
|
||||
}
|
||||
}
|
||||
ESP_LOGV(TAG, "[%s] set_notify: enable=%d; result=%d", this->get_name().c_str(), enable, status);
|
||||
ESP_LOGV(TAG, "[%s] set_notify: enable=%d; result=%d", this->get_name(), enable, status);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -164,7 +165,7 @@ bool BedJetHub::discover_characteristics_() {
|
||||
if (!this->char_handle_cmd_) {
|
||||
chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_COMMAND_UUID);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] No control service found at device, not a BedJet..?", this->get_name().c_str());
|
||||
ESP_LOGW(TAG, "[%s] No control service found at device, not a BedJet..?", this->get_name());
|
||||
result = false;
|
||||
} else {
|
||||
this->char_handle_cmd_ = chr->handle;
|
||||
@@ -174,7 +175,7 @@ bool BedJetHub::discover_characteristics_() {
|
||||
if (!this->char_handle_status_) {
|
||||
chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_STATUS_UUID);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] No status service found at device, not a BedJet..?", this->get_name().c_str());
|
||||
ESP_LOGW(TAG, "[%s] No status service found at device, not a BedJet..?", this->get_name());
|
||||
result = false;
|
||||
} else {
|
||||
this->char_handle_status_ = chr->handle;
|
||||
@@ -204,14 +205,14 @@ bool BedJetHub::discover_characteristics_() {
|
||||
if (!this->char_handle_name_) {
|
||||
chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_NAME_UUID);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] No name service found at device, not a BedJet..?", this->get_name().c_str());
|
||||
ESP_LOGW(TAG, "[%s] No name service found at device, not a BedJet..?", this->get_name());
|
||||
result = false;
|
||||
} else {
|
||||
this->char_handle_name_ = chr->handle;
|
||||
auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
|
||||
this->char_handle_name_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGI(TAG, "[%s] Unable to read name characteristic: %d", this->get_name().c_str(), status);
|
||||
ESP_LOGI(TAG, "[%s] Unable to read name characteristic: %d", this->get_name(), status);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,7 +223,7 @@ bool BedJetHub::discover_characteristics_() {
|
||||
" - Status char: 0x%x\n"
|
||||
" - config descriptor: 0x%x\n"
|
||||
" - Name char: 0x%x",
|
||||
this->get_name().c_str(), this->char_handle_cmd_, this->char_handle_status_, this->config_descr_status_,
|
||||
this->get_name(), this->char_handle_cmd_, this->char_handle_status_, this->config_descr_status_,
|
||||
this->char_handle_name_);
|
||||
|
||||
return result;
|
||||
@@ -241,7 +242,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
auto result = this->discover_characteristics_();
|
||||
|
||||
if (result) {
|
||||
ESP_LOGD(TAG, "[%s] Services complete: obtained char handles.", this->get_name().c_str());
|
||||
ESP_LOGD(TAG, "[%s] Services complete: obtained char handles.", this->get_name());
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
this->set_notify_(true);
|
||||
|
||||
@@ -253,7 +254,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
|
||||
this->dispatch_state_(true);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%s] Failed discovering service characteristics.", this->get_name().c_str());
|
||||
ESP_LOGW(TAG, "[%s] Failed discovering service characteristics.", this->get_name());
|
||||
this->parent()->set_enabled(false);
|
||||
this->status_set_warning();
|
||||
this->dispatch_state_(false);
|
||||
@@ -265,15 +266,15 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
if (param->write.status == ESP_GATT_INVALID_ATTR_LEN) {
|
||||
// This probably means that our hack for notify_en (8 bit vs 16 bit) didn't work right.
|
||||
// Should we try to fall back to BLEClient's way?
|
||||
ESP_LOGW(TAG, "[%s] Invalid attr length writing descr at handle 0x%04d, status=%d", this->get_name().c_str(),
|
||||
ESP_LOGW(TAG, "[%s] Invalid attr length writing descr at handle 0x%04d, status=%d", this->get_name(),
|
||||
param->write.handle, param->write.status);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%s] Error writing descr at handle 0x%04d, status=%d", this->get_name().c_str(),
|
||||
param->write.handle, param->write.status);
|
||||
ESP_LOGW(TAG, "[%s] Error writing descr at handle 0x%04d, status=%d", this->get_name(), param->write.handle,
|
||||
param->write.status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, "[%s] Write to handle 0x%04x status=%d", this->get_name().c_str(), param->write.handle,
|
||||
ESP_LOGD(TAG, "[%s] Write to handle 0x%04x status=%d", this->get_name(), param->write.handle,
|
||||
param->write.status);
|
||||
break;
|
||||
}
|
||||
@@ -306,9 +307,11 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
} else if (param->read.handle == this->char_handle_name_) {
|
||||
// The data should represent the name.
|
||||
if (param->read.status == ESP_GATT_OK && param->read.value_len > 0) {
|
||||
std::string bedjet_name(reinterpret_cast<char const *>(param->read.value), param->read.value_len);
|
||||
ESP_LOGV(TAG, "[%s] Got BedJet name: '%s'", this->get_name().c_str(), bedjet_name.c_str());
|
||||
this->set_name_(bedjet_name);
|
||||
ESP_LOGV(TAG, "[%s] Got BedJet name: '%.*s'", this->get_name(), static_cast<int>(param->read.value_len),
|
||||
reinterpret_cast<const char *>(param->read.value));
|
||||
const size_t len = std::min<size_t>(param->read.value_len, sizeof(this->name_) - 1);
|
||||
memcpy(this->name_, param->read.value, len);
|
||||
this->name_[len] = '\0';
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -322,8 +325,8 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
// doesn't break anything.
|
||||
|
||||
if (param->reg_for_notify.handle != this->char_handle_status_) {
|
||||
ESP_LOGW(TAG, "[%s] Register for notify on unexpected handle 0x%04x, expecting 0x%04x",
|
||||
this->get_name().c_str(), param->reg_for_notify.handle, this->char_handle_status_);
|
||||
ESP_LOGW(TAG, "[%s] Register for notify on unexpected handle 0x%04x, expecting 0x%04x", this->get_name(),
|
||||
param->reg_for_notify.handle, this->char_handle_status_);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -335,8 +338,8 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
||||
// This event is not handled by the parent BLEClient, so we need to do this either way.
|
||||
if (param->unreg_for_notify.handle != this->char_handle_status_) {
|
||||
ESP_LOGW(TAG, "[%s] Unregister for notify on unexpected handle 0x%04x, expecting 0x%04x",
|
||||
this->get_name().c_str(), param->unreg_for_notify.handle, this->char_handle_status_);
|
||||
ESP_LOGW(TAG, "[%s] Unregister for notify on unexpected handle 0x%04x, expecting 0x%04x", this->get_name(),
|
||||
param->unreg_for_notify.handle, this->char_handle_status_);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -350,13 +353,13 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
break;
|
||||
|
||||
if (param->notify.conn_id != this->parent_->get_conn_id()) {
|
||||
ESP_LOGW(TAG, "[%s] Received notify event for unexpected parent conn: expect %x, got %x",
|
||||
this->get_name().c_str(), this->parent_->get_conn_id(), param->notify.conn_id);
|
||||
ESP_LOGW(TAG, "[%s] Received notify event for unexpected parent conn: expect %x, got %x", this->get_name(),
|
||||
this->parent_->get_conn_id(), param->notify.conn_id);
|
||||
// FIXME: bug in BLEClient holding wrong conn_id.
|
||||
}
|
||||
|
||||
if (param->notify.handle != this->char_handle_status_) {
|
||||
ESP_LOGW(TAG, "[%s] Unexpected notify handle, wanted %04X, got %04X", this->get_name().c_str(),
|
||||
ESP_LOGW(TAG, "[%s] Unexpected notify handle, wanted %04X, got %04X", this->get_name(),
|
||||
this->char_handle_status_, param->notify.handle);
|
||||
break;
|
||||
}
|
||||
@@ -372,13 +375,13 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
if (!this->force_refresh_ && this->codec_->compare(param->notify.value, param->notify.value_len)) {
|
||||
// If the packet is meaningfully different, trigger children as well
|
||||
this->force_refresh_ = true;
|
||||
ESP_LOGV(TAG, "[%s] Incoming packet indicates a significant change.", this->get_name().c_str());
|
||||
ESP_LOGV(TAG, "[%s] Incoming packet indicates a significant change.", this->get_name());
|
||||
}
|
||||
|
||||
if (this->last_notify_ == 0 || delta > MIN_NOTIFY_THROTTLE || this->force_refresh_) {
|
||||
// Set reentrant flag to prevent processing multiple packets.
|
||||
this->processing_ = true;
|
||||
ESP_LOGVV(TAG, "[%s] Decoding packet: last=%" PRId32 ", delta=%" PRId32 ", force=%s", this->get_name().c_str(),
|
||||
ESP_LOGVV(TAG, "[%s] Decoding packet: last=%" PRId32 ", delta=%" PRId32 ", force=%s", this->get_name(),
|
||||
this->last_notify_, delta, this->force_refresh_ ? "y" : "n");
|
||||
bool needs_extra = this->codec_->decode_notify(param->notify.value, param->notify.value_len);
|
||||
|
||||
@@ -388,7 +391,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
|
||||
this->char_handle_status_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGI(TAG, "[%s] Unable to read extended status packet", this->get_name().c_str());
|
||||
ESP_LOGI(TAG, "[%s] Unable to read extended status packet", this->get_name());
|
||||
}
|
||||
} else {
|
||||
this->status_packet_ready_();
|
||||
@@ -397,7 +400,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGVV(TAG, "[%s] gattc unhandled event: enum=%d", this->get_name().c_str(), event);
|
||||
ESP_LOGVV(TAG, "[%s] gattc unhandled event: enum=%d", this->get_name(), event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -436,7 +439,7 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
|
||||
return status;
|
||||
}
|
||||
ESP_LOGD(TAG, "[%s] wrote notify=%s to status config 0x%04x, for conn %d", this->get_name().c_str(),
|
||||
ESP_LOGD(TAG, "[%s] wrote notify=%s to status config 0x%04x, for conn %d", this->get_name(),
|
||||
enable ? "true" : "false", handle, this->parent_->get_conn_id());
|
||||
return ESP_GATT_OK;
|
||||
}
|
||||
@@ -468,7 +471,7 @@ void BedJetHub::setup_time_() {
|
||||
|
||||
void BedJetHub::set_clock(uint8_t hour, uint8_t minute) {
|
||||
if (!this->is_connected()) {
|
||||
ESP_LOGV(TAG, "[%s] Not connected, cannot send time.", this->get_name().c_str());
|
||||
ESP_LOGV(TAG, "[%s] Not connected, cannot send time.", this->get_name());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -477,7 +480,7 @@ void BedJetHub::set_clock(uint8_t hour, uint8_t minute) {
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Failed setting BedJet clock: %d", status);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "[%s] BedJet clock set to: %d:%02d", this->get_name().c_str(), hour, minute);
|
||||
ESP_LOGD(TAG, "[%s] BedJet clock set to: %d:%02d", this->get_name(), hour, minute);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,7 +498,7 @@ void BedJetHub::dump_config() {
|
||||
"BedJet Hub '%s'\n"
|
||||
" ble_client.app_id: %d\n"
|
||||
" ble_client.conn_id: %d",
|
||||
this->get_name().c_str(), this->parent()->app_id, this->parent()->get_conn_id());
|
||||
this->get_name(), this->parent()->app_id, this->parent()->get_conn_id());
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size());
|
||||
for (auto *child : this->children_) {
|
||||
@@ -513,10 +516,9 @@ void BedJetHub::dispatch_status_() {
|
||||
auto *status = this->codec_->get_status_packet();
|
||||
|
||||
if (!this->is_connected()) {
|
||||
ESP_LOGD(TAG, "[%s] Not connected, will not send status.", this->get_name().c_str());
|
||||
ESP_LOGD(TAG, "[%s] Not connected, will not send status.", this->get_name());
|
||||
} else if (status != nullptr) {
|
||||
ESP_LOGD(TAG, "[%s] Notifying %d children of latest status @%p.", this->get_name().c_str(), this->children_.size(),
|
||||
status);
|
||||
ESP_LOGD(TAG, "[%s] Notifying %d children of latest status @%p.", this->get_name(), this->children_.size(), status);
|
||||
for (auto *child : this->children_) {
|
||||
child->on_status(status);
|
||||
}
|
||||
@@ -530,13 +532,13 @@ void BedJetHub::dispatch_status_() {
|
||||
// We can try to unregister for notifications now, and then re-register, hoping to clear it up...
|
||||
// But how do we know for sure which state we're in, and how do we actually clear out the buggy state?
|
||||
|
||||
ESP_LOGI(TAG, "[%s] Still waiting for first GATT notify event.", this->get_name().c_str());
|
||||
ESP_LOGI(TAG, "[%s] Still waiting for first GATT notify event.", this->get_name());
|
||||
} else if (diff > NOTIFY_WARN_THRESHOLD) {
|
||||
ESP_LOGW(TAG, "[%s] Last GATT notify was %" PRId32 " seconds ago.", this->get_name().c_str(), diff / 1000);
|
||||
ESP_LOGW(TAG, "[%s] Last GATT notify was %" PRId32 " seconds ago.", this->get_name(), diff / 1000);
|
||||
}
|
||||
|
||||
if (this->timeout_ > 0 && diff > this->timeout_ && this->parent()->enabled) {
|
||||
ESP_LOGW(TAG, "[%s] Timed out after %" PRId32 " sec. Retrying", this->get_name().c_str(), this->timeout_);
|
||||
ESP_LOGW(TAG, "[%s] Timed out after %" PRId32 " sec. Retrying", this->get_name(), this->timeout_);
|
||||
// set_enabled(false) will only close the connection if state != IDLE.
|
||||
this->parent()->set_state(espbt::ClientState::CONNECTING);
|
||||
this->parent()->set_enabled(false);
|
||||
|
||||
@@ -119,13 +119,7 @@ class BedJetHub final : public esphome::ble_client::BLEClientNode, public Pollin
|
||||
float get_setup_priority() const override { return setup_priority::BLUETOOTH; }
|
||||
|
||||
/** @return The BedJet's configured name, or the MAC address if not discovered yet. */
|
||||
std::string get_name() {
|
||||
if (this->name_.empty()) {
|
||||
return this->parent_->address_str();
|
||||
} else {
|
||||
return this->name_;
|
||||
}
|
||||
}
|
||||
const char *get_name() const { return this->name_[0] == '\0' ? this->parent_->address_str() : this->name_; }
|
||||
|
||||
/* BLEClient overrides */
|
||||
|
||||
@@ -151,9 +145,8 @@ class BedJetHub final : public esphome::ble_client::BLEClientNode, public Pollin
|
||||
uint8_t set_notify_(bool enable);
|
||||
/** Send the `BedjetPacket` to the device. */
|
||||
uint8_t write_bedjet_packet_(BedjetPacket *pkt);
|
||||
void set_name_(const std::string &name) { this->name_ = name; }
|
||||
|
||||
std::string name_;
|
||||
// Only used in log lines; longer names are truncated
|
||||
char name_[24]{};
|
||||
|
||||
uint32_t last_notify_ = 0;
|
||||
inline void status_packet_ready_();
|
||||
|
||||
@@ -62,6 +62,13 @@ struct SpiData {
|
||||
|
||||
static SpiData *spi_data = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
// BDK 3.0.78 makes REG_WRITE a statement expression that yields the volatile assignment,
|
||||
// a deprecated volatile read under clang
|
||||
static inline void reg_write(uint32_t addr, uint32_t value) {
|
||||
// NOLINTNEXTLINE(performance-no-int-to-ptr) - MMIO register at a fixed address
|
||||
*reinterpret_cast<volatile uint32_t *>(addr) = value;
|
||||
}
|
||||
|
||||
static void set_spi_ctrl_register(uint32_t bit, bool val) {
|
||||
uint32_t value = REG_READ(SPI_CTRL);
|
||||
if (val == 0) {
|
||||
@@ -69,7 +76,7 @@ static void set_spi_ctrl_register(uint32_t bit, bool val) {
|
||||
} else if (val == 1) {
|
||||
value |= bit;
|
||||
}
|
||||
REG_WRITE(SPI_CTRL, value);
|
||||
reg_write(SPI_CTRL, value);
|
||||
}
|
||||
|
||||
#ifdef BEKEN_SPI_IS_N_LIKE
|
||||
@@ -80,7 +87,7 @@ static void set_spi_config_register(uint32_t bit, bool val) {
|
||||
} else if (val == 1) {
|
||||
value |= bit;
|
||||
}
|
||||
REG_WRITE(SPI_CONFIG, value);
|
||||
reg_write(SPI_CONFIG, value);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -136,7 +143,7 @@ static void spi_set_clock(uint32_t max_hz) {
|
||||
param = REG_READ(SPI_CTRL);
|
||||
param &= ~(SPI_CKR_MASK << SPI_CKR_POSI);
|
||||
param |= (div << SPI_CKR_POSI);
|
||||
REG_WRITE(SPI_CTRL, param);
|
||||
reg_write(SPI_CTRL, param);
|
||||
#ifdef BEKEN_SPI_IS_N_LIKE
|
||||
const int divisor = div;
|
||||
#else
|
||||
@@ -229,7 +236,7 @@ void BekenSPILEDStripLightOutput::setup() {
|
||||
value = REG_READ(SPI_CTRL);
|
||||
value &= ~CTRL_NSSMD_3;
|
||||
value |= (1 << 17);
|
||||
REG_WRITE(SPI_CTRL, value);
|
||||
reg_write(SPI_CTRL, value);
|
||||
|
||||
#ifdef BEKEN_SPI_IS_N_LIKE
|
||||
value = GFUNC_MODE_SPI_DMA;
|
||||
@@ -279,7 +286,7 @@ void BekenSPILEDStripLightOutput::setup() {
|
||||
value = REG_READ(SPI_CONFIG);
|
||||
value &= ~(0xFFF << 8);
|
||||
value |= ((dma_buffer_size & 0xFFF) << 8);
|
||||
REG_WRITE(SPI_CONFIG, value);
|
||||
reg_write(SPI_CONFIG, value);
|
||||
#else
|
||||
// T-like variants: No transfer-length register. The SPI controller shifts out
|
||||
// everything the DMA writes into SPI_DAT.
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_BH1745_ID, BH1745Component, bh1745_ns
|
||||
from .. import CONF_BH1745_ID, BH1745Component
|
||||
|
||||
DEPENDENCIES = ["bh1745"]
|
||||
|
||||
BH1745InterruptPinOutput = bh1745_ns.class_(
|
||||
"BH1745InterruptPinOutput", output.BinaryOutput
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = output.BINARY_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BH1745InterruptPinOutput),
|
||||
cv.GenerateID(): cv.declare_id(output.ApplyBinaryOutput),
|
||||
cv.GenerateID(CONF_BH1745_ID): cv.use_id(BH1745Component),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await output.register_output(var, config)
|
||||
hub = await cg.get_variable(config[CONF_BH1745_ID])
|
||||
await cg.register_parented(var, hub)
|
||||
await output.sub_binary_outputs(config, parent=hub).create(
|
||||
config, None, apply="set_interrupt_state(state)"
|
||||
)
|
||||
cg.add(hub.enable_interrupt_output())
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "../bh1745.h"
|
||||
|
||||
namespace esphome::bh1745 {
|
||||
|
||||
/// Drives the BH1745 INT pin, which switches the LEDs on breakout boards such as the Pimoroni one.
|
||||
class BH1745InterruptPinOutput : public output::BinaryOutput, public Parented<BH1745Component> {
|
||||
protected:
|
||||
void write_state(bool state) override { this->parent_->set_interrupt_state(state); }
|
||||
};
|
||||
|
||||
} // namespace esphome::bh1745
|
||||
@@ -63,6 +63,7 @@ from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority, entity
|
||||
from esphome.core.entity_helpers import (
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
lazy_load_setup,
|
||||
lazy_load_validator,
|
||||
mqtt_component_class,
|
||||
queue_entity_register,
|
||||
@@ -621,10 +622,7 @@ async def setup_binary_sensor_core_(var, config):
|
||||
|
||||
await entity_helpers.setup_entity_integrations(var, config)
|
||||
|
||||
if "zigbee" in CORE.loaded_integrations:
|
||||
from esphome.components import zigbee
|
||||
|
||||
await zigbee.setup_binary_sensor(var, config)
|
||||
await lazy_load_setup("zigbee", "setup_binary_sensor", var, config)
|
||||
|
||||
|
||||
async def register_binary_sensor(var, config):
|
||||
|
||||
@@ -29,6 +29,7 @@ from esphome.components.libretiny.const import (
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.stacktrace import process_crash_handler_line as process_stacktrace # noqa: F401
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .boards import BK72XX_BOARD_PINS, BK72XX_BOARDS
|
||||
|
||||
@@ -21,9 +21,10 @@ public ble_api.h.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import libretiny, wifi
|
||||
from esphome.components import libretiny
|
||||
from esphome.components.libretiny.const import (
|
||||
FAMILY_BK7231N,
|
||||
FAMILY_BK7231Q,
|
||||
@@ -32,14 +33,30 @@ from esphome.components.libretiny.const import (
|
||||
FAMILY_BK7251,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.const import (
|
||||
CONF_ENABLE_ON_BOOT,
|
||||
CONF_ESPHOME,
|
||||
CONF_ID,
|
||||
CONF_PLATFORMIO_OPTIONS,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DEPENDENCIES = ["bk72xx"]
|
||||
CODEOWNERS = ["@Bl00d-B0b"]
|
||||
DOMAIN = "bk72xx_ble"
|
||||
|
||||
BEKEN_BDK_VERSION_OPTION = "custom_versions.beken-bdk"
|
||||
# Older BDKs lay the BLE stack out differently and can crash at boot or erase the
|
||||
# bootloader with BLE compiled in; the libretiny platform builds with this one
|
||||
MIN_BDK_VERSION = (3, 0, 78)
|
||||
# Older LibreTiny releases arm the Beken MCU sleep with wifi power save, which halts
|
||||
# the device once the station stops while BLE runs (libretiny-eu/libretiny#414)
|
||||
MIN_LIBRETINY_VERSION = (1, 14, 0)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
bk72xx_ble_ns = cg.esphome_ns.namespace("bk72xx_ble")
|
||||
@@ -80,20 +97,45 @@ def _unsupported_family_message(family: str) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def check_bdk_version(value: str | None) -> None:
|
||||
"""Reject a beken-bdk override older than the release the BLE stack needs."""
|
||||
if value is None:
|
||||
return
|
||||
# LibreTiny appends the value to the package URL as a git ref and only builds
|
||||
# 3.0.x releases; the 2021.06.07 tag is the pre 3.0 SDK
|
||||
match = re.fullmatch(r"v?(\d+)\.(\d+)\.(\d+)", str(value))
|
||||
if match is not None:
|
||||
version = tuple(int(g) for g in match.groups())
|
||||
if version[:2] == MIN_BDK_VERSION[:2] and version >= MIN_BDK_VERSION:
|
||||
return
|
||||
minimum = ".".join(str(part) for part in MIN_BDK_VERSION)
|
||||
raise cv.Invalid(
|
||||
f"bk72xx_ble needs beken-bdk {minimum} or a newer 3.0.x release, not "
|
||||
f"'{value}': with BLE compiled in, an older BDK can crash at boot or erase "
|
||||
"the bootloader"
|
||||
)
|
||||
|
||||
|
||||
def check_libretiny_version(version: cv.Version) -> None:
|
||||
"""Reject a LibreTiny release older than the one whose WiFi sleep is safe with BLE."""
|
||||
if (version.major, version.minor, version.patch) >= MIN_LIBRETINY_VERSION:
|
||||
return
|
||||
minimum = ".".join(str(part) for part in MIN_LIBRETINY_VERSION)
|
||||
raise cv.Invalid(
|
||||
f"bk72xx_ble needs LibreTiny {minimum} or newer, not {version}: older releases "
|
||||
"arm the Beken MCU sleep with wifi power save and the device halts once the "
|
||||
"station stops while BLE runs"
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
# Warn only: a hard error here would break the validate-only CI fixtures,
|
||||
# which run on a BLE 4.2 board. The hard error is raised at codegen.
|
||||
if msg := _unsupported_family_message(libretiny.get_libretiny_family()):
|
||||
_LOGGER.warning("%s (this configuration cannot compile)", msg)
|
||||
# Any wifi power_save_mode other than NONE also arms the Beken SDK's MCU
|
||||
# sleep. With the BLE controller running, that sleep never wakes up once the
|
||||
# station is stopped (adapter restart after failed roams, wifi.disable): the
|
||||
# device is dead until a power cycle (esphome#18592). Keep power save off
|
||||
# until LibreTiny ships the SDK-side fix (libretiny-eu/libretiny#414).
|
||||
wifi.force_power_save_off(
|
||||
"with BLE running, the Beken SDK's MCU sleep halts the device once the "
|
||||
"station is stopped (https://github.com/esphome/esphome/issues/18592)"
|
||||
)
|
||||
options = fv.full_config.get()[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS) or {}
|
||||
check_bdk_version(options.get(BEKEN_BDK_VERSION_OPTION))
|
||||
check_libretiny_version(CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION])
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
@@ -116,20 +158,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
# so this =1 is the single CFG_SUPPORT_BLE define emitted.
|
||||
cg.add_platformio_option("custom_options.sys_config#h", ["CFG_SUPPORT_BLE=1"])
|
||||
|
||||
# Pin the Beken BDK release the BLE 5.x stack is validated against. The
|
||||
# bundled 3.0.33 has an older BLE header/library layout — and with
|
||||
# CFG_SUPPORT_BLE=1 the SDK runs its BLE init unconditionally during boot
|
||||
# (the reason the libretiny platform sets =0 when BLE is unused), so a
|
||||
# mismatched BDK can crash the device before WiFi comes up regardless of
|
||||
# enable_on_boot. Pinning here makes a plain config build against the
|
||||
# validated BDK without any manual platformio_options.
|
||||
_LOGGER.warning(
|
||||
"bk72xx_ble builds with beken-bdk 3.0.78 instead of the platform's bundled "
|
||||
"default: the default's older BLE layout can crash the device at boot when "
|
||||
"BLE is compiled in"
|
||||
)
|
||||
cg.add_platformio_option("custom_versions.beken-bdk", "3.0.78")
|
||||
|
||||
# The BDK exposes the controller's BLE address as `common_default_bdaddr` on
|
||||
# BK7231N, but NOT on BK7238 (its BLE stack has no such symbol; the address is
|
||||
# derived from the WiFi MAC instead — the BDK's own fallback). Tell the C++
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "bl0939.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
|
||||
@@ -20,7 +21,7 @@ static const uint8_t BL0939_REG_SOFT_RESET = 0x19;
|
||||
static const uint8_t BL0939_REG_USR_WRPROT = 0x1A;
|
||||
static const uint8_t BL0939_REG_TPS_CTRL = 0x1B;
|
||||
|
||||
const uint8_t BL0939_INIT[6][6] = {
|
||||
static constexpr uint8_t BL0939_INIT[6][6] PROGMEM = {
|
||||
// Reset to default
|
||||
{BL0939_WRITE_COMMAND, BL0939_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x33},
|
||||
// Enable User Operation Write
|
||||
@@ -66,8 +67,10 @@ void BL0939::update() {
|
||||
}
|
||||
|
||||
void BL0939::setup() {
|
||||
for (auto *i : BL0939_INIT) {
|
||||
this->write_array(i, 6);
|
||||
uint8_t cmd[sizeof(BL0939_INIT[0])];
|
||||
for (const auto *i : BL0939_INIT) {
|
||||
progmem_memcpy(cmd, i, sizeof(cmd));
|
||||
this->write_array(cmd, sizeof(cmd));
|
||||
delay(1);
|
||||
}
|
||||
this->flush();
|
||||
|
||||
@@ -139,49 +139,24 @@ bool BME680Component::setup_registers_() {
|
||||
this->calibration_.ambient_temperature = 25; // prime ambient temperature
|
||||
|
||||
// Config register
|
||||
uint8_t config_register;
|
||||
if (!this->read_byte(BME680_REGISTER_CONFIG, &config_register)) {
|
||||
return false;
|
||||
}
|
||||
config_register &= ~0b00011100;
|
||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||
if (!this->write_byte(BME680_REGISTER_CONFIG, config_register)) {
|
||||
if (!this->update_register(BME680_REGISTER_CONFIG, 0b00011100, (this->iir_filter_ & 0b111) << 2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Humidity control register
|
||||
uint8_t hum_control;
|
||||
if (!this->read_byte(BME680_REGISTER_CONTROL_HUMIDITY, &hum_control)) {
|
||||
return false;
|
||||
}
|
||||
hum_control &= ~0b00000111;
|
||||
hum_control |= this->humidity_oversampling_ & 0b111;
|
||||
if (!this->write_byte(BME680_REGISTER_CONTROL_HUMIDITY, hum_control)) {
|
||||
if (!this->update_register(BME680_REGISTER_CONTROL_HUMIDITY, 0b00000111, this->humidity_oversampling_ & 0b111)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Gas 1 control register
|
||||
uint8_t gas1_control;
|
||||
if (!this->read_byte(BME680_REGISTER_CONTROL_GAS1, &gas1_control)) {
|
||||
return false;
|
||||
}
|
||||
gas1_control &= ~0b00011111;
|
||||
gas1_control |= 1 << 4;
|
||||
gas1_control |= 0; // profile 0
|
||||
if (!this->write_byte(BME680_REGISTER_CONTROL_GAS1, gas1_control)) {
|
||||
// Gas 1 control register: run gas, heater profile 0
|
||||
if (!this->update_register(BME680_REGISTER_CONTROL_GAS1, 0b00011111, 1 << 4)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool heat_off = this->heater_temperature_ == 0 || this->heater_duration_ == 0;
|
||||
|
||||
// Gas 0 control register
|
||||
uint8_t gas0_control;
|
||||
if (!this->read_byte(BME680_REGISTER_CONTROL_GAS0, &gas0_control)) {
|
||||
return false;
|
||||
}
|
||||
gas0_control &= ~0b00001000;
|
||||
gas0_control |= heat_off << 3;
|
||||
if (!this->write_byte(BME680_REGISTER_CONTROL_GAS0, gas0_control)) {
|
||||
if (!this->update_register(BME680_REGISTER_CONTROL_GAS0, 0b00001000, heat_off << 3)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,9 +66,7 @@ CONFIG_SCHEMA = cv.typed_schema(
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
if config[CONF_TYPE] not in _MAGNETIC_FIELDS:
|
||||
return
|
||||
full_config = fv.full_config.get()
|
||||
bmi270_path = full_config.get_path_for_id(config[CONF_BMI270_ID])[:-1]
|
||||
bmi270_config = full_config.get_config_for_path(bmi270_path)
|
||||
bmi270_config = fv.get_declaring_config(config[CONF_BMI270_ID])
|
||||
if bmi270_config.get(CONF_AUX_DEVICE) != "BMM150":
|
||||
raise cv.Invalid(
|
||||
"This sensor requires the parent bmi270's 'aux_device' to be set to 'BMM150'",
|
||||
|
||||
@@ -80,7 +80,8 @@ void BP1658CJ::loop() {
|
||||
this->update_ = false;
|
||||
}
|
||||
|
||||
void BP1658CJ::set_channel_value_(uint8_t channel, uint16_t value) {
|
||||
void BP1658CJ::write_channel(uint8_t channel, float state) {
|
||||
auto value = static_cast<uint16_t>(state * 0x3FF);
|
||||
if (this->pwm_amounts_[channel] != value) {
|
||||
this->update_ = true;
|
||||
this->update_channel_ = channel;
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::bp1658cj {
|
||||
|
||||
class BP1658CJ final : public Component {
|
||||
public:
|
||||
class Channel;
|
||||
|
||||
void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; }
|
||||
void set_clock_pin(GPIOPin *clock_pin) { clock_pin_ = clock_pin; }
|
||||
void set_max_power_color_channels(uint8_t max_power_color_channels) {
|
||||
@@ -29,23 +26,9 @@ class BP1658CJ final : public Component {
|
||||
/// Send new values if they were updated.
|
||||
void loop() override;
|
||||
|
||||
class Channel final : public output::FloatOutput {
|
||||
public:
|
||||
void set_parent(BP1658CJ *parent) { parent_ = parent; }
|
||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
||||
|
||||
protected:
|
||||
void write_state(float state) override {
|
||||
auto amount = static_cast<uint16_t>(state * 0x3FF);
|
||||
this->parent_->set_channel_value_(this->channel_, amount);
|
||||
}
|
||||
|
||||
BP1658CJ *parent_;
|
||||
uint8_t channel_;
|
||||
};
|
||||
void write_channel(uint8_t channel, float state);
|
||||
|
||||
protected:
|
||||
void set_channel_value_(uint8_t channel, uint16_t value);
|
||||
void write_bit_(bool value);
|
||||
void write_byte_(uint8_t data);
|
||||
void write_buffer_(uint8_t *buffer, uint8_t size);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID
|
||||
@@ -8,16 +7,15 @@ from . import BP1658CJ
|
||||
|
||||
DEPENDENCIES = ["bp1658cj"]
|
||||
|
||||
Channel = BP1658CJ.class_("Channel", output.FloatOutput)
|
||||
|
||||
CONF_BP1658CJ_ID = "bp1658cj_id"
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_BP1658CJ_ID): cv.use_id(BP1658CJ),
|
||||
cv.Required(CONF_ID): cv.declare_id(Channel),
|
||||
cv.Required(CONF_ID): cv.declare_id(output.ApplyFloatOutput),
|
||||
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=4),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = output.final_validate_unique_channel(
|
||||
@@ -26,8 +24,6 @@ FINAL_VALIDATE_SCHEMA = output.final_validate_unique_channel(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await output.register_output(var, config)
|
||||
|
||||
await cg.register_parented(var, config[CONF_BP1658CJ_ID])
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
await output.sub_float_outputs(config, parent=config[CONF_BP1658CJ_ID]).create(
|
||||
config, None, config[CONF_CHANNEL], apply="write_channel(index, state)"
|
||||
)
|
||||
@@ -7,6 +7,7 @@ from esphome.components import i2c
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_UPDATE_INTERVAL, CONF_WATCHDOG
|
||||
from esphome.core import CORE, ID, EsphomeError, Lambda, TimePeriod
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType, TemplateArgsType
|
||||
|
||||
CODEOWNERS = ["@Rapsssito"]
|
||||
@@ -359,8 +360,7 @@ async def bq25186_configure_to_code(
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
|
||||
# Options the action leaves out fall back to the component's configuration
|
||||
path = CORE.config.get_path_for_id(config[CONF_ID])[:-1]
|
||||
parent_config = CORE.config.get_config_for_path(path)
|
||||
parent_config = fv.get_declaring_config(config[CONF_ID], CORE.config)
|
||||
merged = {**parent_config, **config}
|
||||
try:
|
||||
_check_watchdog(merged)
|
||||
|
||||
@@ -28,13 +28,10 @@ bool BQ25186Component::write_register_(uint8_t reg, uint8_t value) {
|
||||
}
|
||||
|
||||
bool BQ25186Component::update_register_(uint8_t reg, uint8_t mask, uint8_t value) {
|
||||
uint8_t reg_value;
|
||||
if (!this->read_byte(reg, ®_value)) {
|
||||
ESP_LOGW(TAG, "Failed to read register 0x%02X", reg);
|
||||
return false;
|
||||
}
|
||||
reg_value = (reg_value & ~mask) | (value & mask);
|
||||
return this->write_register_(reg, reg_value);
|
||||
if (this->update_register(reg, mask, value))
|
||||
return true;
|
||||
ESP_LOGW(TAG, "Failed to update register 0x%02X", reg);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t BQ25186Component::expected_register_(size_t index) const {
|
||||
|
||||
@@ -71,12 +71,12 @@ class BQ25186Component : public PollingComponent, public i2c::I2CDevice {
|
||||
|
||||
protected:
|
||||
bool write_register_(uint8_t reg, uint8_t value);
|
||||
bool update_register_(uint8_t reg, uint8_t mask, uint8_t value);
|
||||
uint8_t expected_register_(size_t index) const;
|
||||
bool config_matches_(const BQ25186StatusRegisters ®s) const;
|
||||
#ifdef USE_BQ25186_PG_GPO
|
||||
void check_pg_gpo_(const BQ25186StatusRegisters &status, bool &pending);
|
||||
#endif
|
||||
bool update_register_(uint8_t reg, uint8_t mask, uint8_t value);
|
||||
|
||||
#ifdef BQ25186_BINARY_SENSOR_COUNT
|
||||
StaticVector<BQ25186BinarySensor *, BQ25186_BINARY_SENSOR_COUNT> binary_sensors_;
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_BUTTON
|
||||
|
||||
#include "bq25186.h"
|
||||
#include "esphome/components/button/button.h"
|
||||
|
||||
namespace esphome::bq25186 {
|
||||
|
||||
/// Writes one SHIP_RST field when pressed
|
||||
class BQ25186Button final : public button::Button, public Parented<BQ25186Component> {
|
||||
public:
|
||||
BQ25186Button(uint8_t mask, uint8_t value) : mask_(mask), value_(value) {}
|
||||
|
||||
protected:
|
||||
void press_action() override { this->parent_->write_ship_rst(this->mask_, this->value_); }
|
||||
|
||||
const uint8_t mask_;
|
||||
const uint8_t value_;
|
||||
};
|
||||
|
||||
} // namespace esphome::bq25186
|
||||
|
||||
#endif // USE_BUTTON
|
||||
@@ -14,11 +14,7 @@ void BQ25186TextSensor::on_status(const BQ25186StatusRegisters &status) {
|
||||
if (value == this->last_)
|
||||
return;
|
||||
this->last_ = value;
|
||||
const ProgmemStr name = StatusStrings::get_progmem_str(this->first_ + value, StatusStrings::LAST_INDEX);
|
||||
char text[32];
|
||||
ESPHOME_strncpy_P(text, reinterpret_cast<ESPHOME_PGM_P>(name), sizeof(text));
|
||||
text[sizeof(text) - 1] = '\0';
|
||||
this->publish_state(text);
|
||||
this->publish_state(StatusStrings::get_progmem_str(this->first_ + value, StatusStrings::LAST_INDEX));
|
||||
}
|
||||
|
||||
} // namespace esphome::bq25186
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import button
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -10,11 +9,10 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import BQ25186_CHILD_SCHEMA, CONF_BQ25186_ID, bq25186_ns
|
||||
from . import BQ25186_CHILD_SCHEMA, CONF_BQ25186_ID
|
||||
|
||||
DEPENDENCIES = ["bq25186"]
|
||||
|
||||
BQ25186Button = bq25186_ns.class_("BQ25186Button", button.Button)
|
||||
|
||||
# SHIP_RST: bit 7 is the software reset, bits 6:5 the shutdown, ship and hardware reset codes
|
||||
SOFTWARE_RESET_MASK = 0x80
|
||||
@@ -33,7 +31,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
BQ25186_CHILD_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(key): button.button_schema(
|
||||
BQ25186Button,
|
||||
button.ApplyButton,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=icon,
|
||||
device_class=device_class,
|
||||
@@ -46,7 +44,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
buttons = button.sub_buttons(config, parent=config[CONF_BQ25186_ID])
|
||||
for key, mask, value, _, _ in _BUTTONS:
|
||||
if (conf := config.get(key)) is not None:
|
||||
var = await button.new_button(conf, mask, value)
|
||||
await cg.register_parented(var, config[CONF_BQ25186_ID])
|
||||
await buttons(key, None, apply=f"write_ship_rst({mask:#04x}, {value:#04x})")
|
||||
@@ -1,7 +1,6 @@
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
@@ -10,7 +9,6 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MQTT_ID,
|
||||
CONF_ON_PRESS,
|
||||
CONF_WEB_SERVER,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_IDENTIFY,
|
||||
DEVICE_CLASS_RESTART,
|
||||
@@ -21,11 +19,12 @@ from esphome.const import (
|
||||
ICON_RESTART,
|
||||
ICON_RESTART_ALERT,
|
||||
)
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority, entity_helpers
|
||||
from esphome.core.entity_helpers import (
|
||||
ApplyEntity,
|
||||
SubEntities,
|
||||
entity_duplicate_validator,
|
||||
mqtt_component_class,
|
||||
queue_entity_register,
|
||||
setup_device_class,
|
||||
setup_entity,
|
||||
@@ -53,11 +52,13 @@ validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||
|
||||
|
||||
_BUTTON_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
cv.ENTITY_BASE_SCHEMA.extend(entity_helpers.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt_component_class("MQTTButtonComponent")
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_DEVICE_CLASS, visibility=cv.Visibility.ADVANCED
|
||||
): validate_device_class,
|
||||
@@ -135,12 +136,7 @@ async def setup_button_core_(var: MockObj, config: ConfigType) -> None:
|
||||
|
||||
setup_device_class(config)
|
||||
|
||||
if mqtt_id := config.get(CONF_MQTT_ID):
|
||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||
await web_server.add_entity_config(var, web_server_config)
|
||||
await entity_helpers.setup_entity_integrations(var, config)
|
||||
|
||||
|
||||
async def register_button(var: MockObj, config: ConfigType) -> None:
|
||||
|
||||
@@ -42,10 +42,8 @@ void CAP1188Component::finish_setup_() {
|
||||
}
|
||||
|
||||
// Set sensitivity
|
||||
uint8_t sensitivity = 0;
|
||||
this->read_byte(CAP1188_SENSITVITY, &sensitivity);
|
||||
sensitivity = sensitivity & 0x0f;
|
||||
this->write_byte(CAP1188_SENSITVITY, sensitivity | this->touch_threshold_);
|
||||
// The threshold replaces the upper nibble and is ORed into the lower one, as before
|
||||
this->update_register(CAP1188_SENSITVITY, 0xF0 | this->touch_threshold_, this->touch_threshold_);
|
||||
|
||||
// Allow multiple touches
|
||||
this->write_byte(CAP1188_MULTI_TOUCH, this->allow_multiple_touches_);
|
||||
|
||||
@@ -3,7 +3,6 @@ from typing import Any
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACTION_STATE_TOPIC,
|
||||
@@ -49,15 +48,15 @@ from esphome.const import (
|
||||
CONF_TEMPERATURE_STEP,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
CONF_VISUAL,
|
||||
CONF_WEB_SERVER,
|
||||
UNIT_KELVIN,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority, entity_helpers
|
||||
from esphome.core.entity_helpers import (
|
||||
TemperatureUnitResolver,
|
||||
convert_visual_temperatures,
|
||||
declare_fixed_temperature_unit,
|
||||
entity_duplicate_validator,
|
||||
mqtt_component_class,
|
||||
queue_entity_register,
|
||||
setup_entity,
|
||||
setup_entity_temperature_unit,
|
||||
@@ -177,11 +176,13 @@ _visual_temperatures_in_entity_unit = partial(
|
||||
|
||||
|
||||
_CLIMATE_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
cv.ENTITY_BASE_SCHEMA.extend(entity_helpers.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt_component_class("MQTTClimateComponent")
|
||||
),
|
||||
cv.Optional(CONF_VISUAL, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature_with_unit,
|
||||
@@ -353,10 +354,8 @@ async def setup_climate_core_(var: MockObj, config: ConfigType) -> None:
|
||||
cg.add(var.set_visual_max_humidity_override(max_humidity))
|
||||
setup_entity_temperature_unit(var, config)
|
||||
|
||||
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
mqtt_ = await entity_helpers.setup_entity_integrations(var, config)
|
||||
if mqtt_ is not None:
|
||||
if (action_state_topic := config.get(CONF_ACTION_STATE_TOPIC)) is not None:
|
||||
cg.add(mqtt_.set_custom_action_state_topic(action_state_topic))
|
||||
if (away_command_topic := config.get(CONF_AWAY_COMMAND_TOPIC)) is not None:
|
||||
@@ -486,9 +485,6 @@ async def setup_climate_core_(var: MockObj, config: ConfigType) -> None:
|
||||
|
||||
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
|
||||
|
||||
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||
await web_server.add_entity_config(var, web_server_config)
|
||||
|
||||
|
||||
def set_custom_fan_modes(var: MockObj, modes: list[str]) -> None:
|
||||
"""Point the climate at a shared flash table of custom fan modes."""
|
||||
|
||||
@@ -3,7 +3,6 @@ import logging
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
@@ -23,7 +22,6 @@ from esphome.const import (
|
||||
CONF_TILT_COMMAND_TOPIC,
|
||||
CONF_TILT_STATE_TOPIC,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_WEB_SERVER,
|
||||
DEVICE_CLASS_AWNING,
|
||||
DEVICE_CLASS_BLIND,
|
||||
DEVICE_CLASS_CURTAIN,
|
||||
@@ -36,9 +34,10 @@ from esphome.const import (
|
||||
DEVICE_CLASS_SHUTTER,
|
||||
DEVICE_CLASS_WINDOW,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority, entity_helpers
|
||||
from esphome.core.entity_helpers import (
|
||||
entity_duplicate_validator,
|
||||
mqtt_component_class,
|
||||
queue_entity_register,
|
||||
setup_device_class,
|
||||
setup_entity,
|
||||
@@ -112,11 +111,13 @@ TRIGGERS = {
|
||||
|
||||
|
||||
_COVER_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
cv.ENTITY_BASE_SCHEMA.extend(entity_helpers.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt_component_class("MQTTCoverComponent")
|
||||
),
|
||||
cv.Optional(CONF_MQTT_JSON_STATE_PAYLOAD): cv.All(
|
||||
cv.requires_component("mqtt"), cv.boolean
|
||||
),
|
||||
@@ -202,10 +203,8 @@ async def setup_cover_core_(var: MockObj, config: ConfigType) -> None:
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
mqtt_ = await entity_helpers.setup_entity_integrations(var, config)
|
||||
if mqtt_ is not None:
|
||||
if (position_state_topic := config.get(CONF_POSITION_STATE_TOPIC)) is not None:
|
||||
cg.add(mqtt_.set_custom_position_state_topic(position_state_topic))
|
||||
if (
|
||||
@@ -220,9 +219,6 @@ async def setup_cover_core_(var: MockObj, config: ConfigType) -> None:
|
||||
if (tilt_command_topic := config.get(CONF_TILT_COMMAND_TOPIC)) is not None:
|
||||
cg.add(mqtt_.set_custom_tilt_command_topic(tilt_command_topic))
|
||||
|
||||
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||
await web_server.add_entity_config(var, web_server_config)
|
||||
|
||||
|
||||
async def register_cover(var: MockObj, config: ConfigType) -> None:
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
|
||||
@@ -28,30 +28,23 @@ void CSE7766Component::loop() {
|
||||
|
||||
// Read all available bytes in batches to reduce UART call overhead.
|
||||
// At 4800 baud (~480 bytes/sec) with ~122 Hz loop rate, typically ~4 bytes per call.
|
||||
uint8_t buf[CSE7766_RAW_DATA_SIZE];
|
||||
while (avail > 0) {
|
||||
size_t to_read = std::min(avail, sizeof(buf));
|
||||
if (!this->read_array(buf, to_read)) {
|
||||
break;
|
||||
}
|
||||
avail -= to_read;
|
||||
this->read_available<CSE7766_RAW_DATA_SIZE>(
|
||||
[this](uint8_t byte) {
|
||||
this->raw_data_[this->raw_data_index_] = byte;
|
||||
if (!this->check_byte_()) {
|
||||
this->raw_data_index_ = 0;
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < to_read; i++) {
|
||||
this->raw_data_[this->raw_data_index_] = buf[i];
|
||||
if (!this->check_byte_()) {
|
||||
this->raw_data_index_ = 0;
|
||||
this->status_set_warning();
|
||||
continue;
|
||||
}
|
||||
if (this->raw_data_index_ == CSE7766_RAW_DATA_SIZE - 1) {
|
||||
this->parse_data_();
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
if (this->raw_data_index_ == CSE7766_RAW_DATA_SIZE - 1) {
|
||||
this->parse_data_();
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
this->raw_data_index_ = (this->raw_data_index_ + 1) % CSE7766_RAW_DATA_SIZE;
|
||||
}
|
||||
}
|
||||
this->raw_data_index_ = (this->raw_data_index_ + 1) % CSE7766_RAW_DATA_SIZE;
|
||||
},
|
||||
avail);
|
||||
}
|
||||
|
||||
bool CSE7766Component::check_byte_() {
|
||||
|
||||
@@ -163,26 +163,21 @@ void CW2017::update() {
|
||||
if (!this->ready_)
|
||||
return;
|
||||
|
||||
bool ok = true;
|
||||
if (this->battery_level_sensor_ != nullptr) {
|
||||
uint8_t soc = 0;
|
||||
if (this->read_byte(REG_SOC, &soc) && soc <= 100) {
|
||||
ok = this->read_byte(REG_SOC, &soc) && soc <= 100;
|
||||
if (ok)
|
||||
this->battery_level_sensor_->publish_state(soc);
|
||||
} else {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->battery_voltage_sensor_ != nullptr) {
|
||||
if (ok && this->battery_voltage_sensor_ != nullptr) {
|
||||
uint16_t raw = 0;
|
||||
if (this->read_byte_16(REG_VCELL, &raw)) {
|
||||
ok = this->read_byte_16(REG_VCELL, &raw);
|
||||
if (ok)
|
||||
this->battery_voltage_sensor_->publish_state((raw & VCELL_MASK) * VCELL_VOLTS_PER_LSB);
|
||||
} else {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->status_clear_warning();
|
||||
this->status_comm_result(ok);
|
||||
}
|
||||
|
||||
void CW2017::dump_config() {
|
||||
|
||||
@@ -12,8 +12,8 @@ class CW2017 : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
explicit CW2017(const uint8_t *profile) : profile_(profile) {}
|
||||
|
||||
void set_battery_level_sensor(sensor::Sensor *sensor) { this->battery_level_sensor_ = sensor; }
|
||||
void set_battery_voltage_sensor(sensor::Sensor *sensor) { this->battery_voltage_sensor_ = sensor; }
|
||||
SUB_SENSOR(battery_level)
|
||||
SUB_SENSOR(battery_voltage)
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
@@ -29,8 +29,6 @@ class CW2017 : public PollingComponent, public i2c::I2CDevice {
|
||||
void wait_ready_(uint8_t attempt);
|
||||
|
||||
const uint8_t *profile_;
|
||||
sensor::Sensor *battery_level_sensor_{nullptr};
|
||||
sensor::Sensor *battery_voltage_sensor_{nullptr};
|
||||
bool ready_{false};
|
||||
bool restart_pending_{false};
|
||||
uint8_t init_attempts_{0};
|
||||
|
||||
@@ -48,15 +48,14 @@ void DAC7678Output::dump_config() {
|
||||
}
|
||||
}
|
||||
|
||||
void DAC7678Output::register_channel(DAC7678Channel *channel) {
|
||||
auto c = channel->channel_;
|
||||
this->min_channel_ = std::min(this->min_channel_, c);
|
||||
this->max_channel_ = std::max(this->max_channel_, c);
|
||||
channel->set_parent(this);
|
||||
ESP_LOGV(TAG, "Registered channel: %01u", channel->channel_);
|
||||
void DAC7678Output::register_channel(uint8_t channel) {
|
||||
this->min_channel_ = std::min(this->min_channel_, channel);
|
||||
this->max_channel_ = std::max(this->max_channel_, channel);
|
||||
ESP_LOGV(TAG, "Registered channel: %01u", channel);
|
||||
}
|
||||
|
||||
void DAC7678Output::set_channel_value_(uint8_t channel, uint16_t value) {
|
||||
void DAC7678Output::write_channel(uint8_t channel, float state) {
|
||||
auto value = static_cast<uint16_t>(roundf(state * 0xFFF));
|
||||
if (channel >= std::size(this->dac_input_reg_))
|
||||
return;
|
||||
if (this->dac_input_reg_[channel] != value) {
|
||||
@@ -71,10 +70,4 @@ void DAC7678Output::set_channel_value_(uint8_t channel, uint16_t value) {
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
void DAC7678Channel::write_state(float state) {
|
||||
const float input_rounded = roundf(state * this->full_scale_);
|
||||
auto input = static_cast<uint16_t>(input_rounded);
|
||||
this->parent_->set_channel_value_(this->channel_, input);
|
||||
}
|
||||
|
||||
} // namespace esphome::dac7678
|
||||
@@ -1,34 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome::dac7678 {
|
||||
|
||||
class DAC7678Output;
|
||||
|
||||
class DAC7678Channel final : public output::FloatOutput, public Parented<DAC7678Output> {
|
||||
public:
|
||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
||||
|
||||
protected:
|
||||
friend class DAC7678Output;
|
||||
|
||||
const uint16_t full_scale_ = 0xFFF;
|
||||
|
||||
void write_state(float state) override;
|
||||
|
||||
uint8_t channel_;
|
||||
};
|
||||
|
||||
/// DAC7678 float output component.
|
||||
class DAC7678Output final : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
DAC7678Output() {}
|
||||
|
||||
void register_channel(DAC7678Channel *channel);
|
||||
void register_channel(uint8_t channel);
|
||||
void write_channel(uint8_t channel, float state);
|
||||
|
||||
void set_internal_reference(const bool value) { this->internal_reference_ = value; }
|
||||
|
||||
@@ -37,12 +20,8 @@ class DAC7678Output final : public Component, public i2c::I2CDevice {
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
protected:
|
||||
friend DAC7678Channel;
|
||||
|
||||
bool internal_reference_;
|
||||
|
||||
void set_channel_value_(uint8_t channel, uint16_t value);
|
||||
|
||||
uint8_t min_channel_{0xFF};
|
||||
uint8_t max_channel_{0x00};
|
||||
uint16_t dac_input_reg_[8] = {
|
||||
|
||||
@@ -2,19 +2,17 @@ import esphome.codegen as cg
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import DAC7678Output, dac7678_ns
|
||||
from . import DAC7678Output
|
||||
|
||||
DEPENDENCIES = ["dac7678"]
|
||||
|
||||
DAC7678Channel = dac7678_ns.class_("DAC7678Channel", output.FloatOutput)
|
||||
CONF_DAC7678_ID = "dac7678_id"
|
||||
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(DAC7678Channel),
|
||||
cv.Required(CONF_ID): cv.declare_id(output.ApplyFloatOutput),
|
||||
cv.GenerateID(CONF_DAC7678_ID): cv.use_id(DAC7678Output),
|
||||
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
|
||||
}
|
||||
@@ -26,10 +24,9 @@ FINAL_VALIDATE_SCHEMA = output.final_validate_unique_channel(
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_DAC7678_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
cg.add(paren.register_channel(var))
|
||||
await output.register_output(var, config)
|
||||
return var
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_DAC7678_ID])
|
||||
await output.sub_float_outputs(config, parent=hub).create(
|
||||
config, None, config[CONF_CHANNEL], apply="write_channel(index, state)"
|
||||
)
|
||||
cg.add(hub.register_channel(config[CONF_CHANNEL]))
|
||||
@@ -2,7 +2,6 @@ from collections.abc import Callable
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, time, web_server
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DATE,
|
||||
@@ -20,12 +19,12 @@ from esphome.const import (
|
||||
CONF_TIME_ID,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE,
|
||||
CONF_WEB_SERVER,
|
||||
CONF_YEAR,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority, entity_helpers
|
||||
from esphome.core.entity_helpers import (
|
||||
entity_duplicate_validator,
|
||||
mqtt_component_class,
|
||||
queue_entity_register,
|
||||
setup_entity,
|
||||
)
|
||||
@@ -38,6 +37,10 @@ DOMAIN = "datetime"
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
datetime_ns = cg.esphome_ns.namespace("datetime")
|
||||
# Matches time.RealTimeClock by name without importing the time component
|
||||
_RealTimeClock = cg.esphome_ns.namespace("time").class_(
|
||||
"RealTimeClock", cg.PollingComponent
|
||||
)
|
||||
DateTimeBase = datetime_ns.class_("DateTimeBase", cg.EntityBase)
|
||||
DateEntity = datetime_ns.class_("DateEntity", DateTimeBase)
|
||||
TimeEntity = datetime_ns.class_("TimeEntity", DateTimeBase)
|
||||
@@ -64,7 +67,7 @@ DATETIME_MODES = [
|
||||
def _validate_time_present(config: ConfigType) -> ConfigType:
|
||||
config = config.copy()
|
||||
if CONF_ON_TIME in config and CONF_TIME_ID not in config:
|
||||
time_id = cv.use_id(time.RealTimeClock)(None)
|
||||
time_id = cv.use_id(_RealTimeClock)(None)
|
||||
config[CONF_TIME_ID] = time_id
|
||||
return config
|
||||
|
||||
@@ -73,10 +76,10 @@ _DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation({}),
|
||||
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||
cv.Optional(CONF_TIME_ID): cv.use_id(_RealTimeClock),
|
||||
}
|
||||
)
|
||||
.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(entity_helpers.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
).add_extra(_validate_time_present)
|
||||
|
||||
@@ -87,7 +90,9 @@ def date_schema(class_: MockObjClass) -> cv.Schema:
|
||||
schema = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(class_),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTDateComponent),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt_component_class("MQTTDateComponent")
|
||||
),
|
||||
cv.Optional(CONF_TYPE, default="DATE"): cv.one_of("DATE", upper=True),
|
||||
}
|
||||
)
|
||||
@@ -98,7 +103,9 @@ def time_schema(class_: MockObjClass) -> cv.Schema:
|
||||
schema = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(class_),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTimeComponent),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt_component_class("MQTTTimeComponent")
|
||||
),
|
||||
cv.Optional(CONF_TYPE, default="TIME"): cv.one_of("TIME", upper=True),
|
||||
cv.Optional(CONF_ON_TIME): automation.validate_automation(
|
||||
{
|
||||
@@ -115,7 +122,7 @@ def datetime_schema(class_: MockObjClass) -> cv.Schema:
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(class_),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt.MQTTDateTimeComponent
|
||||
mqtt_component_class("MQTTDateTimeComponent")
|
||||
),
|
||||
cv.Optional(CONF_TYPE, default="DATETIME"): cv.one_of(
|
||||
"DATETIME", upper=True
|
||||
@@ -132,11 +139,7 @@ def datetime_schema(class_: MockObjClass) -> cv.Schema:
|
||||
|
||||
@setup_entity("datetime")
|
||||
async def setup_datetime_core_(var: MockObj, config: ConfigType) -> None:
|
||||
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||
await web_server.add_entity_config(var, web_server_config)
|
||||
await entity_helpers.setup_entity_integrations(var, config)
|
||||
# The state callback carries nothing; the automation gets the value as an ESPTime.
|
||||
for conf in config.get(CONF_ON_VALUE, []):
|
||||
await automation.build_callback_automation(
|
||||
|
||||
@@ -135,153 +135,140 @@ void DFPlayer::send_cmd_(uint8_t cmd, uint16_t argument) {
|
||||
}
|
||||
|
||||
void DFPlayer::loop() {
|
||||
// Read all available bytes in batches to reduce UART call overhead.
|
||||
size_t avail = this->available();
|
||||
uint8_t buf[64];
|
||||
while (avail > 0) {
|
||||
size_t to_read = std::min(avail, sizeof(buf));
|
||||
if (!this->read_array(buf, to_read)) {
|
||||
break;
|
||||
}
|
||||
avail -= to_read;
|
||||
for (size_t bi = 0; bi < to_read; bi++) {
|
||||
uint8_t byte = buf[bi];
|
||||
this->read_available([this](uint8_t byte) {
|
||||
if (this->read_pos_ == DFPLAYER_READ_BUFFER_LENGTH)
|
||||
this->read_pos_ = 0;
|
||||
|
||||
if (this->read_pos_ == DFPLAYER_READ_BUFFER_LENGTH)
|
||||
this->read_pos_ = 0;
|
||||
|
||||
switch (this->read_pos_) {
|
||||
case 0: // Start mark
|
||||
if (byte != 0x7E)
|
||||
continue;
|
||||
break;
|
||||
case 1: // Version
|
||||
if (byte != 0xFF) {
|
||||
ESP_LOGW(TAG, "Expected Version 0xFF, got %#02x", byte);
|
||||
this->read_pos_ = 0;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case 2: // Buffer length
|
||||
if (byte != 0x06) {
|
||||
ESP_LOGW(TAG, "Expected Buffer length 0x06, got %#02x", byte);
|
||||
this->read_pos_ = 0;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case 9: // End byte
|
||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||
char byte_sequence[100];
|
||||
byte_sequence[0] = '\0';
|
||||
for (size_t i = 0; i < this->read_pos_ + 1; ++i) {
|
||||
snprintf(byte_sequence + strlen(byte_sequence), sizeof(byte_sequence) - strlen(byte_sequence), "%02X ",
|
||||
this->read_buffer_[i]);
|
||||
}
|
||||
ESP_LOGVV(TAG, "Received byte sequence: %s", byte_sequence);
|
||||
#endif
|
||||
if (byte != 0xEF) {
|
||||
ESP_LOGW(TAG, "Expected end byte 0xEF, got %#02x", byte);
|
||||
this->read_pos_ = 0;
|
||||
continue;
|
||||
}
|
||||
// Parse valid received command
|
||||
uint8_t cmd = this->read_buffer_[3];
|
||||
uint16_t argument = (this->read_buffer_[5] << 8) | this->read_buffer_[6];
|
||||
|
||||
ESP_LOGV(TAG, "Received message cmd: %#02x arg %#04x", cmd, argument);
|
||||
|
||||
switch (cmd) {
|
||||
case 0x3A:
|
||||
if (argument == 1) {
|
||||
ESP_LOGI(TAG, "USB loaded");
|
||||
} else if (argument == 2) {
|
||||
ESP_LOGI(TAG, "TF Card loaded");
|
||||
}
|
||||
break;
|
||||
case 0x3B:
|
||||
if (argument == 1) {
|
||||
ESP_LOGI(TAG, "USB unloaded");
|
||||
} else if (argument == 2) {
|
||||
ESP_LOGI(TAG, "TF Card unloaded");
|
||||
}
|
||||
break;
|
||||
case 0x3F:
|
||||
if (argument == 1) {
|
||||
ESP_LOGI(TAG, "USB available");
|
||||
} else if (argument == 2) {
|
||||
ESP_LOGI(TAG, "TF Card available");
|
||||
} else if (argument == 3) {
|
||||
ESP_LOGI(TAG, "USB, TF Card available");
|
||||
}
|
||||
break;
|
||||
case 0x40:
|
||||
ESP_LOGV(TAG, "Nack");
|
||||
this->ack_set_is_playing_ = false;
|
||||
this->ack_reset_is_playing_ = false;
|
||||
switch (argument) {
|
||||
case 0x01:
|
||||
ESP_LOGE(TAG, "Module is busy or uninitialized");
|
||||
break;
|
||||
case 0x02:
|
||||
ESP_LOGE(TAG, "Module is in sleep mode");
|
||||
break;
|
||||
case 0x03:
|
||||
ESP_LOGE(TAG, "Serial receive error");
|
||||
break;
|
||||
case 0x04:
|
||||
ESP_LOGE(TAG, "Checksum incorrect");
|
||||
break;
|
||||
case 0x05:
|
||||
ESP_LOGE(TAG, "Specified track is out of current track scope");
|
||||
this->is_playing_ = false;
|
||||
break;
|
||||
case 0x06:
|
||||
ESP_LOGE(TAG, "Specified track is not found");
|
||||
this->is_playing_ = false;
|
||||
break;
|
||||
case 0x07:
|
||||
ESP_LOGE(TAG,
|
||||
"Insertion error (an inserting operation only can be done when a track is being played)");
|
||||
break;
|
||||
case 0x08:
|
||||
ESP_LOGE(TAG, "SD card reading failed (SD card pulled out or damaged)");
|
||||
break;
|
||||
case 0x09:
|
||||
ESP_LOGE(TAG, "Entered into sleep mode");
|
||||
this->is_playing_ = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 0x41:
|
||||
ESP_LOGV(TAG, "Ack ok");
|
||||
this->is_playing_ |= this->ack_set_is_playing_;
|
||||
this->is_playing_ &= !this->ack_reset_is_playing_;
|
||||
this->ack_set_is_playing_ = false;
|
||||
this->ack_reset_is_playing_ = false;
|
||||
break;
|
||||
case 0x3C:
|
||||
ESP_LOGV(TAG, "Playback finished (USB drive)");
|
||||
this->is_playing_ = false;
|
||||
this->on_finished_playback_callback_.call();
|
||||
break;
|
||||
case 0x3D:
|
||||
ESP_LOGV(TAG, "Playback finished (SD card)");
|
||||
this->is_playing_ = false;
|
||||
this->on_finished_playback_callback_.call();
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Received unknown cmd %#02x arg %#04x", cmd, argument);
|
||||
}
|
||||
this->sent_cmd_ = 0;
|
||||
switch (this->read_pos_) {
|
||||
case 0: // Start mark
|
||||
if (byte != 0x7E)
|
||||
return;
|
||||
break;
|
||||
case 1: // Version
|
||||
if (byte != 0xFF) {
|
||||
ESP_LOGW(TAG, "Expected Version 0xFF, got %#02x", byte);
|
||||
this->read_pos_ = 0;
|
||||
continue;
|
||||
}
|
||||
this->read_buffer_[this->read_pos_] = byte;
|
||||
this->read_pos_++;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 2: // Buffer length
|
||||
if (byte != 0x06) {
|
||||
ESP_LOGW(TAG, "Expected Buffer length 0x06, got %#02x", byte);
|
||||
this->read_pos_ = 0;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 9: // End byte
|
||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||
char byte_sequence[100];
|
||||
byte_sequence[0] = '\0';
|
||||
for (size_t i = 0; i < this->read_pos_ + 1; ++i) {
|
||||
snprintf(byte_sequence + strlen(byte_sequence), sizeof(byte_sequence) - strlen(byte_sequence), "%02X ",
|
||||
this->read_buffer_[i]);
|
||||
}
|
||||
ESP_LOGVV(TAG, "Received byte sequence: %s", byte_sequence);
|
||||
#endif
|
||||
if (byte != 0xEF) {
|
||||
ESP_LOGW(TAG, "Expected end byte 0xEF, got %#02x", byte);
|
||||
this->read_pos_ = 0;
|
||||
return;
|
||||
}
|
||||
// Parse valid received command
|
||||
uint8_t cmd = this->read_buffer_[3];
|
||||
uint16_t argument = (this->read_buffer_[5] << 8) | this->read_buffer_[6];
|
||||
|
||||
ESP_LOGV(TAG, "Received message cmd: %#02x arg %#04x", cmd, argument);
|
||||
|
||||
switch (cmd) {
|
||||
case 0x3A:
|
||||
if (argument == 1) {
|
||||
ESP_LOGI(TAG, "USB loaded");
|
||||
} else if (argument == 2) {
|
||||
ESP_LOGI(TAG, "TF Card loaded");
|
||||
}
|
||||
break;
|
||||
case 0x3B:
|
||||
if (argument == 1) {
|
||||
ESP_LOGI(TAG, "USB unloaded");
|
||||
} else if (argument == 2) {
|
||||
ESP_LOGI(TAG, "TF Card unloaded");
|
||||
}
|
||||
break;
|
||||
case 0x3F:
|
||||
if (argument == 1) {
|
||||
ESP_LOGI(TAG, "USB available");
|
||||
} else if (argument == 2) {
|
||||
ESP_LOGI(TAG, "TF Card available");
|
||||
} else if (argument == 3) {
|
||||
ESP_LOGI(TAG, "USB, TF Card available");
|
||||
}
|
||||
break;
|
||||
case 0x40:
|
||||
ESP_LOGV(TAG, "Nack");
|
||||
this->ack_set_is_playing_ = false;
|
||||
this->ack_reset_is_playing_ = false;
|
||||
switch (argument) {
|
||||
case 0x01:
|
||||
ESP_LOGE(TAG, "Module is busy or uninitialized");
|
||||
break;
|
||||
case 0x02:
|
||||
ESP_LOGE(TAG, "Module is in sleep mode");
|
||||
break;
|
||||
case 0x03:
|
||||
ESP_LOGE(TAG, "Serial receive error");
|
||||
break;
|
||||
case 0x04:
|
||||
ESP_LOGE(TAG, "Checksum incorrect");
|
||||
break;
|
||||
case 0x05:
|
||||
ESP_LOGE(TAG, "Specified track is out of current track scope");
|
||||
this->is_playing_ = false;
|
||||
break;
|
||||
case 0x06:
|
||||
ESP_LOGE(TAG, "Specified track is not found");
|
||||
this->is_playing_ = false;
|
||||
break;
|
||||
case 0x07:
|
||||
ESP_LOGE(TAG, "Insertion error (an inserting operation only can be done when a track is being played)");
|
||||
break;
|
||||
case 0x08:
|
||||
ESP_LOGE(TAG, "SD card reading failed (SD card pulled out or damaged)");
|
||||
break;
|
||||
case 0x09:
|
||||
ESP_LOGE(TAG, "Entered into sleep mode");
|
||||
this->is_playing_ = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 0x41:
|
||||
ESP_LOGV(TAG, "Ack ok");
|
||||
this->is_playing_ |= this->ack_set_is_playing_;
|
||||
this->is_playing_ &= !this->ack_reset_is_playing_;
|
||||
this->ack_set_is_playing_ = false;
|
||||
this->ack_reset_is_playing_ = false;
|
||||
break;
|
||||
case 0x3C:
|
||||
ESP_LOGV(TAG, "Playback finished (USB drive)");
|
||||
this->is_playing_ = false;
|
||||
this->on_finished_playback_callback_.call();
|
||||
break;
|
||||
case 0x3D:
|
||||
ESP_LOGV(TAG, "Playback finished (SD card)");
|
||||
this->is_playing_ = false;
|
||||
this->on_finished_playback_callback_.call();
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Received unknown cmd %#02x arg %#04x", cmd, argument);
|
||||
}
|
||||
this->sent_cmd_ = 0;
|
||||
this->read_pos_ = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->read_buffer_[this->read_pos_] = byte;
|
||||
this->read_pos_++;
|
||||
});
|
||||
}
|
||||
void DFPlayer::dump_config() { ESP_LOGCONFIG(TAG, "DFPlayer:"); }
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
@@ -27,9 +29,12 @@ class DfrobotSen0395SettingsAction final : public Action<Ts...>, public Parented
|
||||
TEMPLATABLE_VALUE(float, det_max4)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
this->parent_->enqueue(make_unique<PowerCommand>(0));
|
||||
// Worst case: stop, factory reset, six settings, save, start
|
||||
if (!this->parent_->has_queue_room(10))
|
||||
return;
|
||||
this->parent_->enqueue(CommandType::COMMAND_TYPE_POWER, false);
|
||||
if (this->factory_reset_.has_value() && this->factory_reset_.value(x...) == true) {
|
||||
this->parent_->enqueue(make_unique<FactoryResetCommand>());
|
||||
this->parent_->enqueue(CommandType::COMMAND_TYPE_FACTORY_RESET);
|
||||
}
|
||||
if (this->det_min1_.has_value() && this->det_max1_.has_value()) {
|
||||
// Each segment is read once, so the guard and the command see the same values.
|
||||
@@ -37,10 +42,10 @@ class DfrobotSen0395SettingsAction final : public Action<Ts...>, public Parented
|
||||
if (min1 >= 0) {
|
||||
const float max1 = this->det_max1_.value(x...);
|
||||
if (max1 >= 0) {
|
||||
this->parent_->enqueue(make_unique<DetRangeCfgCommand>(
|
||||
min1, max1, this->det_min2_.value_or(x..., -1), this->det_max2_.value_or(x..., -1),
|
||||
this->det_min3_.value_or(x..., -1), this->det_max3_.value_or(x..., -1),
|
||||
this->det_min4_.value_or(x..., -1), this->det_max4_.value_or(x..., -1)));
|
||||
this->parent_->enqueue_det_range_cfg(min1, max1, this->det_min2_.value_or(x..., -1),
|
||||
this->det_max2_.value_or(x..., -1), this->det_min3_.value_or(x..., -1),
|
||||
this->det_max3_.value_or(x..., -1), this->det_min4_.value_or(x..., -1),
|
||||
this->det_max4_.value_or(x..., -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,38 +53,35 @@ class DfrobotSen0395SettingsAction final : public Action<Ts...>, public Parented
|
||||
float detect = this->delay_after_detect_.value(x...);
|
||||
float disappear = this->delay_after_disappear_.value(x...);
|
||||
if (detect >= 0 && disappear >= 0) {
|
||||
this->parent_->enqueue(make_unique<SetLatencyCommand>(detect, disappear));
|
||||
this->parent_->enqueue_set_latency(detect, disappear);
|
||||
}
|
||||
}
|
||||
if (this->start_after_power_on_.has_value()) {
|
||||
int8_t val = this->start_after_power_on_.value(x...);
|
||||
if (val >= 0) {
|
||||
this->parent_->enqueue(make_unique<SensorCfgStartCommand>(val));
|
||||
this->parent_->enqueue(CommandType::COMMAND_TYPE_SENSOR_CFG_START, val);
|
||||
}
|
||||
}
|
||||
if (this->turn_on_led_.has_value()) {
|
||||
int8_t val = this->turn_on_led_.value(x...);
|
||||
if (val >= 0) {
|
||||
this->parent_->enqueue(make_unique<LedModeCommand>(val));
|
||||
this->parent_->enqueue(CommandType::COMMAND_TYPE_LED_MODE, val);
|
||||
}
|
||||
}
|
||||
if (this->presence_via_uart_.has_value()) {
|
||||
int8_t val = this->presence_via_uart_.value(x...);
|
||||
if (val >= 0) {
|
||||
this->parent_->enqueue(make_unique<UartOutputCommand>(val));
|
||||
this->parent_->enqueue(CommandType::COMMAND_TYPE_UART_OUTPUT, val);
|
||||
}
|
||||
}
|
||||
if (this->sensitivity_.has_value()) {
|
||||
int8_t val = this->sensitivity_.value(x...);
|
||||
if (val >= 0) {
|
||||
if (val > 9) {
|
||||
val = 9;
|
||||
}
|
||||
this->parent_->enqueue(make_unique<SensitivityCommand>(val));
|
||||
this->parent_->enqueue(CommandType::COMMAND_TYPE_SENSITIVITY, std::min<int8_t>(val, 9));
|
||||
}
|
||||
}
|
||||
this->parent_->enqueue(make_unique<SaveCfgCommand>());
|
||||
this->parent_->enqueue(make_unique<PowerCommand>(1));
|
||||
this->parent_->enqueue(CommandType::COMMAND_TYPE_SAVE_CFG);
|
||||
this->parent_->enqueue(CommandType::COMMAND_TYPE_POWER, true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,332 +0,0 @@
|
||||
#include "commands.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "dfrobot_sen0395.h"
|
||||
|
||||
namespace esphome::dfrobot_sen0395 {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "dfrobot_sen0395.commands");
|
||||
|
||||
uint8_t Command::execute(DfrobotSen0395Component *parent) {
|
||||
this->parent_ = parent;
|
||||
if (this->cmd_sent_) {
|
||||
if (this->parent_->read_message_()) {
|
||||
std::string message(this->parent_->read_buffer_);
|
||||
if (message.rfind("is not recognized as a CLI command") != std::string::npos) {
|
||||
ESP_LOGD(TAG, "Command not recognized properly by sensor");
|
||||
if (this->retries_left_ > 0) {
|
||||
this->retries_left_ -= 1;
|
||||
this->cmd_sent_ = false;
|
||||
ESP_LOGD(TAG, "Retrying");
|
||||
return 0;
|
||||
} else {
|
||||
this->parent_->find_prompt_();
|
||||
return 1; // Command done
|
||||
}
|
||||
}
|
||||
uint8_t rc = on_message(message);
|
||||
if (rc == 2) {
|
||||
if (this->retries_left_ > 0) {
|
||||
this->retries_left_ -= 1;
|
||||
this->cmd_sent_ = false;
|
||||
ESP_LOGD(TAG, "Retrying");
|
||||
return 0;
|
||||
} else {
|
||||
this->parent_->find_prompt_();
|
||||
return 1; // Command done
|
||||
}
|
||||
} else if (rc == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
this->parent_->find_prompt_();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (millis() - this->parent_->ts_last_cmd_sent_ > this->timeout_ms_) {
|
||||
ESP_LOGD(TAG, "Command timeout");
|
||||
if (this->retries_left_ > 0) {
|
||||
this->retries_left_ -= 1;
|
||||
this->cmd_sent_ = false;
|
||||
ESP_LOGD(TAG, "Retrying");
|
||||
} else {
|
||||
return 1; // Command done
|
||||
}
|
||||
}
|
||||
} else if (this->parent_->send_cmd_(this->cmd_.c_str(), this->cmd_duration_ms_)) {
|
||||
this->cmd_sent_ = true;
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t ReadStateCommand::execute(DfrobotSen0395Component *parent) {
|
||||
this->parent_ = parent;
|
||||
if (this->parent_->read_message_()) {
|
||||
std::string message(this->parent_->read_buffer_);
|
||||
if (message.rfind("$JYBSS,0, , , *") != std::string::npos) {
|
||||
this->parent_->set_detected_(false);
|
||||
this->parent_->set_active(true);
|
||||
return 1; // Command done
|
||||
} else if (message.rfind("$JYBSS,1, , , *") != std::string::npos) {
|
||||
this->parent_->set_detected_(true);
|
||||
this->parent_->set_active(true);
|
||||
return 1; // Command done
|
||||
}
|
||||
}
|
||||
if (millis() - this->parent_->ts_last_cmd_sent_ > this->timeout_ms_) {
|
||||
return 1; // Command done, timeout
|
||||
}
|
||||
return 0; // Command not done yet.
|
||||
}
|
||||
|
||||
uint8_t ReadStateCommand::on_message(std::string &message) { return 1; }
|
||||
|
||||
uint8_t PowerCommand::on_message(std::string &message) {
|
||||
if (message == "sensor stopped already") {
|
||||
this->parent_->set_active(false);
|
||||
ESP_LOGI(TAG, "Stopped sensor (already stopped)");
|
||||
return 1; // Command done
|
||||
} else if (message == "sensor started already") {
|
||||
this->parent_->set_active(true);
|
||||
ESP_LOGI(TAG, "Started sensor (already started)");
|
||||
return 1; // Command done
|
||||
} else if (message == "new parameter isn't save, can't startSensor") {
|
||||
this->parent_->set_active(false);
|
||||
ESP_LOGE(TAG, "Can't start sensor! (Use SaveCfgCommand to save config first)");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
this->parent_->set_active(this->power_on_);
|
||||
if (this->power_on_) {
|
||||
ESP_LOGI(TAG, "Started sensor");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Stopped sensor");
|
||||
}
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet.
|
||||
}
|
||||
|
||||
DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float max2, float min3, float max3,
|
||||
float min4, float max4) {
|
||||
// TODO: Print warning when values are rounded
|
||||
if (min1 < 0 || max1 < 0) {
|
||||
this->min1_ = min1 = 0;
|
||||
this->max1_ = max1 = 0;
|
||||
this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 =
|
||||
this->max4_ = max4 = -1;
|
||||
|
||||
ESP_LOGW(TAG, "DetRangeCfgCommand invalid input parameters. Using range config 0 0.");
|
||||
|
||||
this->cmd_ = "detRangeCfg -1 0 0";
|
||||
} else if (min2 < 0 || max2 < 0) {
|
||||
this->min1_ = min1 = roundf(min1 / 0.15f) * 0.15f;
|
||||
this->max1_ = max1 = roundf(max1 / 0.15f) * 0.15f;
|
||||
this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 =
|
||||
this->max4_ = max4 = -1;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f", min1 / 0.15f, max1 / 0.15f);
|
||||
this->cmd_ = buf;
|
||||
} else if (min3 < 0 || max3 < 0) {
|
||||
this->min1_ = min1 = roundf(min1 / 0.15f) * 0.15f;
|
||||
this->max1_ = max1 = roundf(max1 / 0.15f) * 0.15f;
|
||||
this->min2_ = min2 = roundf(min2 / 0.15f) * 0.15f;
|
||||
this->max2_ = max2 = roundf(max2 / 0.15f) * 0.15f;
|
||||
this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = this->max4_ = max4 = -1;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15f, max1 / 0.15f, min2 / 0.15f,
|
||||
max2 / 0.15f);
|
||||
this->cmd_ = buf;
|
||||
} else if (min4 < 0 || max4 < 0) {
|
||||
this->min1_ = min1 = roundf(min1 / 0.15f) * 0.15f;
|
||||
this->max1_ = max1 = roundf(max1 / 0.15f) * 0.15f;
|
||||
this->min2_ = min2 = roundf(min2 / 0.15f) * 0.15f;
|
||||
this->max2_ = max2 = roundf(max2 / 0.15f) * 0.15f;
|
||||
this->min3_ = min3 = roundf(min3 / 0.15f) * 0.15f;
|
||||
this->max3_ = max3 = roundf(max3 / 0.15f) * 0.15f;
|
||||
this->min4_ = min4 = this->max4_ = max4 = -1;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15f, max1 / 0.15f, min2 / 0.15f,
|
||||
max2 / 0.15f, min3 / 0.15f, max3 / 0.15f);
|
||||
this->cmd_ = buf;
|
||||
} else {
|
||||
this->min1_ = min1 = roundf(min1 / 0.15f) * 0.15f;
|
||||
this->max1_ = max1 = roundf(max1 / 0.15f) * 0.15f;
|
||||
this->min2_ = min2 = roundf(min2 / 0.15f) * 0.15f;
|
||||
this->max2_ = max2 = roundf(max2 / 0.15f) * 0.15f;
|
||||
this->min3_ = min3 = roundf(min3 / 0.15f) * 0.15f;
|
||||
this->max3_ = max3 = roundf(max3 / 0.15f) * 0.15f;
|
||||
this->min4_ = min4 = roundf(min4 / 0.15f) * 0.15f;
|
||||
this->max4_ = max4 = roundf(max4 / 0.15f) * 0.15f;
|
||||
|
||||
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
|
||||
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15f, max1 / 0.15f,
|
||||
min2 / 0.15f, max2 / 0.15f, min3 / 0.15f, max3 / 0.15f, min4 / 0.15f, max4 / 0.15f);
|
||||
this->cmd_ = buf;
|
||||
}
|
||||
|
||||
this->min1_ = min1;
|
||||
this->max1_ = max1;
|
||||
this->min2_ = min2;
|
||||
this->max2_ = max2;
|
||||
this->min3_ = min3;
|
||||
this->max3_ = max3;
|
||||
this->min4_ = min4;
|
||||
this->max4_ = max4;
|
||||
};
|
||||
|
||||
uint8_t DetRangeCfgCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot configure range config. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG,
|
||||
"Updated detection area config:\n"
|
||||
" Detection area 1 from %.02fm to %.02fm.",
|
||||
this->min1_, this->max1_);
|
||||
if (this->min2_ >= 0 && this->max2_ >= 0) {
|
||||
ESP_LOGI(TAG, " Detection area 2 from %.02fm to %.02fm.", this->min2_, this->max2_);
|
||||
}
|
||||
if (this->min3_ >= 0 && this->max3_ >= 0) {
|
||||
ESP_LOGI(TAG, " Detection area 3 from %.02fm to %.02fm.", this->min3_, this->max3_);
|
||||
}
|
||||
if (this->min4_ >= 0 && this->max4_ >= 0) {
|
||||
ESP_LOGI(TAG, " Detection area 4 from %.02fm to %.02fm.", this->min4_, this->max4_);
|
||||
}
|
||||
ESP_LOGD(TAG, " Used command: %s", this->cmd_.c_str());
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet.
|
||||
}
|
||||
|
||||
SetLatencyCommand::SetLatencyCommand(float delay_after_detection, float delay_after_disappear) {
|
||||
delay_after_detection = std::round(delay_after_detection / 0.025f) * 0.025f;
|
||||
delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f;
|
||||
this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f);
|
||||
this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f);
|
||||
// max 32: "setLatency "(11) + float(8) + " "(1) + float(8) + null, rounded to 32
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
|
||||
this->cmd_ = buf;
|
||||
};
|
||||
|
||||
uint8_t SetLatencyCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG,
|
||||
"Updated output latency config:\n"
|
||||
" Signal that someone was detected is delayed by %.03f s.\n"
|
||||
" Signal that nobody is detected anymore is delayed by %.03f s.",
|
||||
this->delay_after_detection_, this->delay_after_disappear_);
|
||||
ESP_LOGD(TAG, " Used command: %s", this->cmd_.c_str());
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t SensorCfgStartCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot configure sensor startup behavior. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Updated sensor startup behavior:");
|
||||
if (startup_mode_) {
|
||||
this->parent_->set_start_after_boot(true);
|
||||
ESP_LOGI(TAG, "Sensor will start automatically after power-on.");
|
||||
} else {
|
||||
this->parent_->set_start_after_boot(false);
|
||||
ESP_LOGI(TAG, "Sensor needs to be started manually after power-on.");
|
||||
}
|
||||
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t FactoryResetCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot factory reset. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Sensor factory reset done.");
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t ResetSystemCommand::on_message(std::string &message) {
|
||||
if (message == "leapMMW:/>") {
|
||||
ESP_LOGI(TAG, "Restarted sensor.");
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t SaveCfgCommand::on_message(std::string &message) {
|
||||
if (message == "no parameter has changed") {
|
||||
ESP_LOGI(TAG, "Not saving config (no parameter changed).");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Saved config. Saving a lot may damage the sensor.");
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t LedModeCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot set led mode. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Set led mode done.");
|
||||
if (this->active_) {
|
||||
this->parent_->set_led_active(true);
|
||||
ESP_LOGI(TAG, "Sensor LED will blink.");
|
||||
} else {
|
||||
this->parent_->set_led_active(false);
|
||||
ESP_LOGI(TAG, "Turned off LED.");
|
||||
}
|
||||
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t UartOutputCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot set uart output mode. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Set uart mode done.");
|
||||
if (this->active_) {
|
||||
this->parent_->set_uart_presence_active(true);
|
||||
ESP_LOGI(TAG, "Presence information is sent via UART and GPIO.");
|
||||
} else {
|
||||
this->parent_->set_uart_presence_active(false);
|
||||
ESP_LOGI(TAG, "Presence information is only sent via GPIO.");
|
||||
}
|
||||
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t SensitivityCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot set sensitivity. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Set sensitivity done. Set to value %d.", this->sensitivity_);
|
||||
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
} // namespace esphome::dfrobot_sen0395
|
||||
@@ -1,152 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::dfrobot_sen0395 {
|
||||
|
||||
class DfrobotSen0395Component;
|
||||
|
||||
// Use command queue and time stamps to avoid blocking.
|
||||
// When component has run time, check if minimum time (1s) between
|
||||
// commands has passed. After that run a command from the queue.
|
||||
class Command {
|
||||
public:
|
||||
virtual ~Command() = default;
|
||||
virtual uint8_t execute(DfrobotSen0395Component *parent);
|
||||
virtual uint8_t on_message(std::string &message) = 0;
|
||||
|
||||
protected:
|
||||
DfrobotSen0395Component *parent_{nullptr};
|
||||
std::string cmd_;
|
||||
bool cmd_sent_{false};
|
||||
int8_t retries_left_{2};
|
||||
uint32_t cmd_duration_ms_{1000};
|
||||
uint32_t timeout_ms_{1500};
|
||||
};
|
||||
|
||||
class ReadStateCommand : public Command {
|
||||
public:
|
||||
ReadStateCommand() { timeout_ms_ = 500; }
|
||||
uint8_t execute(DfrobotSen0395Component *parent) override;
|
||||
uint8_t on_message(std::string &message) override;
|
||||
};
|
||||
|
||||
class PowerCommand : public Command {
|
||||
public:
|
||||
PowerCommand(bool power_on) : power_on_(power_on) {
|
||||
if (power_on) {
|
||||
cmd_ = "sensorStart";
|
||||
} else {
|
||||
cmd_ = "sensorStop";
|
||||
}
|
||||
};
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
bool power_on_;
|
||||
};
|
||||
|
||||
class DetRangeCfgCommand : public Command {
|
||||
public:
|
||||
DetRangeCfgCommand(float min1, float max1, float min2, float max2, float min3, float max3, float min4, float max4);
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
float min1_, max1_, min2_, max2_, min3_, max3_, min4_, max4_;
|
||||
// TODO: Set min max values in component, so they can be published as sensor.
|
||||
};
|
||||
|
||||
class SetLatencyCommand : public Command {
|
||||
public:
|
||||
SetLatencyCommand(float delay_after_detection, float delay_after_disappear);
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
float delay_after_detection_;
|
||||
float delay_after_disappear_;
|
||||
};
|
||||
|
||||
class SensorCfgStartCommand : public Command {
|
||||
public:
|
||||
SensorCfgStartCommand(bool startup_mode) : startup_mode_(startup_mode) {
|
||||
char tmp_cmd[20]; // "sensorCfgStart " (15) + "0/1" (1) + null = 17
|
||||
buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "sensorCfgStart %d", startup_mode);
|
||||
cmd_ = std::string(tmp_cmd);
|
||||
}
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
bool startup_mode_;
|
||||
};
|
||||
|
||||
class FactoryResetCommand : public Command {
|
||||
public:
|
||||
FactoryResetCommand() { cmd_ = "factoryReset 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89"; };
|
||||
uint8_t on_message(std::string &message) override;
|
||||
};
|
||||
|
||||
class ResetSystemCommand : public Command {
|
||||
public:
|
||||
ResetSystemCommand() { cmd_ = "resetSystem"; }
|
||||
uint8_t on_message(std::string &message) override;
|
||||
};
|
||||
|
||||
class SaveCfgCommand : public Command {
|
||||
public:
|
||||
SaveCfgCommand() {
|
||||
cmd_ = "saveCfg 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89";
|
||||
cmd_duration_ms_ = 3000;
|
||||
timeout_ms_ = 3500;
|
||||
}
|
||||
uint8_t on_message(std::string &message) override;
|
||||
};
|
||||
|
||||
class LedModeCommand : public Command {
|
||||
public:
|
||||
LedModeCommand(bool active) : active_(active) {
|
||||
if (active) {
|
||||
cmd_ = "setLedMode 1 0";
|
||||
} else {
|
||||
cmd_ = "setLedMode 1 1";
|
||||
}
|
||||
};
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
bool active_;
|
||||
};
|
||||
|
||||
class UartOutputCommand : public Command {
|
||||
public:
|
||||
UartOutputCommand(bool active) : active_(active) {
|
||||
if (active) {
|
||||
cmd_ = "setUartOutput 1 1";
|
||||
} else {
|
||||
cmd_ = "setUartOutput 1 0";
|
||||
}
|
||||
};
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
bool active_;
|
||||
};
|
||||
|
||||
class SensitivityCommand : public Command {
|
||||
public:
|
||||
SensitivityCommand(uint8_t sensitivity) : sensitivity_(sensitivity) {
|
||||
if (sensitivity > 9)
|
||||
sensitivity_ = sensitivity = 9;
|
||||
char tmp_cmd[20]; // "setSensitivity " (15) + "0-9" (1) + null = 17
|
||||
buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "setSensitivity %d", sensitivity);
|
||||
cmd_ = std::string(tmp_cmd);
|
||||
};
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
uint8_t sensitivity_;
|
||||
};
|
||||
|
||||
} // namespace esphome::dfrobot_sen0395
|
||||
@@ -1,13 +1,27 @@
|
||||
#include "dfrobot_sen0395.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
namespace esphome::dfrobot_sen0395 {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "dfrobot_sen0395");
|
||||
const char ASCII_CR = 0x0D;
|
||||
const char ASCII_LF = 0x0A;
|
||||
static constexpr char ASCII_CR = 0x0D;
|
||||
static constexpr char ASCII_LF = 0x0A;
|
||||
static constexpr float DET_RANGE_STEP_M = 0.15f;
|
||||
static constexpr float LATENCY_STEP_S = 0.025f;
|
||||
|
||||
// What each CommandType cannot do while the sensor runs; empty for commands that do not need it stopped
|
||||
PROGMEM_STRING_TABLE(BusyActions, "", "configure range config", "configure output latency",
|
||||
"configure sensor startup behavior", "factory reset", "", "", "set led mode",
|
||||
"set uart output mode", "set sensitivity");
|
||||
static_assert(BusyActions::COUNT == static_cast<size_t>(CommandType::COMMAND_TYPE_SENSITIVITY) + 1,
|
||||
"BusyActions needs one entry per CommandType");
|
||||
|
||||
void DfrobotSen0395Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Dfrobot Mmwave Radar:");
|
||||
@@ -23,23 +37,270 @@ void DfrobotSen0395Component::dump_config() {
|
||||
}
|
||||
|
||||
void DfrobotSen0395Component::loop() {
|
||||
if (cmd_queue_.is_empty()) {
|
||||
// Command queue empty. Read sensor state.
|
||||
cmd_queue_.enqueue(make_unique<ReadStateCommand>());
|
||||
if (this->cmd_queue_.empty()) {
|
||||
this->read_state_();
|
||||
return;
|
||||
}
|
||||
|
||||
// Commands are non-blocking and need to be called repeatedly.
|
||||
if (cmd_queue_.process(this)) {
|
||||
// Dequeue if command is done
|
||||
cmd_queue_.dequeue();
|
||||
// Commands are non-blocking and need to be called repeatedly
|
||||
if (this->run_command_(this->cmd_queue_.front(), App.get_loop_component_start_time())) {
|
||||
this->cmd_queue_.pop();
|
||||
this->cmd_sent_ = false;
|
||||
this->retries_left_ = 2;
|
||||
}
|
||||
}
|
||||
|
||||
int8_t DfrobotSen0395Component::enqueue(std::unique_ptr<Command> cmd) {
|
||||
return cmd_queue_.enqueue(std::move(cmd)); // Transfer ownership using std::move
|
||||
bool DfrobotSen0395Component::enqueue(CommandType type, uint8_t arg) {
|
||||
if (!this->cmd_queue_.push({type, arg})) {
|
||||
ESP_LOGE(TAG, "Command queue is full");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t DfrobotSen0395Component::read_message_() {
|
||||
bool DfrobotSen0395Component::has_queue_room(size_t count) const {
|
||||
if (COMMAND_QUEUE_SIZE - this->cmd_queue_.size() >= count)
|
||||
return true;
|
||||
ESP_LOGE(TAG, "Command queue is full");
|
||||
return false;
|
||||
}
|
||||
|
||||
void DfrobotSen0395Component::enqueue_det_range_cfg(float min1, float max1, float min2, float max2, float min3,
|
||||
float max3, float min4, float max4) {
|
||||
// TODO: Print warning when values are rounded
|
||||
const float values[8] = {min1, max1, min2, max2, min3, max3, min4, max4};
|
||||
int16_t steps[8];
|
||||
bool in_use = true;
|
||||
for (size_t i = 0; i < 8; i += 2) {
|
||||
in_use = in_use && values[i] >= 0 && values[i + 1] >= 0;
|
||||
for (size_t j = i; j < i + 2; j++) {
|
||||
steps[j] = in_use ? static_cast<int16_t>(clamp(roundf(values[j] / DET_RANGE_STEP_M), 0.0f, 32767.0f)) : -1;
|
||||
}
|
||||
}
|
||||
if (steps[0] < 0) {
|
||||
ESP_LOGW(TAG, "Invalid detection range, using 0 0");
|
||||
steps[0] = steps[1] = 0;
|
||||
}
|
||||
if (this->enqueue(CommandType::COMMAND_TYPE_DET_RANGE_CFG))
|
||||
memcpy(this->det_range_steps_, steps, sizeof(steps));
|
||||
}
|
||||
|
||||
void DfrobotSen0395Component::enqueue_set_latency(float delay_after_detection, float delay_after_disappear) {
|
||||
if (!this->enqueue(CommandType::COMMAND_TYPE_SET_LATENCY))
|
||||
return;
|
||||
// 1638.375 s is 65535 steps
|
||||
this->latency_steps_[0] =
|
||||
static_cast<uint16_t>(clamp(std::round(delay_after_detection / LATENCY_STEP_S), 0.0f, 65535.0f));
|
||||
this->latency_steps_[1] =
|
||||
static_cast<uint16_t>(clamp(std::round(delay_after_disappear / LATENCY_STEP_S), 0.0f, 65535.0f));
|
||||
}
|
||||
|
||||
void DfrobotSen0395Component::enqueue_saved_setting(CommandType type, bool value) {
|
||||
const bool was_active = this->active_;
|
||||
if (!this->has_queue_room(was_active ? 4 : 2))
|
||||
return;
|
||||
if (was_active)
|
||||
this->enqueue(CommandType::COMMAND_TYPE_POWER, false);
|
||||
this->enqueue(type, value);
|
||||
this->enqueue(CommandType::COMMAND_TYPE_SAVE_CFG);
|
||||
if (was_active)
|
||||
this->enqueue(CommandType::COMMAND_TYPE_POWER, true);
|
||||
}
|
||||
|
||||
bool DfrobotSen0395Component::run_command_(const Command &cmd, uint32_t now) {
|
||||
// The interval between two commands must be larger than the command duration
|
||||
const uint32_t duration = cmd.type == CommandType::COMMAND_TYPE_SAVE_CFG ? 3000 : 1000;
|
||||
if (!this->cmd_sent_) {
|
||||
if (now - this->ts_last_cmd_sent_ > duration) {
|
||||
char buf[64]; // "detRangeCfg -1" + 8 * " 32767" + null
|
||||
this->format_command(cmd, buf, sizeof(buf));
|
||||
ESP_LOGD(TAG, "Sending command: %s", buf);
|
||||
this->write_str(buf);
|
||||
this->ts_last_cmd_sent_ = now;
|
||||
this->cmd_sent_ = true;
|
||||
}
|
||||
return false; // Command not done yet
|
||||
}
|
||||
|
||||
if (this->read_message_()) {
|
||||
if (ESPHOME_strstr_P(this->read_buffer_, ESPHOME_PSTR("is not recognized as a CLI command")) == nullptr) {
|
||||
if (!this->handle_reply_(cmd))
|
||||
return false; // Command not done yet
|
||||
this->read_message_(); // Skip the prompt
|
||||
return true;
|
||||
}
|
||||
ESP_LOGD(TAG, "Command not recognized properly by sensor");
|
||||
} else if (now - this->ts_last_cmd_sent_ <= duration + 500) {
|
||||
return false; // Command not done yet
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Command timeout");
|
||||
}
|
||||
|
||||
if (this->retries_left_ == 0) {
|
||||
this->read_message_(); // Skip the prompt
|
||||
return true; // Give up
|
||||
}
|
||||
this->retries_left_--;
|
||||
this->cmd_sent_ = false;
|
||||
ESP_LOGD(TAG, "Retrying");
|
||||
return false;
|
||||
}
|
||||
|
||||
void DfrobotSen0395Component::read_state_() {
|
||||
if (!this->read_message_())
|
||||
return;
|
||||
if (ESPHOME_strstr_P(this->read_buffer_, ESPHOME_PSTR("$JYBSS,0, , , *")) != nullptr) {
|
||||
this->set_detected_(false);
|
||||
this->set_active(true);
|
||||
} else if (ESPHOME_strstr_P(this->read_buffer_, ESPHOME_PSTR("$JYBSS,1, , , *")) != nullptr) {
|
||||
this->set_detected_(true);
|
||||
this->set_active(true);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t append_cfg_key(char *buf, size_t size, size_t pos) {
|
||||
return buf_append_str(buf, size, pos, " 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89");
|
||||
}
|
||||
|
||||
void DfrobotSen0395Component::format_command(const Command &cmd, char *buf, size_t size) const {
|
||||
buf[0] = '\0';
|
||||
switch (cmd.type) {
|
||||
case CommandType::COMMAND_TYPE_POWER:
|
||||
if (cmd.arg) {
|
||||
buf_append_str(buf, size, 0, "sensorStart");
|
||||
} else {
|
||||
buf_append_str(buf, size, 0, "sensorStop");
|
||||
}
|
||||
break;
|
||||
case CommandType::COMMAND_TYPE_DET_RANGE_CFG: {
|
||||
size_t pos = buf_append_str(buf, size, 0, "detRangeCfg -1");
|
||||
for (size_t i = 0; i < 8 && this->det_range_steps_[i] >= 0; i += 2) {
|
||||
pos = buf_append_printf(buf, size, pos, " %d %d", this->det_range_steps_[i], this->det_range_steps_[i + 1]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CommandType::COMMAND_TYPE_SET_LATENCY:
|
||||
buf_append_printf(buf, size, 0, "setLatency %.03f %.03f", this->latency_steps_[0] * LATENCY_STEP_S,
|
||||
this->latency_steps_[1] * LATENCY_STEP_S);
|
||||
break;
|
||||
case CommandType::COMMAND_TYPE_SENSOR_CFG_START:
|
||||
buf_append_printf(buf, size, 0, "sensorCfgStart %d", cmd.arg);
|
||||
break;
|
||||
case CommandType::COMMAND_TYPE_FACTORY_RESET:
|
||||
append_cfg_key(buf, size, buf_append_str(buf, size, 0, "factoryReset"));
|
||||
break;
|
||||
case CommandType::COMMAND_TYPE_RESET_SYSTEM:
|
||||
buf_append_str(buf, size, 0, "resetSystem");
|
||||
break;
|
||||
case CommandType::COMMAND_TYPE_SAVE_CFG:
|
||||
append_cfg_key(buf, size, buf_append_str(buf, size, 0, "saveCfg"));
|
||||
break;
|
||||
case CommandType::COMMAND_TYPE_LED_MODE:
|
||||
buf_append_printf(buf, size, 0, "setLedMode 1 %d", cmd.arg ? 0 : 1);
|
||||
break;
|
||||
case CommandType::COMMAND_TYPE_UART_OUTPUT:
|
||||
buf_append_printf(buf, size, 0, "setUartOutput 1 %d", cmd.arg ? 1 : 0);
|
||||
break;
|
||||
case CommandType::COMMAND_TYPE_SENSITIVITY:
|
||||
buf_append_printf(buf, size, 0, "setSensitivity %d", cmd.arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool DfrobotSen0395Component::handle_reply_(const Command &cmd) {
|
||||
const char *message = this->read_buffer_;
|
||||
auto is = [message](ESPHOME_PGM_P text) { return ESPHOME_strcmp_P(message, text) == 0; };
|
||||
|
||||
// Commands with their own replies
|
||||
switch (cmd.type) {
|
||||
case CommandType::COMMAND_TYPE_POWER:
|
||||
if (is(ESPHOME_PSTR("sensor stopped already"))) {
|
||||
this->set_active(false);
|
||||
ESP_LOGI(TAG, "Stopped sensor (already stopped)");
|
||||
} else if (is(ESPHOME_PSTR("sensor started already"))) {
|
||||
this->set_active(true);
|
||||
ESP_LOGI(TAG, "Started sensor (already started)");
|
||||
} else if (is(ESPHOME_PSTR("new parameter isn't save, can't startSensor"))) {
|
||||
this->set_active(false);
|
||||
ESP_LOGE(TAG, "Can't start sensor! (Use SaveCfgCommand to save config first)");
|
||||
} else if (is(ESPHOME_PSTR("Done"))) {
|
||||
this->set_active(cmd.arg);
|
||||
ESP_LOGI(TAG, "%s", cmd.arg ? LOG_STR_LITERAL("Started sensor") : LOG_STR_LITERAL("Stopped sensor"));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
case CommandType::COMMAND_TYPE_RESET_SYSTEM:
|
||||
if (!is(ESPHOME_PSTR("leapMMW:/>")))
|
||||
return false;
|
||||
ESP_LOGI(TAG, "Restarted sensor.");
|
||||
return true;
|
||||
case CommandType::COMMAND_TYPE_SAVE_CFG:
|
||||
if (is(ESPHOME_PSTR("no parameter has changed"))) {
|
||||
ESP_LOGI(TAG, "Not saving config (no parameter changed).");
|
||||
} else if (is(ESPHOME_PSTR("Done"))) {
|
||||
ESP_LOGI(TAG, "Saved config. Saving a lot may damage the sensor.");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Configuration commands, which need the sensor stopped
|
||||
if (is(ESPHOME_PSTR("sensor is not stopped"))) {
|
||||
ESP_LOGE(TAG, "Cannot %s. Sensor is not stopped!",
|
||||
LOG_STR_ARG(BusyActions::get_log_str(static_cast<uint8_t>(cmd.type), 0)));
|
||||
return true;
|
||||
}
|
||||
if (!is(ESPHOME_PSTR("Done")))
|
||||
return false;
|
||||
|
||||
switch (cmd.type) {
|
||||
case CommandType::COMMAND_TYPE_DET_RANGE_CFG:
|
||||
ESP_LOGI(TAG, "Updated detection area config:");
|
||||
for (size_t i = 0; i < 8 && this->det_range_steps_[i] >= 0; i += 2) {
|
||||
ESP_LOGI(TAG, " Detection area %u from %.02fm to %.02fm.", static_cast<unsigned>(i / 2 + 1),
|
||||
this->det_range_steps_[i] * DET_RANGE_STEP_M, this->det_range_steps_[i + 1] * DET_RANGE_STEP_M);
|
||||
}
|
||||
break;
|
||||
case CommandType::COMMAND_TYPE_SET_LATENCY:
|
||||
ESP_LOGI(TAG,
|
||||
"Updated output latency config:\n"
|
||||
" Signal that someone was detected is delayed by %.03f s.\n"
|
||||
" Signal that nobody is detected anymore is delayed by %.03f s.",
|
||||
this->latency_steps_[0] * LATENCY_STEP_S, this->latency_steps_[1] * LATENCY_STEP_S);
|
||||
break;
|
||||
case CommandType::COMMAND_TYPE_SENSOR_CFG_START:
|
||||
this->set_start_after_boot(cmd.arg);
|
||||
ESP_LOGI(TAG, "Updated sensor startup behavior: %s",
|
||||
cmd.arg ? LOG_STR_LITERAL("sensor will start automatically after power-on.")
|
||||
: LOG_STR_LITERAL("sensor needs to be started manually after power-on."));
|
||||
break;
|
||||
case CommandType::COMMAND_TYPE_FACTORY_RESET:
|
||||
ESP_LOGI(TAG, "Sensor factory reset done.");
|
||||
break;
|
||||
case CommandType::COMMAND_TYPE_LED_MODE:
|
||||
this->set_led_active(cmd.arg);
|
||||
ESP_LOGI(TAG, "Set led mode done. %s",
|
||||
cmd.arg ? LOG_STR_LITERAL("Sensor LED will blink.") : LOG_STR_LITERAL("Turned off LED."));
|
||||
break;
|
||||
case CommandType::COMMAND_TYPE_UART_OUTPUT:
|
||||
this->set_uart_presence_active(cmd.arg);
|
||||
ESP_LOGI(TAG, "Set uart mode done. %s",
|
||||
cmd.arg ? LOG_STR_LITERAL("Presence information is sent via UART and GPIO.")
|
||||
: LOG_STR_LITERAL("Presence information is only sent via GPIO."));
|
||||
break;
|
||||
case CommandType::COMMAND_TYPE_SENSITIVITY:
|
||||
ESP_LOGI(TAG, "Set sensitivity done. Set to value %d.", cmd.arg);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DfrobotSen0395Component::read_message_() {
|
||||
while (this->available()) {
|
||||
uint8_t byte;
|
||||
this->read_byte(&byte);
|
||||
@@ -62,33 +323,11 @@ uint8_t DfrobotSen0395Component::read_message_() {
|
||||
this->read_buffer_[this->read_pos_] = 0;
|
||||
this->read_pos_ = 0;
|
||||
ESP_LOGV(TAG, "Message: %s", this->read_buffer_);
|
||||
return 1; // Full message in buffer
|
||||
} else {
|
||||
this->read_pos_++;
|
||||
return true; // Full message in buffer
|
||||
}
|
||||
this->read_pos_++;
|
||||
}
|
||||
return 0; // No full message yet
|
||||
}
|
||||
|
||||
uint8_t DfrobotSen0395Component::find_prompt_() {
|
||||
if (this->read_message_()) {
|
||||
std::string message(this->read_buffer_);
|
||||
if (message.rfind("leapMMW:/>") != std::string::npos) {
|
||||
return 1; // Prompt found
|
||||
}
|
||||
}
|
||||
return 0; // Not found yet
|
||||
}
|
||||
|
||||
uint8_t DfrobotSen0395Component::send_cmd_(const char *cmd, uint32_t duration) {
|
||||
// The interval between two commands must be larger than the specified duration (in ms).
|
||||
if (millis() - ts_last_cmd_sent_ > duration) {
|
||||
this->write_str(cmd);
|
||||
ts_last_cmd_sent_ = millis();
|
||||
return 1; // Command sent
|
||||
}
|
||||
// Could not send command yet as command duration did not fully pass yet.
|
||||
return 0;
|
||||
return false; // No full message yet
|
||||
}
|
||||
|
||||
void DfrobotSen0395Component::set_detected_(bool detected) {
|
||||
@@ -99,44 +338,4 @@ void DfrobotSen0395Component::set_detected_(bool detected) {
|
||||
#endif
|
||||
}
|
||||
|
||||
int8_t CircularCommandQueue::enqueue(std::unique_ptr<Command> cmd) {
|
||||
if (this->is_full()) {
|
||||
ESP_LOGE(TAG, "Command queue is full");
|
||||
return -1;
|
||||
} else if (this->is_empty()) {
|
||||
front_++;
|
||||
}
|
||||
rear_ = (rear_ + 1) % COMMAND_QUEUE_SIZE;
|
||||
commands_[rear_] = std::move(cmd); // Transfer ownership using std::move
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::unique_ptr<Command> CircularCommandQueue::dequeue() {
|
||||
if (this->is_empty())
|
||||
return nullptr;
|
||||
std::unique_ptr<Command> dequeued_cmd = std::move(commands_[front_]);
|
||||
if (front_ == rear_) {
|
||||
front_ = -1;
|
||||
rear_ = -1;
|
||||
} else {
|
||||
front_ = (front_ + 1) % COMMAND_QUEUE_SIZE;
|
||||
}
|
||||
|
||||
return dequeued_cmd;
|
||||
}
|
||||
|
||||
bool CircularCommandQueue::is_empty() { return front_ == -1; }
|
||||
|
||||
bool CircularCommandQueue::is_full() { return (rear_ + 1) % COMMAND_QUEUE_SIZE == front_; }
|
||||
|
||||
// Run execute method of first in line command.
|
||||
// Execute is non-blocking and has to be called until it returns 1.
|
||||
uint8_t CircularCommandQueue::process(DfrobotSen0395Component *parent) {
|
||||
if (!is_empty()) {
|
||||
return commands_[front_]->execute(parent);
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::dfrobot_sen0395
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
@@ -11,29 +12,30 @@
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#endif
|
||||
|
||||
#include "commands.h"
|
||||
|
||||
namespace esphome::dfrobot_sen0395 {
|
||||
|
||||
const uint8_t MMWAVE_READ_BUFFER_LENGTH = 255;
|
||||
static constexpr uint8_t MMWAVE_READ_BUFFER_LENGTH = 255;
|
||||
static constexpr uint8_t COMMAND_QUEUE_SIZE = 20;
|
||||
|
||||
// forward declaration due to circular dependency
|
||||
class DfrobotSen0395Component;
|
||||
// Keep in sync with the BusyActions table in dfrobot_sen0395.cpp
|
||||
enum class CommandType : uint8_t {
|
||||
COMMAND_TYPE_POWER,
|
||||
COMMAND_TYPE_DET_RANGE_CFG,
|
||||
COMMAND_TYPE_SET_LATENCY,
|
||||
COMMAND_TYPE_SENSOR_CFG_START,
|
||||
COMMAND_TYPE_FACTORY_RESET,
|
||||
COMMAND_TYPE_RESET_SYSTEM,
|
||||
COMMAND_TYPE_SAVE_CFG,
|
||||
COMMAND_TYPE_LED_MODE,
|
||||
COMMAND_TYPE_UART_OUTPUT,
|
||||
COMMAND_TYPE_SENSITIVITY,
|
||||
};
|
||||
|
||||
static const uint8_t COMMAND_QUEUE_SIZE = 20;
|
||||
|
||||
class CircularCommandQueue {
|
||||
public:
|
||||
int8_t enqueue(std::unique_ptr<Command> cmd);
|
||||
std::unique_ptr<Command> dequeue();
|
||||
bool is_empty();
|
||||
bool is_full();
|
||||
uint8_t process(DfrobotSen0395Component *parent);
|
||||
|
||||
protected:
|
||||
int front_{-1};
|
||||
int rear_{-1};
|
||||
std::unique_ptr<Command> commands_[COMMAND_QUEUE_SIZE];
|
||||
/// A queued command. arg is the on/off flag, startup mode or sensitivity; the detection range and
|
||||
/// latency values are held by the component.
|
||||
struct Command {
|
||||
CommandType type;
|
||||
uint8_t arg;
|
||||
};
|
||||
|
||||
class DfrobotSen0395Component final : public uart::UARTDevice, public Component {
|
||||
@@ -93,9 +95,21 @@ class DfrobotSen0395Component final : public uart::UARTDevice, public Component
|
||||
}
|
||||
#endif
|
||||
|
||||
int8_t enqueue(std::unique_ptr<Command> cmd);
|
||||
// Commands run one at a time from a queue, at least 1 s apart, so loop() never blocks
|
||||
bool enqueue(CommandType type, uint8_t arg = 0);
|
||||
/// True when count more commands fit; logs when they do not, so a stop/start sequence is never queued in part
|
||||
bool has_queue_room(size_t count) const;
|
||||
/// Queue a detection range config; segments are used up to the first one with a negative bound
|
||||
void enqueue_det_range_cfg(float min1, float max1, float min2, float max2, float min3, float max3, float min4,
|
||||
float max4);
|
||||
/// Queue an output latency config, both delays in seconds
|
||||
void enqueue_set_latency(float delay_after_detection, float delay_after_disappear);
|
||||
/// Queue a setting change, stopping the sensor around it and saving it when the sensor is running
|
||||
void enqueue_saved_setting(CommandType type, bool value);
|
||||
/// Queue a sensor reset
|
||||
void reset_system() { this->enqueue(make_unique<ResetSystemCommand>()); }
|
||||
void reset_system() { this->enqueue(CommandType::COMMAND_TYPE_RESET_SYSTEM); }
|
||||
/// Write the text sent to the sensor for cmd into buf
|
||||
void format_command(const Command &cmd, char *buf, size_t size) const;
|
||||
|
||||
protected:
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
@@ -109,17 +123,22 @@ class DfrobotSen0395Component final : public uart::UARTDevice, public Component
|
||||
bool start_after_boot_{false};
|
||||
char read_buffer_[MMWAVE_READ_BUFFER_LENGTH];
|
||||
size_t read_pos_{0};
|
||||
CircularCommandQueue cmd_queue_;
|
||||
bool cmd_sent_{false};
|
||||
uint8_t retries_left_{2};
|
||||
StaticRingBuffer<Command, COMMAND_QUEUE_SIZE> cmd_queue_;
|
||||
uint32_t ts_last_cmd_sent_{0};
|
||||
// Range and latency of the queued commands, in sensor steps (0.15 m, 0.025 s); a later command overwrites them
|
||||
int16_t det_range_steps_[8]{};
|
||||
uint16_t latency_steps_[2]{};
|
||||
|
||||
uint8_t read_message_();
|
||||
uint8_t find_prompt_();
|
||||
uint8_t send_cmd_(const char *cmd, uint32_t duration);
|
||||
bool read_message_();
|
||||
void read_state_();
|
||||
/// Run the command at the front of the queue; true when it is done
|
||||
bool run_command_(const Command &cmd, uint32_t now);
|
||||
/// Handle a reply to a sent command; true when the command is done
|
||||
bool handle_reply_(const Command &cmd);
|
||||
|
||||
void set_detected_(bool detected);
|
||||
|
||||
friend class Command;
|
||||
friend class ReadStateCommand;
|
||||
};
|
||||
|
||||
} // namespace esphome::dfrobot_sen0395
|
||||
@@ -2,63 +2,49 @@ import esphome.codegen as cg
|
||||
from esphome.components import switch
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_TYPE, ENTITY_CATEGORY_CONFIG
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_DFROBOT_SEN0395_ID, DfrobotSen0395Component
|
||||
from .. import CONF_DFROBOT_SEN0395_ID, DfrobotSen0395Component, dfrobot_sen0395_ns
|
||||
|
||||
DEPENDENCIES = ["dfrobot_sen0395"]
|
||||
|
||||
dfrobot_sen0395_ns = cg.esphome_ns.namespace("dfrobot_sen0395")
|
||||
DfrobotSen0395Switch = dfrobot_sen0395_ns.class_(
|
||||
"DfrobotSen0395Switch",
|
||||
switch.Switch,
|
||||
cg.Component,
|
||||
cg.Parented.template(DfrobotSen0395Component),
|
||||
)
|
||||
CommandType = dfrobot_sen0395_ns.enum("CommandType", is_class=True)
|
||||
|
||||
Sen0395PowerSwitch = dfrobot_sen0395_ns.class_(
|
||||
"Sen0395PowerSwitch", DfrobotSen0395Switch
|
||||
)
|
||||
Sen0395LedSwitch = dfrobot_sen0395_ns.class_("Sen0395LedSwitch", DfrobotSen0395Switch)
|
||||
Sen0395UartPresenceSwitch = dfrobot_sen0395_ns.class_(
|
||||
"Sen0395UartPresenceSwitch", DfrobotSen0395Switch
|
||||
)
|
||||
Sen0395StartAfterBootSwitch = dfrobot_sen0395_ns.class_(
|
||||
"Sen0395StartAfterBootSwitch", DfrobotSen0395Switch
|
||||
)
|
||||
|
||||
|
||||
def _switch_schema(class_: MockObjClass) -> cv.Schema:
|
||||
return (
|
||||
switch.switch_schema(
|
||||
class_,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_DFROBOT_SEN0395_ID): cv.use_id(
|
||||
DfrobotSen0395Component
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.typed_schema(
|
||||
_SWITCH_SCHEMA = switch.switch_schema(
|
||||
switch.ApplySwitch,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
).extend(
|
||||
{
|
||||
"sensor_active": _switch_schema(Sen0395PowerSwitch),
|
||||
"turn_on_led": _switch_schema(Sen0395LedSwitch),
|
||||
"presence_via_uart": _switch_schema(Sen0395UartPresenceSwitch),
|
||||
"start_after_boot": _switch_schema(Sen0395StartAfterBootSwitch),
|
||||
cv.GenerateID(CONF_DFROBOT_SEN0395_ID): cv.use_id(DfrobotSen0395Component),
|
||||
}
|
||||
)
|
||||
|
||||
# Per type: the hub setter that stores the switch, and the command its writes queue
|
||||
_SWITCHES = {
|
||||
"sensor_active": (
|
||||
lambda hub: hub.set_sensor_active_switch,
|
||||
f"enqueue({CommandType.COMMAND_TYPE_POWER}, state)",
|
||||
),
|
||||
"turn_on_led": (
|
||||
lambda hub: hub.set_turn_on_led_switch,
|
||||
f"enqueue_saved_setting({CommandType.COMMAND_TYPE_LED_MODE}, state)",
|
||||
),
|
||||
"presence_via_uart": (
|
||||
lambda hub: hub.set_presence_via_uart_switch,
|
||||
f"enqueue_saved_setting({CommandType.COMMAND_TYPE_UART_OUTPUT}, state)",
|
||||
),
|
||||
"start_after_boot": (
|
||||
lambda hub: hub.set_start_after_boot_switch,
|
||||
f"enqueue_saved_setting({CommandType.COMMAND_TYPE_SENSOR_CFG_START}, state)",
|
||||
),
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.typed_schema(dict.fromkeys(_SWITCHES, _SWITCH_SCHEMA))
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
parent = await cg.get_variable(config[CONF_DFROBOT_SEN0395_ID])
|
||||
var = await switch.new_switch(config)
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_parented(var, parent)
|
||||
cg.add(getattr(parent, f"set_{config[CONF_TYPE]}_switch")(var))
|
||||
hub = await cg.get_variable(config[CONF_DFROBOT_SEN0395_ID])
|
||||
setter, apply = _SWITCHES[config[CONF_TYPE]]
|
||||
await switch.sub_switches(config, parent=hub).create(
|
||||
config, setter(hub), apply=apply
|
||||
)
|
||||
@@ -1,46 +0,0 @@
|
||||
#include "dfrobot_sen0395_switch.h"
|
||||
|
||||
namespace esphome::dfrobot_sen0395 {
|
||||
|
||||
void Sen0395PowerSwitch::write_state(bool state) { this->parent_->enqueue(make_unique<PowerCommand>(state)); }
|
||||
|
||||
void Sen0395LedSwitch::write_state(bool state) {
|
||||
bool was_active = false;
|
||||
if (this->parent_->is_active()) {
|
||||
was_active = true;
|
||||
this->parent_->enqueue(make_unique<PowerCommand>(false));
|
||||
}
|
||||
this->parent_->enqueue(make_unique<LedModeCommand>(state));
|
||||
this->parent_->enqueue(make_unique<SaveCfgCommand>());
|
||||
if (was_active) {
|
||||
this->parent_->enqueue(make_unique<PowerCommand>(true));
|
||||
}
|
||||
}
|
||||
|
||||
void Sen0395UartPresenceSwitch::write_state(bool state) {
|
||||
bool was_active = false;
|
||||
if (this->parent_->is_active()) {
|
||||
was_active = true;
|
||||
this->parent_->enqueue(make_unique<PowerCommand>(false));
|
||||
}
|
||||
this->parent_->enqueue(make_unique<UartOutputCommand>(state));
|
||||
this->parent_->enqueue(make_unique<SaveCfgCommand>());
|
||||
if (was_active) {
|
||||
this->parent_->enqueue(make_unique<PowerCommand>(true));
|
||||
}
|
||||
}
|
||||
|
||||
void Sen0395StartAfterBootSwitch::write_state(bool state) {
|
||||
bool was_active = false;
|
||||
if (this->parent_->is_active()) {
|
||||
was_active = true;
|
||||
this->parent_->enqueue(make_unique<PowerCommand>(false));
|
||||
}
|
||||
this->parent_->enqueue(make_unique<SensorCfgStartCommand>(state));
|
||||
this->parent_->enqueue(make_unique<SaveCfgCommand>());
|
||||
if (was_active) {
|
||||
this->parent_->enqueue(make_unique<PowerCommand>(true));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::dfrobot_sen0395
|
||||
@@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include "../dfrobot_sen0395.h"
|
||||
|
||||
namespace esphome::dfrobot_sen0395 {
|
||||
|
||||
class DfrobotSen0395Switch : public switch_::Switch, public Component, public Parented<DfrobotSen0395Component> {};
|
||||
|
||||
class Sen0395PowerSwitch final : public DfrobotSen0395Switch {
|
||||
public:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
class Sen0395LedSwitch final : public DfrobotSen0395Switch {
|
||||
public:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
class Sen0395UartPresenceSwitch final : public DfrobotSen0395Switch {
|
||||
public:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
class Sen0395StartAfterBootSwitch final : public DfrobotSen0395Switch {
|
||||
public:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
} // namespace esphome::dfrobot_sen0395
|
||||
@@ -34,10 +34,7 @@ DFRobotSEN0465Component = dfrobot_sen0465_base_ns.class_(
|
||||
"DFRobotSEN0465Component", cg.PollingComponent
|
||||
)
|
||||
AlarmType = dfrobot_sen0465_base_ns.enum("AlarmType", is_class=True)
|
||||
ALARM_TYPES = {
|
||||
"LOW": AlarmType.ALARM_TYPE_LOW,
|
||||
"HIGH": AlarmType.ALARM_TYPE_HIGH,
|
||||
}
|
||||
ALARM_TYPES = AlarmType.options("LOW", "HIGH", prefix="ALARM_TYPE_")
|
||||
|
||||
# One piece of the temperature compensation from the DFRobot_MultiGasSensor library:
|
||||
# for low < T <= high, compensated = raw / (a * T + b) - (c * T + d)
|
||||
|
||||
@@ -72,10 +72,7 @@ void DFRobotSEN0465Component::dump_config() {
|
||||
if (this->alarm_type_ == AlarmType::ALARM_TYPE_OFF) {
|
||||
ESP_LOGCONFIG(TAG, " Alarm: off");
|
||||
} else {
|
||||
float scale = 1.0f;
|
||||
for (uint8_t i = 0; i < this->alarm_decimals_; i++)
|
||||
scale *= 10.0f;
|
||||
const float threshold = this->alarm_threshold_ / scale;
|
||||
const float threshold = this->alarm_threshold_ / pow10_int(this->alarm_decimals_);
|
||||
ESP_LOGCONFIG(TAG, " Alarm: %s %.*f",
|
||||
this->alarm_type_ == AlarmType::ALARM_TYPE_HIGH ? LOG_STR_LITERAL("above") : LOG_STR_LITERAL("below"),
|
||||
this->alarm_decimals_, threshold);
|
||||
@@ -83,9 +80,6 @@ void DFRobotSEN0465Component::dump_config() {
|
||||
if (this->setup_incomplete_) {
|
||||
ESP_LOGCONFIG(TAG, " Setup incomplete: yes");
|
||||
}
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Concentration", this->concentration_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
@@ -228,23 +222,14 @@ void DFRobotSEN0465Component::handle_data_(const uint8_t *frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
float concentration = encode_uint16(frame[2], frame[3]);
|
||||
switch (frame[5]) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
concentration *= 0.1f;
|
||||
break;
|
||||
case 2:
|
||||
concentration *= 0.01f;
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Invalid number of decimal places: %u", frame[5]);
|
||||
if (this->concentration_sensor_ != nullptr)
|
||||
this->concentration_sensor_->publish_state(NAN);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
if (frame[5] > 2) {
|
||||
ESP_LOGW(TAG, "Invalid number of decimal places: %u", frame[5]);
|
||||
if (this->concentration_sensor_ != nullptr)
|
||||
this->concentration_sensor_->publish_state(NAN);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
float concentration = encode_uint16(frame[2], frame[3]) * pow10_int(-frame[5]);
|
||||
|
||||
#ifdef USE_DFROBOT_SEN0465_TEMPERATURE_COMPENSATION
|
||||
if (!this->compensation_.empty())
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
#include "dfrobot_sen0465_uart.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome::dfrobot_sen0465_uart {
|
||||
|
||||
using dfrobot_sen0465_base::FRAME_HEAD;
|
||||
using dfrobot_sen0465_base::FRAME_SIZE;
|
||||
using dfrobot_sen0465_base::RESPONSE_TIMEOUT_ID;
|
||||
|
||||
@@ -18,30 +15,21 @@ void DFRobotSEN0465UARTComponent::setup() {
|
||||
}
|
||||
|
||||
void DFRobotSEN0465UARTComponent::loop() {
|
||||
// The tail of a reading the probe sent on its own may precede the answer; start at the next frame head.
|
||||
uint8_t byte;
|
||||
while (this->rx_count_ == 0 && this->peek_byte(&byte) && byte != FRAME_HEAD)
|
||||
this->read_byte(&byte);
|
||||
const size_t available = this->available();
|
||||
if (available == 0)
|
||||
return;
|
||||
const size_t len = std::min<size_t>(available, FRAME_SIZE - this->rx_count_);
|
||||
if (!this->read_array(this->rx_buffer_ + this->rx_count_, len))
|
||||
return;
|
||||
this->rx_count_ += len;
|
||||
if (this->rx_count_ < FRAME_SIZE)
|
||||
// The tail of a reading the probe sent on its own may precede the answer; the reader skips to the frame head.
|
||||
const uint8_t *frame = nullptr;
|
||||
this->read_available<FRAME_SIZE>([&](uint8_t c) { return (frame = this->reader_.feed(c)) == nullptr; });
|
||||
if (frame == nullptr)
|
||||
return;
|
||||
|
||||
this->rx_count_ = 0;
|
||||
this->cancel_timeout(RESPONSE_TIMEOUT_ID);
|
||||
this->disable_loop();
|
||||
this->handle_response_(this->rx_buffer_);
|
||||
this->handle_response_(frame);
|
||||
}
|
||||
|
||||
bool DFRobotSEN0465UARTComponent::write_frame(const uint8_t *frame) {
|
||||
// Drop anything left over, such as the answer to the mode change or readings sent before passive mode was set
|
||||
this->discard_available();
|
||||
this->rx_count_ = 0;
|
||||
this->reader_.reset();
|
||||
this->write_array(frame, FRAME_SIZE);
|
||||
return true;
|
||||
}
|
||||
@@ -50,7 +38,7 @@ void DFRobotSEN0465UARTComponent::read_response() {
|
||||
this->enable_loop();
|
||||
this->set_timeout(RESPONSE_TIMEOUT_ID, RESPONSE_TIMEOUT_MS, [this]() {
|
||||
this->disable_loop();
|
||||
this->rx_count_ = 0;
|
||||
this->reader_.reset();
|
||||
this->handle_no_response_();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "esphome/components/dfrobot_sen0465_base/dfrobot_sen0465_base.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/frame_reader.h"
|
||||
|
||||
namespace esphome::dfrobot_sen0465_uart {
|
||||
|
||||
@@ -17,8 +18,7 @@ class DFRobotSEN0465UARTComponent final : public dfrobot_sen0465_base::DFRobotSE
|
||||
bool write_frame(const uint8_t *frame) override;
|
||||
void read_response() override;
|
||||
|
||||
uint8_t rx_buffer_[dfrobot_sen0465_base::FRAME_SIZE];
|
||||
uint8_t rx_count_{0};
|
||||
FrameReader<dfrobot_sen0465_base::FRAME_SIZE, dfrobot_sen0465_base::FRAME_HEAD> reader_;
|
||||
};
|
||||
|
||||
} // namespace esphome::dfrobot_sen0465_uart
|
||||
@@ -3,8 +3,7 @@ from collections.abc import Callable
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_MODE, CONF_MODEL
|
||||
from esphome.core import ID
|
||||
from esphome.const import CONF_MODE, CONF_MODEL
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
@@ -59,16 +58,11 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
)
|
||||
|
||||
|
||||
def get_hub_config(root: ConfigType, hub_id: ID) -> ConfigType:
|
||||
"""Return the configuration of the hub with the given id."""
|
||||
return next(conf for conf in root[DOMAIN] if conf[CONF_ID] == hub_id)
|
||||
|
||||
|
||||
def final_validate_mode(keys: dict[str, str]) -> Callable[[ConfigType], None]:
|
||||
"""Reject entity keys that need a different hub mode; `keys` maps each key to the mode it needs."""
|
||||
|
||||
def validator(config: ConfigType) -> None:
|
||||
hub = get_hub_config(fv.full_config.get(), config[CONF_DFROBOT_SEN0609_ID])
|
||||
hub = fv.get_declaring_config(config[CONF_DFROBOT_SEN0609_ID])
|
||||
for key, mode in keys.items():
|
||||
if key in config and hub[CONF_MODE] != mode:
|
||||
raise cv.Invalid(
|
||||
|
||||
@@ -2,44 +2,19 @@ import esphome.codegen as cg
|
||||
from esphome.components import button
|
||||
from esphome.components.const import CONF_QUERY_PARAMS
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_FACTORY_RESET,
|
||||
CONF_ID,
|
||||
CONF_RESTART,
|
||||
DEVICE_CLASS_RESTART,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_DATABASE,
|
||||
ICON_RESTART,
|
||||
ICON_RESTART_ALERT,
|
||||
)
|
||||
from esphome.const import CONF_FACTORY_RESET, CONF_ID, CONF_RESTART
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_DFROBOT_SEN0609_ID, DFRobotSEN0609Component, dfrobot_sen0609_ns
|
||||
|
||||
DFRobotSEN0609Button = dfrobot_sen0609_ns.class_("DFRobotSEN0609Button", button.Button)
|
||||
ButtonAction = dfrobot_sen0609_ns.enum("ButtonAction")
|
||||
from .. import CONF_DFROBOT_SEN0609_ID, DFRobotSEN0609Component
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_DFROBOT_SEN0609_ID): cv.use_id(DFRobotSEN0609Component),
|
||||
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
||||
DFRobotSEN0609Button,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART_ALERT,
|
||||
),
|
||||
cv.Optional(CONF_QUERY_PARAMS): button.button_schema(
|
||||
DFRobotSEN0609Button,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_DATABASE,
|
||||
),
|
||||
cv.Optional(CONF_RESTART): button.button_schema(
|
||||
DFRobotSEN0609Button,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_RESTART,
|
||||
cv.Optional(CONF_FACTORY_RESET): button.factory_reset_button_schema(
|
||||
button.ApplyButton
|
||||
),
|
||||
cv.Optional(CONF_QUERY_PARAMS): button.query_button_schema(button.ApplyButton),
|
||||
cv.Optional(CONF_RESTART): button.restart_button_schema(button.ApplyButton),
|
||||
}
|
||||
|
||||
|
||||
@@ -47,11 +22,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_DFROBOT_SEN0609_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(
|
||||
CONF_FACTORY_RESET,
|
||||
hub.set_factory_reset_button,
|
||||
ButtonAction.BUTTON_FACTORY_RESET,
|
||||
CONF_FACTORY_RESET, hub.set_factory_reset_button, apply="factory_reset()"
|
||||
)
|
||||
await buttons(
|
||||
CONF_QUERY_PARAMS, hub.set_query_button, ButtonAction.BUTTON_QUERY_PARAMS
|
||||
)
|
||||
await buttons(CONF_RESTART, hub.set_restart_button, ButtonAction.BUTTON_RESTART)
|
||||
await buttons(CONF_QUERY_PARAMS, hub.set_query_button, apply="query_params()")
|
||||
await buttons(CONF_RESTART, hub.set_restart_button, apply="restart()")
|
||||
@@ -1,30 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../dfrobot_sen0609.h"
|
||||
|
||||
namespace esphome::dfrobot_sen0609 {
|
||||
|
||||
class DFRobotSEN0609Button final : public button::Button, public Parented<DFRobotSEN0609Component> {
|
||||
public:
|
||||
explicit DFRobotSEN0609Button(ButtonAction action) : action_(action) {}
|
||||
|
||||
protected:
|
||||
void press_action() override {
|
||||
switch (this->action_) {
|
||||
case BUTTON_RESTART:
|
||||
this->parent_->restart();
|
||||
break;
|
||||
case BUTTON_FACTORY_RESET:
|
||||
this->parent_->factory_reset();
|
||||
break;
|
||||
case BUTTON_QUERY_PARAMS:
|
||||
this->parent_->query_params();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ButtonAction action_;
|
||||
};
|
||||
|
||||
} // namespace esphome::dfrobot_sen0609
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -95,12 +94,6 @@ static bool answer_means_done(Operation operation, const char *line) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool parse_float(const char *str, float &value) {
|
||||
char *end;
|
||||
value = strtof(str, &end);
|
||||
return end != str && *end == '\0';
|
||||
}
|
||||
|
||||
static const LogString *operation_to_str(Operation operation) {
|
||||
switch (operation) {
|
||||
case Operation::OPERATION_STOP:
|
||||
@@ -138,9 +131,9 @@ void DFRobotSEN0609Component::dump_config() {
|
||||
LOG_BINARY_SENSOR(" ", "HasTarget", this->has_target_binary_sensor_);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "TargetDistance", this->target_distance_sensor_);
|
||||
LOG_SENSOR(" ", "TargetSpeed", this->target_speed_sensor_);
|
||||
LOG_SENSOR(" ", "TargetEnergy", this->target_energy_sensor_);
|
||||
LOG_SENSOR_WITH_DEDUP(" ", "TargetDistance", this->target_distance_sensor_);
|
||||
LOG_SENSOR_WITH_DEDUP(" ", "TargetSpeed", this->target_speed_sensor_);
|
||||
LOG_SENSOR_WITH_DEDUP(" ", "TargetEnergy", this->target_energy_sensor_);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_);
|
||||
@@ -173,29 +166,19 @@ void DFRobotSEN0609Component::setup() {
|
||||
switch_::Switch *sw = this->switches_[i];
|
||||
if (sw == nullptr)
|
||||
continue;
|
||||
const bool on = sw->get_initial_state_with_restore_mode().value_or(true);
|
||||
this->publish_switch_(static_cast<SwitchSetting>(i), on != sw->is_inverted());
|
||||
// A disabled restore mode leaves the switch on, which an inverted switch writes as off
|
||||
this->publish_switch_(static_cast<SwitchSetting>(i), sw->get_initial_write_state().value_or(!sw->is_inverted()));
|
||||
}
|
||||
#endif
|
||||
this->queue_configuration_();
|
||||
}
|
||||
|
||||
void DFRobotSEN0609Component::loop() {
|
||||
uint8_t buf[MAX_LINE_LENGTH];
|
||||
size_t avail = this->available();
|
||||
while (avail > 0) {
|
||||
const size_t to_read = std::min(avail, sizeof(buf));
|
||||
if (!this->read_array(buf, to_read)) {
|
||||
if (!this->read_failed_) {
|
||||
ESP_LOGW(TAG, "UART read failed");
|
||||
}
|
||||
this->read_failed_ = true;
|
||||
break;
|
||||
}
|
||||
if (this->read_available<MAX_LINE_LENGTH>([this](uint8_t b) { this->readline_(b); })) {
|
||||
this->read_failed_ = false;
|
||||
avail -= to_read;
|
||||
for (size_t i = 0; i < to_read; i++)
|
||||
this->readline_(buf[i]);
|
||||
} else if (!this->read_failed_) {
|
||||
ESP_LOGW(TAG, "UART read failed");
|
||||
this->read_failed_ = true;
|
||||
}
|
||||
this->run_sequencer_();
|
||||
}
|
||||
@@ -286,9 +269,6 @@ void DFRobotSEN0609Component::handle_frame_(char *line) {
|
||||
return;
|
||||
}
|
||||
char *fields[FRAME_FIELDS];
|
||||
float distance;
|
||||
float speed;
|
||||
float energy;
|
||||
if (split_fields(line + 7, fields, FRAME_FIELDS) < FRAME_FIELDS) {
|
||||
this->log_bad_frame_(LOG_STR("Malformed target frame"), line);
|
||||
return;
|
||||
@@ -308,7 +288,10 @@ void DFRobotSEN0609Component::handle_frame_(char *line) {
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
if (!parse_float(fields[2], distance) || !parse_float(fields[3], speed) || !parse_float(fields[4], energy)) {
|
||||
const optional<float> distance = parse_number<float>(fields[2]);
|
||||
const optional<float> speed = parse_number<float>(fields[3]);
|
||||
const optional<float> energy = parse_number<float>(fields[4]);
|
||||
if (!distance.has_value() || !speed.has_value() || !energy.has_value()) {
|
||||
this->log_bad_frame_(LOG_STR("Malformed target frame"), line);
|
||||
return;
|
||||
}
|
||||
@@ -317,7 +300,7 @@ void DFRobotSEN0609Component::handle_frame_(char *line) {
|
||||
this->has_target_binary_sensor_->publish_state(true);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
this->publish_target_(distance, speed, energy);
|
||||
this->publish_target_(*distance, *speed, *energy);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -330,17 +313,18 @@ void DFRobotSEN0609Component::log_bad_frame_(const LogString *problem, const cha
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
static void publish_deduplicated(sensor::Sensor *sens, Deduplicator<float> &dedup, float value) {
|
||||
if (sens == nullptr)
|
||||
return;
|
||||
if (std::isnan(value) ? dedup.next_unknown() : dedup.next(value))
|
||||
sens->publish_state(value);
|
||||
static void publish_deduplicated(sensor::SensorWithDedup<float> &sens, float value) {
|
||||
if (std::isnan(value)) {
|
||||
sens.publish_state_unknown();
|
||||
} else {
|
||||
sens.publish_state_if_not_dup(value);
|
||||
}
|
||||
}
|
||||
|
||||
void DFRobotSEN0609Component::publish_target_(float distance, float speed, float energy) {
|
||||
publish_deduplicated(this->target_distance_sensor_, this->distance_dedup_, distance);
|
||||
publish_deduplicated(this->target_speed_sensor_, this->speed_dedup_, speed);
|
||||
publish_deduplicated(this->target_energy_sensor_, this->energy_dedup_, energy);
|
||||
publish_deduplicated(this->target_distance_sensor_, distance);
|
||||
publish_deduplicated(this->target_speed_sensor_, speed);
|
||||
publish_deduplicated(this->target_energy_sensor_, energy);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -459,6 +443,15 @@ void DFRobotSEN0609Component::control_number(NumberSetting setting, float value)
|
||||
this->request_write_(static_cast<Param>(progmem_read_byte(&NUMBER_PARAMS[setting])));
|
||||
}
|
||||
|
||||
void DFRobotSEN0609Component::control_number(number::Number *number, float value) {
|
||||
for (uint8_t i = 0; i < NUMBER_COUNT; i++) {
|
||||
if (this->numbers_[i] == number) {
|
||||
this->control_number(static_cast<NumberSetting>(i), value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DFRobotSEN0609Component::forget_number_changes_(Param param, bool unknown) {
|
||||
for (uint8_t i = 0; i < NUMBER_COUNT; i++) {
|
||||
if (progmem_read_byte(&NUMBER_PARAMS[i]) != param)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/sensor/sensor_with_dedup.h"
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
@@ -62,12 +62,6 @@ enum SwitchSetting : uint8_t {
|
||||
SWITCH_COUNT,
|
||||
};
|
||||
|
||||
enum ButtonAction : uint8_t {
|
||||
BUTTON_RESTART,
|
||||
BUTTON_FACTORY_RESET,
|
||||
BUTTON_QUERY_PARAMS,
|
||||
};
|
||||
|
||||
/// Module parameters; a write or read of each is one command. Pending work runs in this order.
|
||||
enum Param : uint8_t {
|
||||
PARAM_RUN_APP,
|
||||
@@ -103,9 +97,9 @@ class DFRobotSEN0609Component final : public Component, public uart::UARTDevice
|
||||
SUB_BINARY_SENSOR(has_target)
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
SUB_SENSOR(target_distance)
|
||||
SUB_SENSOR(target_speed)
|
||||
SUB_SENSOR(target_energy)
|
||||
SUB_SENSOR_WITH_DEDUP(target_distance, float)
|
||||
SUB_SENSOR_WITH_DEDUP(target_speed, float)
|
||||
SUB_SENSOR_WITH_DEDUP(target_energy, float)
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
SUB_TEXT_SENSOR(version)
|
||||
@@ -130,6 +124,8 @@ class DFRobotSEN0609Component final : public Component, public uart::UARTDevice
|
||||
#ifdef USE_NUMBER
|
||||
void set_number(NumberSetting setting, number::Number *number) { this->numbers_[setting] = number; }
|
||||
void control_number(NumberSetting setting, float value);
|
||||
// Finds the setting by pointer so every number shares one Apply function
|
||||
void control_number(number::Number *number, float value);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void set_switch(SwitchSetting setting, switch_::Switch *sw) { this->switches_[setting] = sw; }
|
||||
@@ -175,9 +171,6 @@ class DFRobotSEN0609Component final : public Component, public uart::UARTDevice
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
void publish_target_(float distance, float speed, float energy);
|
||||
Deduplicator<float> distance_dedup_;
|
||||
Deduplicator<float> speed_dedup_;
|
||||
Deduplicator<float> energy_dedup_;
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
|
||||
@@ -18,6 +18,7 @@ from esphome.const import (
|
||||
UNIT_SECOND,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import (
|
||||
@@ -28,10 +29,8 @@ from .. import (
|
||||
DFRobotSEN0609Component,
|
||||
dfrobot_sen0609_ns,
|
||||
final_validate_mode,
|
||||
get_hub_config,
|
||||
)
|
||||
|
||||
DFRobotSEN0609Number = dfrobot_sen0609_ns.class_("DFRobotSEN0609Number", number.Number)
|
||||
NumberSetting = dfrobot_sen0609_ns.enum("NumberSetting")
|
||||
|
||||
CONF_HOLD_SENSITIVITY = "hold_sensitivity"
|
||||
@@ -55,7 +54,7 @@ class Setting(NamedTuple):
|
||||
|
||||
def _distance(icon: str = ICON_RULER) -> cv.Schema:
|
||||
return number.number_schema(
|
||||
DFRobotSEN0609Number,
|
||||
number.ApplyNumber,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=icon,
|
||||
@@ -65,7 +64,7 @@ def _distance(icon: str = ICON_RULER) -> cv.Schema:
|
||||
|
||||
def _duration() -> cv.Schema:
|
||||
return number.number_schema(
|
||||
DFRobotSEN0609Number,
|
||||
number.ApplyNumber,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_TIMELAPSE,
|
||||
@@ -75,7 +74,7 @@ def _duration() -> cv.Schema:
|
||||
|
||||
def _plain() -> cv.Schema:
|
||||
return number.number_schema(
|
||||
DFRobotSEN0609Number,
|
||||
number.ApplyNumber,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
)
|
||||
@@ -181,14 +180,14 @@ FINAL_VALIDATE_SCHEMA = final_validate_mode(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub_id = config[CONF_DFROBOT_SEN0609_ID]
|
||||
hub = await cg.get_variable(hub_id)
|
||||
max_range = MAX_RANGES[get_hub_config(CORE.config, hub_id)[CONF_MODEL]]
|
||||
max_range = MAX_RANGES[fv.get_declaring_config(hub_id, CORE.config)[CONF_MODEL]]
|
||||
numbers = number.sub_numbers(config, parent=hub)
|
||||
for s in SETTINGS:
|
||||
await numbers(
|
||||
s.key,
|
||||
lambda n, setting=s.setting: hub.set_number(setting, n),
|
||||
s.setting,
|
||||
min_value=s.min_value,
|
||||
max_value=max_range if s.max_value is None else s.max_value,
|
||||
step=s.step,
|
||||
apply="control_number(entity, value)",
|
||||
)
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "../dfrobot_sen0609.h"
|
||||
|
||||
namespace esphome::dfrobot_sen0609 {
|
||||
|
||||
class DFRobotSEN0609Number final : public number::Number, public Parented<DFRobotSEN0609Component> {
|
||||
public:
|
||||
explicit DFRobotSEN0609Number(NumberSetting setting) : setting_(setting) {}
|
||||
|
||||
protected:
|
||||
void control(float value) override { this->parent_->control_number(this->setting_, value); }
|
||||
|
||||
NumberSetting setting_;
|
||||
};
|
||||
|
||||
} // namespace esphome::dfrobot_sen0609
|
||||
@@ -28,7 +28,7 @@ from esphome.const import (
|
||||
SCHEDULER_DONT_RUN,
|
||||
)
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.final_validate import full_config
|
||||
from esphome.final_validate import full_config, get_declaring_config
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DOMAIN = "display"
|
||||
@@ -225,9 +225,7 @@ def get_display_metadata(display_id: ID) -> DisplayMetaData:
|
||||
return meta
|
||||
# No metadata found, display driver may not yet support it.
|
||||
# Read the raw config to populate the returned data
|
||||
global_config = full_config.get()
|
||||
path = global_config.get_path_for_id(display_id)[:-1]
|
||||
disp_config = global_config.get_config_for_path(path)
|
||||
disp_config = get_declaring_config(display_id)
|
||||
dimensions = disp_config.get(CONF_DIMENSIONS, (0, 0))
|
||||
if isinstance(dimensions, dict):
|
||||
dimensions = (dimensions.get(CONF_WIDTH, 0), dimensions.get(CONF_HEIGHT, 0))
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "ds1603l.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -10,35 +8,28 @@ namespace esphome::ds1603l {
|
||||
ESPHOME_LOG_TAG(TAG, "ds1603l.sensor");
|
||||
|
||||
void DS1603L::loop() {
|
||||
// Assemble frames one byte at a time so a stream that starts mid-frame can realign
|
||||
uint8_t byte;
|
||||
while (this->available() > 0 && this->read_byte(&byte)) {
|
||||
if (this->rx_count_ == 0 && byte != HEADER_BYTE) {
|
||||
ESP_LOGV(TAG, "Skipping byte 0x%02X while looking for header", byte);
|
||||
continue;
|
||||
this->read_available([this](uint8_t byte) {
|
||||
const uint8_t *frame = this->reader_.feed(byte);
|
||||
if (frame == nullptr) {
|
||||
if (this->reader_.length() == 0) {
|
||||
ESP_LOGV(TAG, "Skipping byte 0x%02X while looking for header", byte);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this->rx_buffer_[this->rx_count_++] = byte;
|
||||
if (this->rx_count_ < FRAME_SIZE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this->parse_data_()) {
|
||||
this->rx_count_ = 0;
|
||||
} else {
|
||||
if (!this->parse_data_(frame)) {
|
||||
// The header byte was part of the payload of a misaligned frame, so realign instead of dropping everything
|
||||
this->resync_();
|
||||
this->reader_.resync();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void DS1603L::dump_config() { LOG_SENSOR("", "DS1603L", this); }
|
||||
|
||||
bool DS1603L::parse_data_() {
|
||||
uint8_t header = this->rx_buffer_[0];
|
||||
uint8_t data_h = this->rx_buffer_[1];
|
||||
uint8_t data_l = this->rx_buffer_[2];
|
||||
uint8_t checksum = this->rx_buffer_[3];
|
||||
bool DS1603L::parse_data_(const uint8_t *frame) {
|
||||
uint8_t header = frame[0];
|
||||
uint8_t data_h = frame[1];
|
||||
uint8_t data_l = frame[2];
|
||||
uint8_t checksum = frame[3];
|
||||
|
||||
uint8_t computed_checksum = (header + data_h + data_l) & 0xFF;
|
||||
|
||||
@@ -53,16 +44,4 @@ bool DS1603L::parse_data_() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DS1603L::resync_() {
|
||||
// Drop the byte that was treated as the header, then look for the next candidate header in what is left
|
||||
size_t start = 1;
|
||||
while (start < this->rx_count_ && this->rx_buffer_[start] != HEADER_BYTE) {
|
||||
start++;
|
||||
}
|
||||
this->rx_count_ -= start;
|
||||
if (this->rx_count_ > 0) {
|
||||
memmove(this->rx_buffer_, this->rx_buffer_ + start, this->rx_count_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::ds1603l
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/frame_reader.h"
|
||||
|
||||
namespace esphome::ds1603l {
|
||||
|
||||
@@ -18,13 +19,10 @@ class DS1603L final : public sensor::Sensor, public Component, public uart::UART
|
||||
static constexpr uint8_t HEADER_BYTE = 0xFF;
|
||||
static constexpr size_t FRAME_SIZE = 4;
|
||||
|
||||
// Validates the checksum of the frame in rx_buffer_ and publishes it. Returns false if the frame is invalid.
|
||||
bool parse_data_();
|
||||
// Drops the first buffered byte and realigns the buffer on the next possible header byte.
|
||||
void resync_();
|
||||
// Validates the checksum of the frame and publishes it. Returns false if the frame is invalid.
|
||||
bool parse_data_(const uint8_t *frame);
|
||||
|
||||
uint8_t rx_buffer_[FRAME_SIZE]; // Buffer for the frame being assembled
|
||||
size_t rx_count_{0}; // Number of bytes currently in rx_buffer_
|
||||
FrameReader<FRAME_SIZE, HEADER_BYTE> reader_;
|
||||
};
|
||||
|
||||
} // namespace esphome::ds1603l
|
||||
@@ -32,9 +32,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
"""Validate that the channel is within the parent's channel count."""
|
||||
fconf = fv.full_config.get()
|
||||
path = fconf.get_path_for_id(config[CONF_DS248X_ID])[:-1]
|
||||
parent_config = fconf.get_config_for_path(path)
|
||||
parent_config = fv.get_declaring_config(config[CONF_DS248X_ID])
|
||||
channel_count = get_channel_count(parent_config)
|
||||
channel = config[CONF_CHANNEL]
|
||||
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_EMC2101_ID, EMC2101_COMPONENT_SCHEMA, emc2101_ns
|
||||
from .. import CONF_EMC2101_ID, EMC2101_COMPONENT_SCHEMA
|
||||
|
||||
DEPENDENCIES = ["emc2101"]
|
||||
|
||||
EMC2101Output = emc2101_ns.class_("EMC2101Output", output.FloatOutput)
|
||||
|
||||
CONFIG_SCHEMA = EMC2101_COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(EMC2101Output),
|
||||
cv.Required(CONF_ID): cv.declare_id(output.ApplyFloatOutput),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
paren = await cg.get_variable(config[CONF_EMC2101_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], paren)
|
||||
await output.register_output(var, config)
|
||||
await output.sub_float_outputs(config, parent=config[CONF_EMC2101_ID]).create(
|
||||
config, None, apply="set_duty_cycle(state)"
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "emc2101_output.h"
|
||||
|
||||
namespace esphome::emc2101 {
|
||||
|
||||
void EMC2101Output::write_state(float state) { this->parent_->set_duty_cycle(state); }
|
||||
|
||||
} // namespace esphome::emc2101
|
||||
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../emc2101.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
namespace esphome::emc2101 {
|
||||
|
||||
/// This class allows to control the EMC2101 output.
|
||||
class EMC2101Output final : public output::FloatOutput {
|
||||
public:
|
||||
EMC2101Output(Emc2101Component *parent) : parent_(parent) {}
|
||||
|
||||
protected:
|
||||
/** Used by ESPHome framework. */
|
||||
void write_state(float state) override;
|
||||
|
||||
Emc2101Component *parent_;
|
||||
};
|
||||
|
||||
} // namespace esphome::emc2101
|
||||
@@ -50,7 +50,7 @@ void ES7210::setup() {
|
||||
ES7210_ERROR_FAILED(this->write_byte(ES7210_ADC34_HPF1_REG21, 0x2a));
|
||||
|
||||
// Secondary I2S mode settings
|
||||
ES7210_ERROR_FAILED(this->es7210_update_reg_bit_(ES7210_MODE_CONFIG_REG08, 0x01, 0x00));
|
||||
ES7210_ERROR_FAILED(this->update_register(ES7210_MODE_CONFIG_REG08, 0x01, 0x00));
|
||||
|
||||
// Configure analog power
|
||||
ES7210_ERROR_FAILED(this->write_byte(ES7210_ANALOG_REG40, 0xC3));
|
||||
@@ -141,39 +141,39 @@ bool ES7210::configure_sample_rate_() {
|
||||
|
||||
bool ES7210::configure_mic_gain_() {
|
||||
for (uint8_t i = 0; i < 4; ++i) {
|
||||
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43 + i, 0x10, 0x00));
|
||||
ES7210_ERROR_CHECK(this->update_register(ES7210_MIC1_GAIN_REG43 + i, 0x10, 0x00));
|
||||
}
|
||||
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0xff));
|
||||
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0xff));
|
||||
|
||||
// Configure mic 1
|
||||
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00));
|
||||
ES7210_ERROR_CHECK(this->update_register(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00));
|
||||
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x00));
|
||||
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43, 0x10, 0x10));
|
||||
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC1_GAIN_REG43, 0x0f,
|
||||
this->es7210_gain_reg_value_(this->channel_gains_[0])));
|
||||
ES7210_ERROR_CHECK(this->update_register(ES7210_MIC1_GAIN_REG43, 0x10, 0x10));
|
||||
ES7210_ERROR_CHECK(
|
||||
this->update_register(ES7210_MIC1_GAIN_REG43, 0x0f, this->es7210_gain_reg_value_(this->channel_gains_[0])));
|
||||
|
||||
// Configure mic 2
|
||||
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00));
|
||||
ES7210_ERROR_CHECK(this->update_register(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00));
|
||||
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC12_POWER_REG4B, 0x00));
|
||||
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x10, 0x10));
|
||||
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x0f,
|
||||
this->es7210_gain_reg_value_(this->channel_gains_[1])));
|
||||
ES7210_ERROR_CHECK(this->update_register(ES7210_MIC2_GAIN_REG44, 0x10, 0x10));
|
||||
ES7210_ERROR_CHECK(
|
||||
this->update_register(ES7210_MIC2_GAIN_REG44, 0x0f, this->es7210_gain_reg_value_(this->channel_gains_[1])));
|
||||
|
||||
// Configure mic 3
|
||||
// MIC3 uses the ADC3/4 and MIC3/4 clock domains (bits 2 and 4), not the MIC1/2 domains.
|
||||
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x15, 0x00));
|
||||
ES7210_ERROR_CHECK(this->update_register(ES7210_CLOCK_OFF_REG01, 0x15, 0x00));
|
||||
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x00));
|
||||
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x10, 0x10));
|
||||
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x0f,
|
||||
this->es7210_gain_reg_value_(this->channel_gains_[2])));
|
||||
ES7210_ERROR_CHECK(this->update_register(ES7210_MIC3_GAIN_REG45, 0x10, 0x10));
|
||||
ES7210_ERROR_CHECK(
|
||||
this->update_register(ES7210_MIC3_GAIN_REG45, 0x0f, this->es7210_gain_reg_value_(this->channel_gains_[2])));
|
||||
|
||||
// Configure mic 4
|
||||
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x15, 0x00));
|
||||
ES7210_ERROR_CHECK(this->update_register(ES7210_CLOCK_OFF_REG01, 0x15, 0x00));
|
||||
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x00));
|
||||
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x10, 0x10));
|
||||
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x0f,
|
||||
this->es7210_gain_reg_value_(this->channel_gains_[3])));
|
||||
ES7210_ERROR_CHECK(this->update_register(ES7210_MIC4_GAIN_REG46, 0x10, 0x10));
|
||||
ES7210_ERROR_CHECK(
|
||||
this->update_register(ES7210_MIC4_GAIN_REG46, 0x0f, this->es7210_gain_reg_value_(this->channel_gains_[3])));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -227,11 +227,4 @@ bool ES7210::configure_i2s_format_() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ES7210::es7210_update_reg_bit_(uint8_t reg_addr, uint8_t update_bits, uint8_t data) {
|
||||
uint8_t regv;
|
||||
ES7210_ERROR_CHECK(this->read_byte(reg_addr, ®v));
|
||||
regv = (regv & (~update_bits)) | (update_bits & data);
|
||||
return this->write_byte(reg_addr, regv);
|
||||
}
|
||||
|
||||
} // namespace esphome::es7210
|
||||
@@ -40,13 +40,6 @@ class ES7210 final : public audio_adc::AudioAdc, public Component, public i2c::I
|
||||
float mic_gain() override { return this->channel_gains_[0]; };
|
||||
|
||||
protected:
|
||||
/// @brief Updates an I2C registry address by modifying the current state
|
||||
/// @param reg_addr I2C register address
|
||||
/// @param update_bits Mask of allowed bits to be modified
|
||||
/// @param data Bit values to be written
|
||||
/// @return True if successful, false otherwise
|
||||
bool es7210_update_reg_bit_(uint8_t reg_addr, uint8_t update_bits, uint8_t data);
|
||||
|
||||
/// @brief Convert floating point mic gain value to register value
|
||||
/// @param mic_gain Gain value to convert
|
||||
/// @return Corresponding register value for specified gain
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
from esphome.components.audio_dac import AudioDac
|
||||
from esphome.components.speaker import DOMAIN as SPEAKER_DOMAIN
|
||||
from esphome.components.speaker import DOMAIN as SPEAKER_DOMAIN, get_audio_dac_ids
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_AUDIO_DAC, CONF_BITS_PER_SAMPLE, CONF_ID
|
||||
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_ID
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
@@ -30,10 +30,8 @@ def _final_validate(config: ConfigType) -> None:
|
||||
# Check all speaker configurations for ones that reference this es8156
|
||||
speaker_configs = full_config.get(SPEAKER_DOMAIN, [])
|
||||
for speaker_config in speaker_configs:
|
||||
audio_dac_id = speaker_config.get(CONF_AUDIO_DAC)
|
||||
if (
|
||||
audio_dac_id is not None
|
||||
and audio_dac_id == config[CONF_ID]
|
||||
config[CONF_ID] in get_audio_dac_ids(speaker_config)
|
||||
and (bits_per_sample := speaker_config.get(CONF_BITS_PER_SAMPLE))
|
||||
is not None
|
||||
and bits_per_sample > 24
|
||||
|
||||
@@ -91,20 +91,9 @@ float ES8156::volume() {
|
||||
}
|
||||
|
||||
bool ES8156::set_mute_state_(bool mute_state) {
|
||||
uint8_t reg13;
|
||||
|
||||
this->is_muted_ = mute_state;
|
||||
|
||||
if (!this->read_byte(ES8156_REG13_DAC_MUTE, ®13)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Read ES8156_REG13_DAC_MUTE: %u", reg13);
|
||||
|
||||
assign_bits(reg13, BIT(1) | BIT(2), mute_state);
|
||||
|
||||
ESP_LOGV(TAG, "Setting ES8156_REG13_DAC_MUTE to %u (muted: %s)", reg13, YESNO(mute_state));
|
||||
return this->write_byte(ES8156_REG13_DAC_MUTE, reg13);
|
||||
const uint8_t mask = BIT(1) | BIT(2);
|
||||
return this->update_register(ES8156_REG13_DAC_MUTE, mask, mute_state ? mask : 0);
|
||||
}
|
||||
|
||||
} // namespace esphome::es8156
|
||||
@@ -21,6 +21,23 @@ ESPHOME_LOG_TAG(TAG, "es8311");
|
||||
return false; \
|
||||
}
|
||||
|
||||
static constexpr i2c::I2CRegValue ES8311_POWER_UP[] PROGMEM = {
|
||||
// Power up analog circuitry
|
||||
{ES8311_REG0D_SYSTEM, 0x01},
|
||||
// Enable analog PGA, enable ADC modulator
|
||||
{ES8311_REG0E_SYSTEM, 0x02},
|
||||
// Power up DAC
|
||||
{ES8311_REG12_SYSTEM, 0x00},
|
||||
// Enable output to HP drive
|
||||
{ES8311_REG13_SYSTEM, 0x10},
|
||||
// ADC Equalizer bypass, cancel DC offset in digital domain
|
||||
{ES8311_REG1C_ADC, 0x6A},
|
||||
// Bypass DAC equalizer
|
||||
{ES8311_REG37_DAC, 0x08},
|
||||
// Power On
|
||||
{ES8311_REG00_RESET, 0x80},
|
||||
};
|
||||
|
||||
void ES8311::setup() {
|
||||
// Reset
|
||||
ES8311_ERROR_FAILED(this->write_byte(ES8311_REG00_RESET, 0x1F));
|
||||
@@ -33,20 +50,7 @@ void ES8311::setup() {
|
||||
// Set initial volume
|
||||
this->set_volume(0.75); // 0.75 = 0xBF = 0dB
|
||||
|
||||
// Power up analog circuitry
|
||||
ES8311_ERROR_FAILED(this->write_byte(ES8311_REG0D_SYSTEM, 0x01));
|
||||
// Enable analog PGA, enable ADC modulator
|
||||
ES8311_ERROR_FAILED(this->write_byte(ES8311_REG0E_SYSTEM, 0x02));
|
||||
// Power up DAC
|
||||
ES8311_ERROR_FAILED(this->write_byte(ES8311_REG12_SYSTEM, 0x00));
|
||||
// Enable output to HP drive
|
||||
ES8311_ERROR_FAILED(this->write_byte(ES8311_REG13_SYSTEM, 0x10));
|
||||
// ADC Equalizer bypass, cancel DC offset in digital domain
|
||||
ES8311_ERROR_FAILED(this->write_byte(ES8311_REG1C_ADC, 0x6A));
|
||||
// Bypass DAC equalizer
|
||||
ES8311_ERROR_FAILED(this->write_byte(ES8311_REG37_DAC, 0x08));
|
||||
// Power On
|
||||
ES8311_ERROR_FAILED(this->write_byte(ES8311_REG00_RESET, 0x80));
|
||||
ES8311_ERROR_FAILED(this->write_registers(ES8311_POWER_UP));
|
||||
}
|
||||
|
||||
void ES8311::dump_config() {
|
||||
@@ -134,12 +138,9 @@ bool ES8311::configure_clock_() {
|
||||
}
|
||||
|
||||
// Register 0x02
|
||||
uint8_t reg02;
|
||||
ES8311_ERROR_CHECK(this->read_byte(ES8311_REG02_CLK_MANAGER, ®02));
|
||||
reg02 &= 0x07;
|
||||
reg02 |= (coefficient->pre_div - 1) << 5;
|
||||
reg02 |= encode_pre_mult(coefficient->pre_mult) << 3;
|
||||
ES8311_ERROR_CHECK(this->write_byte(ES8311_REG02_CLK_MANAGER, reg02));
|
||||
ES8311_ERROR_CHECK(
|
||||
this->update_register(ES8311_REG02_CLK_MANAGER, 0xF8,
|
||||
((coefficient->pre_div - 1) << 5) | (encode_pre_mult(coefficient->pre_mult) << 3)));
|
||||
|
||||
// Register 0x03
|
||||
const uint8_t reg03 = (coefficient->fs_mode << 6) | coefficient->adc_osr;
|
||||
@@ -153,23 +154,13 @@ bool ES8311::configure_clock_() {
|
||||
ES8311_ERROR_CHECK(this->write_byte(ES8311_REG05_CLK_MANAGER, reg05));
|
||||
|
||||
// Register 0x06
|
||||
uint8_t reg06;
|
||||
ES8311_ERROR_CHECK(this->read_byte(ES8311_REG06_CLK_MANAGER, ®06));
|
||||
assign_bits(reg06, BIT(5), this->sclk_inverted_);
|
||||
reg06 &= 0xE0;
|
||||
if (coefficient->bclk_div < 19) {
|
||||
reg06 |= (coefficient->bclk_div - 1) << 0;
|
||||
} else {
|
||||
reg06 |= (coefficient->bclk_div) << 0;
|
||||
}
|
||||
ES8311_ERROR_CHECK(this->write_byte(ES8311_REG06_CLK_MANAGER, reg06));
|
||||
uint8_t reg06 = coefficient->bclk_div < 19 ? coefficient->bclk_div - 1 : coefficient->bclk_div;
|
||||
if (this->sclk_inverted_)
|
||||
reg06 |= BIT(5);
|
||||
ES8311_ERROR_CHECK(this->update_register(ES8311_REG06_CLK_MANAGER, BIT(5) | 0x1F, reg06));
|
||||
|
||||
// Register 0x07
|
||||
uint8_t reg07;
|
||||
ES8311_ERROR_CHECK(this->read_byte(ES8311_REG07_CLK_MANAGER, ®07));
|
||||
reg07 &= 0xC0;
|
||||
reg07 |= coefficient->lrck_h << 0;
|
||||
ES8311_ERROR_CHECK(this->write_byte(ES8311_REG07_CLK_MANAGER, reg07));
|
||||
ES8311_ERROR_CHECK(this->update_register(ES8311_REG07_CLK_MANAGER, 0x3F, coefficient->lrck_h));
|
||||
|
||||
// Register 0x08
|
||||
ES8311_ERROR_CHECK(this->write_byte(ES8311_REG08_CLK_MANAGER, coefficient->lrck_l));
|
||||
@@ -180,10 +171,7 @@ bool ES8311::configure_clock_() {
|
||||
|
||||
bool ES8311::configure_format_() {
|
||||
// Configure I2S mode and format
|
||||
uint8_t reg00;
|
||||
ES8311_ERROR_CHECK(this->read_byte(ES8311_REG00_RESET, ®00));
|
||||
reg00 &= 0xBF;
|
||||
ES8311_ERROR_CHECK(this->write_byte(ES8311_REG00_RESET, reg00));
|
||||
ES8311_ERROR_CHECK(this->update_register(ES8311_REG00_RESET, BIT(6), 0));
|
||||
|
||||
// Configure SDP in resolution
|
||||
uint8_t reg09 = calculate_resolution_value(this->resolution_in_);
|
||||
@@ -212,17 +200,9 @@ bool ES8311::configure_mic_() {
|
||||
}
|
||||
|
||||
bool ES8311::set_mute_state_(bool mute_state) {
|
||||
uint8_t reg31;
|
||||
|
||||
this->is_muted_ = mute_state;
|
||||
|
||||
if (!this->read_byte(ES8311_REG31_DAC, ®31)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assign_bits(reg31, BIT(6) | BIT(5), mute_state);
|
||||
|
||||
return this->write_byte(ES8311_REG31_DAC, reg31);
|
||||
const uint8_t mask = BIT(6) | BIT(5);
|
||||
return this->update_register(ES8311_REG31_DAC, mask, mute_state ? mask : 0);
|
||||
}
|
||||
|
||||
} // namespace esphome::es8311
|
||||
@@ -152,8 +152,8 @@ void ES8388::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ES8388 Audio Codec:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
#ifdef USE_ES8388_SELECT
|
||||
LOG_SELECT(" ", "DacOutputSelect", this->dac_output_select_);
|
||||
LOG_SELECT(" ", "ADCInputMicSelect", this->adc_input_mic_select_);
|
||||
LOG_SELECT(" ", "DAC Output", this->dac_output_select_);
|
||||
LOG_SELECT(" ", "ADC Input Mic", this->adc_input_mic_select_);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -174,21 +174,11 @@ float ES8388::volume() {
|
||||
}
|
||||
|
||||
bool ES8388::set_mute_state_(bool mute_state) {
|
||||
uint8_t value = 0;
|
||||
|
||||
this->is_muted_ = mute_state;
|
||||
|
||||
ES8388_ERROR_CHECK(this->read_byte(ES8388_DACCONTROL3, &value));
|
||||
ESP_LOGV(TAG, "Read ES8388_DACCONTROL3: 0x%02X", value);
|
||||
|
||||
// Only toggle the DACMute bit; the other bits of this register hold unrelated
|
||||
// DAC settings that must be preserved. Previously muting overwrote the whole
|
||||
// register with 0x3C and unmuting never cleared the bit, so once muted the DAC
|
||||
// could not be unmuted again.
|
||||
assign_bits(value, ES8388_DACCONTROL3_DAC_MUTE, mute_state);
|
||||
|
||||
ESP_LOGV(TAG, "Setting ES8388_DACCONTROL3 to 0x%02X (muted: %s)", value, YESNO(mute_state));
|
||||
return this->write_byte(ES8388_DACCONTROL3, value);
|
||||
// Only toggle the DACMute bit; the other bits of this register hold unrelated DAC settings
|
||||
return this->update_register(ES8388_DACCONTROL3, ES8388_DACCONTROL3_DAC_MUTE,
|
||||
mute_state ? ES8388_DACCONTROL3_DAC_MUTE : 0);
|
||||
}
|
||||
|
||||
// Set dac power output
|
||||
|
||||
@@ -24,14 +24,12 @@ ADC_INPUT_MIC_OPTIONS = AdcInputMicLine.options(
|
||||
)
|
||||
ADC_INPUT_MIC_OPTION_NAMES = list(ADC_INPUT_MIC_OPTIONS)
|
||||
|
||||
DacOutputSelect = es8388_ns.class_("DacOutputSelect", select.Select)
|
||||
ADCInputMicSelect = es8388_ns.class_("ADCInputMicSelect", select.Select)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(CONF_ES8388_ID): cv.use_id(ES8388),
|
||||
cv.Optional(CONF_DAC_OUTPUT): select.select_schema(
|
||||
DacOutputSelect,
|
||||
select.ApplySelect,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_CHIP,
|
||||
).extend(
|
||||
@@ -42,7 +40,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ADC_INPUT_MIC): select.select_schema(
|
||||
ADCInputMicSelect,
|
||||
select.ApplySelect,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_CHIP,
|
||||
).extend(
|
||||
@@ -61,12 +59,18 @@ async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_ES8388_ID])
|
||||
selects = select.sub_selects(config, parent=hub)
|
||||
await selects(
|
||||
CONF_DAC_OUTPUT, hub.set_dac_output_select, options=DAC_OUTPUT_OPTION_NAMES
|
||||
CONF_DAC_OUTPUT,
|
||||
hub.set_dac_output_select,
|
||||
options=DAC_OUTPUT_OPTION_NAMES,
|
||||
apply="set_dac_output(static_cast<es8388::DacOutputLine>(index))",
|
||||
publish="before",
|
||||
)
|
||||
await selects(
|
||||
CONF_ADC_INPUT_MIC,
|
||||
hub.set_adc_input_mic_select,
|
||||
options=ADC_INPUT_MIC_OPTION_NAMES,
|
||||
apply="set_adc_input_mic(static_cast<es8388::AdcInputMicLine>(index))",
|
||||
publish="before",
|
||||
)
|
||||
if (
|
||||
initial := config.get(CONF_DAC_OUTPUT, {}).get(CONF_INITIAL_OPTION)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#include "adc_input_mic_select.h"
|
||||
|
||||
namespace esphome::es8388 {
|
||||
|
||||
void ADCInputMicSelect::control(size_t index) {
|
||||
this->publish_state(index);
|
||||
this->parent_->set_adc_input_mic(static_cast<AdcInputMicLine>(index));
|
||||
}
|
||||
|
||||
} // namespace esphome::es8388
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/es8388/es8388.h"
|
||||
#include "esphome/components/select/select.h"
|
||||
|
||||
namespace esphome::es8388 {
|
||||
|
||||
class ADCInputMicSelect final : public select::Select, public Parented<ES8388> {
|
||||
protected:
|
||||
void control(size_t index) override;
|
||||
};
|
||||
|
||||
} // namespace esphome::es8388
|
||||
@@ -1,10 +0,0 @@
|
||||
#include "dac_output_select.h"
|
||||
|
||||
namespace esphome::es8388 {
|
||||
|
||||
void DacOutputSelect::control(size_t index) {
|
||||
this->publish_state(index);
|
||||
this->parent_->set_dac_output(static_cast<DacOutputLine>(index));
|
||||
}
|
||||
|
||||
} // namespace esphome::es8388
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/es8388/es8388.h"
|
||||
#include "esphome/components/select/select.h"
|
||||
|
||||
namespace esphome::es8388 {
|
||||
|
||||
class DacOutputSelect final : public select::Select, public Parented<ES8388> {
|
||||
protected:
|
||||
void control(size_t index) override;
|
||||
};
|
||||
|
||||
} // namespace esphome::es8388
|
||||
@@ -1,7 +1,5 @@
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
@@ -41,23 +39,20 @@ from .const import (
|
||||
VARIANT_ESP32S31,
|
||||
esp32_ns,
|
||||
)
|
||||
from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports
|
||||
from .gpio_esp32_c2 import esp32_c2_validate_gpio_pin, esp32_c2_validate_supports
|
||||
from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports
|
||||
from .gpio_esp32_c5 import esp32_c5_validate_gpio_pin, esp32_c5_validate_supports
|
||||
from .gpio_esp32_c6 import esp32_c6_validate_gpio_pin, esp32_c6_validate_supports
|
||||
from .gpio_esp32_c61 import esp32_c61_validate_gpio_pin, esp32_c61_validate_supports
|
||||
from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports
|
||||
from .gpio_esp32_h4 import esp32_h4_validate_gpio_pin, esp32_h4_validate_supports
|
||||
from .gpio_esp32_h21 import esp32_h21_validate_gpio_pin, esp32_h21_validate_supports
|
||||
from .gpio_esp32_p4 import esp32_p4_validate_gpio_pin, esp32_p4_validate_supports
|
||||
from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports
|
||||
from .gpio_esp32_s3 import (
|
||||
esp32_s3_final_validate_pins,
|
||||
esp32_s3_validate_gpio_pin,
|
||||
esp32_s3_validate_supports,
|
||||
)
|
||||
from .gpio_esp32_s31 import esp32_s31_validate_gpio_pin, esp32_s31_validate_supports
|
||||
from .gpio_common import ESP32PinSpec
|
||||
from .gpio_esp32 import ESP32_PIN_SPEC
|
||||
from .gpio_esp32_c2 import ESP32_C2_PIN_SPEC
|
||||
from .gpio_esp32_c3 import ESP32_C3_PIN_SPEC
|
||||
from .gpio_esp32_c5 import ESP32_C5_PIN_SPEC
|
||||
from .gpio_esp32_c6 import ESP32_C6_PIN_SPEC
|
||||
from .gpio_esp32_c61 import ESP32_C61_PIN_SPEC
|
||||
from .gpio_esp32_h2 import ESP32_H2_PIN_SPEC
|
||||
from .gpio_esp32_h4 import ESP32_H4_PIN_SPEC
|
||||
from .gpio_esp32_h21 import ESP32_H21_PIN_SPEC
|
||||
from .gpio_esp32_p4 import ESP32_P4_PIN_SPEC
|
||||
from .gpio_esp32_s2 import ESP32_S2_PIN_SPEC
|
||||
from .gpio_esp32_s3 import ESP32_S3_PIN_SPEC, esp32_s3_final_validate_pins
|
||||
from .gpio_esp32_s31 import ESP32_S31_PIN_SPEC
|
||||
|
||||
ESP32InternalGPIOPin = esp32_ns.class_("ESP32InternalGPIOPin", cg.InternalGPIOPin)
|
||||
|
||||
@@ -98,66 +93,23 @@ def _translate_pin(value):
|
||||
return _lookup_pin(value)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ESP32ValidationFunctions:
|
||||
pin_validation: Callable[[int], int]
|
||||
usage_validation: Callable[[dict[str, Any]], dict[str, Any]]
|
||||
final_validate: Callable[[ConfigType], None] | None = None
|
||||
|
||||
|
||||
_esp32_validations = {
|
||||
VARIANT_ESP32: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_validate_gpio_pin, usage_validation=esp32_validate_supports
|
||||
),
|
||||
VARIANT_ESP32C2: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_c2_validate_gpio_pin,
|
||||
usage_validation=esp32_c2_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32C3: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_c3_validate_gpio_pin,
|
||||
usage_validation=esp32_c3_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32C5: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_c5_validate_gpio_pin,
|
||||
usage_validation=esp32_c5_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32C6: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_c6_validate_gpio_pin,
|
||||
usage_validation=esp32_c6_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32C61: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_c61_validate_gpio_pin,
|
||||
usage_validation=esp32_c61_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32H2: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_h2_validate_gpio_pin,
|
||||
usage_validation=esp32_h2_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32H4: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_h4_validate_gpio_pin,
|
||||
usage_validation=esp32_h4_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32H21: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_h21_validate_gpio_pin,
|
||||
usage_validation=esp32_h21_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32P4: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_p4_validate_gpio_pin,
|
||||
usage_validation=esp32_p4_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32S2: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_s2_validate_gpio_pin,
|
||||
usage_validation=esp32_s2_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32S3: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_s3_validate_gpio_pin,
|
||||
usage_validation=esp32_s3_validate_supports,
|
||||
final_validate=esp32_s3_final_validate_pins,
|
||||
),
|
||||
VARIANT_ESP32S31: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_s31_validate_gpio_pin,
|
||||
usage_validation=esp32_s31_validate_supports,
|
||||
),
|
||||
_esp32_pin_specs: dict[str, ESP32PinSpec] = {
|
||||
VARIANT_ESP32: ESP32_PIN_SPEC,
|
||||
VARIANT_ESP32C2: ESP32_C2_PIN_SPEC,
|
||||
VARIANT_ESP32C3: ESP32_C3_PIN_SPEC,
|
||||
VARIANT_ESP32C5: ESP32_C5_PIN_SPEC,
|
||||
VARIANT_ESP32C6: ESP32_C6_PIN_SPEC,
|
||||
VARIANT_ESP32C61: ESP32_C61_PIN_SPEC,
|
||||
VARIANT_ESP32H2: ESP32_H2_PIN_SPEC,
|
||||
VARIANT_ESP32H4: ESP32_H4_PIN_SPEC,
|
||||
VARIANT_ESP32H21: ESP32_H21_PIN_SPEC,
|
||||
VARIANT_ESP32P4: ESP32_P4_PIN_SPEC,
|
||||
VARIANT_ESP32S2: ESP32_S2_PIN_SPEC,
|
||||
VARIANT_ESP32S3: ESP32_S3_PIN_SPEC,
|
||||
VARIANT_ESP32S31: ESP32_S31_PIN_SPEC,
|
||||
}
|
||||
_final_validations: dict[str, Callable[[ConfigType], None]] = {
|
||||
VARIANT_ESP32S3: esp32_s3_final_validate_pins,
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +126,7 @@ def gpio_pin_number_validator(value):
|
||||
return value
|
||||
|
||||
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||
if variant not in _esp32_validations:
|
||||
if variant not in _esp32_pin_specs:
|
||||
raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
|
||||
|
||||
return value
|
||||
@@ -182,12 +134,12 @@ def gpio_pin_number_validator(value):
|
||||
|
||||
def validate_gpio_pin(pin):
|
||||
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||
if variant not in _esp32_validations:
|
||||
if variant not in _esp32_pin_specs:
|
||||
raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
|
||||
|
||||
ignore_pin_validation_warning = pin[CONF_IGNORE_PIN_VALIDATION_ERROR]
|
||||
try:
|
||||
pin[CONF_NUMBER] = _esp32_validations[variant].pin_validation(pin[CONF_NUMBER])
|
||||
pin[CONF_NUMBER] = _esp32_pin_specs[variant].validate_gpio_pin(pin[CONF_NUMBER])
|
||||
except cv.Invalid as exc:
|
||||
if not ignore_pin_validation_warning:
|
||||
raise
|
||||
@@ -198,10 +150,10 @@ def validate_gpio_pin(pin):
|
||||
exc,
|
||||
)
|
||||
else:
|
||||
# `ignore_pin_validation_error` only suppresses an error raised by the
|
||||
# variant's pin_validation above (e.g. SPI flash/PSRAM pins, invalid pin
|
||||
# numbers). If that didn't raise, the option is a no-op -- warn so the
|
||||
# user can clean it up, but don't block the build.
|
||||
# `ignore_pin_validation_error` only suppresses an error raised by
|
||||
# validate_gpio_pin above (e.g. SPI flash/PSRAM pins); pin numbers out of
|
||||
# range still fail validate_supports. If nothing raised, the option is a
|
||||
# no-op -- warn so the user can clean it up, but don't block the build.
|
||||
if ignore_pin_validation_warning:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d has no validation errors to ignore; "
|
||||
@@ -217,7 +169,7 @@ def validate_supports(value):
|
||||
is_output = mode[CONF_OUTPUT]
|
||||
is_open_drain = mode[CONF_OPEN_DRAIN]
|
||||
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||
if variant not in _esp32_validations:
|
||||
if variant not in _esp32_pin_specs:
|
||||
raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
|
||||
|
||||
if is_open_drain and not is_output:
|
||||
@@ -225,7 +177,7 @@ def validate_supports(value):
|
||||
"Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN]
|
||||
)
|
||||
|
||||
return _esp32_validations[variant].usage_validation(value)
|
||||
return _esp32_pin_specs[variant].validate_supports(value)
|
||||
|
||||
|
||||
# https://docs.espressif.com/projects/esp-idf/en/v3.3.5/api-reference/peripherals/gpio.html#_CPPv416gpio_drive_cap_t
|
||||
@@ -276,6 +228,5 @@ async def esp32_pin_to_code(config):
|
||||
|
||||
def final_validate_pins(full_config: ConfigType) -> None:
|
||||
"""Run the active variant's pin final-validation, if it defines one."""
|
||||
funcs = _esp32_validations.get(CORE.data[KEY_ESP32][KEY_VARIANT])
|
||||
if funcs is not None and funcs.final_validate is not None:
|
||||
funcs.final_validate(full_config)
|
||||
if (final := _final_validations.get(CORE.data[KEY_ESP32][KEY_VARIANT])) is not None:
|
||||
final(full_config)
|
||||
@@ -1,26 +1,129 @@
|
||||
"""Pin checks shared by the ESP32 variant validators."""
|
||||
|
||||
from logging import Logger
|
||||
from typing import Any
|
||||
from collections.abc import Iterable, Mapping
|
||||
import logging
|
||||
from types import MappingProxyType
|
||||
|
||||
from esphome.components.const import CONF_HOLD_STATE
|
||||
from esphome.const import CONF_NUMBER
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
from esphome.pins import check_strapping_pin
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_NO_PINS: Mapping[int, str] = MappingProxyType({})
|
||||
|
||||
_INPUT_ONLY_REJECTED_MODES = (
|
||||
(CONF_OUTPUT, "does not support output pin mode."),
|
||||
(CONF_PULLUP, "does not support pullups."),
|
||||
(CONF_PULLDOWN, "does not support pulldowns."),
|
||||
)
|
||||
|
||||
|
||||
def check_usb_jtag_pin(num: int, usb_jtag_pins: set[int], logger: Logger) -> None:
|
||||
if num in usb_jtag_pins:
|
||||
logger.warning(
|
||||
"GPIO%d is used by the USB-Serial-JTAG interface."
|
||||
" Using this pin as GPIO will conflict with USB-Serial-JTAG.",
|
||||
num,
|
||||
)
|
||||
def spi_psram_errors(variant: str, functions: Mapping[int, str]) -> dict[int, str]:
|
||||
"""Error messages for pins taken by the SPI flash/PSRAM interface."""
|
||||
return {
|
||||
num: f"This pin cannot be used on {variant}s and is already used by the"
|
||||
f" SPI/PSRAM interface (function: {function})"
|
||||
for num, function in functions.items()
|
||||
}
|
||||
|
||||
|
||||
def check_usb_jtag_hold(
|
||||
conf: dict[str, Any], usb_jtag_pins: set[int], logger: Logger
|
||||
) -> None:
|
||||
num = conf[CONF_NUMBER]
|
||||
if conf.get(CONF_HOLD_STATE) and num in usb_jtag_pins:
|
||||
logger.warning(
|
||||
"GPIO%d cannot hold at low level during wakeup from deep sleep.", num
|
||||
)
|
||||
def unusable_errors(variant: str, pins: Iterable[int]) -> dict[int, str]:
|
||||
"""Error messages for pins that are missing from the GPIO matrix."""
|
||||
return {num: f"The pin GPIO{num} is not usable on {variant}s." for num in pins}
|
||||
|
||||
|
||||
class ESP32PinSpec:
|
||||
"""Pin facts of one ESP32 variant, with the two validators built from them.
|
||||
|
||||
`error_pins` fail pin validation, which `ignore_pin_validation_error` bypasses;
|
||||
`fatal_pins` and pins above `max_pin` fail both validators, so the option cannot bypass them.
|
||||
`input_only_label` is inserted after "GPIOn" in the input-only errors.
|
||||
A plain class, not a dataclass: building a dataclass costs ~0.5 ms of import.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"error_pins",
|
||||
"fatal_pins",
|
||||
"input_only_label",
|
||||
"input_only_pins",
|
||||
"max_pin",
|
||||
"no_hold_pins",
|
||||
"strapping_pins",
|
||||
"usb_jtag_pins",
|
||||
"warning_pins",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
max_pin: int,
|
||||
strapping_pins: frozenset[int],
|
||||
usb_jtag_pins: frozenset[int] = frozenset(),
|
||||
error_pins: Mapping[int, str] = _NO_PINS,
|
||||
fatal_pins: Mapping[int, str] = _NO_PINS,
|
||||
warning_pins: Mapping[int, str] = _NO_PINS,
|
||||
input_only_pins: frozenset[int] = frozenset(),
|
||||
input_only_label: str = "",
|
||||
no_hold_pins: frozenset[int] = frozenset(),
|
||||
) -> None:
|
||||
self.max_pin = max_pin
|
||||
self.strapping_pins = strapping_pins
|
||||
self.usb_jtag_pins = usb_jtag_pins
|
||||
self.error_pins = error_pins
|
||||
self.fatal_pins = fatal_pins
|
||||
self.warning_pins = warning_pins
|
||||
self.input_only_pins = input_only_pins
|
||||
self.input_only_label = input_only_label
|
||||
self.no_hold_pins = no_hold_pins
|
||||
|
||||
def _check_pin_exists(self, num: int) -> None:
|
||||
if num < 0 or num > self.max_pin:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-{self.max_pin})")
|
||||
if (message := self.fatal_pins.get(num)) is not None:
|
||||
raise cv.Invalid(message)
|
||||
|
||||
def validate_gpio_pin(self, value: int) -> int:
|
||||
self._check_pin_exists(value)
|
||||
if (message := self.error_pins.get(value)) is not None:
|
||||
raise cv.Invalid(message)
|
||||
if (message := self.warning_pins.get(value)) is not None:
|
||||
_LOGGER.warning("%s", message)
|
||||
if value in self.usb_jtag_pins:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is used by the USB-Serial-JTAG interface."
|
||||
" Using this pin as GPIO will conflict with USB-Serial-JTAG.",
|
||||
value,
|
||||
)
|
||||
return value
|
||||
|
||||
def validate_supports(self, value: ConfigType) -> ConfigType:
|
||||
num = value[CONF_NUMBER]
|
||||
self._check_pin_exists(num)
|
||||
hold = value.get(CONF_HOLD_STATE)
|
||||
if num in self.input_only_pins:
|
||||
prefix = f"GPIO{num}{self.input_only_label}"
|
||||
mode = value[CONF_MODE]
|
||||
for key, problem in _INPUT_ONLY_REJECTED_MODES:
|
||||
if mode[key]:
|
||||
raise cv.Invalid(f"{prefix} {problem}", [CONF_MODE, key])
|
||||
if hold:
|
||||
raise cv.Invalid(
|
||||
f"{prefix} is input-only and cannot be held.", [CONF_HOLD_STATE]
|
||||
)
|
||||
if hold and num in self.no_hold_pins:
|
||||
raise cv.Invalid(f"GPIO{num} has no hold function.", [CONF_HOLD_STATE])
|
||||
if hold and num in self.usb_jtag_pins:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d cannot hold at low level during wakeup from deep sleep.", num
|
||||
)
|
||||
check_strapping_pin(value, self.strapping_pins, _LOGGER)
|
||||
return value
|
||||
@@ -1,17 +1,4 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from esphome.components.const import CONF_HOLD_STATE
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_INPUT,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
from esphome.pins import check_strapping_pin
|
||||
from .gpio_common import ESP32PinSpec, unusable_errors
|
||||
|
||||
_ESP_SDIO_PINS = {
|
||||
6: "Flash Clock",
|
||||
@@ -20,66 +7,28 @@ _ESP_SDIO_PINS = {
|
||||
11: "Flash Command",
|
||||
}
|
||||
|
||||
_ESP32_STRAPPING_PINS = {0, 2, 5, 12, 15}
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 39:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
|
||||
if value in _ESP_SDIO_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32s and is already used by the flash interface"
|
||||
f" (function: {_ESP_SDIO_PINS[value]})."
|
||||
f" If you are using an ESP32 module that uses a different flash pin"
|
||||
f" configuration (e.g. ESP32-PICO-V3-02), you can set"
|
||||
f" 'ignore_pin_validation_error: true' to bypass this check."
|
||||
)
|
||||
if 9 <= value <= 10:
|
||||
_LOGGER.warning(
|
||||
"Pin %s (9-10) might already be used by the flash interface in QUAD IO flash mode.",
|
||||
value,
|
||||
)
|
||||
if value in (24, 28, 29, 30, 31):
|
||||
ESP32_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=39,
|
||||
strapping_pins=frozenset({0, 2, 5, 12, 15}),
|
||||
error_pins={
|
||||
**{
|
||||
num: "This pin cannot be used on ESP32s and is already used by the flash interface"
|
||||
f" (function: {function})."
|
||||
" If you are using an ESP32 module that uses a different flash pin"
|
||||
" configuration (e.g. ESP32-PICO-V3-02), you can set"
|
||||
" 'ignore_pin_validation_error: true' to bypass this check."
|
||||
for num, function in _ESP_SDIO_PINS.items()
|
||||
},
|
||||
# These pins are not exposed in GPIO mux (reason unknown)
|
||||
# but they're missing from IO_MUX list in datasheet
|
||||
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.")
|
||||
return value
|
||||
|
||||
|
||||
def esp32_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
is_output = mode[CONF_OUTPUT]
|
||||
is_pullup = mode[CONF_PULLUP]
|
||||
is_pulldown = mode[CONF_PULLDOWN]
|
||||
is_hold = value.get(CONF_HOLD_STATE)
|
||||
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
if is_output and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support output pin mode.",
|
||||
[CONF_MODE, CONF_OUTPUT],
|
||||
)
|
||||
if is_pullup and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP]
|
||||
)
|
||||
if is_pulldown and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
|
||||
)
|
||||
if is_hold and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) is input-only and cannot be held.",
|
||||
[CONF_HOLD_STATE],
|
||||
)
|
||||
if is_hold and num == 20:
|
||||
# Not in the digital hold mask and not an RTC pad, so the driver cannot hold it
|
||||
raise cv.Invalid("GPIO20 has no hold function.", [CONF_HOLD_STATE])
|
||||
|
||||
check_strapping_pin(value, _ESP32_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
**unusable_errors("ESP32", (24, 28, 29, 30, 31)),
|
||||
},
|
||||
warning_pins={
|
||||
num: f"Pin {num} (9-10) might already be used by the flash interface in QUAD IO flash mode."
|
||||
for num in (9, 10)
|
||||
},
|
||||
input_only_pins=frozenset(range(34, 40)),
|
||||
input_only_label=" (34-39)",
|
||||
# Not in the digital hold mask and not an RTC pad, so the driver cannot hold it
|
||||
no_hold_pins=frozenset({20}),
|
||||
)
|
||||
@@ -1,33 +1,3 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
from .gpio_common import ESP32PinSpec
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
_ESP32C2_STRAPPING_PINS = {8, 9}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_c2_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 20:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-20)")
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_c2_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 20:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-20)")
|
||||
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
|
||||
check_strapping_pin(value, _ESP32C2_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
ESP32_C2_PIN_SPEC = ESP32PinSpec(max_pin=20, strapping_pins=frozenset({8, 9}))
|
||||
@@ -1,52 +1,18 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
from .gpio_common import ESP32PinSpec, spi_psram_errors
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
_ESP32C3_SPI_PSRAM_PINS = {
|
||||
12: "SPIHD",
|
||||
13: "SPIWP",
|
||||
14: "SPICS0",
|
||||
15: "SPICLK",
|
||||
16: "SPID",
|
||||
17: "SPIQ",
|
||||
}
|
||||
|
||||
_ESP32C3_USB_JTAG_PINS = {18, 19}
|
||||
|
||||
_ESP32C3_STRAPPING_PINS = {2, 8, 9}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_c3_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 21:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
|
||||
if value in _ESP32C3_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-C3s and is already used by the SPI/PSRAM interface (function: {_ESP32C3_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
check_usb_jtag_pin(value, _ESP32C3_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_c3_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 21:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-21)")
|
||||
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
check_usb_jtag_hold(value, _ESP32C3_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
check_strapping_pin(value, _ESP32C3_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
ESP32_C3_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=21,
|
||||
strapping_pins=frozenset({2, 8, 9}),
|
||||
usb_jtag_pins=frozenset({18, 19}),
|
||||
error_pins=spi_psram_errors(
|
||||
"ESP32-C3",
|
||||
{
|
||||
12: "SPIHD",
|
||||
13: "SPIWP",
|
||||
14: "SPICS0",
|
||||
15: "SPICLK",
|
||||
16: "SPID",
|
||||
17: "SPIQ",
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -1,59 +1,28 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA
|
||||
from esphome.pins import check_strapping_pin
|
||||
from esphome.const import CONF_SCL, CONF_SDA
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
from .gpio_common import ESP32PinSpec, spi_psram_errors
|
||||
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/esp_hal_i2c/esp32c5/include/hal/i2c_ll.h
|
||||
_ESP32C5_I2C_LP_PINS = {"SDA": 2, "SCL": 3}
|
||||
|
||||
_ESP32C5_SPI_PSRAM_PINS = {
|
||||
16: "SPICS0",
|
||||
17: "SPIQ",
|
||||
18: "SPIWP",
|
||||
19: "VDD_SPI",
|
||||
20: "SPIHD",
|
||||
21: "SPICLK",
|
||||
22: "SPID",
|
||||
}
|
||||
|
||||
_ESP32C5_STRAPPING_PINS = {2, 7, 27, 28}
|
||||
|
||||
_ESP32C5_USB_JTAG_PINS = {13, 14}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_c5_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 28:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-28)")
|
||||
if value in _ESP32C5_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-C5s and is already used by the SPI/PSRAM interface (function: {_ESP32C5_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
|
||||
check_usb_jtag_pin(value, _ESP32C5_USB_JTAG_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
|
||||
def esp32_c5_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 28:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-28)")
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
|
||||
check_usb_jtag_hold(value, _ESP32C5_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
check_strapping_pin(value, _ESP32C5_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
ESP32_C5_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=28,
|
||||
strapping_pins=frozenset({2, 7, 27, 28}),
|
||||
usb_jtag_pins=frozenset({13, 14}),
|
||||
error_pins=spi_psram_errors(
|
||||
"ESP32-C5",
|
||||
{
|
||||
16: "SPICS0",
|
||||
17: "SPIQ",
|
||||
18: "SPIWP",
|
||||
19: "VDD_SPI",
|
||||
20: "SPIHD",
|
||||
21: "SPICLK",
|
||||
22: "SPID",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def esp32_c5_validate_lp_i2c(value):
|
||||
|
||||
@@ -1,58 +1,28 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA
|
||||
from esphome.pins import check_strapping_pin
|
||||
from esphome.const import CONF_SCL, CONF_SDA
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
from .gpio_common import ESP32PinSpec, spi_psram_errors
|
||||
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/esp_hal_i2c/esp32c6/include/hal/i2c_ll.h
|
||||
_ESP32C6_I2C_LP_PINS = {"SDA": 6, "SCL": 7}
|
||||
|
||||
_ESP32C6_SPI_PSRAM_PINS = {
|
||||
24: "SPICS0",
|
||||
25: "SPIQ",
|
||||
26: "SPIWP",
|
||||
27: "VDD_SPI",
|
||||
28: "SPIHD",
|
||||
29: "SPICLK",
|
||||
30: "SPID",
|
||||
}
|
||||
|
||||
_ESP32C6_USB_JTAG_PINS = {12, 13}
|
||||
|
||||
_ESP32C6_STRAPPING_PINS = {4, 5, 8, 9, 15}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_c6_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 30:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-30)")
|
||||
if value in _ESP32C6_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-C6s and is already used by the SPI/PSRAM interface (function: {_ESP32C6_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
check_usb_jtag_pin(value, _ESP32C6_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_c6_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 30:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-30)")
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
check_usb_jtag_hold(value, _ESP32C6_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
check_strapping_pin(value, _ESP32C6_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
ESP32_C6_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=30,
|
||||
strapping_pins=frozenset({4, 5, 8, 9, 15}),
|
||||
usb_jtag_pins=frozenset({12, 13}),
|
||||
error_pins=spi_psram_errors(
|
||||
"ESP32-C6",
|
||||
{
|
||||
24: "SPICS0",
|
||||
25: "SPIQ",
|
||||
26: "SPIWP",
|
||||
27: "VDD_SPI",
|
||||
28: "SPIHD",
|
||||
29: "SPICLK",
|
||||
30: "SPID",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def esp32_c6_validate_lp_i2c(value):
|
||||
|
||||
@@ -1,54 +1,20 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
from .gpio_common import ESP32PinSpec, spi_psram_errors
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
# GPIO14-17, GPIO19-21 are used for SPI flash/PSRAM
|
||||
_ESP32C61_SPI_PSRAM_PINS = {
|
||||
14: "SPICS0",
|
||||
15: "SPICLK",
|
||||
16: "SPID",
|
||||
17: "SPIQ",
|
||||
19: "SPIWP",
|
||||
20: "SPIHD",
|
||||
21: "VDD_SPI",
|
||||
}
|
||||
|
||||
_ESP32C61_STRAPPING_PINS = {8, 9}
|
||||
|
||||
_ESP32C61_USB_JTAG_PINS = {12, 13}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_c61_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 29:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-29)")
|
||||
if value in _ESP32C61_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-C61s and is already used by the SPI/PSRAM interface (function: {_ESP32C61_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
|
||||
check_usb_jtag_pin(value, _ESP32C61_USB_JTAG_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
|
||||
def esp32_c61_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 29:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-29)")
|
||||
if is_input:
|
||||
# All ESP32-C61 pins support input mode
|
||||
pass
|
||||
|
||||
check_usb_jtag_hold(value, _ESP32C61_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
check_strapping_pin(value, _ESP32C61_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
ESP32_C61_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=29,
|
||||
strapping_pins=frozenset({8, 9}),
|
||||
usb_jtag_pins=frozenset({12, 13}),
|
||||
# GPIO14-17, GPIO19-21 are used for SPI flash/PSRAM
|
||||
error_pins=spi_psram_errors(
|
||||
"ESP32-C61",
|
||||
{
|
||||
14: "SPICS0",
|
||||
15: "SPICLK",
|
||||
16: "SPID",
|
||||
17: "SPIQ",
|
||||
19: "SPIWP",
|
||||
20: "SPIHD",
|
||||
21: "VDD_SPI",
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -1,46 +1,13 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
from .gpio_common import ESP32PinSpec
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
_ESP32H2_SPI_FLASH_PINS = {6, 7, 15, 16, 17, 18, 19, 20, 21}
|
||||
|
||||
_ESP32H2_USB_JTAG_PINS = {26, 27}
|
||||
|
||||
_ESP32H2_STRAPPING_PINS = {8, 9, 25}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_h2_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 27:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-27)")
|
||||
if value in _ESP32H2_SPI_FLASH_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is reserved for SPI Flash communication on some ESP32-H2 chip variants.\n"
|
||||
"Utilizing SPI-reserved pins could cause unexpected failures.\n"
|
||||
"See https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/gpio.html",
|
||||
value,
|
||||
)
|
||||
check_usb_jtag_pin(value, _ESP32H2_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_h2_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 27:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-27)")
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
check_usb_jtag_hold(value, _ESP32H2_USB_JTAG_PINS, _LOGGER)
|
||||
check_strapping_pin(value, _ESP32H2_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
ESP32_H2_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=27,
|
||||
strapping_pins=frozenset({8, 9, 25}),
|
||||
usb_jtag_pins=frozenset({26, 27}),
|
||||
warning_pins={
|
||||
num: f"GPIO{num} is reserved for SPI Flash communication on some ESP32-H2 chip variants.\n"
|
||||
"Utilizing SPI-reserved pins could cause unexpected failures.\n"
|
||||
"See https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/gpio.html"
|
||||
for num in (6, 7, 15, 16, 17, 18, 19, 20, 21)
|
||||
},
|
||||
)
|
||||
@@ -1,41 +1,10 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
from .gpio_common import ESP32PinSpec
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
# Partial set from the ESP-IDF / esptool boot-mode docs:
|
||||
# https://docs.espressif.com/projects/esptool/en/latest/esp32h21/advanced-topics/boot-mode-selection.html
|
||||
# The full list awaits the ESP32-H21 datasheet's "Strapping Pins" section.
|
||||
_ESP32H21_STRAPPING_PINS: set[int] = {13, 14}
|
||||
|
||||
_ESP32H21_USB_JTAG_PINS = {17, 18}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_h21_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 25:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-25)")
|
||||
check_usb_jtag_pin(value, _ESP32H21_USB_JTAG_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
|
||||
def esp32_h21_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 25:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-25)")
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
|
||||
check_usb_jtag_hold(value, _ESP32H21_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
check_strapping_pin(value, _ESP32H21_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
ESP32_H21_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=25,
|
||||
# Partial set from the ESP-IDF / esptool boot-mode docs:
|
||||
# https://docs.espressif.com/projects/esptool/en/latest/esp32h21/advanced-topics/boot-mode-selection.html
|
||||
# The full list awaits the ESP32-H21 datasheet's "Strapping Pins" section.
|
||||
strapping_pins=frozenset({13, 14}),
|
||||
usb_jtag_pins=frozenset({17, 18}),
|
||||
)
|
||||
@@ -1,41 +1,10 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
from .gpio_common import ESP32PinSpec
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
|
||||
# Partial set from the ESP-IDF / esptool boot-mode docs:
|
||||
# https://docs.espressif.com/projects/esptool/en/latest/esp32h4/advanced-topics/boot-mode-selection.html
|
||||
# The full list awaits the ESP32-H4 datasheet's "Strapping Pins" section.
|
||||
_ESP32H4_STRAPPING_PINS: set[int] = {13, 14}
|
||||
|
||||
_ESP32H4_USB_JTAG_PINS = {13, 14}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_h4_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 39:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
|
||||
check_usb_jtag_pin(value, _ESP32H4_USB_JTAG_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
|
||||
def esp32_h4_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 39:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-39)")
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
|
||||
check_usb_jtag_hold(value, _ESP32H4_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
check_strapping_pin(value, _ESP32H4_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
ESP32_H4_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=39,
|
||||
# Partial set from the ESP-IDF / esptool boot-mode docs:
|
||||
# https://docs.espressif.com/projects/esptool/en/latest/esp32h4/advanced-topics/boot-mode-selection.html
|
||||
# The full list awaits the ESP32-H4 datasheet's "Strapping Pins" section.
|
||||
strapping_pins=frozenset({13, 14}),
|
||||
usb_jtag_pins=frozenset({13, 14}),
|
||||
)
|
||||
@@ -1,43 +1,16 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA
|
||||
from esphome.pins import check_strapping_pin
|
||||
from esphome.const import CONF_SCL, CONF_SDA
|
||||
|
||||
from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin
|
||||
from .gpio_common import ESP32PinSpec
|
||||
|
||||
# https://documentation.espressif.com/esp32-p4-chip-revision-v1.3_datasheet_en.pdf
|
||||
_ESP32P4_LP_PINS = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
|
||||
|
||||
_ESP32P4_USB_JTAG_PINS = {24, 25}
|
||||
|
||||
_ESP32P4_STRAPPING_PINS = {34, 35, 36, 37, 38}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_p4_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 54:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-54)")
|
||||
check_usb_jtag_pin(value, _ESP32P4_USB_JTAG_PINS, _LOGGER)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_p4_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 54:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-54)")
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
check_usb_jtag_hold(value, _ESP32P4_USB_JTAG_PINS, _LOGGER)
|
||||
check_strapping_pin(value, _ESP32P4_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
ESP32_P4_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=54,
|
||||
strapping_pins=frozenset({34, 35, 36, 37, 38}),
|
||||
usb_jtag_pins=frozenset({24, 25}),
|
||||
)
|
||||
|
||||
|
||||
def esp32_p4_validate_lp_i2c(value):
|
||||
|
||||
@@ -1,81 +1,30 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from esphome.components.const import CONF_HOLD_STATE
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_INPUT,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
_ESP32S2_SPI_PSRAM_PINS = {
|
||||
26: "SPICS1",
|
||||
27: "SPIHD",
|
||||
28: "SPIWP",
|
||||
29: "SPICS0",
|
||||
30: "SPICLK",
|
||||
31: "SPIQ",
|
||||
32: "SPID",
|
||||
}
|
||||
|
||||
_ESP32S2_STRAPPING_PINS = {0, 45, 46}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
from .gpio_common import ESP32PinSpec, spi_psram_errors, unusable_errors
|
||||
|
||||
|
||||
def esp32_s2_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 46:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)")
|
||||
|
||||
if value in _ESP32S2_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-S2s and is already used by the SPI/PSRAM interface (function: {_ESP32S2_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
|
||||
if value in (22, 23, 24, 25):
|
||||
def s2_s3_error_pins(variant: str) -> dict[int, str]:
|
||||
"""Errors for the flash/PSRAM and missing pins, which S2 and S3 share."""
|
||||
return {
|
||||
**spi_psram_errors(
|
||||
variant,
|
||||
{
|
||||
26: "SPICS1",
|
||||
27: "SPIHD",
|
||||
28: "SPIWP",
|
||||
29: "SPICS0",
|
||||
30: "SPICLK",
|
||||
31: "SPIQ",
|
||||
32: "SPID",
|
||||
},
|
||||
),
|
||||
# These pins are not exposed in GPIO mux (reason unknown)
|
||||
# but they're missing from IO_MUX list in datasheet
|
||||
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S2s.")
|
||||
|
||||
return value
|
||||
**unusable_errors(variant, (22, 23, 24, 25)),
|
||||
}
|
||||
|
||||
|
||||
def esp32_s2_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
is_output = mode[CONF_OUTPUT]
|
||||
is_pullup = mode[CONF_PULLUP]
|
||||
is_pulldown = mode[CONF_PULLDOWN]
|
||||
|
||||
if num < 0 or num > 46:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)")
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
if is_output and num == 46:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} does not support output pin mode.",
|
||||
[CONF_MODE, CONF_OUTPUT],
|
||||
)
|
||||
if is_pullup and num == 46:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} does not support pullups.", [CONF_MODE, CONF_PULLUP]
|
||||
)
|
||||
if is_pulldown and num == 46:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
|
||||
)
|
||||
if value.get(CONF_HOLD_STATE) and num == 46:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} is input-only and cannot be held.",
|
||||
[CONF_HOLD_STATE],
|
||||
)
|
||||
|
||||
check_strapping_pin(value, _ESP32S2_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
ESP32_S2_PIN_SPEC = ESP32PinSpec(
|
||||
max_pin=46,
|
||||
strapping_pins=frozenset({0, 45, 46}),
|
||||
error_pins=s2_s3_error_pins("ESP32-S2"),
|
||||
input_only_pins=frozenset({46}),
|
||||
)
|
||||
Loaded 100 of 675 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user