mirror of
https://github.com/esphome/esphome.git
synced 2026-09-15 17:18:40 +00:00
Merge remote-tracking branch 'upstream/dev' into remove_posix_tz_parser
# Conflicts: # esphome/components/time/posix_tz.cpp # esphome/components/time/posix_tz.h
This commit is contained in:
+172
-2
@@ -239,6 +239,123 @@ This document provides essential context for AI models interacting with this pro
|
||||
var = await switch.new_switch(config)
|
||||
```
|
||||
|
||||
* **Automations (Triggers, Actions, Conditions):**
|
||||
|
||||
Automations have three building blocks: **Triggers** (fire when something happens), **Actions** (do something), and **Conditions** (check if something is true).
|
||||
|
||||
* **Triggers -- Callback method (preferred):**
|
||||
|
||||
Use `build_callback_automation()` for simple triggers. This eliminates the need for a C++ Trigger class by using a lightweight pointer-sized forwarder struct registered directly as a callback. No `CONF_TRIGGER_ID` in the schema.
|
||||
|
||||
**Python:**
|
||||
```python
|
||||
from esphome import automation
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(MyComponent),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation({}),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_state_callback", [(bool, "x")], conf
|
||||
)
|
||||
```
|
||||
|
||||
`build_callback_automation` arguments: `parent`, `callback_method` (C++ method name), `args` (template args as `[(type, name)]` tuples), `config`, and optional `forwarder` (defaults to `TriggerForwarder<Ts...>`).
|
||||
|
||||
For boolean filtering (e.g. `on_press`/`on_release`), use built-in forwarders with `args=[]`:
|
||||
```python
|
||||
for conf_key, forwarder in (
|
||||
(CONF_ON_PRESS, automation.TriggerOnTrueForwarder),
|
||||
(CONF_ON_RELEASE, automation.TriggerOnFalseForwarder),
|
||||
):
|
||||
for conf in config.get(conf_key, []):
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_state_callback", [], conf, forwarder=forwarder
|
||||
)
|
||||
```
|
||||
|
||||
**C++ -- no trigger class needed.** The callback registration method must be templatized to accept both `std::function` and lightweight forwarder structs (which avoid heap allocation):
|
||||
```cpp
|
||||
class MyComponent : public Component {
|
||||
public:
|
||||
// Must be a template -- accepts both std::function and pointer-sized forwarder structs
|
||||
template<typename F> void add_on_state_callback(F &&callback) {
|
||||
this->state_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
protected:
|
||||
// Use CallbackManager when callbacks are always registered (e.g. core components)
|
||||
CallbackManager<void(bool)> state_callback_;
|
||||
// Use LazyCallbackManager when callbacks are often not registered -- saves 8 bytes
|
||||
// (nullptr vs empty std::vector) per instance when no callbacks are added
|
||||
// LazyCallbackManager<void(bool)> state_callback_;
|
||||
};
|
||||
```
|
||||
|
||||
* **Triggers -- Trigger class method:**
|
||||
|
||||
Use `build_automation()` with a `Trigger<Ts...>` subclass only when the forwarder needs **mutable state beyond a single `Automation*` pointer** (e.g. edge detection tracking previous state, timing logic).
|
||||
|
||||
**Python:**
|
||||
```python
|
||||
TurnOnTrigger = my_ns.class_("TurnOnTrigger", automation.Trigger.template())
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
|
||||
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TurnOnTrigger)}
|
||||
),
|
||||
})
|
||||
|
||||
async def to_code(config):
|
||||
for conf in config.get(CONF_ON_TURN_ON, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
```
|
||||
|
||||
**C++:**
|
||||
```cpp
|
||||
class TurnOnTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit TurnOnTrigger(MyComponent *parent) : last_on_{false} {
|
||||
parent->add_on_state_callback([this](bool state) {
|
||||
if (state && !this->last_on_)
|
||||
this->trigger();
|
||||
this->last_on_ = state;
|
||||
});
|
||||
}
|
||||
protected:
|
||||
bool last_on_;
|
||||
};
|
||||
```
|
||||
|
||||
* **Actions:**
|
||||
```cpp
|
||||
template<typename... Ts> class MyAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit MyAction(MyComponent *parent) : parent_(parent) {}
|
||||
void play(const Ts &...) override { this->parent_->do_something(); }
|
||||
protected:
|
||||
MyComponent *parent_;
|
||||
};
|
||||
```
|
||||
Register with `@automation.register_action("my_component.do_something", MyAction, schema, synchronous=True)`. Use `synchronous=True` for actions that run to completion inside `play()` without deferring. Use `synchronous=False` if the action may suspend/defer execution (e.g. `delay`, `wait_until`, `script.wait`) or store trigger arguments for later use.
|
||||
|
||||
* **Conditions:**
|
||||
```cpp
|
||||
template<typename... Ts> class MyCondition : public Condition<Ts...> {
|
||||
public:
|
||||
explicit MyCondition(MyComponent *parent) : parent_(parent) {}
|
||||
bool check(const Ts &...) override { return this->parent_->is_active(); }
|
||||
protected:
|
||||
MyComponent *parent_;
|
||||
};
|
||||
```
|
||||
Register with `@automation.register_condition("my_component.is_active", MyCondition, schema)`.
|
||||
|
||||
* **Configuration Validation:**
|
||||
* **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`.
|
||||
* **Complex Validation:** `cv.All(cv.string, cv.Length(min=1, max=50))`, `cv.Any(cv.int_, cv.string)`.
|
||||
@@ -274,10 +391,39 @@ This document provides essential context for AI models interacting with this pro
|
||||
* **Component Tests:** YAML-based compilation tests are located in `tests/`. The structure is as follows:
|
||||
```
|
||||
tests/
|
||||
├── test_build_components/ # Base test configurations
|
||||
└── components/[component]/ # Component-specific tests
|
||||
├── test_build_components/
|
||||
│ └── common/ # Shared bus packages (uart, i2c, spi, etc.)
|
||||
│ ├── uart/ # UART at default baud rate
|
||||
│ ├── uart_115200/ # UART at 115200 baud
|
||||
│ ├── i2c/ # I2C bus
|
||||
│ └── spi/ # SPI bus
|
||||
└── components/[component]/
|
||||
├── common.yaml # Component-only config (no bus definitions)
|
||||
├── test.esp32-idf.yaml
|
||||
├── test.esp8266-ard.yaml
|
||||
└── test.rp2040-ard.yaml
|
||||
```
|
||||
Run them using `script/test_build_components`. Use `-c <component>` to test specific components and `-t <target>` for specific platforms.
|
||||
|
||||
* **Test Grouping with Packages:** Components that use shared bus packages can be grouped together in CI to reduce build count. **Never define buses (uart, i2c, spi, modbus) directly in test YAML files** — always use packages from `test_build_components/common/`:
|
||||
```yaml
|
||||
# test.esp32-idf.yaml — use packages for buses
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart_115200/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
```
|
||||
```yaml
|
||||
# common.yaml — component config only, NO bus definitions
|
||||
my_component:
|
||||
id: my_instance
|
||||
|
||||
sensor:
|
||||
- platform: my_component
|
||||
name: My Sensor
|
||||
```
|
||||
Components that define buses directly are flagged as "NEEDS MIGRATION" and cannot be grouped, increasing CI build time.
|
||||
|
||||
* **Testing All Components Together:** To verify that all components can be tested together without ID conflicts or configuration issues, use:
|
||||
```bash
|
||||
./script/test_component_grouping.py -e config --all
|
||||
@@ -417,6 +563,30 @@ This document provides essential context for AI models interacting with this pro
|
||||
|
||||
Note: Avoiding heap allocation after `setup()` is always required regardless of component type. The prioritization above is about the effort spent on container optimization (e.g., migrating from `std::vector` to `StaticVector`).
|
||||
|
||||
**Callback Managers:**
|
||||
|
||||
ESPHome provides two callback manager types in `esphome/core/helpers.h` for the observer pattern. Both support `std::function`, lambdas, and lightweight forwarder structs via their templatized `add()` method.
|
||||
|
||||
| Type | Idle overhead (32-bit) | When to use |
|
||||
|------|----------------------|-------------|
|
||||
| `CallbackManager<void(Ts...)>` | 12 bytes (empty `std::vector`) | Callbacks are always or almost always registered |
|
||||
| `LazyCallbackManager<void(Ts...)>` | 4 bytes (`nullptr`) | Callbacks are often not registered (common case) |
|
||||
|
||||
`LazyCallbackManager` is a drop-in replacement for `CallbackManager` that defers allocation until the first callback is added. Prefer it for entity-level callbacks where most instances have no subscribers.
|
||||
|
||||
**Important:** Registration methods that add to a callback manager **must always be templatized** to accept both `std::function` and pointer-sized forwarder structs (used by `build_callback_automation`). Never use `std::function` in the method signature:
|
||||
```cpp
|
||||
// Bad -- forces heap allocation for forwarder structs
|
||||
void add_on_state_callback(std::function<void(bool)> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
// Good -- accepts any callable without forcing std::function wrapping
|
||||
template<typename F> void add_on_state_callback(F &&callback) {
|
||||
this->state_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
```
|
||||
|
||||
* **State Management:** Use `CORE.data` for component state that needs to persist during configuration generation. Avoid module-level mutable globals.
|
||||
|
||||
**Bad Pattern (Module-Level Globals):**
|
||||
|
||||
@@ -154,7 +154,7 @@ jobs:
|
||||
. venv/bin/activate
|
||||
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5.5.3
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Save Python virtual environment cache
|
||||
@@ -339,7 +339,7 @@ jobs:
|
||||
echo "binary=$BINARY" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run CodSpeed benchmarks
|
||||
uses: CodSpeedHQ/action@1c8ae4843586d3ba879736b7f6b7b0c990757fab # v4
|
||||
uses: CodSpeedHQ/action@d872884a306dd4853acf0f584f4b706cf0cc72a2 # v4
|
||||
with:
|
||||
run: ${{ steps.build.outputs.binary }}
|
||||
mode: simulation
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -86,6 +86,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.15.6
|
||||
rev: v0.15.8
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -92,6 +92,7 @@ esphome/components/bmp3xx_i2c/* @latonita
|
||||
esphome/components/bmp3xx_spi/* @latonita
|
||||
esphome/components/bmp581_base/* @danielkent-net @kahrendt
|
||||
esphome/components/bmp581_i2c/* @danielkent-net @kahrendt
|
||||
esphome/components/bmp581_spi/* @danielkent-net @kahrendt
|
||||
esphome/components/bp1658cj/* @Cossid
|
||||
esphome/components/bp5758d/* @Cossid
|
||||
esphome/components/bthome_mithermometer/* @nagyrobi
|
||||
@@ -216,6 +217,7 @@ esphome/components/hbridge/light/* @DotNetDann
|
||||
esphome/components/hbridge/switch/* @dwmw2
|
||||
esphome/components/hc8/* @omartijn
|
||||
esphome/components/hdc2010/* @optimusprimespace @ssieb
|
||||
esphome/components/hdc2080/* @G-Pereira @jesserockz
|
||||
esphome/components/hdc302x/* @joshuasing
|
||||
esphome/components/he60r/* @clydebarrow
|
||||
esphome/components/heatpumpir/* @rob-deutsch
|
||||
@@ -329,6 +331,7 @@ esphome/components/mipi_dsi/* @clydebarrow
|
||||
esphome/components/mipi_rgb/* @clydebarrow
|
||||
esphome/components/mipi_spi/* @clydebarrow
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/mitsubishi_cn105/* @crnjan
|
||||
esphome/components/mixer/speaker/* @kahrendt
|
||||
esphome/components/mlx90393/* @functionpointer
|
||||
esphome/components/mlx90614/* @jesserockz
|
||||
|
||||
+59
-5
@@ -137,6 +137,9 @@ UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
|
||||
SuspendComponentAction = cg.esphome_ns.class_("SuspendComponentAction", Action)
|
||||
ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action)
|
||||
Automation = cg.esphome_ns.class_("Automation")
|
||||
TriggerForwarder = cg.esphome_ns.class_("TriggerForwarder")
|
||||
TriggerOnTrueForwarder = cg.esphome_ns.class_("TriggerOnTrueForwarder")
|
||||
TriggerOnFalseForwarder = cg.esphome_ns.class_("TriggerOnFalseForwarder")
|
||||
|
||||
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
|
||||
StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", Condition)
|
||||
@@ -247,7 +250,9 @@ async def and_condition_to_code(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
|
||||
|
||||
@register_condition("or", OrCondition, validate_condition_list)
|
||||
@@ -258,7 +263,9 @@ async def or_condition_to_code(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
|
||||
|
||||
@register_condition("all", AndCondition, validate_condition_list)
|
||||
@@ -269,7 +276,9 @@ async def all_condition_to_code(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
|
||||
|
||||
@register_condition("any", OrCondition, validate_condition_list)
|
||||
@@ -280,7 +289,9 @@ async def any_condition_to_code(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
|
||||
|
||||
@register_condition("not", NotCondition, validate_potentially_and_condition)
|
||||
@@ -302,7 +313,9 @@ async def xor_condition_to_code(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
|
||||
|
||||
@register_condition("lambda", LambdaCondition, cv.returning_lambda)
|
||||
@@ -661,3 +674,44 @@ async def build_automation(
|
||||
actions = await build_action_list(config[CONF_THEN], templ, args)
|
||||
cg.add(obj.add_actions(actions))
|
||||
return obj
|
||||
|
||||
|
||||
async def build_callback_automation(
|
||||
parent: MockObj,
|
||||
callback_method: str,
|
||||
args: TemplateArgsType,
|
||||
config: ConfigType,
|
||||
forwarder: MockObj | MockObjClass | None = None,
|
||||
) -> None:
|
||||
"""Build an Automation and register it as a callback on the parent.
|
||||
|
||||
Eliminates the need for a Trigger wrapper object by registering the
|
||||
automation's trigger() directly as a callback on the parent component.
|
||||
|
||||
Uses template forwarder structs so the compiler deduplicates the operator()
|
||||
body across all call sites with the same signature. The forwarder must be
|
||||
pointer-sized (single Automation* field) to fit inline in Callback::ctx_
|
||||
and avoid heap allocation.
|
||||
|
||||
:param parent: The component object (e.g., button, sensor).
|
||||
:param callback_method: Name of the callback method (e.g., "add_on_press_callback").
|
||||
:param args: Automation template args as list of (type, name) tuples.
|
||||
:param config: The automation config dict.
|
||||
:param forwarder: Optional forwarder type to use instead of the default
|
||||
TriggerForwarder<Ts...>. Pass any struct type whose aggregate init takes
|
||||
a single Automation pointer (e.g., TriggerOnTrueForwarder).
|
||||
"""
|
||||
arg_types = [arg[0] for arg in args]
|
||||
templ = cg.TemplateArguments(*arg_types)
|
||||
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ)
|
||||
actions = await build_action_list(config[CONF_THEN], templ, args)
|
||||
cg.add(obj.add_actions(actions))
|
||||
# Use template forwarder structs for deduplication. The compiler generates
|
||||
# one operator() per forwarder type; different automation pointers are just
|
||||
# data in the struct.
|
||||
if forwarder is None:
|
||||
forwarder = TriggerForwarder.template(templ)
|
||||
# RawExpression for aggregate init — both forwarder and obj are codegen
|
||||
# MockObjs (not user input), and there's no Expression type for positional
|
||||
# aggregate initialization (StructInitializer uses named fields).
|
||||
cg.add(getattr(parent, callback_method)(cg.RawExpression(f"{forwarder}{{{obj}}}")))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "adc_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace adc {
|
||||
@@ -346,7 +347,8 @@ float ADCSensor::sample_autorange_() {
|
||||
ESP_LOGVV(TAG, "Autorange summary:");
|
||||
ESP_LOGVV(TAG, " Raw readings: 12db=%d, 6db=%d, 2.5db=%d, 0db=%d", raw12, raw6, raw2, raw0);
|
||||
ESP_LOGVV(TAG, " Voltages: 12db=%.6f, 6db=%.6f, 2.5db=%.6f, 0db=%.6f", mv12, mv6, mv2, mv0);
|
||||
ESP_LOGVV(TAG, " Coefficients: c12=%u, c6=%u, c2=%u, c0=%u, sum=%u", c12, c6, c2, c0, csum);
|
||||
ESP_LOGVV(TAG, " Coefficients: c12=%" PRIu32 ", c6=%" PRIu32 ", c2=%" PRIu32 ", c0=%" PRIu32 ", sum=%" PRIu32, c12,
|
||||
c6, c2, c0, csum);
|
||||
|
||||
if (csum == 0) {
|
||||
ESP_LOGE(TAG, "Invalid weight sum in autorange calculation");
|
||||
@@ -354,8 +356,10 @@ float ADCSensor::sample_autorange_() {
|
||||
}
|
||||
|
||||
const float final_result = (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
|
||||
ESP_LOGV(TAG, "Autorange final: (%.6f*%u + %.6f*%u + %.6f*%u + %.6f*%u)/%u = %.6fV", mv12, c12, mv6, c6, mv2, c2, mv0,
|
||||
c0, csum, final_result);
|
||||
ESP_LOGV(TAG,
|
||||
"Autorange final: (%.6f*%" PRIu32 " + %.6f*%" PRIu32 " + %.6f*%" PRIu32 " + %.6f*%" PRIu32 ")/%" PRIu32
|
||||
" = %.6fV",
|
||||
mv12, c12, mv6, c6, mv2, c2, mv0, c0, csum, final_result);
|
||||
|
||||
return final_result;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MQTT_ID,
|
||||
CONF_ON_STATE,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
@@ -34,39 +33,9 @@ CONF_ON_READY = "on_ready"
|
||||
alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel")
|
||||
AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase)
|
||||
|
||||
StateTrigger = alarm_control_panel_ns.class_(
|
||||
"StateTrigger", automation.Trigger.template()
|
||||
)
|
||||
TriggeredTrigger = alarm_control_panel_ns.class_(
|
||||
"TriggeredTrigger", automation.Trigger.template()
|
||||
)
|
||||
ClearedTrigger = alarm_control_panel_ns.class_(
|
||||
"ClearedTrigger", automation.Trigger.template()
|
||||
)
|
||||
ArmingTrigger = alarm_control_panel_ns.class_(
|
||||
"ArmingTrigger", automation.Trigger.template()
|
||||
)
|
||||
PendingTrigger = alarm_control_panel_ns.class_(
|
||||
"PendingTrigger", automation.Trigger.template()
|
||||
)
|
||||
ArmedHomeTrigger = alarm_control_panel_ns.class_(
|
||||
"ArmedHomeTrigger", automation.Trigger.template()
|
||||
)
|
||||
ArmedNightTrigger = alarm_control_panel_ns.class_(
|
||||
"ArmedNightTrigger", automation.Trigger.template()
|
||||
)
|
||||
ArmedAwayTrigger = alarm_control_panel_ns.class_(
|
||||
"ArmedAwayTrigger", automation.Trigger.template()
|
||||
)
|
||||
DisarmedTrigger = alarm_control_panel_ns.class_(
|
||||
"DisarmedTrigger", automation.Trigger.template()
|
||||
)
|
||||
ChimeTrigger = alarm_control_panel_ns.class_(
|
||||
"ChimeTrigger", automation.Trigger.template()
|
||||
)
|
||||
ReadyTrigger = alarm_control_panel_ns.class_(
|
||||
"ReadyTrigger", automation.Trigger.template()
|
||||
)
|
||||
StateAnyForwarder = alarm_control_panel_ns.class_("StateAnyForwarder")
|
||||
StateEnterForwarder = alarm_control_panel_ns.class_("StateEnterForwarder")
|
||||
AlarmControlPanelState = alarm_control_panel_ns.enum("AlarmControlPanelState")
|
||||
|
||||
ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action)
|
||||
ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action)
|
||||
@@ -89,61 +58,17 @@ _ALARM_CONTROL_PANEL_SCHEMA = (
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt.MQTTAlarmControlPanelComponent
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ARMING): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PENDING): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_DISARMED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_CHIME): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_READY): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_ARMING): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_PENDING): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_DISARMED): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_CLEARED): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_CHIME): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_READY): automation.validate_automation({}),
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -189,38 +114,39 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id(
|
||||
@setup_entity("alarm_control_panel")
|
||||
async def setup_alarm_control_panel_core_(var, config):
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_TRIGGERED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_ARMING, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_PENDING, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_ARMED_HOME, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_ARMED_NIGHT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_ARMED_AWAY, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_DISARMED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_state_callback", [], conf, forwarder=StateAnyForwarder
|
||||
)
|
||||
_STATE_ENTER_MAP = {
|
||||
CONF_ON_TRIGGERED: AlarmControlPanelState.ACP_STATE_TRIGGERED,
|
||||
CONF_ON_ARMING: AlarmControlPanelState.ACP_STATE_ARMING,
|
||||
CONF_ON_PENDING: AlarmControlPanelState.ACP_STATE_PENDING,
|
||||
CONF_ON_ARMED_HOME: AlarmControlPanelState.ACP_STATE_ARMED_HOME,
|
||||
CONF_ON_ARMED_NIGHT: AlarmControlPanelState.ACP_STATE_ARMED_NIGHT,
|
||||
CONF_ON_ARMED_AWAY: AlarmControlPanelState.ACP_STATE_ARMED_AWAY,
|
||||
CONF_ON_DISARMED: AlarmControlPanelState.ACP_STATE_DISARMED,
|
||||
}
|
||||
for conf_key, state_enum in _STATE_ENTER_MAP.items():
|
||||
for conf in config.get(conf_key, []):
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_on_state_callback",
|
||||
[],
|
||||
conf,
|
||||
forwarder=StateEnterForwarder.template(state_enum),
|
||||
)
|
||||
for conf in config.get(CONF_ON_CLEARED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_cleared_callback", [], conf
|
||||
)
|
||||
for conf in config.get(CONF_ON_CHIME, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_chime_callback", [], conf
|
||||
)
|
||||
for conf in config.get(CONF_ON_READY, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_ready_callback", [], conf
|
||||
)
|
||||
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):
|
||||
|
||||
@@ -35,8 +35,8 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
|
||||
LOG_STR_ARG(alarm_control_panel_state_to_string(state)),
|
||||
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
|
||||
this->current_state_ = state;
|
||||
// Single state callback - triggers check get_state() for specific states
|
||||
this->state_callback_.call();
|
||||
// Single state callback - listeners receive the new state as an argument
|
||||
this->state_callback_.call(state);
|
||||
#if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_alarm_control_panel_update(this);
|
||||
#endif
|
||||
|
||||
@@ -145,8 +145,8 @@ class AlarmControlPanel : public EntityBase {
|
||||
uint32_t last_update_;
|
||||
// the call control function
|
||||
virtual void control(const AlarmControlPanelCall &call) = 0;
|
||||
// state callback - triggers check get_state() for specific state
|
||||
LazyCallbackManager<void()> state_callback_{};
|
||||
// state callback - passes the new state to listeners
|
||||
LazyCallbackManager<void(AlarmControlPanelState)> state_callback_{};
|
||||
// clear callback - fires when leaving TRIGGERED state
|
||||
LazyCallbackManager<void()> cleared_callback_{};
|
||||
// chime callback
|
||||
|
||||
@@ -5,60 +5,27 @@
|
||||
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
/// Trigger on any state change
|
||||
class StateTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit StateTrigger(AlarmControlPanel *alarm_control_panel) {
|
||||
alarm_control_panel->add_on_state_callback([this]() { this->trigger(); });
|
||||
/// Callback forwarder that triggers an Automation<> on any state change.
|
||||
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
|
||||
struct StateAnyForwarder {
|
||||
Automation<> *automation;
|
||||
void operator()(AlarmControlPanelState /*state*/) const { this->automation->trigger(); }
|
||||
};
|
||||
|
||||
/// Callback forwarder that triggers an Automation<> only when the alarm enters a specific state.
|
||||
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
|
||||
template<AlarmControlPanelState State> struct StateEnterForwarder {
|
||||
Automation<> *automation;
|
||||
void operator()(AlarmControlPanelState state) const {
|
||||
if (state == State)
|
||||
this->automation->trigger();
|
||||
}
|
||||
};
|
||||
|
||||
/// Template trigger that fires when entering a specific state
|
||||
template<AlarmControlPanelState State> class StateEnterTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit StateEnterTrigger(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {
|
||||
alarm_control_panel->add_on_state_callback([this]() {
|
||||
if (this->alarm_control_panel_->get_state() == State)
|
||||
this->trigger();
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
AlarmControlPanel *alarm_control_panel_;
|
||||
};
|
||||
|
||||
// Type aliases for state-specific triggers
|
||||
using TriggeredTrigger = StateEnterTrigger<ACP_STATE_TRIGGERED>;
|
||||
using ArmingTrigger = StateEnterTrigger<ACP_STATE_ARMING>;
|
||||
using PendingTrigger = StateEnterTrigger<ACP_STATE_PENDING>;
|
||||
using ArmedHomeTrigger = StateEnterTrigger<ACP_STATE_ARMED_HOME>;
|
||||
using ArmedNightTrigger = StateEnterTrigger<ACP_STATE_ARMED_NIGHT>;
|
||||
using ArmedAwayTrigger = StateEnterTrigger<ACP_STATE_ARMED_AWAY>;
|
||||
using DisarmedTrigger = StateEnterTrigger<ACP_STATE_DISARMED>;
|
||||
|
||||
/// Trigger when leaving TRIGGERED state (alarm cleared)
|
||||
class ClearedTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) {
|
||||
alarm_control_panel->add_on_cleared_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
/// Trigger on chime event (zone opened while disarmed)
|
||||
class ChimeTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ChimeTrigger(AlarmControlPanel *alarm_control_panel) {
|
||||
alarm_control_panel->add_on_chime_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
/// Trigger on ready state change
|
||||
class ReadyTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ReadyTrigger(AlarmControlPanel *alarm_control_panel) {
|
||||
alarm_control_panel->add_on_ready_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(StateAnyForwarder) <= sizeof(void *));
|
||||
static_assert(std::is_trivially_copyable_v<StateAnyForwarder>);
|
||||
static_assert(sizeof(StateEnterForwarder<ACP_STATE_TRIGGERED>) <= sizeof(void *));
|
||||
static_assert(std::is_trivially_copyable_v<StateEnterForwarder<ACP_STATE_TRIGGERED>>);
|
||||
|
||||
template<typename... Ts> class ArmAwayAction : public Action<Ts...> {
|
||||
public:
|
||||
|
||||
@@ -132,8 +132,6 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); }
|
||||
|
||||
void APIConnection::start() {
|
||||
this->last_traffic_ = App.get_loop_component_start_time();
|
||||
|
||||
@@ -1465,7 +1463,7 @@ void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent
|
||||
void APIConnection::on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg) {
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %u out of range (max %u)", msg.instance,
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range (max %" PRIu32 ")", msg.instance,
|
||||
static_cast<uint32_t>(proxies.size()));
|
||||
return;
|
||||
}
|
||||
@@ -1476,7 +1474,7 @@ void APIConnection::on_serial_proxy_configure_request(const SerialProxyConfigure
|
||||
void APIConnection::on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) {
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
|
||||
return;
|
||||
}
|
||||
proxies[msg.instance]->write_from_client(msg.data, msg.data_len);
|
||||
@@ -1485,7 +1483,7 @@ void APIConnection::on_serial_proxy_write_request(const SerialProxyWriteRequest
|
||||
void APIConnection::on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg) {
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
|
||||
return;
|
||||
}
|
||||
proxies[msg.instance]->set_modem_pins(msg.line_states);
|
||||
@@ -1494,7 +1492,7 @@ void APIConnection::on_serial_proxy_set_modem_pins_request(const SerialProxySetM
|
||||
void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) {
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
|
||||
return;
|
||||
}
|
||||
SerialProxyGetModemPinsResponse resp{};
|
||||
@@ -1506,7 +1504,7 @@ void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetM
|
||||
void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
|
||||
return;
|
||||
}
|
||||
switch (msg.type) {
|
||||
@@ -1536,7 +1534,7 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown serial proxy request type: %u", static_cast<uint32_t>(msg.type));
|
||||
ESP_LOGW(TAG, "Unknown serial proxy request type: %" PRIu32, static_cast<uint32_t>(msg.type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2023,24 +2021,23 @@ uint16_t APIConnection::encode_to_buffer(uint32_t calculated_size, MessageEncode
|
||||
|
||||
auto &shared_buf = conn->parent_->get_shared_buffer_ref();
|
||||
|
||||
size_t to_add;
|
||||
if (conn->flags_.batch_first_message) {
|
||||
// First message - buffer already prepared by caller, just clear flag
|
||||
conn->flags_.batch_first_message = false;
|
||||
to_add = calculated_size;
|
||||
} else {
|
||||
// Batch message second or later
|
||||
// Add padding for previous message footer + this message header
|
||||
size_t current_size = shared_buf.size();
|
||||
shared_buf.reserve_and_resize(current_size + total_calculated_size, current_size + footer_size + header_padding);
|
||||
// Reserve for full message, resize to include footer gap + header padding + payload
|
||||
to_add = total_calculated_size;
|
||||
}
|
||||
|
||||
// Pre-resize buffer to include payload, then encode through raw pointer
|
||||
size_t write_start = shared_buf.size();
|
||||
shared_buf.resize(write_start + calculated_size);
|
||||
ProtoWriteBuffer buffer{&shared_buf, write_start};
|
||||
shared_buf.resize(shared_buf.size() + to_add);
|
||||
ProtoWriteBuffer buffer{&shared_buf, shared_buf.size() - calculated_size};
|
||||
encode_fn(msg, buffer);
|
||||
|
||||
// Return total size (header + payload + footer)
|
||||
return static_cast<uint16_t>(header_padding + calculated_size + footer_size);
|
||||
return static_cast<uint16_t>(total_calculated_size);
|
||||
}
|
||||
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
||||
const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE);
|
||||
@@ -2072,37 +2069,9 @@ void APIConnection::on_fatal_error() {
|
||||
this->flags_.remove = true;
|
||||
}
|
||||
|
||||
void __attribute__((flatten)) APIConnection::DeferredBatch::push_item(const BatchItem &item) { items.push_back(item); }
|
||||
|
||||
void APIConnection::DeferredBatch::add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
|
||||
uint8_t aux_data_index) {
|
||||
// Check if we already have a message of this type for this entity
|
||||
// This provides deduplication per entity/message_type combination
|
||||
// O(n) but optimized for RAM and not performance.
|
||||
// Skip deduplication for events - they are edge-triggered, every occurrence matters
|
||||
#ifdef USE_EVENT
|
||||
if (message_type != EventResponse::MESSAGE_TYPE)
|
||||
#endif
|
||||
{
|
||||
for (const auto &item : items) {
|
||||
if (item.entity == entity && item.message_type == message_type)
|
||||
return; // Already queued
|
||||
}
|
||||
}
|
||||
// No existing item found (or event), add new one
|
||||
this->push_item({entity, message_type, estimated_size, aux_data_index});
|
||||
}
|
||||
|
||||
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
|
||||
// Add high priority message and swap to front
|
||||
// This avoids expensive vector::insert which shifts all elements
|
||||
// Note: We only ever have one high-priority message at a time (ping OR disconnect)
|
||||
// If we're disconnecting, pings are blocked, so this simple swap is sufficient
|
||||
this->push_item({entity, message_type, estimated_size, AUX_DATA_UNUSED});
|
||||
if (items.size() > 1) {
|
||||
// Swap the new high-priority item to the front
|
||||
std::swap(items.front(), items.back());
|
||||
}
|
||||
bool APIConnection::schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
|
||||
this->deferred_batch_.add_item_front(entity, message_type, estimated_size);
|
||||
return this->schedule_batch_();
|
||||
}
|
||||
|
||||
bool APIConnection::send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
|
||||
|
||||
@@ -44,10 +44,22 @@ static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= AP
|
||||
static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH,
|
||||
"MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH");
|
||||
|
||||
#ifdef USE_BENCHMARK
|
||||
class APIConnection;
|
||||
void bench_enable_immediate_send(APIConnection *conn);
|
||||
void bench_clear_batch(APIConnection *conn);
|
||||
void bench_process_batch(APIConnection *conn);
|
||||
#endif
|
||||
|
||||
class APIConnection final : public APIServerConnectionBase {
|
||||
public:
|
||||
friend class APIServer;
|
||||
friend class ListEntitiesIterator;
|
||||
#ifdef USE_BENCHMARK
|
||||
friend void bench_enable_immediate_send(APIConnection *conn);
|
||||
friend void bench_clear_batch(APIConnection *conn);
|
||||
friend void bench_process_batch(APIConnection *conn);
|
||||
#endif
|
||||
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
|
||||
~APIConnection();
|
||||
|
||||
@@ -632,11 +644,28 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
|
||||
// Add item to the batch (with deduplication)
|
||||
void add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
|
||||
uint8_t aux_data_index = AUX_DATA_UNUSED);
|
||||
uint8_t aux_data_index = AUX_DATA_UNUSED) {
|
||||
// Dedup: O(n) scan but optimized for RAM over performance
|
||||
// Skip deduplication for events - they are edge-triggered, every occurrence matters
|
||||
#ifdef USE_EVENT
|
||||
if (message_type != EventResponse::MESSAGE_TYPE)
|
||||
#endif
|
||||
{
|
||||
for (const auto &item : this->items) {
|
||||
if (item.entity == entity && item.message_type == message_type)
|
||||
return; // Already queued
|
||||
}
|
||||
}
|
||||
this->items.push_back({entity, message_type, estimated_size, aux_data_index});
|
||||
}
|
||||
// Add item to the front of the batch (for high priority messages like ping)
|
||||
void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size);
|
||||
// Single push_back site to avoid duplicate _M_realloc_insert instantiation
|
||||
void push_item(const BatchItem &item);
|
||||
void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
|
||||
// Swap to front avoids expensive vector::insert which shifts all elements
|
||||
this->items.push_back({entity, message_type, estimated_size, AUX_DATA_UNUSED});
|
||||
if (this->items.size() > 1) {
|
||||
std::swap(this->items.front(), this->items.back());
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all items
|
||||
void clear() {
|
||||
@@ -701,7 +730,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
ActiveIterator active_iterator_{ActiveIterator::NONE};
|
||||
// Total: 2 (flags) + 2 + 2 + 1 = 7 bytes, then 1 byte padding to next 4-byte boundary
|
||||
|
||||
uint32_t get_batch_delay_ms_() const;
|
||||
uint32_t get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); }
|
||||
// Message will use 8 more bytes than the minimum size, and typical
|
||||
// MTU is 1500. Sometimes users will see as low as 1460 MTU.
|
||||
// If its IPv6 the header is 40 bytes, and if its IPv4
|
||||
@@ -768,10 +797,8 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
}
|
||||
|
||||
// Helper function to schedule a high priority message at the front of the batch
|
||||
bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
|
||||
this->deferred_batch_.add_item_front(entity, message_type, estimated_size);
|
||||
return this->schedule_batch_();
|
||||
}
|
||||
// Out-of-line: callers (on_shutdown, check_keepalive_) are cold paths
|
||||
bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size);
|
||||
|
||||
// Helper function to log client messages with name and peername
|
||||
void log_client_(int level, const LogString *message);
|
||||
|
||||
@@ -257,7 +257,13 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
uint32_t val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
|
||||
uint32_t val;
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
// Protobuf fixed32 is little-endian — direct load on LE platforms
|
||||
memcpy(&val, ptr, 4);
|
||||
#else
|
||||
val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
|
||||
#endif
|
||||
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
||||
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,9 @@ import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ANGLE,
|
||||
CONF_GAIN,
|
||||
CONF_ID,
|
||||
CONF_MAGNITUDE,
|
||||
CONF_POSITION,
|
||||
CONF_STATUS,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_MAGNET,
|
||||
@@ -21,7 +19,6 @@ DEPENDENCIES = ["as5600"]
|
||||
|
||||
AS5600Sensor = as5600_ns.class_("AS5600Sensor", sensor.Sensor, cg.PollingComponent)
|
||||
|
||||
CONF_RAW_ANGLE = "raw_angle"
|
||||
CONF_RAW_POSITION = "raw_position"
|
||||
CONF_SLOW_FILTER = "slow_filter"
|
||||
CONF_FAST_FILTER = "fast_filter"
|
||||
@@ -89,18 +86,6 @@ async def to_code(config):
|
||||
if out_of_range_mode_config := config.get(CONF_OUT_OF_RANGE_MODE):
|
||||
cg.add(var.set_out_of_range_mode(out_of_range_mode_config))
|
||||
|
||||
if angle_config := config.get(CONF_ANGLE):
|
||||
sens = await sensor.new_sensor(angle_config)
|
||||
cg.add(var.set_angle_sensor(sens))
|
||||
|
||||
if raw_angle_config := config.get(CONF_RAW_ANGLE):
|
||||
sens = await sensor.new_sensor(raw_angle_config)
|
||||
cg.add(var.set_raw_angle_sensor(sens))
|
||||
|
||||
if position_config := config.get(CONF_POSITION):
|
||||
sens = await sensor.new_sensor(position_config)
|
||||
cg.add(var.set_position_sensor(sens))
|
||||
|
||||
if raw_position_config := config.get(CONF_RAW_POSITION):
|
||||
sens = await sensor.new_sensor(raw_position_config)
|
||||
cg.add(var.set_raw_position_sensor(sens))
|
||||
|
||||
@@ -25,27 +25,10 @@ static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R
|
||||
void AS5600Sensor::dump_config() {
|
||||
LOG_SENSOR("", "AS5600 Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Out of Range Mode: %u", this->out_of_range_mode_);
|
||||
if (this->angle_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Angle Sensor", this->angle_sensor_);
|
||||
}
|
||||
if (this->raw_angle_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Raw Angle Sensor", this->raw_angle_sensor_);
|
||||
}
|
||||
if (this->position_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Position Sensor", this->position_sensor_);
|
||||
}
|
||||
if (this->raw_position_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Raw Position Sensor", this->raw_position_sensor_);
|
||||
}
|
||||
if (this->gain_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Gain Sensor", this->gain_sensor_);
|
||||
}
|
||||
if (this->magnitude_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Magnitude Sensor", this->magnitude_sensor_);
|
||||
}
|
||||
if (this->status_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Status Sensor", this->status_sensor_);
|
||||
}
|
||||
LOG_SENSOR(" ", "Raw Position Sensor", this->raw_position_sensor_);
|
||||
LOG_SENSOR(" ", "Gain Sensor", this->gain_sensor_);
|
||||
LOG_SENSOR(" ", "Magnitude Sensor", this->magnitude_sensor_);
|
||||
LOG_SENSOR(" ", "Status Sensor", this->status_sensor_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,6 @@ class AS5600Sensor : public PollingComponent, public Parented<AS5600Component>,
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_angle_sensor(sensor::Sensor *angle_sensor) { this->angle_sensor_ = angle_sensor; }
|
||||
void set_raw_angle_sensor(sensor::Sensor *raw_angle_sensor) { this->raw_angle_sensor_ = raw_angle_sensor; }
|
||||
void set_position_sensor(sensor::Sensor *position_sensor) { this->position_sensor_ = position_sensor; }
|
||||
void set_raw_position_sensor(sensor::Sensor *raw_position_sensor) {
|
||||
this->raw_position_sensor_ = raw_position_sensor;
|
||||
}
|
||||
@@ -28,9 +25,6 @@ class AS5600Sensor : public PollingComponent, public Parented<AS5600Component>,
|
||||
OutRangeMode get_out_of_range_mode() { return this->out_of_range_mode_; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *angle_sensor_{nullptr};
|
||||
sensor::Sensor *raw_angle_sensor_{nullptr};
|
||||
sensor::Sensor *position_sensor_{nullptr};
|
||||
sensor::Sensor *raw_position_sensor_{nullptr};
|
||||
sensor::Sensor *gain_sensor_{nullptr};
|
||||
sensor::Sensor *magnitude_sensor_{nullptr};
|
||||
|
||||
@@ -550,8 +550,8 @@ float ATM90E32Component::get_phase_harmonic_active_power_(uint8_t phase) {
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_phase_angle_(uint8_t phase) {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0;
|
||||
return (val > 180) ? (float) (val - 360.0f) : (float) val;
|
||||
float val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0f;
|
||||
return (val > 180.0f) ? val - 360.0f : val;
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_phase_peak_current_(uint8_t phase) {
|
||||
|
||||
@@ -134,7 +134,6 @@ class ATM90E32Component : public PollingComponent,
|
||||
void set_freq_status_text_sensor(text_sensor::TextSensor *sensor) { this->freq_status_text_sensor_ = sensor; }
|
||||
#endif
|
||||
uint16_t calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier);
|
||||
int32_t last_periodic_millis = millis();
|
||||
|
||||
protected:
|
||||
#ifdef USE_NUMBER
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "esphome/components/audio/audio_decoder.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome::audio_file {
|
||||
@@ -249,7 +250,7 @@ void AudioFileMediaSource::decode_task(void *params) {
|
||||
|
||||
audio::AudioStreamInfo stream_info = decoder->get_audio_stream_info().value();
|
||||
|
||||
ESP_LOGD(TAG, "Bits per sample: %d, Channels: %d, Sample rate: %d", stream_info.get_bits_per_sample(),
|
||||
ESP_LOGD(TAG, "Bits per sample: %d, Channels: %d, Sample rate: %" PRIu32, stream_info.get_bits_per_sample(),
|
||||
stream_info.get_channels(), stream_info.get_sample_rate());
|
||||
|
||||
if (stream_info.get_bits_per_sample() != 16 || stream_info.get_channels() > 2) {
|
||||
|
||||
@@ -120,25 +120,15 @@ BinarySensorInitiallyOff = binary_sensor_ns.class_(
|
||||
BinarySensorPtr = BinarySensor.operator("ptr")
|
||||
|
||||
# Triggers
|
||||
PressTrigger = binary_sensor_ns.class_("PressTrigger", automation.Trigger.template())
|
||||
ReleaseTrigger = binary_sensor_ns.class_(
|
||||
"ReleaseTrigger", automation.Trigger.template()
|
||||
)
|
||||
ClickTrigger = binary_sensor_ns.class_("ClickTrigger", automation.Trigger.template())
|
||||
DoubleClickTrigger = binary_sensor_ns.class_(
|
||||
"DoubleClickTrigger", automation.Trigger.template()
|
||||
)
|
||||
MultiClickTrigger = binary_sensor_ns.class_(
|
||||
"MultiClickTrigger", automation.Trigger.template(), cg.Component
|
||||
MultiClickTriggerBase = binary_sensor_ns.class_(
|
||||
"MultiClickTriggerBase", automation.Trigger.template(), cg.Component
|
||||
)
|
||||
MultiClickTrigger = binary_sensor_ns.class_("MultiClickTrigger", MultiClickTriggerBase)
|
||||
MultiClickTriggerEvent = binary_sensor_ns.struct("MultiClickTriggerEvent")
|
||||
StateTrigger = binary_sensor_ns.class_(
|
||||
"StateTrigger", automation.Trigger.template(bool)
|
||||
)
|
||||
StateChangeTrigger = binary_sensor_ns.class_(
|
||||
"StateChangeTrigger",
|
||||
automation.Trigger.template(cg.optional.template(bool), cg.optional.template(bool)),
|
||||
)
|
||||
|
||||
BinarySensorPublishAction = binary_sensor_ns.class_(
|
||||
"BinarySensorPublishAction", automation.Action
|
||||
@@ -266,6 +256,7 @@ async def delayed_off_filter_to_code(config, filter_id):
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
),
|
||||
cv.Length(max=254),
|
||||
),
|
||||
)
|
||||
async def autorepeat_filter_to_code(config, filter_id):
|
||||
@@ -294,7 +285,7 @@ async def autorepeat_filter_to_code(config, filter_id):
|
||||
),
|
||||
)
|
||||
]
|
||||
var = cg.new_Pvariable(filter_id, timings)
|
||||
var = cg.new_Pvariable(filter_id, cg.TemplateArguments(len(timings)), timings)
|
||||
await cg.register_component(var, {})
|
||||
return var
|
||||
|
||||
@@ -458,16 +449,8 @@ _BINARY_SENSOR_SCHEMA = (
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(CONF_FILTERS): validate_filters,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_RELEASE): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_CLICK): cv.All(
|
||||
automation.validate_automation(
|
||||
{
|
||||
@@ -502,23 +485,17 @@ _BINARY_SENSOR_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
|
||||
cv.Required(CONF_TIMING): cv.All(
|
||||
[parse_multi_click_timing_str], validate_multi_click_timing
|
||||
[parse_multi_click_timing_str],
|
||||
validate_multi_click_timing,
|
||||
cv.Length(min=1, max=255),
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_INVALID_COOLDOWN, default="1s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE_CHANGE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateChangeTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_STATE_CHANGE): automation.validate_automation({}),
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -556,13 +533,14 @@ def binary_sensor_schema(
|
||||
|
||||
@coroutine_with_priority(CoroPriority.AUTOMATION)
|
||||
async def _build_binary_sensor_automations(var, config):
|
||||
for conf in config.get(CONF_ON_PRESS, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_RELEASE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf_key, forwarder in (
|
||||
(CONF_ON_PRESS, automation.TriggerOnTrueForwarder),
|
||||
(CONF_ON_RELEASE, automation.TriggerOnFalseForwarder),
|
||||
):
|
||||
for conf in config.get(conf_key, []):
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_state_callback", [], conf, forwarder=forwarder
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_CLICK, []):
|
||||
trigger = cg.new_Pvariable(
|
||||
@@ -586,20 +564,23 @@ async def _build_binary_sensor_automations(var, config):
|
||||
)
|
||||
for tim in conf[CONF_TIMING]
|
||||
]
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings)
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], cg.TemplateArguments(len(timings)), var, timings
|
||||
)
|
||||
if CONF_INVALID_COOLDOWN in conf:
|
||||
cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN]))
|
||||
await cg.register_component(trigger, conf)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(bool, "x")], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_state_callback", [(bool, "x")], conf
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_STATE_CHANGE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger,
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_full_state_callback",
|
||||
[
|
||||
(cg.optional.template(bool), "x_previous"),
|
||||
(cg.optional.template(bool), "x"),
|
||||
|
||||
@@ -13,7 +13,7 @@ constexpr uint32_t MULTICLICK_COOLDOWN_ID = 1;
|
||||
constexpr uint32_t MULTICLICK_IS_VALID_ID = 2;
|
||||
constexpr uint32_t MULTICLICK_IS_NOT_VALID_ID = 3;
|
||||
|
||||
void MultiClickTrigger::on_state_(bool state) {
|
||||
void MultiClickTriggerBase::on_state_(bool state) {
|
||||
// Handle duplicate events
|
||||
if (state == this->last_state_) {
|
||||
return;
|
||||
@@ -32,7 +32,7 @@ void MultiClickTrigger::on_state_(bool state) {
|
||||
ESP_LOGV(TAG, "START min=%" PRIu32 " max=%" PRIu32, evt.min_length, evt.max_length);
|
||||
ESP_LOGV(TAG, "Multi Click: Starting multi click action!");
|
||||
this->at_index_ = 1;
|
||||
if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) {
|
||||
if (this->timing_count_ == 1 && evt.max_length == 4294967294UL) {
|
||||
this->set_timeout(MULTICLICK_TRIGGER_ID, evt.min_length, [this]() { this->trigger_(); });
|
||||
} else {
|
||||
this->schedule_is_valid_(evt.min_length);
|
||||
@@ -50,7 +50,7 @@ void MultiClickTrigger::on_state_(bool state) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (*this->at_index_ == this->timing_.size()) {
|
||||
if (*this->at_index_ == this->timing_count_) {
|
||||
this->trigger_();
|
||||
return;
|
||||
}
|
||||
@@ -61,7 +61,7 @@ void MultiClickTrigger::on_state_(bool state) {
|
||||
ESP_LOGV(TAG, "A i=%zu min=%" PRIu32 " max=%" PRIu32, *this->at_index_, evt.min_length, evt.max_length); // NOLINT
|
||||
this->schedule_is_valid_(evt.min_length);
|
||||
this->schedule_is_not_valid_(evt.max_length);
|
||||
} else if (*this->at_index_ + 1 != this->timing_.size()) {
|
||||
} else if (*this->at_index_ + 1 != this->timing_count_) {
|
||||
ESP_LOGV(TAG, "B i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
|
||||
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
|
||||
this->schedule_is_valid_(evt.min_length);
|
||||
@@ -74,7 +74,7 @@ void MultiClickTrigger::on_state_(bool state) {
|
||||
|
||||
*this->at_index_ = *this->at_index_ + 1;
|
||||
}
|
||||
void MultiClickTrigger::schedule_cooldown_() {
|
||||
void MultiClickTriggerBase::schedule_cooldown_() {
|
||||
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_);
|
||||
this->is_in_cooldown_ = true;
|
||||
this->set_timeout(MULTICLICK_COOLDOWN_ID, this->invalid_cooldown_, [this]() {
|
||||
@@ -86,7 +86,7 @@ void MultiClickTrigger::schedule_cooldown_() {
|
||||
this->cancel_timeout(MULTICLICK_IS_VALID_ID);
|
||||
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
|
||||
}
|
||||
void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
||||
void MultiClickTriggerBase::schedule_is_valid_(uint32_t min_length) {
|
||||
if (min_length == 0) {
|
||||
this->is_valid_ = true;
|
||||
return;
|
||||
@@ -97,19 +97,19 @@ void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
||||
this->is_valid_ = true;
|
||||
});
|
||||
}
|
||||
void MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
|
||||
void MultiClickTriggerBase::schedule_is_not_valid_(uint32_t max_length) {
|
||||
this->set_timeout(MULTICLICK_IS_NOT_VALID_ID, max_length, [this]() {
|
||||
ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS");
|
||||
this->is_valid_ = false;
|
||||
this->schedule_cooldown_();
|
||||
});
|
||||
}
|
||||
void MultiClickTrigger::cancel() {
|
||||
void MultiClickTriggerBase::cancel() {
|
||||
ESP_LOGV(TAG, "Multi Click: Sequence explicitly cancelled.");
|
||||
this->is_valid_ = false;
|
||||
this->schedule_cooldown_();
|
||||
}
|
||||
void MultiClickTrigger::trigger_() {
|
||||
void MultiClickTriggerBase::trigger_() {
|
||||
ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!");
|
||||
this->at_index_.reset();
|
||||
this->cancel_timeout(MULTICLICK_TRIGGER_ID);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <utility>
|
||||
|
||||
@@ -89,10 +90,10 @@ class DoubleClickTrigger : public Trigger<> {
|
||||
uint32_t max_length_; /// Maximum length of click. 0 means no maximum.
|
||||
};
|
||||
|
||||
class MultiClickTrigger : public Trigger<>, public Component {
|
||||
/// Non-template base for MultiClickTrigger (keeps large method bodies out of the header).
|
||||
class MultiClickTriggerBase : public Trigger<>, public Component {
|
||||
public:
|
||||
explicit MultiClickTrigger(BinarySensor *parent, std::initializer_list<MultiClickTriggerEvent> timing)
|
||||
: parent_(parent), timing_(timing) {}
|
||||
explicit MultiClickTriggerBase(BinarySensor *parent) : parent_(parent) {}
|
||||
|
||||
void setup() override {
|
||||
this->last_state_ = this->parent_->get_state_default(false);
|
||||
@@ -104,6 +105,8 @@ class MultiClickTrigger : public Trigger<>, public Component {
|
||||
void set_invalid_cooldown(uint32_t invalid_cooldown) { this->invalid_cooldown_ = invalid_cooldown; }
|
||||
|
||||
void cancel();
|
||||
MultiClickTriggerBase(const MultiClickTriggerBase &) = delete;
|
||||
MultiClickTriggerBase &operator=(const MultiClickTriggerBase &) = delete;
|
||||
|
||||
protected:
|
||||
void on_state_(bool state);
|
||||
@@ -113,14 +116,30 @@ class MultiClickTrigger : public Trigger<>, public Component {
|
||||
void trigger_();
|
||||
|
||||
BinarySensor *parent_;
|
||||
FixedVector<MultiClickTriggerEvent> timing_;
|
||||
const MultiClickTriggerEvent *timing_{nullptr};
|
||||
uint32_t invalid_cooldown_{1000};
|
||||
optional<size_t> at_index_{};
|
||||
uint8_t timing_count_{0};
|
||||
bool last_state_{false};
|
||||
bool is_in_cooldown_{false};
|
||||
bool is_valid_{false};
|
||||
};
|
||||
|
||||
/// Template wrapper that provides inline std::array storage for timing events.
|
||||
/// N is set by code generation to match the exact number of timing events configured in YAML.
|
||||
template<size_t N> class MultiClickTrigger : public MultiClickTriggerBase {
|
||||
public:
|
||||
MultiClickTrigger(BinarySensor *parent, std::initializer_list<MultiClickTriggerEvent> timing)
|
||||
: MultiClickTriggerBase(parent) {
|
||||
init_array_from(this->timing_storage_, timing);
|
||||
this->timing_ = this->timing_storage_.data();
|
||||
this->timing_count_ = N;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::array<MultiClickTriggerEvent, N> timing_storage_{};
|
||||
};
|
||||
|
||||
class StateTrigger : public Trigger<bool> {
|
||||
public:
|
||||
explicit StateTrigger(BinarySensor *parent) {
|
||||
|
||||
@@ -32,13 +32,6 @@ void BinarySensor::publish_initial_state(bool new_state) {
|
||||
this->invalidate_state();
|
||||
this->publish_state(new_state);
|
||||
}
|
||||
void BinarySensor::send_state_internal(bool new_state) {
|
||||
// copy the new state to the visible property for backwards compatibility, before any callbacks
|
||||
this->state = new_state;
|
||||
// Note that set_new_state_ de-dups and will only trigger callbacks if the state has actually changed
|
||||
this->set_new_state(new_state);
|
||||
}
|
||||
|
||||
bool BinarySensor::set_new_state(const optional<bool> &new_state) {
|
||||
if (StatefulEntityBase::set_new_state(new_state)) {
|
||||
// weirdly, this file could be compiled even without USE_BINARY_SENSOR defined
|
||||
|
||||
@@ -32,7 +32,10 @@ void log_binary_sensor(const char *tag, const char *prefix, const char *type, Bi
|
||||
*/
|
||||
class BinarySensor : public StatefulEntityBase<bool> {
|
||||
public:
|
||||
explicit BinarySensor(){};
|
||||
explicit BinarySensor() = default;
|
||||
|
||||
const bool &get_state() const override { return this->state; }
|
||||
void set_trigger_on_initial_state(bool value) { this->trigger_on_initial_state_ = value; }
|
||||
|
||||
/** Publish a new state to the front-end.
|
||||
*
|
||||
@@ -54,16 +57,24 @@ class BinarySensor : public StatefulEntityBase<bool> {
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void send_state_internal(bool new_state);
|
||||
void send_state_internal(bool new_state) {
|
||||
// Fast path: skip virtual dispatch when state hasn't changed
|
||||
if (this->flags_.has_state && this->state == new_state)
|
||||
return;
|
||||
this->set_new_state(new_state);
|
||||
}
|
||||
|
||||
/// Return whether this binary sensor has outputted a state.
|
||||
virtual bool is_status_binary_sensor() const;
|
||||
|
||||
// For backward compatibility, provide an accessible property
|
||||
|
||||
/// The current state of this binary sensor. Also used as the backing storage for StatefulEntityBase.
|
||||
bool state{};
|
||||
|
||||
protected:
|
||||
bool get_trigger_on_initial_state() const override { return this->trigger_on_initial_state_; }
|
||||
void set_state_value(const bool &value) override { this->state = value; }
|
||||
|
||||
bool trigger_on_initial_state_{true};
|
||||
#ifdef USE_BINARY_SENSOR_FILTER
|
||||
Filter *filter_list_{nullptr};
|
||||
#endif
|
||||
@@ -73,7 +84,7 @@ class BinarySensor : public StatefulEntityBase<bool> {
|
||||
|
||||
class BinarySensorInitiallyOff : public BinarySensor {
|
||||
public:
|
||||
bool has_state() const override { return true; }
|
||||
BinarySensorInitiallyOff() { this->set_has_state(true); }
|
||||
};
|
||||
|
||||
} // namespace esphome::binary_sensor
|
||||
|
||||
@@ -76,14 +76,11 @@ float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARD
|
||||
|
||||
optional<bool> InvertFilter::new_value(bool value) { return !value; }
|
||||
|
||||
AutorepeatFilter::AutorepeatFilter(std::initializer_list<AutorepeatFilterTiming> timings) : timings_(timings) {}
|
||||
|
||||
optional<bool> AutorepeatFilter::new_value(bool value) {
|
||||
// AutorepeatFilterBase
|
||||
optional<bool> AutorepeatFilterBase::new_value(bool value) {
|
||||
if (value) {
|
||||
// Ignore if already running
|
||||
if (this->active_timing_ != 0)
|
||||
return {};
|
||||
|
||||
this->next_timing_();
|
||||
return true;
|
||||
} else {
|
||||
@@ -94,34 +91,26 @@ optional<bool> AutorepeatFilter::new_value(bool value) {
|
||||
}
|
||||
}
|
||||
|
||||
void AutorepeatFilter::next_timing_() {
|
||||
// Entering this method
|
||||
// 1st time: starts waiting the first delay
|
||||
// 2nd time: starts waiting the second delay and starts toggling with the first time_off / _on
|
||||
// last time: no delay to start but have to bump the index to reflect the last
|
||||
if (this->active_timing_ < this->timings_.size()) {
|
||||
void AutorepeatFilterBase::next_timing_() {
|
||||
if (this->active_timing_ < this->timings_count_) {
|
||||
this->set_timeout(AUTOREPEAT_TIMING_ID, this->timings_[this->active_timing_].delay,
|
||||
[this]() { this->next_timing_(); });
|
||||
}
|
||||
|
||||
if (this->active_timing_ <= this->timings_.size()) {
|
||||
if (this->active_timing_ <= this->timings_count_) {
|
||||
this->active_timing_++;
|
||||
}
|
||||
|
||||
if (this->active_timing_ == 2)
|
||||
this->next_value_(false);
|
||||
|
||||
// Leaving this method: if the toggling is started, it has to use [active_timing_ - 2] for the intervals
|
||||
}
|
||||
|
||||
void AutorepeatFilter::next_value_(bool val) {
|
||||
void AutorepeatFilterBase::next_value_(bool val) {
|
||||
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
|
||||
this->output(val); // This is at least the second one so not initial
|
||||
this->output(val);
|
||||
this->set_timeout(AUTOREPEAT_ON_OFF_ID, val ? timing.time_on : timing.time_off,
|
||||
[this, val]() { this->next_value_(!val); });
|
||||
}
|
||||
|
||||
float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
float AutorepeatFilterBase::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move(f)) {}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_BINARY_SENSOR_FILTER
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -86,22 +88,39 @@ struct AutorepeatFilterTiming {
|
||||
uint32_t time_on;
|
||||
};
|
||||
|
||||
class AutorepeatFilter : public Filter, public Component {
|
||||
/// Non-template base for AutorepeatFilter — all methods in filter.cpp.
|
||||
/// Lambdas capture this base pointer, so set_timeout/cancel_timeout are instantiated once.
|
||||
class AutorepeatFilterBase : public Filter, public Component {
|
||||
public:
|
||||
explicit AutorepeatFilter(std::initializer_list<AutorepeatFilterTiming> timings);
|
||||
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
AutorepeatFilterBase(const AutorepeatFilterBase &) = delete;
|
||||
AutorepeatFilterBase &operator=(const AutorepeatFilterBase &) = delete;
|
||||
|
||||
protected:
|
||||
AutorepeatFilterBase() = default;
|
||||
void next_timing_();
|
||||
void next_value_(bool val);
|
||||
|
||||
FixedVector<AutorepeatFilterTiming> timings_;
|
||||
const AutorepeatFilterTiming *timings_{nullptr};
|
||||
uint8_t timings_count_{0};
|
||||
uint8_t active_timing_{0};
|
||||
};
|
||||
|
||||
/// Template wrapper that provides inline std::array storage for timings.
|
||||
/// N is set by code generation to match the exact number of timings configured in YAML.
|
||||
template<size_t N> class AutorepeatFilter : public AutorepeatFilterBase {
|
||||
public:
|
||||
explicit AutorepeatFilter(std::initializer_list<AutorepeatFilterTiming> timings) {
|
||||
init_array_from(this->timings_storage_, timings);
|
||||
this->timings_ = this->timings_storage_.data();
|
||||
this->timings_count_ = N;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::array<AutorepeatFilterTiming, N> timings_storage_{};
|
||||
};
|
||||
|
||||
class LambdaFilter : public Filter {
|
||||
public:
|
||||
explicit LambdaFilter(std::function<optional<bool>(bool)> f);
|
||||
|
||||
@@ -124,7 +124,7 @@ def set_reference_values(config):
|
||||
config.setdefault(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_LEGACY_UREF)
|
||||
config.setdefault(CONF_CURRENT_REFERENCE, DEFAULT_BL0940_LEGACY_IREF)
|
||||
config.setdefault(CONF_POWER_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
|
||||
config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
|
||||
config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_EREF)
|
||||
else:
|
||||
vref = config.get(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_VREF)
|
||||
r_one = config.get(CONF_RESISTOR_ONE, DEFAULT_BL0940_R1)
|
||||
|
||||
@@ -30,6 +30,19 @@ void BluetoothProxy::setup() {
|
||||
this->configured_scan_active_ = this->parent_->get_scan_active();
|
||||
|
||||
this->parent_->add_scanner_state_listener(this);
|
||||
|
||||
this->set_interval(100, [this]() {
|
||||
if (api::global_api_server->is_connected() && this->api_connection_ != nullptr) {
|
||||
this->flush_pending_advertisements_();
|
||||
return;
|
||||
}
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
auto *connection = this->connections_[i];
|
||||
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
|
||||
connection->disconnect();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void BluetoothProxy::on_scanner_state(esp32_ble_tracker::ScannerState state) {
|
||||
@@ -101,25 +114,15 @@ bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results,
|
||||
|
||||
// Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE
|
||||
if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) {
|
||||
this->flush_pending_advertisements();
|
||||
this->flush_pending_advertisements_();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BluetoothProxy::flush_pending_advertisements() {
|
||||
if (this->response_.advertisements_len == 0 || !api::global_api_server->is_connected() ||
|
||||
this->api_connection_ == nullptr)
|
||||
return;
|
||||
|
||||
// Send the message
|
||||
this->api_connection_->send_message(this->response_);
|
||||
|
||||
void BluetoothProxy::log_advertisement_flush_() {
|
||||
ESP_LOGV(TAG, "Sent batch of %u BLE advertisements", this->response_.advertisements_len);
|
||||
|
||||
// Reset the length for the next batch
|
||||
this->response_.advertisements_len = 0;
|
||||
}
|
||||
|
||||
void BluetoothProxy::dump_config() {
|
||||
@@ -130,27 +133,6 @@ void BluetoothProxy::dump_config() {
|
||||
YESNO(this->active_), this->connection_count_);
|
||||
}
|
||||
|
||||
void BluetoothProxy::loop() {
|
||||
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
auto *connection = this->connections_[i];
|
||||
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
|
||||
connection->disconnect();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Flush any pending BLE advertisements that have been accumulated but not yet sent
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
// Flush accumulated advertisements every 100ms
|
||||
if (now - this->last_advertisement_flush_time_ >= 100) {
|
||||
this->flush_pending_advertisements();
|
||||
this->last_advertisement_flush_time_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() {
|
||||
return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS;
|
||||
}
|
||||
|
||||
@@ -65,8 +65,6 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
bool parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) override;
|
||||
void dump_config() override;
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void flush_pending_advertisements();
|
||||
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
|
||||
|
||||
void register_connection(BluetoothConnection *connection) {
|
||||
@@ -150,6 +148,18 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
protected:
|
||||
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
|
||||
|
||||
/// Caller must ensure api_connection_ is non-null and API server is connected.
|
||||
void flush_pending_advertisements_() {
|
||||
if (this->response_.advertisements_len == 0)
|
||||
return;
|
||||
this->api_connection_->send_message(this->response_);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
this->log_advertisement_flush_();
|
||||
#endif
|
||||
this->response_.advertisements_len = 0;
|
||||
}
|
||||
void log_advertisement_flush_();
|
||||
|
||||
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
|
||||
void log_connection_request_ignored_(BluetoothConnection *connection, espbt::ClientState state);
|
||||
void log_connection_info_(BluetoothConnection *connection, const char *message);
|
||||
@@ -166,9 +176,6 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
// BLE advertisement batching
|
||||
api::BluetoothLERawAdvertisementsResponse response_;
|
||||
|
||||
// Group 3: 4-byte types
|
||||
uint32_t last_advertisement_flush_time_{0};
|
||||
|
||||
// Pre-allocated response message - always ready to send
|
||||
api::BluetoothConnectionsFreeResponse connections_free_response_;
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include "bm8563.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::bm8563 {
|
||||
@@ -146,10 +149,10 @@ optional<uint8_t> BM8563::read_register_(uint8_t reg) {
|
||||
}
|
||||
|
||||
void BM8563::set_timer_irq_(uint32_t duration_s) {
|
||||
ESP_LOGI(TAG, "Timer Duration: %u s", duration_s);
|
||||
ESP_LOGI(TAG, "Timer Duration: %" PRIu32 " s", duration_s);
|
||||
|
||||
if (duration_s > MAX_TIMER_DURATION_S) {
|
||||
ESP_LOGW(TAG, "Timer duration %u s exceeds maximum %u s", duration_s, MAX_TIMER_DURATION_S);
|
||||
ESP_LOGW(TAG, "Timer duration %" PRIu32 " s exceeds maximum %" PRIu32 " s", duration_s, MAX_TIMER_DURATION_S);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@
|
||||
#ifdef USE_BSEC2
|
||||
#include "bme68x_bsec2.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace bme68x_bsec2 {
|
||||
namespace esphome::bme68x_bsec2 {
|
||||
|
||||
#define BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(a) (a == ALGORITHM_OUTPUT_CLASSIFICATION ? "Classification" : "Regression")
|
||||
#define BME68X_BSEC2_OPERATING_AGE_LOG(o) (o == OPERATING_AGE_4D ? "4 days" : "28 days")
|
||||
@@ -18,9 +15,19 @@ namespace bme68x_bsec2 {
|
||||
|
||||
static const char *const TAG = "bme68x_bsec2.sensor";
|
||||
|
||||
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
|
||||
static constexpr const char *const IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
|
||||
|
||||
static bool is_no_new_data_warning(int8_t status) {
|
||||
#ifdef BME68X_W_NO_NEW_DATA
|
||||
return status == BME68X_W_NO_NEW_DATA;
|
||||
#else
|
||||
return status == 2;
|
||||
#endif
|
||||
}
|
||||
|
||||
void BME68xBSEC2Component::setup() {
|
||||
this->warn_if_blocking_over_ = 60; // initial reads may block for up to 60ms
|
||||
|
||||
this->bsec_status_ = bsec_init_m(&this->bsec_instance_);
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
this->mark_failed();
|
||||
@@ -82,7 +89,7 @@ void BME68xBSEC2Component::dump_config() {
|
||||
" Operating age: %s\n"
|
||||
" Sample rate: %s\n"
|
||||
" Voltage: %s\n"
|
||||
" State save interval: %ims\n"
|
||||
" State save interval: %" PRIu32 "ms\n"
|
||||
" Temperature offset: %.2f",
|
||||
BME68X_BSEC2_OPERATING_AGE_LOG(this->operating_age_), BME68X_BSEC2_SAMPLE_RATE_LOG(this->sample_rate_),
|
||||
BME68X_BSEC2_VOLTAGE_LOG(this->voltage_), this->state_save_interval_ms_, this->temperature_offset_);
|
||||
@@ -114,7 +121,8 @@ void BME68xBSEC2Component::loop() {
|
||||
} else {
|
||||
this->status_clear_error();
|
||||
}
|
||||
if (this->bsec_status_ > BSEC_OK || this->bme68x_status_ > BME68X_OK) {
|
||||
const bool has_bme68x_warning = this->bme68x_status_ > BME68X_OK && !is_no_new_data_warning(this->bme68x_status_);
|
||||
if (this->bsec_status_ > BSEC_OK || has_bme68x_warning) {
|
||||
this->status_set_warning();
|
||||
} else {
|
||||
this->status_clear_warning();
|
||||
@@ -130,7 +138,7 @@ void BME68xBSEC2Component::loop() {
|
||||
|
||||
void BME68xBSEC2Component::set_config_(const uint8_t *config, uint32_t len) {
|
||||
if (len > BSEC_MAX_PROPERTY_BLOB_SIZE) {
|
||||
ESP_LOGE(TAG, "Configuration is larger than BSEC_MAX_PROPERTY_BLOB_SIZE");
|
||||
ESP_LOGE(TAG, "Configuration blob too large");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -212,14 +220,12 @@ void BME68xBSEC2Component::run_() {
|
||||
if (curr_time_ns < this->bsec_settings_.next_call) {
|
||||
return;
|
||||
}
|
||||
uint8_t status;
|
||||
|
||||
ESP_LOGV(TAG, "Performing sensor run");
|
||||
|
||||
struct bme68x_conf bme68x_conf;
|
||||
this->bsec_status_ = bsec_sensor_control_m(&this->bsec_instance_, curr_time_ns, &this->bsec_settings_);
|
||||
if (this->bsec_status_ < BSEC_OK) {
|
||||
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC2 error code %d)", this->bsec_status_);
|
||||
ESP_LOGW(TAG, "Fetching control settings failed (BSEC2 error code %d)", this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -235,9 +241,9 @@ void BME68xBSEC2Component::run_() {
|
||||
this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature;
|
||||
this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration;
|
||||
|
||||
// status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_);
|
||||
status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
|
||||
status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_);
|
||||
// this->bme68x_status_ = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_);
|
||||
this->bme68x_status_ = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
|
||||
this->bme68x_status_ = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_);
|
||||
this->op_mode_ = BME68X_FORCED_MODE;
|
||||
ESP_LOGV(TAG, "Using forced mode");
|
||||
|
||||
@@ -259,9 +265,8 @@ void BME68xBSEC2Component::run_() {
|
||||
BSEC_TOTAL_HEAT_DUR -
|
||||
(bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000));
|
||||
|
||||
status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
|
||||
|
||||
status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_);
|
||||
this->bme68x_status_ = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
|
||||
this->bme68x_status_ = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_);
|
||||
this->op_mode_ = BME68X_PARALLEL_MODE;
|
||||
ESP_LOGV(TAG, "Using parallel mode");
|
||||
}
|
||||
@@ -278,28 +283,19 @@ void BME68xBSEC2Component::run_() {
|
||||
if (this->bsec_settings_.trigger_measurement && this->bsec_settings_.op_mode != BME68X_SLEEP_MODE) {
|
||||
bme68x_get_conf(&bme68x_conf, &this->bme68x_);
|
||||
uint32_t meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_);
|
||||
ESP_LOGV(TAG, "Queueing read in %uus", meas_dur);
|
||||
ESP_LOGV(TAG, "Queueing read in %" PRIu32 "us", meas_dur);
|
||||
this->trigger_time_ns_ = curr_time_ns;
|
||||
this->set_timeout("read", meas_dur / 1000, [this]() { this->read_(this->trigger_time_ns_); });
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Measurement not required");
|
||||
this->read_(curr_time_ns);
|
||||
ESP_LOGV(TAG, "Measurement not required, queueing immediate read");
|
||||
this->trigger_time_ns_ = curr_time_ns;
|
||||
this->set_timeout("read", 0, [this]() { this->read_(this->trigger_time_ns_); });
|
||||
}
|
||||
}
|
||||
|
||||
void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
|
||||
ESP_LOGV(TAG, "Reading data");
|
||||
|
||||
if (this->bsec_settings_.trigger_measurement) {
|
||||
uint8_t current_op_mode;
|
||||
this->bme68x_status_ = bme68x_get_op_mode(¤t_op_mode, &this->bme68x_);
|
||||
|
||||
if (current_op_mode == BME68X_SLEEP_MODE) {
|
||||
ESP_LOGV(TAG, "Still in sleep mode, doing nothing");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->bsec_settings_.process_data) {
|
||||
ESP_LOGV(TAG, "Data processing not required");
|
||||
return;
|
||||
@@ -309,12 +305,16 @@ void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
|
||||
uint8_t nFields = 0;
|
||||
this->bme68x_status_ = bme68x_get_data(this->op_mode_, &data[0], &nFields, &this->bme68x_);
|
||||
|
||||
if (is_no_new_data_warning(this->bme68x_status_)) {
|
||||
ESP_LOGV(TAG, "BME68X did not provide new data");
|
||||
return;
|
||||
}
|
||||
if (this->bme68x_status_ != BME68X_OK) {
|
||||
ESP_LOGW(TAG, "Failed to get sensor data (BME68X error code %d)", this->bme68x_status_);
|
||||
ESP_LOGW(TAG, "Fetching data failed (BME68X error code %d)", this->bme68x_status_);
|
||||
return;
|
||||
}
|
||||
if (nFields < 1) {
|
||||
ESP_LOGD(TAG, "BME68X did not provide new data");
|
||||
ESP_LOGV(TAG, "BME68X did not provide new fields");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -373,7 +373,7 @@ void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
|
||||
uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
|
||||
this->bsec_status_ = bsec_do_steps_m(&this->bsec_instance_, inputs, num_inputs, outputs, &num_outputs);
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
ESP_LOGW(TAG, "BSEC2 failed to process signals (BSEC2 error code %d)", this->bsec_status_);
|
||||
ESP_LOGW(TAG, "Signal processing failed (BSEC2 error code %d)", this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
if (num_outputs < 1) {
|
||||
@@ -474,7 +474,7 @@ void BME68xBSEC2Component::publish_sensor_(sensor::Sensor *sensor, float value,
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void BME68xBSEC2Component::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) {
|
||||
void BME68xBSEC2Component::publish_sensor_(text_sensor::TextSensor *sensor, const char *value) {
|
||||
if (!sensor || (sensor->has_state() && sensor->state == value)) {
|
||||
return;
|
||||
}
|
||||
@@ -526,6 +526,5 @@ void BME68xBSEC2Component::save_state_(uint8_t accuracy) {
|
||||
ESP_LOGI(TAG, "Saved state");
|
||||
}
|
||||
|
||||
} // namespace bme68x_bsec2
|
||||
} // namespace esphome
|
||||
} // namespace esphome::bme68x_bsec2
|
||||
#endif
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
|
||||
#include <bsec2.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace bme68x_bsec2 {
|
||||
namespace esphome::bme68x_bsec2 {
|
||||
|
||||
enum AlgorithmOutput {
|
||||
ALGORITHM_OUTPUT_IAQ,
|
||||
@@ -97,7 +96,7 @@ class BME68xBSEC2Component : public Component {
|
||||
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
|
||||
void publish_sensor_(text_sensor::TextSensor *sensor, const char *value);
|
||||
#endif
|
||||
|
||||
void load_state_();
|
||||
@@ -108,39 +107,12 @@ class BME68xBSEC2Component : public Component {
|
||||
struct bme68x_dev bme68x_;
|
||||
bsec_bme_settings_t bsec_settings_;
|
||||
bsec_version_t version_;
|
||||
uint8_t bsec_instance_[BSEC_INSTANCE_SIZE];
|
||||
|
||||
struct bme68x_heatr_conf bme68x_heatr_conf_;
|
||||
uint8_t op_mode_; // operating mode of sensor
|
||||
bsec_library_return_t bsec_status_{BSEC_OK};
|
||||
int8_t bme68x_status_{BME68X_OK};
|
||||
|
||||
int64_t last_time_ms_{0};
|
||||
int64_t trigger_time_ns_{0}; // Stored for set_timeout lambda to help avoid heap allocation on supported 32-bit
|
||||
// toolchains with small std::function SBO
|
||||
uint32_t millis_overflow_counter_{0};
|
||||
|
||||
std::queue<std::function<void()>> queue_;
|
||||
ESPPreferenceObject bsec_state_;
|
||||
|
||||
uint8_t const *bsec2_configuration_{nullptr};
|
||||
uint32_t bsec2_configuration_length_{0};
|
||||
bool bsec2_blob_configured_{false};
|
||||
|
||||
ESPPreferenceObject bsec_state_;
|
||||
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
|
||||
uint32_t last_state_save_ms_ = 0;
|
||||
|
||||
float temperature_offset_{0};
|
||||
|
||||
AlgorithmOutput algorithm_output_{ALGORITHM_OUTPUT_IAQ};
|
||||
OperatingAge operating_age_{OPERATING_AGE_28D};
|
||||
Voltage voltage_{VOLTAGE_3_3V};
|
||||
|
||||
SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate
|
||||
SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
@@ -155,8 +127,32 @@ class BME68xBSEC2Component : public Component {
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
text_sensor::TextSensor *iaq_accuracy_text_sensor_{nullptr};
|
||||
#endif
|
||||
|
||||
int64_t last_time_ms_{0};
|
||||
int64_t trigger_time_ns_{0}; // Stored for set_timeout lambda to help avoid heap allocation on supported 32-bit
|
||||
// toolchains with small std::function SBO
|
||||
|
||||
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
|
||||
uint32_t last_state_save_ms_{0};
|
||||
uint32_t millis_overflow_counter_{0};
|
||||
uint32_t bsec2_configuration_length_{0};
|
||||
bsec_library_return_t bsec_status_{BSEC_OK};
|
||||
|
||||
float temperature_offset_{0};
|
||||
|
||||
AlgorithmOutput algorithm_output_{ALGORITHM_OUTPUT_IAQ};
|
||||
OperatingAge operating_age_{OPERATING_AGE_28D};
|
||||
Voltage voltage_{VOLTAGE_3_3V};
|
||||
SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate
|
||||
SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
|
||||
uint8_t bsec_instance_[BSEC_INSTANCE_SIZE];
|
||||
uint8_t op_mode_; // operating mode of sensor
|
||||
int8_t bme68x_status_{BME68X_OK};
|
||||
bool bsec2_blob_configured_{false};
|
||||
};
|
||||
|
||||
} // namespace bme68x_bsec2
|
||||
} // namespace esphome
|
||||
} // namespace esphome::bme68x_bsec2
|
||||
#endif
|
||||
|
||||
@@ -126,7 +126,7 @@ void BMP581Component::setup() {
|
||||
}
|
||||
|
||||
// verify id
|
||||
if (chip_id != BMP581_ASIC_ID) {
|
||||
if (chip_id != BMP581_ASIC_ID && chip_id != BMP585_ASIC_ID) {
|
||||
ESP_LOGE(TAG, "Unknown chip ID");
|
||||
|
||||
this->error_code_ = ERROR_WRONG_CHIP_ID;
|
||||
@@ -469,14 +469,18 @@ bool BMP581Component::read_temperature_and_pressure_(float &temperature, float &
|
||||
}
|
||||
|
||||
bool BMP581Component::reset_() {
|
||||
// - activates interface (only relevant for SPI mode)
|
||||
// - writes reset command to the command register
|
||||
// - waits for sensor to complete reset
|
||||
// - activates interface (only relevant for SPI mode)
|
||||
// - returns the Power-On-Reboot interrupt status, which is asserted if successful
|
||||
|
||||
// activates communication interface (SPI only)
|
||||
this->activate_interface();
|
||||
|
||||
// writes reset command to BMP's command register
|
||||
if (!this->bmp_write_byte(BMP581_COMMAND, RESET_COMMAND)) {
|
||||
ESP_LOGE(TAG, "Failed to write reset command");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -484,6 +488,9 @@ bool BMP581Component::reset_() {
|
||||
// - round up to 3 ms
|
||||
delay(3);
|
||||
|
||||
// reactivates communication interface after reset (SPI only)
|
||||
this->activate_interface();
|
||||
|
||||
// read interrupt status register
|
||||
if (!this->bmp_read_byte(BMP581_INT_STATUS, &this->int_status_.reg)) {
|
||||
ESP_LOGE(TAG, "Failed to read interrupt status register");
|
||||
@@ -491,7 +498,7 @@ bool BMP581Component::reset_() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Power-On-Reboot bit is asserted if sensor successfully reset
|
||||
// power-On-Reboot bit is asserted if sensor successfully reset
|
||||
return this->int_status_.bit.por;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
namespace esphome::bmp581_base {
|
||||
|
||||
static const uint8_t BMP581_ASIC_ID = 0x50; // BMP581's ASIC chip ID (page 51 of datasheet)
|
||||
static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command
|
||||
static const uint8_t BMP585_ASIC_ID = 0x51;
|
||||
static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command
|
||||
|
||||
// BMP581 Register Addresses
|
||||
enum {
|
||||
@@ -87,6 +88,9 @@ class BMP581Component : public PollingComponent {
|
||||
virtual bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
|
||||
virtual bool bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
|
||||
|
||||
// Interface activation function. Only used for SPI interface; no-op for I2C.
|
||||
virtual void activate_interface() {}
|
||||
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
#include "bmp581_spi.h"
|
||||
#include "esphome/components/bmp581_base/bmp581_base.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
namespace esphome::bmp581_spi {
|
||||
|
||||
static const char *const TAG = "bmp581_spi";
|
||||
|
||||
// OR (|) register with BMP_SPI_READ for read
|
||||
inline constexpr uint8_t BMP_SPI_READ = 0x80;
|
||||
|
||||
// AND (&) register with BMP_SPI_WRITE for write
|
||||
inline constexpr uint8_t BMP_SPI_WRITE = 0x7F;
|
||||
|
||||
void BMP581SPIComponent::dump_config() {
|
||||
BMP581Component::dump_config();
|
||||
LOG_SPI_DEVICE(this);
|
||||
}
|
||||
|
||||
void BMP581SPIComponent::setup() {
|
||||
this->spi_setup();
|
||||
BMP581Component::setup();
|
||||
}
|
||||
|
||||
void BMP581SPIComponent::activate_interface() {
|
||||
// - forces the device into SPI mode using a dummy read
|
||||
uint8_t dummy_read = 0;
|
||||
this->bmp_read_byte(bmp581_base::BMP581_CHIP_ID, &dummy_read);
|
||||
}
|
||||
|
||||
// In SPI mode, only 7 bits of the register addresses are used; the MSB of register address is not used
|
||||
// and replaced by a read/write bit (RW = ‘0’ for write and RW = ‘1’ for read).
|
||||
// Example: address 0xF7 is accessed by using SPI register address 0x77. For write access, the byte
|
||||
// 0x77 is transferred, for read access, the byte 0xF7 is transferred.
|
||||
// The expressions BMP_SPI_READ (| with register) and BMP_SPI_WRITE (& with register)
|
||||
// are defined for readability.
|
||||
// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp581-ds004.pdf
|
||||
|
||||
bool BMP581SPIComponent::bmp_read_byte(uint8_t a_register, uint8_t *data) {
|
||||
this->enable();
|
||||
this->transfer_byte(a_register | BMP_SPI_READ);
|
||||
*data = this->transfer_byte(0);
|
||||
this->disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMP581SPIComponent::bmp_write_byte(uint8_t a_register, uint8_t data) {
|
||||
this->enable();
|
||||
this->transfer_byte(a_register & BMP_SPI_WRITE);
|
||||
this->transfer_byte(data);
|
||||
this->disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMP581SPIComponent::bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
|
||||
this->enable();
|
||||
this->transfer_byte(a_register | BMP_SPI_READ);
|
||||
this->read_array(data, len);
|
||||
this->disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMP581SPIComponent::bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) {
|
||||
this->enable();
|
||||
this->transfer_byte(a_register & BMP_SPI_WRITE);
|
||||
this->write_array(data, len);
|
||||
this->disable();
|
||||
return true;
|
||||
}
|
||||
} // namespace esphome::bmp581_spi
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/bmp581_base/bmp581_base.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
namespace esphome::bmp581_spi {
|
||||
|
||||
// BMP581 is technically compatible with SPI Mode0 and Mode3. Default to Mode3.
|
||||
class BMP581SPIComponent : public esphome::bmp581_base::BMP581Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
|
||||
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
|
||||
public:
|
||||
void setup() override;
|
||||
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override;
|
||||
bool bmp_write_byte(uint8_t a_register, uint8_t data) override;
|
||||
bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
||||
bool bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void activate_interface() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::bmp581_spi
|
||||
@@ -0,0 +1,48 @@
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import spi
|
||||
from esphome.components.spi import CONF_SPI_MODE
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from ..bmp581_base import CONFIG_SCHEMA_BASE, to_code_base
|
||||
|
||||
AUTO_LOAD = ["bmp581_base"]
|
||||
CODEOWNERS = ["@kahrendt", "@danielkent-net"]
|
||||
DEPENDENCIES = ["spi"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
VALID_SPI_MODES = {
|
||||
0: "MODE0",
|
||||
"0": "MODE0",
|
||||
"MODE0": "MODE0",
|
||||
3: "MODE3",
|
||||
"3": "MODE3",
|
||||
"MODE3": "MODE3",
|
||||
}
|
||||
|
||||
bmp581_ns = cg.esphome_ns.namespace("bmp581_spi")
|
||||
BMP581SPIComponent = bmp581_ns.class_(
|
||||
"BMP581SPIComponent", cg.PollingComponent, spi.SPIDevice
|
||||
)
|
||||
|
||||
|
||||
def check_spi_mode(config):
|
||||
spi_mode = config.get(CONF_SPI_MODE)
|
||||
if spi_mode not in VALID_SPI_MODES:
|
||||
raise cv.Invalid("BMP581 only supports SPI mode 3")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
CONFIG_SCHEMA_BASE.extend(spi.spi_device_schema(default_mode="mode3")).extend(
|
||||
{cv.GenerateID(): cv.declare_id(BMP581SPIComponent)}
|
||||
),
|
||||
check_spi_mode,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await to_code_base(config)
|
||||
await spi.register_spi_device(var, config)
|
||||
@@ -10,7 +10,6 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MQTT_ID,
|
||||
CONF_ON_PRESS,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_WEB_SERVER,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_IDENTIFY,
|
||||
@@ -41,10 +40,6 @@ ButtonPtr = Button.operator("ptr")
|
||||
|
||||
PressAction = button_ns.class_("PressAction", automation.Action)
|
||||
|
||||
ButtonPressTrigger = button_ns.class_(
|
||||
"ButtonPressTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||
|
||||
|
||||
@@ -55,11 +50,7 @@ _BUTTON_SCHEMA = (
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation({}),
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -91,8 +82,9 @@ def button_schema(
|
||||
@setup_entity("button")
|
||||
async def setup_button_core_(var, config):
|
||||
for conf in config.get(CONF_ON_PRESS, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_press_callback", [], conf
|
||||
)
|
||||
|
||||
setup_device_class(config)
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
buffer = cg.new_Pvariable(config[CONF_ENCODER_BUFFER_ID])
|
||||
cg.add(buffer.set_buffer_size(config[CONF_BUFFER_SIZE]))
|
||||
if config[CONF_TYPE] == ESP32_CAMERA_ENCODER:
|
||||
add_idf_component(name="espressif/esp32-camera", ref="2.1.5")
|
||||
add_idf_component(name="espressif/esp32-camera", ref="2.1.6")
|
||||
cg.add_define("USE_ESP32_CAMERA_JPEG_ENCODER")
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
|
||||
@@ -124,12 +124,6 @@ bool CH422GComponent::write_outputs_() {
|
||||
|
||||
float CH422GComponent::get_setup_priority() const { return setup_priority::IO; }
|
||||
|
||||
#ifdef USE_LOOP_PRIORITY
|
||||
// Run our loop() method very early in the loop, so that we cache read values
|
||||
// before other components call our digital_read() method.
|
||||
float CH422GComponent::get_loop_priority() const { return 9.0f; } // Just after WIFI
|
||||
#endif
|
||||
|
||||
void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
||||
bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; }
|
||||
|
||||
|
||||
@@ -23,9 +23,6 @@ class CH422GComponent : public Component, public i2c::I2CDevice {
|
||||
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||
|
||||
float get_setup_priority() const override;
|
||||
#ifdef USE_LOOP_PRIORITY
|
||||
float get_loop_priority() const override;
|
||||
#endif
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -129,12 +129,6 @@ bool CH423Component::write_outputs_() {
|
||||
|
||||
float CH423Component::get_setup_priority() const { return setup_priority::IO; }
|
||||
|
||||
#ifdef USE_LOOP_PRIORITY
|
||||
// Run our loop() method very early in the loop, so that we cache read values
|
||||
// before other components call our digital_read() method.
|
||||
float CH423Component::get_loop_priority() const { return 9.0f; } // Just after WIFI
|
||||
#endif
|
||||
|
||||
void CH423GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
||||
bool CH423GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; }
|
||||
|
||||
|
||||
@@ -22,9 +22,6 @@ class CH423Component : public Component, public i2c::I2CDevice {
|
||||
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||
|
||||
float get_setup_priority() const override;
|
||||
#ifdef USE_LOOP_PRIORITY
|
||||
float get_loop_priority() const override;
|
||||
#endif
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -367,7 +367,7 @@ optional<ClimateDeviceRestoreState> Climate::restore_state_() {
|
||||
return recovered;
|
||||
}
|
||||
|
||||
void Climate::save_state_() {
|
||||
void Climate::save_state_(const ClimateTraits &traits) {
|
||||
#if (defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \
|
||||
!defined(CLANG_TIDY)
|
||||
#pragma GCC diagnostic ignored "-Wclass-memaccess"
|
||||
@@ -382,7 +382,6 @@ void Climate::save_state_() {
|
||||
#endif
|
||||
|
||||
state.mode = this->mode;
|
||||
auto traits = this->get_traits();
|
||||
if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||
state.target_temperature_low = this->target_temperature_low;
|
||||
@@ -480,7 +479,7 @@ void Climate::publish_state() {
|
||||
ControllerRegistry::notify_climate_update(this);
|
||||
#endif
|
||||
// Save state
|
||||
this->save_state_();
|
||||
this->save_state_(traits);
|
||||
}
|
||||
|
||||
ClimateTraits Climate::get_traits() {
|
||||
|
||||
@@ -335,7 +335,8 @@ class Climate : public EntityBase {
|
||||
/** Internal method to save the state of the climate device to recover memory. This is automatically
|
||||
* called from publish_state()
|
||||
*/
|
||||
void save_state_();
|
||||
void save_state_(const ClimateTraits &traits);
|
||||
void save_state_() { this->save_state_(this->traits()); }
|
||||
|
||||
void dump_traits_(const char *tag);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace copy {
|
||||
static const char *const TAG = "copy.lock";
|
||||
|
||||
void CopyLock::setup() {
|
||||
source_->add_on_state_callback([this]() { this->publish_state(source_->state); });
|
||||
source_->add_on_state_callback([this](lock::LockState state) { this->publish_state(state); });
|
||||
|
||||
traits.set_assumed_state(source_->traits.get_assumed_state());
|
||||
traits.set_requires_code(source_->traits.get_requires_code());
|
||||
|
||||
@@ -91,6 +91,49 @@ void DebugComponent::log_partition_info_() {
|
||||
flash_area_foreach(fa_cb, nullptr);
|
||||
}
|
||||
|
||||
#ifdef ESPHOME_LOG_HAS_VERBOSE
|
||||
// Check if an nRF peripheral's ENABLE register indicates it is enabled.
|
||||
// periph: peripheral register prefix (e.g. USBD, UARTE, SPI)
|
||||
// reg: register block pointer (e.g. NRF_USBD, NRF_UARTE0)
|
||||
#define NRF_PERIPH_ENABLED(periph, reg) \
|
||||
YESNO(((reg)->ENABLE & periph##_ENABLE_ENABLE_Msk) == (periph##_ENABLE_ENABLE_Enabled << periph##_ENABLE_ENABLE_Pos))
|
||||
|
||||
static void log_peripherals_info() {
|
||||
// most peripherals are enabled only when in use so ESP_LOGV is enough
|
||||
ESP_LOGV(TAG, "Peripherals status:");
|
||||
ESP_LOGV(TAG, " USBD: %-3s| UARTE0: %-3s| UARTE1: %-3s| UART0: %-3s", //
|
||||
NRF_PERIPH_ENABLED(USBD, NRF_USBD), NRF_PERIPH_ENABLED(UARTE, NRF_UARTE0),
|
||||
NRF_PERIPH_ENABLED(UARTE, NRF_UARTE1), NRF_PERIPH_ENABLED(UART, NRF_UART0));
|
||||
ESP_LOGV(TAG, " TWIS0: %-3s| TWIS1: %-3s| TWIM0: %-3s| TWIM1: %-3s", //
|
||||
NRF_PERIPH_ENABLED(TWIS, NRF_TWIS0), NRF_PERIPH_ENABLED(TWIS, NRF_TWIS1),
|
||||
NRF_PERIPH_ENABLED(TWIM, NRF_TWIM0), NRF_PERIPH_ENABLED(TWIM, NRF_TWIM1));
|
||||
ESP_LOGV(TAG, " TWI0: %-3s| TWI1: %-3s| COMP: %-3s| CCM: %-3s", //
|
||||
NRF_PERIPH_ENABLED(TWI, NRF_TWI0), NRF_PERIPH_ENABLED(TWI, NRF_TWI1), NRF_PERIPH_ENABLED(COMP, NRF_COMP),
|
||||
NRF_PERIPH_ENABLED(CCM, NRF_CCM));
|
||||
ESP_LOGV(TAG, " PDM: %-3s| SPIS0: %-3s| SPIS1: %-3s| SPIS2: %-3s", //
|
||||
NRF_PERIPH_ENABLED(PDM, NRF_PDM), NRF_PERIPH_ENABLED(SPIS, NRF_SPIS0), NRF_PERIPH_ENABLED(SPIS, NRF_SPIS1),
|
||||
NRF_PERIPH_ENABLED(SPIS, NRF_SPIS2));
|
||||
ESP_LOGV(TAG, " SPIM0: %-3s| SPIM1: %-3s| SPIM2: %-3s| SPIM3: %-3s", //
|
||||
NRF_PERIPH_ENABLED(SPIM, NRF_SPIM0), NRF_PERIPH_ENABLED(SPIM, NRF_SPIM1),
|
||||
NRF_PERIPH_ENABLED(SPIM, NRF_SPIM2), NRF_PERIPH_ENABLED(SPIM, NRF_SPIM3));
|
||||
ESP_LOGV(TAG, " SPI0: %-3s| SPI1: %-3s| SPI2: %-3s| SAADC: %-3s", //
|
||||
NRF_PERIPH_ENABLED(SPI, NRF_SPI0), NRF_PERIPH_ENABLED(SPI, NRF_SPI1), NRF_PERIPH_ENABLED(SPI, NRF_SPI2),
|
||||
NRF_PERIPH_ENABLED(SAADC, NRF_SAADC));
|
||||
ESP_LOGV(TAG, " QSPI: %-3s| QDEC: %-3s| LPCOMP: %-3s| I2S: %-3s", //
|
||||
NRF_PERIPH_ENABLED(QSPI, NRF_QSPI), NRF_PERIPH_ENABLED(QDEC, NRF_QDEC),
|
||||
NRF_PERIPH_ENABLED(LPCOMP, NRF_LPCOMP), NRF_PERIPH_ENABLED(I2S, NRF_I2S));
|
||||
ESP_LOGV(TAG, " PWM0: %-3s| PWM1: %-3s| PWM2: %-3s| PWM3: %-3s", //
|
||||
NRF_PERIPH_ENABLED(PWM, NRF_PWM0), NRF_PERIPH_ENABLED(PWM, NRF_PWM1), NRF_PERIPH_ENABLED(PWM, NRF_PWM2),
|
||||
NRF_PERIPH_ENABLED(PWM, NRF_PWM3));
|
||||
ESP_LOGV(TAG, " AAR: %-3s| QSPI deep power-down:%-3s| CRYPTOCELL: %-3s", NRF_PERIPH_ENABLED(AAR, NRF_AAR),
|
||||
YESNO((NRF_QSPI->IFCONFIG0 & QSPI_IFCONFIG0_DPMENABLE_Msk) ==
|
||||
(QSPI_IFCONFIG0_DPMENABLE_Enable << QSPI_IFCONFIG0_DPMENABLE_Pos)),
|
||||
YESNO((NRF_CRYPTOCELL->ENABLE & CRYPTOCELL_ENABLE_ENABLE_Msk) ==
|
||||
(CRYPTOCELL_ENABLE_ENABLE_Enabled << CRYPTOCELL_ENABLE_ENABLE_Pos)));
|
||||
}
|
||||
#undef NRF_PERIPH_ENABLED
|
||||
#endif
|
||||
|
||||
static const char *regout0_to_str(uint32_t value) {
|
||||
switch (value) {
|
||||
case (UICR_REGOUT0_VOUT_DEFAULT):
|
||||
@@ -354,7 +397,9 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
};
|
||||
ESP_LOGD(TAG, " NRFFW %s", uicr(NRF_UICR->NRFFW, 13).c_str());
|
||||
ESP_LOGD(TAG, " NRFHW %s", uicr(NRF_UICR->NRFHW, 12).c_str());
|
||||
|
||||
#ifdef ESPHOME_LOG_HAS_VERBOSE
|
||||
log_peripherals_info();
|
||||
#endif
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,12 +40,6 @@ void DeepSleepComponent::loop() {
|
||||
this->begin_sleep();
|
||||
}
|
||||
|
||||
#ifdef USE_LOOP_PRIORITY
|
||||
float DeepSleepComponent::get_loop_priority() const {
|
||||
return -100.0f; // run after everything else is ready
|
||||
}
|
||||
#endif
|
||||
|
||||
void DeepSleepComponent::set_sleep_duration(uint32_t time_ms) { this->sleep_duration_ = uint64_t(time_ms) * 1000; }
|
||||
|
||||
void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; }
|
||||
|
||||
@@ -113,9 +113,6 @@ class DeepSleepComponent : public Component {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
#ifdef USE_LOOP_PRIORITY
|
||||
float get_loop_priority() const override;
|
||||
#endif
|
||||
float get_setup_priority() const override;
|
||||
|
||||
/// Helper to enter deep sleep mode
|
||||
|
||||
@@ -2,16 +2,13 @@ from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DEVICE, CONF_FILE, CONF_ID, CONF_TRIGGER_ID, CONF_VOLUME
|
||||
from esphome.const import CONF_DEVICE, CONF_FILE, CONF_ID, CONF_VOLUME
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
CODEOWNERS = ["@glmnet"]
|
||||
|
||||
dfplayer_ns = cg.esphome_ns.namespace("dfplayer")
|
||||
DFPlayer = dfplayer_ns.class_("DFPlayer", cg.Component)
|
||||
DFPlayerFinishedPlaybackTrigger = dfplayer_ns.class_(
|
||||
"DFPlayerFinishedPlaybackTrigger", automation.Trigger.template()
|
||||
)
|
||||
DFPlayerIsPlayingCondition = dfplayer_ns.class_(
|
||||
"DFPlayerIsPlayingCondition", automation.Condition
|
||||
)
|
||||
@@ -58,13 +55,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DFPlayer),
|
||||
cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DFPlayerFinishedPlaybackTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation({}),
|
||||
}
|
||||
).extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
@@ -79,8 +70,9 @@ async def to_code(config):
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
for conf in config.get(CONF_ON_FINISHED_PLAYBACK, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_finished_playback_callback", [], conf
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
||||
@@ -171,12 +171,5 @@ template<typename... Ts> class DFPlayerIsPlayingCondition : public Condition<Ts.
|
||||
bool check(const Ts &...x) override { return this->parent_->is_playing(); }
|
||||
};
|
||||
|
||||
class DFPlayerFinishedPlaybackTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit DFPlayerFinishedPlaybackTrigger(DFPlayer *parent) {
|
||||
parent->add_on_finished_playback_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace dfplayer
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dht {
|
||||
namespace esphome::dht {
|
||||
|
||||
static const char *const TAG = "dht";
|
||||
|
||||
@@ -45,16 +44,13 @@ void DHT::update() {
|
||||
}
|
||||
|
||||
if (success) {
|
||||
ESP_LOGD(TAG, "Temperature %.1f°C Humidity %.1f%%", temperature, humidity);
|
||||
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
this->status_clear_warning();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Invalid readings! Check pin number and pull-up resistor%s.",
|
||||
this->is_auto_detect_ ? " and try manually specifying the model" : "");
|
||||
ESP_LOGW(TAG, "Invalid readings");
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(NAN);
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
@@ -73,8 +69,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
||||
*temperature = NAN;
|
||||
|
||||
int error_code = 0;
|
||||
int8_t i = 0;
|
||||
uint8_t data[5] = {0, 0, 0, 0, 0};
|
||||
uint8_t data[5] = {};
|
||||
|
||||
#ifndef USE_ESP32
|
||||
this->pin_.pin_mode(gpio::FLAG_OUTPUT);
|
||||
@@ -107,7 +102,9 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
||||
uint8_t bit = 7;
|
||||
uint8_t byte = 0;
|
||||
|
||||
for (i = -1; i < 40; i++) {
|
||||
// On 32-bit Xtensa/RISC-V cores, int8_t would require masking/sign-extension for comparisons
|
||||
// vs. native int. Using int i is native word size — small win in the timing-critical section.
|
||||
for (int i = -1; i < 40; i++) {
|
||||
uint32_t start_time = micros();
|
||||
|
||||
// Wait for rising edge
|
||||
@@ -156,11 +153,9 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!report_errors && error_code != 0)
|
||||
return false;
|
||||
|
||||
if (error_code) {
|
||||
ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
if (error_code != 0) {
|
||||
if (report_errors)
|
||||
ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -177,7 +172,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
||||
|
||||
if (checksum_a != data[4] && checksum_b != data[4]) {
|
||||
if (report_errors) {
|
||||
ESP_LOGW(TAG, "Checksum invalid: %u!=%u", checksum_a, data[4]);
|
||||
ESP_LOGW(TAG, "Invalid checksum");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -234,5 +229,4 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace dht
|
||||
} // namespace esphome
|
||||
} // namespace esphome::dht
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dht {
|
||||
namespace esphome::dht {
|
||||
|
||||
enum DHTModel {
|
||||
enum DHTModel : uint8_t {
|
||||
DHT_MODEL_AUTO_DETECT = 0,
|
||||
DHT_MODEL_DHT11,
|
||||
DHT_MODEL_DHT22,
|
||||
@@ -42,7 +41,6 @@ class DHT : public PollingComponent {
|
||||
this->t_pin_ = pin;
|
||||
this->pin_ = pin->to_isr();
|
||||
}
|
||||
void set_model(DHTModel model) { model_ = model; }
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
|
||||
@@ -55,13 +53,12 @@ class DHT : public PollingComponent {
|
||||
protected:
|
||||
bool read_sensor_(float *temperature, float *humidity, bool report_errors);
|
||||
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
InternalGPIOPin *t_pin_;
|
||||
ISRInternalGPIOPin pin_;
|
||||
DHTModel model_{DHT_MODEL_AUTO_DETECT};
|
||||
bool is_auto_detect_{false};
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace dht
|
||||
} // namespace esphome
|
||||
} // namespace esphome::dht
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "dlms_meter.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#if defined(USE_ESP8266_FRAMEWORK_ARDUINO)
|
||||
#include <bearssl/bearssl.h>
|
||||
#elif defined(USE_ESP32)
|
||||
@@ -21,7 +23,7 @@ void DlmsMeterComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"DLMS Meter:\n"
|
||||
" Provider: %s\n"
|
||||
" Read Timeout: %u ms",
|
||||
" Read Timeout: %" PRIu32 " ms",
|
||||
provider_name, this->read_timeout_);
|
||||
#define DLMS_METER_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s##_sensor_);
|
||||
DLMS_METER_SENSOR_LIST(DLMS_METER_LOG_SENSOR, )
|
||||
|
||||
@@ -690,9 +690,15 @@ ARDUINO_IDF_VERSION_LOOKUP = {
|
||||
ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
|
||||
"recommended": cv.Version(5, 5, 3, "1"),
|
||||
"latest": cv.Version(5, 5, 3, "1"),
|
||||
"dev": cv.Version(5, 5, 3, "1"),
|
||||
"dev": cv.Version(5, 5, 4),
|
||||
}
|
||||
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
||||
cv.Version(
|
||||
6, 0, 0
|
||||
): "https://github.com/pioarduino/platform-espressif32.git#prep_IDF6",
|
||||
cv.Version(
|
||||
5, 5, 4
|
||||
): "https://github.com/pioarduino/platform-espressif32.git#develop",
|
||||
cv.Version(5, 5, 3, "1"): cv.Version(55, 3, 37),
|
||||
cv.Version(5, 5, 3): cv.Version(55, 3, 37),
|
||||
cv.Version(5, 5, 2): cv.Version(55, 3, 37),
|
||||
|
||||
@@ -129,11 +129,15 @@ bool ESP32Preferences::sync() {
|
||||
}
|
||||
s_pending_save.clear();
|
||||
|
||||
ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
|
||||
failed);
|
||||
if (failed > 0) {
|
||||
ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err),
|
||||
last_key);
|
||||
ESP_LOGE(TAG, "Writing %d items: %d cached, %d written, %d failed. Last error=%s for key=%" PRIu32,
|
||||
cached + written + failed, cached, written, failed, esp_err_to_name(last_err), last_key);
|
||||
} else if (written > 0) {
|
||||
ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
|
||||
failed);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
|
||||
failed);
|
||||
}
|
||||
|
||||
// note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
|
||||
|
||||
@@ -134,10 +134,38 @@ class HandlerCounts:
|
||||
_handler_counts = HandlerCounts()
|
||||
|
||||
|
||||
def _add_callback(
|
||||
parent_var: cg.MockObj,
|
||||
method: str,
|
||||
handler_var: cg.MockObj,
|
||||
params: str,
|
||||
call_args: str,
|
||||
) -> None:
|
||||
"""Generate a lambda callback that forwards to a handler method.
|
||||
|
||||
Uses a braced scope with a local pointer variable so the generated C++
|
||||
lambda captures only that pointer, avoiding GCC warnings about capturing
|
||||
variables with static storage duration.
|
||||
"""
|
||||
cg.add(
|
||||
cg.RawStatement(
|
||||
f"{{ auto *h = {handler_var}; "
|
||||
f"{parent_var}->{method}("
|
||||
f"[h]({params}) {{ h->{call_args}; }}); }}"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def register_gap_event_handler(parent_var: cg.MockObj, handler_var: cg.MockObj) -> None:
|
||||
"""Register a GAP event handler and track the count."""
|
||||
_handler_counts.gap_event += 1
|
||||
cg.add(parent_var.register_gap_event_handler(handler_var))
|
||||
_add_callback(
|
||||
parent_var,
|
||||
"add_gap_event_callback",
|
||||
handler_var,
|
||||
"esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param",
|
||||
"gap_event_handler(event, param)",
|
||||
)
|
||||
|
||||
|
||||
def register_gap_scan_event_handler(
|
||||
@@ -145,7 +173,13 @@ def register_gap_scan_event_handler(
|
||||
) -> None:
|
||||
"""Register a GAP scan event handler and track the count."""
|
||||
_handler_counts.gap_scan_event += 1
|
||||
cg.add(parent_var.register_gap_scan_event_handler(handler_var))
|
||||
_add_callback(
|
||||
parent_var,
|
||||
"add_gap_scan_event_callback",
|
||||
handler_var,
|
||||
"const esphome::esp32_ble::BLEScanResult &scan_result",
|
||||
"gap_scan_event_handler(scan_result)",
|
||||
)
|
||||
|
||||
|
||||
def register_gattc_event_handler(
|
||||
@@ -153,7 +187,13 @@ def register_gattc_event_handler(
|
||||
) -> None:
|
||||
"""Register a GATTc event handler and track the count."""
|
||||
_handler_counts.gattc_event += 1
|
||||
cg.add(parent_var.register_gattc_event_handler(handler_var))
|
||||
_add_callback(
|
||||
parent_var,
|
||||
"add_gattc_event_callback",
|
||||
handler_var,
|
||||
"esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param",
|
||||
"gattc_event_handler(event, gattc_if, param)",
|
||||
)
|
||||
|
||||
|
||||
def register_gatts_event_handler(
|
||||
@@ -161,7 +201,13 @@ def register_gatts_event_handler(
|
||||
) -> None:
|
||||
"""Register a GATTs event handler and track the count."""
|
||||
_handler_counts.gatts_event += 1
|
||||
cg.add(parent_var.register_gatts_event_handler(handler_var))
|
||||
_add_callback(
|
||||
parent_var,
|
||||
"add_gatts_event_callback",
|
||||
handler_var,
|
||||
"esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param",
|
||||
"gatts_event_handler(event, gatts_if, param)",
|
||||
)
|
||||
|
||||
|
||||
def register_ble_status_event_handler(
|
||||
@@ -169,7 +215,13 @@ def register_ble_status_event_handler(
|
||||
) -> None:
|
||||
"""Register a BLE status event handler and track the count."""
|
||||
_handler_counts.ble_status_event += 1
|
||||
cg.add(parent_var.register_ble_status_event_handler(handler_var))
|
||||
_add_callback(
|
||||
parent_var,
|
||||
"add_ble_status_event_callback",
|
||||
handler_var,
|
||||
"",
|
||||
"ble_before_disabled_event_handler()",
|
||||
)
|
||||
|
||||
|
||||
def register_bt_logger(*loggers: BTLoggers) -> None:
|
||||
@@ -225,10 +277,6 @@ NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
||||
esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
|
||||
ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component)
|
||||
|
||||
GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler")
|
||||
GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler")
|
||||
GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler")
|
||||
|
||||
BLEEnabledCondition = esp32_ble_ns.class_("BLEEnabledCondition", automation.Condition)
|
||||
BLEEnableAction = esp32_ble_ns.class_("BLEEnableAction", automation.Action)
|
||||
BLEDisableAction = esp32_ble_ns.class_("BLEDisableAction", automation.Action)
|
||||
|
||||
@@ -408,9 +408,7 @@ void ESP32BLE::loop() {
|
||||
esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if;
|
||||
esp_ble_gatts_cb_param_t *param = &ble_event->event_.gatts.gatts_param;
|
||||
ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
|
||||
for (auto *gatts_handler : this->gatts_event_handlers_) {
|
||||
gatts_handler->gatts_event_handler(event, gatts_if, param);
|
||||
}
|
||||
this->gatts_event_callbacks_.call(event, gatts_if, param);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
@@ -420,9 +418,7 @@ void ESP32BLE::loop() {
|
||||
esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if;
|
||||
esp_ble_gattc_cb_param_t *param = &ble_event->event_.gattc.gattc_param;
|
||||
ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
|
||||
for (auto *gattc_handler : this->gattc_event_handlers_) {
|
||||
gattc_handler->gattc_event_handler(event, gattc_if, param);
|
||||
}
|
||||
this->gattc_event_callbacks_.call(event, gattc_if, param);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
@@ -431,10 +427,7 @@ void ESP32BLE::loop() {
|
||||
switch (gap_event) {
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
||||
#ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT
|
||||
// Use the new scan event handler - no memcpy!
|
||||
for (auto *scan_handler : this->gap_scan_event_handlers_) {
|
||||
scan_handler->gap_scan_event_handler(ble_event->scan_result());
|
||||
}
|
||||
this->gap_scan_event_callbacks_.call(ble_event->scan_result());
|
||||
#endif
|
||||
break;
|
||||
|
||||
@@ -478,9 +471,7 @@ void ESP32BLE::loop() {
|
||||
}
|
||||
// clang-format on
|
||||
// Dispatch to all registered handlers
|
||||
for (auto *gap_handler : this->gap_event_handlers_) {
|
||||
gap_handler->gap_event_handler(gap_event, param);
|
||||
}
|
||||
this->gap_event_callbacks_.call(gap_event, param);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
@@ -518,9 +509,7 @@ void ESP32BLE::loop_handle_state_transition_not_active_() {
|
||||
ESP_LOGD(TAG, "Disabling");
|
||||
|
||||
#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT
|
||||
for (auto *ble_event_handler : this->ble_status_event_handlers_) {
|
||||
ble_event_handler->ble_before_disabled_event_handler();
|
||||
}
|
||||
this->ble_status_event_callbacks_.call();
|
||||
#endif
|
||||
|
||||
if (!ble_dismantle_()) {
|
||||
|
||||
@@ -87,37 +87,6 @@ enum BLEComponentState : uint8_t {
|
||||
BLE_COMPONENT_STATE_ACTIVE,
|
||||
};
|
||||
|
||||
class GAPEventHandler {
|
||||
public:
|
||||
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
|
||||
};
|
||||
|
||||
class GAPScanEventHandler {
|
||||
public:
|
||||
virtual void gap_scan_event_handler(const BLEScanResult &scan_result) = 0;
|
||||
};
|
||||
|
||||
#ifdef USE_ESP32_BLE_CLIENT
|
||||
class GATTcEventHandler {
|
||||
public:
|
||||
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) = 0;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
class GATTsEventHandler {
|
||||
public:
|
||||
virtual void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param) = 0;
|
||||
};
|
||||
#endif
|
||||
|
||||
class BLEStatusEventHandler {
|
||||
public:
|
||||
virtual void ble_before_disabled_event_handler() = 0;
|
||||
};
|
||||
|
||||
class ESP32BLE : public Component {
|
||||
public:
|
||||
void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; }
|
||||
@@ -154,22 +123,28 @@ class ESP32BLE : public Component {
|
||||
#endif
|
||||
|
||||
#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
|
||||
void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); }
|
||||
template<typename F> void add_gap_event_callback(F &&callback) {
|
||||
this->gap_event_callbacks_.add(std::forward<F>(callback));
|
||||
}
|
||||
#endif
|
||||
#ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT
|
||||
void register_gap_scan_event_handler(GAPScanEventHandler *handler) {
|
||||
this->gap_scan_event_handlers_.push_back(handler);
|
||||
template<typename F> void add_gap_scan_event_callback(F &&callback) {
|
||||
this->gap_scan_event_callbacks_.add(std::forward<F>(callback));
|
||||
}
|
||||
#endif
|
||||
#if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT)
|
||||
void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); }
|
||||
template<typename F> void add_gattc_event_callback(F &&callback) {
|
||||
this->gattc_event_callbacks_.add(std::forward<F>(callback));
|
||||
}
|
||||
#endif
|
||||
#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT)
|
||||
void register_gatts_event_handler(GATTsEventHandler *handler) { this->gatts_event_handlers_.push_back(handler); }
|
||||
template<typename F> void add_gatts_event_callback(F &&callback) {
|
||||
this->gatts_event_callbacks_.add(std::forward<F>(callback));
|
||||
}
|
||||
#endif
|
||||
#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT
|
||||
void register_ble_status_event_handler(BLEStatusEventHandler *handler) {
|
||||
this->ble_status_event_handlers_.push_back(handler);
|
||||
template<typename F> void add_ble_status_event_callback(F &&callback) {
|
||||
this->ble_status_event_callbacks_.add(std::forward<F>(callback));
|
||||
}
|
||||
#endif
|
||||
void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; }
|
||||
@@ -202,21 +177,27 @@ class ESP32BLE : public Component {
|
||||
private:
|
||||
template<typename... Args> friend void enqueue_ble_event(Args... args);
|
||||
|
||||
// Handler vectors - use StaticVector when counts are known at compile time
|
||||
#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
|
||||
StaticVector<GAPEventHandler *, ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT> gap_event_handlers_;
|
||||
StaticCallbackManager<ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT,
|
||||
void(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *)>
|
||||
gap_event_callbacks_;
|
||||
#endif
|
||||
#ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT
|
||||
StaticVector<GAPScanEventHandler *, ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT> gap_scan_event_handlers_;
|
||||
StaticCallbackManager<ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT, void(const BLEScanResult &)>
|
||||
gap_scan_event_callbacks_;
|
||||
#endif
|
||||
#if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT)
|
||||
StaticVector<GATTcEventHandler *, ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT> gattc_event_handlers_;
|
||||
StaticCallbackManager<ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT,
|
||||
void(esp_gattc_cb_event_t, esp_gatt_if_t, esp_ble_gattc_cb_param_t *)>
|
||||
gattc_event_callbacks_;
|
||||
#endif
|
||||
#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT)
|
||||
StaticVector<GATTsEventHandler *, ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT> gatts_event_handlers_;
|
||||
StaticCallbackManager<ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT,
|
||||
void(esp_gatts_cb_event_t, esp_gatt_if_t, esp_ble_gatts_cb_param_t *)>
|
||||
gatts_event_callbacks_;
|
||||
#endif
|
||||
#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT
|
||||
StaticVector<BLEStatusEventHandler *, ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT> ble_status_event_handlers_;
|
||||
StaticCallbackManager<ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT, void()> ble_status_event_callbacks_;
|
||||
#endif
|
||||
|
||||
// Large objects (size depends on template parameters, but typically aligned to 4 bytes)
|
||||
|
||||
@@ -13,7 +13,6 @@ esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon")
|
||||
ESP32BLEBeacon = esp32_ble_beacon_ns.class_(
|
||||
"ESP32BLEBeacon",
|
||||
cg.Component,
|
||||
esp32_ble.GAPEventHandler,
|
||||
cg.Parented.template(esp32_ble.ESP32BLE),
|
||||
)
|
||||
CONF_MAJOR = "major"
|
||||
|
||||
@@ -35,7 +35,7 @@ using esp_ble_ibeacon_t = struct {
|
||||
|
||||
using namespace esp32_ble;
|
||||
|
||||
class ESP32BLEBeacon : public Component, public GAPEventHandler, public Parented<ESP32BLE> {
|
||||
class ESP32BLEBeacon : public Component, public Parented<ESP32BLE> {
|
||||
public:
|
||||
explicit ESP32BLEBeacon(const std::array<uint8_t, 16> &uuid) : uuid_(uuid) {}
|
||||
|
||||
@@ -51,7 +51,7 @@ class ESP32BLEBeacon : public Component, public GAPEventHandler, public Parented
|
||||
#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
|
||||
void set_tx_power(esp_power_level_t val) { this->tx_power_ = val; }
|
||||
#endif
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
|
||||
protected:
|
||||
void on_advertise_();
|
||||
|
||||
@@ -350,7 +350,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
// For V3_WITHOUT_CACHE, we already set fast params before connecting
|
||||
// No need to update them again here
|
||||
this->log_event_("Searching for services");
|
||||
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
|
||||
esp_ble_gattc_search_service(esp_gattc_if, param->open.conn_id, nullptr);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_CONNECT_EVT: {
|
||||
|
||||
@@ -72,7 +72,6 @@ BLECharacteristic_ns = esp32_ble_server_ns.namespace("BLECharacteristic")
|
||||
BLEServer = esp32_ble_server_ns.class_(
|
||||
"BLEServer",
|
||||
cg.Component,
|
||||
esp32_ble.GATTsEventHandler,
|
||||
cg.Parented.template(esp32_ble.ESP32BLE),
|
||||
)
|
||||
esp32_ble_server_automations_ns = esp32_ble_server_ns.namespace(
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace esp32_ble_server {
|
||||
using namespace esp32_ble;
|
||||
using namespace bytebuffer;
|
||||
|
||||
class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEventHandler, public Parented<ESP32BLE> {
|
||||
class BLEServer : public Component, public Parented<ESP32BLE> {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
@@ -53,10 +53,9 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv
|
||||
const uint16_t *get_clients() const { return this->clients_; }
|
||||
uint8_t get_client_count() const { return this->client_count_; }
|
||||
|
||||
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param) override;
|
||||
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
void ble_before_disabled_event_handler() override;
|
||||
void ble_before_disabled_event_handler();
|
||||
|
||||
// Direct callback registration - supports multiple callbacks
|
||||
void on_connect(std::function<void(uint16_t)> &&callback) {
|
||||
|
||||
@@ -70,6 +70,7 @@ template<typename... Ts> class BLECharacteristicSetValueAction : public Action<T
|
||||
public:
|
||||
BLECharacteristicSetValueAction(BLECharacteristic *characteristic) : parent_(characteristic) {}
|
||||
TEMPLATABLE_VALUE(std::vector<uint8_t>, buffer)
|
||||
void set_buffer(std::initializer_list<uint8_t> buffer) { this->buffer_ = std::vector<uint8_t>(buffer); }
|
||||
void set_buffer(ByteBuffer buffer) { this->set_buffer(buffer.get_data()); }
|
||||
void play(const Ts &...x) override {
|
||||
// If the listener is already set, do nothing
|
||||
@@ -115,6 +116,7 @@ template<typename... Ts> class BLEDescriptorSetValueAction : public Action<Ts...
|
||||
public:
|
||||
BLEDescriptorSetValueAction(BLEDescriptor *descriptor) : parent_(descriptor) {}
|
||||
TEMPLATABLE_VALUE(std::vector<uint8_t>, buffer)
|
||||
void set_buffer(std::initializer_list<uint8_t> buffer) { this->buffer_ = std::vector<uint8_t>(buffer); }
|
||||
void set_buffer(ByteBuffer buffer) { this->set_buffer(buffer.get_data()); }
|
||||
void play(const Ts &...x) override { this->parent_->set_value(this->buffer_.value(x...)); }
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_STOP_EVT: {
|
||||
if (param->start.service_handle == this->handle_) {
|
||||
if (param->stop.service_handle == this->handle_) {
|
||||
this->state_ = STOPPED;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -90,8 +90,6 @@ esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker")
|
||||
ESP32BLETracker = esp32_ble_tracker_ns.class_(
|
||||
"ESP32BLETracker",
|
||||
cg.Component,
|
||||
esp32_ble.GAPEventHandler,
|
||||
esp32_ble.GATTcEventHandler,
|
||||
cg.Parented.template(esp32_ble.ESP32BLE),
|
||||
)
|
||||
ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient")
|
||||
|
||||
@@ -88,12 +88,18 @@ void ESP32BLETracker::setup() {
|
||||
#ifdef USE_OTA_STATE_LISTENER
|
||||
void ESP32BLETracker::on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) {
|
||||
if (state == ota::OTA_STARTED) {
|
||||
this->scan_continuous_before_ota_ = this->scan_continuous_;
|
||||
this->stop_scan();
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
||||
for (auto *client : this->clients_) {
|
||||
client->disconnect();
|
||||
}
|
||||
#endif
|
||||
} else if ((state == ota::OTA_ERROR || state == ota::OTA_ABORT) && this->scan_continuous_before_ota_) {
|
||||
this->scan_continuous_before_ota_ = false;
|
||||
this->scan_continuous_ = true;
|
||||
// Do not restart scanning immediately here; allow loop() to
|
||||
// safely restart scanning once the scanner and all clients are idle.
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -243,7 +249,7 @@ void ESP32BLETracker::start_scan_(bool first) {
|
||||
return;
|
||||
}
|
||||
this->set_scanner_state_(ScannerState::STARTING);
|
||||
ESP_LOGD(TAG, "Starting scan, set scanner state to STARTING.");
|
||||
ESP_LOGV(TAG, "Starting scan, set scanner state to STARTING.");
|
||||
if (!first) {
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
|
||||
for (auto *listener : this->listeners_)
|
||||
@@ -849,7 +855,7 @@ void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
|
||||
}
|
||||
|
||||
void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) {
|
||||
ESP_LOGD(TAG, "Scan %scomplete, set scanner state to IDLE.", is_stop_complete ? "stop " : "");
|
||||
ESP_LOGV(TAG, "Scan %scomplete, set scanner state to IDLE.", is_stop_complete ? "stop " : "");
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
this->already_discovered_.clear();
|
||||
#endif
|
||||
|
||||
@@ -291,10 +291,6 @@ class ESPBTClient : public ESPBTDeviceListener {
|
||||
};
|
||||
|
||||
class ESP32BLETracker : public Component,
|
||||
public GAPEventHandler,
|
||||
public GAPScanEventHandler,
|
||||
public GATTcEventHandler,
|
||||
public BLEStatusEventHandler,
|
||||
#ifdef USE_OTA_STATE_LISTENER
|
||||
public ota::OTAGlobalStateListener,
|
||||
#endif
|
||||
@@ -325,11 +321,10 @@ class ESP32BLETracker : public Component,
|
||||
void start_scan();
|
||||
void stop_scan();
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||
void gap_scan_event_handler(const BLEScanResult &scan_result) override;
|
||||
void ble_before_disabled_event_handler() override;
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
void gap_scan_event_handler(const BLEScanResult &scan_result);
|
||||
void ble_before_disabled_event_handler();
|
||||
|
||||
#ifdef USE_OTA_STATE_LISTENER
|
||||
void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override;
|
||||
@@ -436,6 +431,9 @@ class ESP32BLETracker : public Component,
|
||||
ScannerState scanner_state_{ScannerState::IDLE};
|
||||
bool scan_continuous_;
|
||||
bool scan_active_;
|
||||
#ifdef USE_OTA_STATE_LISTENER
|
||||
bool scan_continuous_before_ota_{false};
|
||||
#endif
|
||||
bool ble_was_disabled_{true};
|
||||
bool raw_advertisements_{false};
|
||||
bool parse_advertisements_{false};
|
||||
|
||||
@@ -400,7 +400,7 @@ async def to_code(config):
|
||||
if config[CONF_JPEG_QUALITY] != 0 and config[CONF_PIXEL_FORMAT] != "JPEG":
|
||||
cg.add_define("USE_ESP32_CAMERA_JPEG_CONVERSION")
|
||||
|
||||
add_idf_component(name="espressif/esp32-camera", ref="2.1.5")
|
||||
add_idf_component(name="espressif/esp32-camera", ref="2.1.6")
|
||||
add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_NEW", True)
|
||||
add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_LEGACY", False)
|
||||
|
||||
|
||||
@@ -448,6 +448,13 @@ void Esp32HostedUpdate::perform(bool force) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
if (this->firmware_url_.empty()) {
|
||||
ESP_LOGW(TAG, "No firmware URL available, run check first");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
update::UpdateState prev_state = this->state_;
|
||||
this->state_ = update::UPDATE_STATE_INSTALLING;
|
||||
this->update_info_.has_progress = false;
|
||||
|
||||
@@ -217,7 +217,7 @@ void ESP32TouchComponent::setup() {
|
||||
for (uint32_t i = 0; i < ONESHOT_SCAN_COUNT; i++) {
|
||||
err = touch_sensor_trigger_oneshot_scanning(this->sens_handle_, ONESHOT_SCAN_TIMEOUT_MS);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Oneshot scan %d failed: %s", i, esp_err_to_name(err));
|
||||
ESP_LOGW(TAG, "Oneshot scan %" PRIu32 " failed: %s", i, esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
@@ -18,8 +19,9 @@ from esphome.const import (
|
||||
PLATFORM_ESP8266,
|
||||
ThreadModel,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, CoroPriority, Lambda, coroutine_with_priority
|
||||
from esphome.helpers import copy_file_if_changed
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .boards import BOARDS, ESP8266_LD_SCRIPTS
|
||||
from .const import (
|
||||
@@ -40,12 +42,42 @@ from .const import (
|
||||
)
|
||||
from .gpio import PinInitialState, add_pin_initial_states_array
|
||||
|
||||
CONF_ENABLE_SCANF_FLOAT = "enable_scanf_float"
|
||||
# Heuristically matches scanf/sscanf calls with float format specifiers.
|
||||
# Standard scanf float conversions: %f %F %e %E %g %G %a %A
|
||||
# With optional modifiers: %*f (suppression), %8f (width), %lf %Lf (length)
|
||||
# Also matches non-standard patterns like %.2f as a heuristic — these are
|
||||
# invalid in scanf but users may write them by analogy with printf.
|
||||
# Uses [^;]*? to stay within a single statement, preventing false positives
|
||||
# from e.g. sscanf(buf, "%d", &x); printf("%f", val);
|
||||
_SCANF_FLOAT_RE = re.compile(r"scanf\s*\([^;]*?%[*\d.]*[hlL]*[feEgGaAF]")
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
AUTO_LOAD = ["preferences"]
|
||||
IS_TARGET_PLATFORM = True
|
||||
|
||||
|
||||
def lambdas_use_scanf_float(config: ConfigType) -> bool:
|
||||
"""Check if any lambda in the config uses scanf with a float format specifier.
|
||||
|
||||
Comments are stripped before matching to avoid false positives from
|
||||
commented-out code. The cost of a false positive is only ~8KB flash.
|
||||
"""
|
||||
stack: list = [config]
|
||||
while stack:
|
||||
obj = stack.pop()
|
||||
if isinstance(obj, Lambda):
|
||||
src = obj.comment_remover(obj.value)
|
||||
if _SCANF_FLOAT_RE.search(src):
|
||||
return True
|
||||
elif isinstance(obj, dict):
|
||||
stack.extend(obj.values())
|
||||
elif isinstance(obj, list):
|
||||
stack.extend(obj)
|
||||
return False
|
||||
|
||||
|
||||
def set_core_data(config):
|
||||
CORE.data[KEY_ESP8266] = {}
|
||||
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_ESP8266
|
||||
@@ -181,6 +213,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_ENABLE_SERIAL): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_SERIAL1): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_FULL_PRINTF, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_SCANF_FLOAT): cv.boolean,
|
||||
}
|
||||
),
|
||||
set_core_data,
|
||||
@@ -201,16 +234,23 @@ async def to_code(config):
|
||||
cg.add_define("ESPHOME_VARIANT", "ESP8266")
|
||||
cg.add_define(ThreadModel.SINGLE)
|
||||
|
||||
cg.add_platformio_option(
|
||||
"extra_scripts",
|
||||
[
|
||||
"pre:testing_mode.py",
|
||||
"pre:exclude_updater.py",
|
||||
"pre:exclude_waveform.py",
|
||||
"pre:remove_float_scanf.py",
|
||||
"post:post_build.py",
|
||||
],
|
||||
)
|
||||
enable_scanf_float = config.get(CONF_ENABLE_SCANF_FLOAT)
|
||||
if enable_scanf_float is None and lambdas_use_scanf_float(CORE.config):
|
||||
enable_scanf_float = True
|
||||
_LOGGER.warning(
|
||||
"Lambda uses scanf with a float format specifier; "
|
||||
"enabling scanf float support (~8KB flash)"
|
||||
)
|
||||
|
||||
extra_scripts = [
|
||||
"pre:testing_mode.py",
|
||||
"pre:exclude_updater.py",
|
||||
"pre:exclude_waveform.py",
|
||||
]
|
||||
if not enable_scanf_float:
|
||||
extra_scripts.append("pre:remove_float_scanf.py")
|
||||
extra_scripts.append("post:post_build.py")
|
||||
cg.add_platformio_option("extra_scripts", extra_scripts)
|
||||
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#include "espnow_err.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -266,7 +268,7 @@ void ESPNowComponent::loop() {
|
||||
if (wifi::global_wifi_component != nullptr && wifi::global_wifi_component->is_connected()) {
|
||||
int32_t new_channel = wifi::global_wifi_component->get_wifi_channel();
|
||||
if (new_channel != this->wifi_channel_) {
|
||||
ESP_LOGI(TAG, "Wifi Channel is changed from %d to %d.", this->wifi_channel_, new_channel);
|
||||
ESP_LOGI(TAG, "Wifi Channel is changed from %d to %" PRId32 ".", this->wifi_channel_, new_channel);
|
||||
this->wifi_channel_ = new_channel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MQTT_ID,
|
||||
CONF_ON_EVENT,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_WEB_SERVER,
|
||||
DEVICE_CLASS_BUTTON,
|
||||
DEVICE_CLASS_DOORBELL,
|
||||
@@ -41,8 +40,6 @@ EventPtr = Event.operator("ptr")
|
||||
|
||||
TriggerEventAction = event_ns.class_("TriggerEventAction", automation.Action)
|
||||
|
||||
EventTrigger = event_ns.class_("EventTrigger", automation.Trigger.template())
|
||||
|
||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||
|
||||
_EVENT_SCHEMA = (
|
||||
@@ -53,11 +50,7 @@ _EVENT_SCHEMA = (
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTEventComponent),
|
||||
cv.GenerateID(): cv.declare_id(Event),
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(CONF_ON_EVENT): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(EventTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_EVENT): automation.validate_automation({}),
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -92,8 +85,9 @@ def event_schema(
|
||||
@setup_entity("event")
|
||||
async def setup_event_core_(var, config, *, event_types: list[str]):
|
||||
for conf in config.get(CONF_ON_EVENT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.StringRef, "event_type")], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_event_callback", [(cg.StringRef, "event_type")], conf
|
||||
)
|
||||
|
||||
cg.add(var.set_event_types(event_types))
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
#pragma once
|
||||
#include <utility>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "ezo.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ezo {
|
||||
|
||||
class LedTrigger : public Trigger<bool> {
|
||||
public:
|
||||
explicit LedTrigger(EZOSensor *ezo) {
|
||||
ezo->add_led_state_callback([this](bool value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
class CustomTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
explicit CustomTrigger(EZOSensor *ezo) {
|
||||
ezo->add_custom_callback([this](const std::string &value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
class TTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
explicit TTrigger(EZOSensor *ezo) {
|
||||
ezo->add_t_callback([this](const std::string &value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
class CalibrationTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
explicit CalibrationTrigger(EZOSensor *ezo) {
|
||||
ezo->add_calibration_callback([this](const std::string &value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
class SlopeTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
explicit SlopeTrigger(EZOSensor *ezo) {
|
||||
ezo->add_slope_callback([this](const std::string &value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
class DeviceInformationTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
explicit DeviceInformationTrigger(EZOSensor *ezo) {
|
||||
ezo->add_device_infomation_callback([this](const std::string &value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ezo
|
||||
} // namespace esphome
|
||||
@@ -2,7 +2,7 @@ from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@ssieb"]
|
||||
|
||||
@@ -21,61 +21,16 @@ EZOSensor = ezo_ns.class_(
|
||||
"EZOSensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CustomTrigger = ezo_ns.class_(
|
||||
"CustomTrigger", automation.Trigger.template(cg.std_string)
|
||||
)
|
||||
|
||||
|
||||
TTrigger = ezo_ns.class_("TTrigger", automation.Trigger.template(cg.std_string))
|
||||
|
||||
SlopeTrigger = ezo_ns.class_("SlopeTrigger", automation.Trigger.template(cg.std_string))
|
||||
|
||||
CalibrationTrigger = ezo_ns.class_(
|
||||
"CalibrationTrigger", automation.Trigger.template(cg.std_string)
|
||||
)
|
||||
|
||||
DeviceInformationTrigger = ezo_ns.class_(
|
||||
"DeviceInformationTrigger", automation.Trigger.template(cg.std_string)
|
||||
)
|
||||
|
||||
LedTrigger = ezo_ns.class_("LedTrigger", automation.Trigger.template(cg.bool_))
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(EZOSensor)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_ON_CUSTOM): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CustomTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_CALIBRATION): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CalibrationTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_SLOPE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SlopeTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_T): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_DEVICE_INFORMATION): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DeviceInformationTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_LED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LedTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_CUSTOM): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_CALIBRATION): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_SLOPE): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_T): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_DEVICE_INFORMATION): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_LED): automation.validate_automation({}),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
@@ -90,25 +45,26 @@ async def to_code(config):
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
for conf in config.get(CONF_ON_CUSTOM, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
|
||||
await automation.build_callback_automation(
|
||||
var, "add_custom_callback", [(cg.std_string, "x")], conf
|
||||
)
|
||||
for conf in config.get(CONF_ON_LED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(bool, "x")], conf)
|
||||
|
||||
await automation.build_callback_automation(
|
||||
var, "add_led_state_callback", [(bool, "x")], conf
|
||||
)
|
||||
for conf in config.get(CONF_ON_DEVICE_INFORMATION, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
|
||||
await automation.build_callback_automation(
|
||||
var, "add_device_infomation_callback", [(cg.std_string, "x")], conf
|
||||
)
|
||||
for conf in config.get(CONF_ON_SLOPE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
|
||||
await automation.build_callback_automation(
|
||||
var, "add_slope_callback", [(cg.std_string, "x")], conf
|
||||
)
|
||||
for conf in config.get(CONF_ON_CALIBRATION, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
|
||||
await automation.build_callback_automation(
|
||||
var, "add_calibration_callback", [(cg.std_string, "x")], conf
|
||||
)
|
||||
for conf in config.get(CONF_ON_T, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_t_callback", [(cg.std_string, "x")], conf
|
||||
)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from esphome.automation import Trigger, build_automation, validate_automation
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp8266 import CONF_RESTORE_FROM_FLASH, KEY_ESP8266
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_TRIGGER_ID,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
@@ -18,7 +17,6 @@ CODEOWNERS = ["@anatoly-savchenkov"]
|
||||
|
||||
factory_reset_ns = cg.esphome_ns.namespace("factory_reset")
|
||||
FactoryResetComponent = factory_reset_ns.class_("FactoryResetComponent", cg.Component)
|
||||
FastBootTrigger = factory_reset_ns.class_("FastBootTrigger", Trigger, cg.Component)
|
||||
|
||||
CONF_MAX_DELAY = "max_delay"
|
||||
CONF_RESETS_REQUIRED = "resets_required"
|
||||
@@ -55,11 +53,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_RESETS_REQUIRED): cv.positive_not_null_int,
|
||||
cv.Optional(CONF_ON_INCREMENT): validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FastBootTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_INCREMENT): automation.validate_automation({}),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
_validate,
|
||||
@@ -88,12 +82,9 @@ async def to_code(config):
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
for conf in config.get(CONF_ON_INCREMENT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await build_automation(
|
||||
trigger,
|
||||
[
|
||||
(cg.uint8, "x"),
|
||||
(cg.uint8, "target"),
|
||||
],
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_increment_callback",
|
||||
[(cg.uint8, "x"), (cg.uint8, "target")],
|
||||
conf,
|
||||
)
|
||||
|
||||
@@ -30,12 +30,6 @@ class FactoryResetComponent : public Component {
|
||||
uint8_t required_count_; // The number of boot attempts before fast boot is enabled
|
||||
};
|
||||
|
||||
class FastBootTrigger : public Trigger<uint8_t, uint8_t> {
|
||||
public:
|
||||
explicit FastBootTrigger(FactoryResetComponent *parent) {
|
||||
parent->add_increment_callback([this](uint8_t current, uint8_t target) { this->trigger(current, target); });
|
||||
}
|
||||
};
|
||||
} // namespace esphome::factory_reset
|
||||
|
||||
#endif // !defined(USE_RP2040) && !defined(USE_HOST)
|
||||
|
||||
@@ -21,7 +21,6 @@ from esphome.const import (
|
||||
CONF_SENSING_PIN,
|
||||
CONF_SPEED,
|
||||
CONF_STATE,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@OnFreund", "@loongyh", "@alexborro"]
|
||||
@@ -38,38 +37,6 @@ FingerprintGrowComponent = fingerprint_grow_ns.class_(
|
||||
"FingerprintGrowComponent", cg.PollingComponent, uart.UARTDevice
|
||||
)
|
||||
|
||||
FingerScanStartTrigger = fingerprint_grow_ns.class_(
|
||||
"FingerScanStartTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
FingerScanMatchedTrigger = fingerprint_grow_ns.class_(
|
||||
"FingerScanMatchedTrigger", automation.Trigger.template(cg.uint16, cg.uint16)
|
||||
)
|
||||
|
||||
FingerScanUnmatchedTrigger = fingerprint_grow_ns.class_(
|
||||
"FingerScanUnmatchedTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
FingerScanMisplacedTrigger = fingerprint_grow_ns.class_(
|
||||
"FingerScanMisplacedTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
FingerScanInvalidTrigger = fingerprint_grow_ns.class_(
|
||||
"FingerScanInvalidTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
EnrollmentScanTrigger = fingerprint_grow_ns.class_(
|
||||
"EnrollmentScanTrigger", automation.Trigger.template(cg.uint8, cg.uint16)
|
||||
)
|
||||
|
||||
EnrollmentDoneTrigger = fingerprint_grow_ns.class_(
|
||||
"EnrollmentDoneTrigger", automation.Trigger.template(cg.uint16)
|
||||
)
|
||||
|
||||
EnrollmentFailedTrigger = fingerprint_grow_ns.class_(
|
||||
"EnrollmentFailedTrigger", automation.Trigger.template(cg.uint16)
|
||||
)
|
||||
|
||||
EnrollmentAction = fingerprint_grow_ns.class_("EnrollmentAction", automation.Action)
|
||||
CancelEnrollmentAction = fingerprint_grow_ns.class_(
|
||||
"CancelEnrollmentAction", automation.Action
|
||||
@@ -125,62 +92,22 @@ CONFIG_SCHEMA = cv.All(
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_PASSWORD): cv.uint32_t,
|
||||
cv.Optional(CONF_NEW_PASSWORD): cv.uint32_t,
|
||||
cv.Optional(CONF_ON_FINGER_SCAN_START): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
FingerScanStartTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_FINGER_SCAN_START): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_FINGER_SCAN_MATCHED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
FingerScanMatchedTrigger
|
||||
),
|
||||
}
|
||||
{}
|
||||
),
|
||||
cv.Optional(CONF_ON_FINGER_SCAN_UNMATCHED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
FingerScanUnmatchedTrigger
|
||||
),
|
||||
}
|
||||
{}
|
||||
),
|
||||
cv.Optional(CONF_ON_FINGER_SCAN_MISPLACED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
FingerScanMisplacedTrigger
|
||||
),
|
||||
}
|
||||
{}
|
||||
),
|
||||
cv.Optional(CONF_ON_FINGER_SCAN_INVALID): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
FingerScanInvalidTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ENROLLMENT_SCAN): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
EnrollmentScanTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
EnrollmentDoneTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
EnrollmentFailedTrigger
|
||||
),
|
||||
}
|
||||
{}
|
||||
),
|
||||
cv.Optional(CONF_ON_ENROLLMENT_SCAN): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation({}),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("500ms"))
|
||||
@@ -214,40 +141,43 @@ async def to_code(config):
|
||||
cg.add(var.set_idle_period_to_sleep_ms(idle_period_to_sleep_ms))
|
||||
|
||||
for conf in config.get(CONF_ON_FINGER_SCAN_START, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_finger_scan_start_callback", [], conf
|
||||
)
|
||||
for conf in config.get(CONF_ON_FINGER_SCAN_MATCHED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.uint16, "finger_id"), (cg.uint16, "confidence")], conf
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_on_finger_scan_matched_callback",
|
||||
[(cg.uint16, "finger_id"), (cg.uint16, "confidence")],
|
||||
conf,
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_FINGER_SCAN_UNMATCHED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_finger_scan_unmatched_callback", [], conf
|
||||
)
|
||||
for conf in config.get(CONF_ON_FINGER_SCAN_MISPLACED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_finger_scan_misplaced_callback", [], conf
|
||||
)
|
||||
for conf in config.get(CONF_ON_FINGER_SCAN_INVALID, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_finger_scan_invalid_callback", [], conf
|
||||
)
|
||||
for conf in config.get(CONF_ON_ENROLLMENT_SCAN, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.uint8, "scan_num"), (cg.uint16, "finger_id")], conf
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_on_enrollment_scan_callback",
|
||||
[(cg.uint8, "scan_num"), (cg.uint16, "finger_id")],
|
||||
conf,
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_ENROLLMENT_DONE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.uint16, "finger_id")], conf)
|
||||
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_enrollment_done_callback", [(cg.uint16, "finger_id")], conf
|
||||
)
|
||||
for conf in config.get(CONF_ON_ENROLLMENT_FAILED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.uint16, "finger_id")], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_enrollment_failed_callback", [(cg.uint16, "finger_id")], conf
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
||||
@@ -210,64 +210,6 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
|
||||
CallbackManager<void(uint16_t)> enrollment_failed_callback_;
|
||||
};
|
||||
|
||||
class FingerScanStartTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit FingerScanStartTrigger(FingerprintGrowComponent *parent) {
|
||||
parent->add_on_finger_scan_start_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
class FingerScanMatchedTrigger : public Trigger<uint16_t, uint16_t> {
|
||||
public:
|
||||
explicit FingerScanMatchedTrigger(FingerprintGrowComponent *parent) {
|
||||
parent->add_on_finger_scan_matched_callback(
|
||||
[this](uint16_t finger_id, uint16_t confidence) { this->trigger(finger_id, confidence); });
|
||||
}
|
||||
};
|
||||
|
||||
class FingerScanUnmatchedTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit FingerScanUnmatchedTrigger(FingerprintGrowComponent *parent) {
|
||||
parent->add_on_finger_scan_unmatched_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
class FingerScanMisplacedTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit FingerScanMisplacedTrigger(FingerprintGrowComponent *parent) {
|
||||
parent->add_on_finger_scan_misplaced_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
class FingerScanInvalidTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit FingerScanInvalidTrigger(FingerprintGrowComponent *parent) {
|
||||
parent->add_on_finger_scan_invalid_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
class EnrollmentScanTrigger : public Trigger<uint8_t, uint16_t> {
|
||||
public:
|
||||
explicit EnrollmentScanTrigger(FingerprintGrowComponent *parent) {
|
||||
parent->add_on_enrollment_scan_callback(
|
||||
[this](uint8_t scan_num, uint16_t finger_id) { this->trigger(scan_num, finger_id); });
|
||||
}
|
||||
};
|
||||
|
||||
class EnrollmentDoneTrigger : public Trigger<uint16_t> {
|
||||
public:
|
||||
explicit EnrollmentDoneTrigger(FingerprintGrowComponent *parent) {
|
||||
parent->add_on_enrollment_done_callback([this](uint16_t finger_id) { this->trigger(finger_id); });
|
||||
}
|
||||
};
|
||||
|
||||
class EnrollmentFailedTrigger : public Trigger<uint16_t> {
|
||||
public:
|
||||
explicit EnrollmentFailedTrigger(FingerprintGrowComponent *parent) {
|
||||
parent->add_on_enrollment_failed_callback([this](uint16_t finger_id) { this->trigger(finger_id); });
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class EnrollmentAction : public Action<Ts...>, public Parented<FingerprintGrowComponent> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, finger_id)
|
||||
|
||||
@@ -22,7 +22,6 @@ from esphome.const import (
|
||||
CONF_SUPPORTED_SWING_MODES,
|
||||
CONF_TARGET_TEMPERATURE,
|
||||
CONF_TEMPERATURE_STEP,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_VISUAL,
|
||||
CONF_WIFI,
|
||||
)
|
||||
@@ -122,21 +121,6 @@ SUPPORTED_HON_CONTROL_METHODS = {
|
||||
"SET_SINGLE_PARAMETER": HonControlMethod.SET_SINGLE_PARAMETER,
|
||||
}
|
||||
|
||||
HaierAlarmStartTrigger = haier_ns.class_(
|
||||
"HaierAlarmStartTrigger",
|
||||
automation.Trigger.template(cg.uint8, cg.const_char_ptr),
|
||||
)
|
||||
|
||||
HaierAlarmEndTrigger = haier_ns.class_(
|
||||
"HaierAlarmEndTrigger",
|
||||
automation.Trigger.template(cg.uint8, cg.const_char_ptr),
|
||||
)
|
||||
|
||||
StatusMessageTrigger = haier_ns.class_(
|
||||
"StatusMessageTrigger",
|
||||
automation.Trigger.template(cg.const_char_ptr, cg.size_t),
|
||||
)
|
||||
|
||||
|
||||
def validate_visual(config):
|
||||
if CONF_VISUAL in config:
|
||||
@@ -203,13 +187,7 @@ def _base_config_schema(class_: MockObjClass) -> cv.Schema:
|
||||
cv.Optional(
|
||||
CONF_ANSWER_TIMEOUT,
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
StatusMessageTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation({}),
|
||||
}
|
||||
)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
@@ -264,19 +242,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
f"The {CONF_OUTDOOR_TEMPERATURE} option is deprecated, use a sensor for a haier platform instead"
|
||||
),
|
||||
cv.Optional(CONF_ON_ALARM_START): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
HaierAlarmStartTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ALARM_END): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
HaierAlarmEndTrigger
|
||||
),
|
||||
}
|
||||
{}
|
||||
),
|
||||
cv.Optional(CONF_ON_ALARM_END): automation.validate_automation({}),
|
||||
}
|
||||
),
|
||||
},
|
||||
@@ -530,19 +498,25 @@ async def to_code(config):
|
||||
var.set_status_message_header_size(config[CONF_STATUS_MESSAGE_HEADER_SIZE])
|
||||
)
|
||||
for conf in config.get(CONF_ON_ALARM_START, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_alarm_start_callback",
|
||||
[(cg.uint8, "code"), (cg.const_char_ptr, "message")],
|
||||
conf,
|
||||
)
|
||||
for conf in config.get(CONF_ON_ALARM_END, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_alarm_end_callback",
|
||||
[(cg.uint8, "code"), (cg.const_char_ptr, "message")],
|
||||
conf,
|
||||
)
|
||||
for conf in config.get(CONF_ON_STATUS_MESSAGE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.const_char_ptr, "data"), (cg.size_t, "data_size")], conf
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_status_message_callback",
|
||||
[(cg.const_char_ptr, "data"), (cg.size_t, "data_size")],
|
||||
conf,
|
||||
)
|
||||
# https://github.com/paveldn/HaierProtocol
|
||||
cg.add_library("pavlodn/HaierProtocol", "0.9.31")
|
||||
|
||||
@@ -177,12 +177,5 @@ class HaierClimateBase : public esphome::Component,
|
||||
ESPPreferenceObject base_rtc_;
|
||||
};
|
||||
|
||||
class StatusMessageTrigger : public Trigger<const char *, size_t> {
|
||||
public:
|
||||
explicit StatusMessageTrigger(HaierClimateBase *parent) {
|
||||
parent->add_status_message_callback([this](const char *data, size_t data_size) { this->trigger(data, data_size); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace haier
|
||||
} // namespace esphome
|
||||
|
||||
@@ -675,7 +675,6 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
|
||||
this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
|
||||
}
|
||||
out_data->beeper_status = ((!this->get_beeper_state()) || (!has_hvac_settings)) ? 1 : 0;
|
||||
control_out_buffer[4] = 0; // This byte should be cleared before setting values
|
||||
out_data->display_status = this->get_display_state() ? 1 : 0;
|
||||
this->display_status_ = (SwitchState) ((uint8_t) this->display_status_ & 0b01);
|
||||
out_data->health_mode = this->get_health_mode() ? 1 : 0;
|
||||
|
||||
@@ -200,21 +200,5 @@ class HonClimate : public HaierClimateBase {
|
||||
SwitchState quiet_mode_state_{SwitchState::OFF};
|
||||
};
|
||||
|
||||
class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> {
|
||||
public:
|
||||
explicit HaierAlarmStartTrigger(HonClimate *parent) {
|
||||
parent->add_alarm_start_callback(
|
||||
[this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); });
|
||||
}
|
||||
};
|
||||
|
||||
class HaierAlarmEndTrigger : public Trigger<uint8_t, const char *> {
|
||||
public:
|
||||
explicit HaierAlarmEndTrigger(HonClimate *parent) {
|
||||
parent->add_alarm_end_callback(
|
||||
[this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace haier
|
||||
} // namespace esphome
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@G-Pereira", "@jesserockz"]
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "hdc2080.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::hdc2080 {
|
||||
|
||||
static const char *const TAG = "hdc2080";
|
||||
|
||||
// Register map (Table 8-6)
|
||||
static constexpr uint8_t REG_TEMPERATURE_LOW = 0x00; // Temperature [7:0]
|
||||
static constexpr uint8_t REG_TEMPERATURE_HIGH = 0x01; // Temperature [15:8]
|
||||
static constexpr uint8_t REG_HUMIDITY_LOW = 0x02; // Humidity [7:0]
|
||||
static constexpr uint8_t REG_HUMIDITY_HIGH = 0x03; // Humidity [15:8]
|
||||
static constexpr uint8_t REG_RESET_DRDY_INT_CONF = 0x0E; // Soft Reset and Interrupt Configuration
|
||||
static constexpr uint8_t REG_MEASUREMENT_CONFIGURATION = 0x0F;
|
||||
|
||||
// Measurement register (0x0F) bit fields
|
||||
static constexpr uint8_t MEAS_TRIG = 0x01; // Bit 0: start measurement
|
||||
static constexpr uint8_t MEAS_CONF_TEMP = 0x02; // Bits 2:1 = 01: temperature only
|
||||
static constexpr uint8_t MEAS_CONF_HUM = 0x04; // Bits 2:1 = 10: humidity only
|
||||
|
||||
void HDC2080Component::setup() {
|
||||
const uint8_t data = 0x00; // automatic measurement mode disabled, heater off
|
||||
if (this->write_register(REG_RESET_DRDY_INT_CONF, &data, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void HDC2080Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "HDC2080:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
void HDC2080Component::update() {
|
||||
uint8_t data = MEAS_TRIG; // 14-bit resolution, measure both, start
|
||||
if (this->temperature_sensor_ != nullptr && this->humidity_sensor_ == nullptr) {
|
||||
data = MEAS_TRIG | MEAS_CONF_TEMP;
|
||||
} else if (this->temperature_sensor_ == nullptr && this->humidity_sensor_ != nullptr) {
|
||||
data = MEAS_TRIG | MEAS_CONF_HUM;
|
||||
}
|
||||
if (this->write_register(REG_MEASUREMENT_CONFIGURATION, &data, 1) != i2c::ERROR_OK) {
|
||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
||||
return;
|
||||
}
|
||||
// wait for conversion to complete 2ms should be enough, more is fine
|
||||
this->set_timeout(5, [this]() {
|
||||
uint8_t raw_data[4];
|
||||
if (this->read_register(REG_TEMPERATURE_LOW, raw_data, 4) != i2c::ERROR_OK) {
|
||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
||||
return;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
float temp = encode_uint16(raw_data[1], raw_data[0]) * (165.0f / 65536.0f) - 40.5f;
|
||||
this->temperature_sensor_->publish_state(temp);
|
||||
}
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
float humidity = encode_uint16(raw_data[3], raw_data[2]) * (100.0f / 65536.0f);
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace esphome::hdc2080
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome::hdc2080 {
|
||||
|
||||
class HDC2080Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature(sensor::Sensor *temperature) { this->temperature_sensor_ = temperature; }
|
||||
void set_humidity(sensor::Sensor *humidity) { this->humidity_sensor_ = humidity; }
|
||||
|
||||
/// Setup the sensor and check for connection.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace esphome::hdc2080
|
||||
@@ -0,0 +1,57 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
hdc2080_ns = cg.esphome_ns.namespace("hdc2080")
|
||||
HDC2080Component = hdc2080_ns.class_(
|
||||
"HDC2080Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HDC2080Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x40))
|
||||
.add_extra(cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_HUMIDITY))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity(sens))
|
||||
@@ -8,7 +8,6 @@ from esphome.const import (
|
||||
CONF_NAME,
|
||||
CONF_ON_ENROLLMENT_DONE,
|
||||
CONF_ON_ENROLLMENT_FAILED,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@OnFreund"]
|
||||
@@ -28,33 +27,6 @@ HlkFm22xComponent = hlk_fm22x_ns.class_(
|
||||
"HlkFm22xComponent", cg.PollingComponent, uart.UARTDevice
|
||||
)
|
||||
|
||||
FaceScanMatchedTrigger = hlk_fm22x_ns.class_(
|
||||
"FaceScanMatchedTrigger", automation.Trigger.template(cg.int16, cg.std_string)
|
||||
)
|
||||
|
||||
FaceScanUnmatchedTrigger = hlk_fm22x_ns.class_(
|
||||
"FaceScanUnmatchedTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
FaceScanInvalidTrigger = hlk_fm22x_ns.class_(
|
||||
"FaceScanInvalidTrigger", automation.Trigger.template(cg.uint8)
|
||||
)
|
||||
|
||||
FaceInfoTrigger = hlk_fm22x_ns.class_(
|
||||
"FaceInfoTrigger",
|
||||
automation.Trigger.template(
|
||||
cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16
|
||||
),
|
||||
)
|
||||
|
||||
EnrollmentDoneTrigger = hlk_fm22x_ns.class_(
|
||||
"EnrollmentDoneTrigger", automation.Trigger.template(cg.int16, cg.uint8)
|
||||
)
|
||||
|
||||
EnrollmentFailedTrigger = hlk_fm22x_ns.class_(
|
||||
"EnrollmentFailedTrigger", automation.Trigger.template(cg.uint8)
|
||||
)
|
||||
|
||||
EnrollmentAction = hlk_fm22x_ns.class_("EnrollmentAction", automation.Action)
|
||||
DeleteAction = hlk_fm22x_ns.class_("DeleteAction", automation.Action)
|
||||
DeleteAllAction = hlk_fm22x_ns.class_("DeleteAllAction", automation.Action)
|
||||
@@ -65,46 +37,14 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HlkFm22xComponent),
|
||||
cv.Optional(CONF_ON_FACE_SCAN_MATCHED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
FaceScanMatchedTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_FACE_SCAN_MATCHED): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_FACE_SCAN_UNMATCHED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
FaceScanUnmatchedTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_FACE_SCAN_INVALID): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
FaceScanInvalidTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_FACE_INFO): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FaceInfoTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
EnrollmentDoneTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
EnrollmentFailedTrigger
|
||||
),
|
||||
}
|
||||
{}
|
||||
),
|
||||
cv.Optional(CONF_ON_FACE_SCAN_INVALID): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_FACE_INFO): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation({}),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("50ms"))
|
||||
@@ -118,23 +58,27 @@ async def to_code(config):
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
for conf in config.get(CONF_ON_FACE_SCAN_MATCHED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.int16, "face_id"), (cg.std_string, "name")], conf
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_on_face_scan_matched_callback",
|
||||
[(cg.int16, "face_id"), (cg.std_string, "name")],
|
||||
conf,
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_FACE_SCAN_UNMATCHED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_face_scan_unmatched_callback", [], conf
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_FACE_SCAN_INVALID, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.uint8, "error")], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_face_scan_invalid_callback", [(cg.uint8, "error")], conf
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_FACE_INFO, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger,
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_on_face_info_callback",
|
||||
[
|
||||
(cg.int16, "status"),
|
||||
(cg.int16, "left"),
|
||||
@@ -149,14 +93,17 @@ async def to_code(config):
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_ENROLLMENT_DONE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.int16, "face_id"), (cg.uint8, "direction")], conf
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_on_enrollment_done_callback",
|
||||
[(cg.int16, "face_id"), (cg.uint8, "direction")],
|
||||
conf,
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_ENROLLMENT_FAILED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.uint8, "error")], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_enrollment_failed_callback", [(cg.uint8, "error")], conf
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
||||
@@ -141,52 +141,6 @@ class HlkFm22xComponent : public PollingComponent, public uart::UARTDevice {
|
||||
CallbackManager<void(uint8_t)> enrollment_failed_callback_;
|
||||
};
|
||||
|
||||
class FaceScanMatchedTrigger : public Trigger<int16_t, std::string> {
|
||||
public:
|
||||
explicit FaceScanMatchedTrigger(HlkFm22xComponent *parent) {
|
||||
parent->add_on_face_scan_matched_callback(
|
||||
[this](int16_t face_id, const std::string &name) { this->trigger(face_id, name); });
|
||||
}
|
||||
};
|
||||
|
||||
class FaceScanUnmatchedTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit FaceScanUnmatchedTrigger(HlkFm22xComponent *parent) {
|
||||
parent->add_on_face_scan_unmatched_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
class FaceScanInvalidTrigger : public Trigger<uint8_t> {
|
||||
public:
|
||||
explicit FaceScanInvalidTrigger(HlkFm22xComponent *parent) {
|
||||
parent->add_on_face_scan_invalid_callback([this](uint8_t error) { this->trigger(error); });
|
||||
}
|
||||
};
|
||||
|
||||
class FaceInfoTrigger : public Trigger<int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t> {
|
||||
public:
|
||||
explicit FaceInfoTrigger(HlkFm22xComponent *parent) {
|
||||
parent->add_on_face_info_callback(
|
||||
[this](int16_t status, int16_t left, int16_t top, int16_t right, int16_t bottom, int16_t yaw, int16_t pitch,
|
||||
int16_t roll) { this->trigger(status, left, top, right, bottom, yaw, pitch, roll); });
|
||||
}
|
||||
};
|
||||
|
||||
class EnrollmentDoneTrigger : public Trigger<int16_t, uint8_t> {
|
||||
public:
|
||||
explicit EnrollmentDoneTrigger(HlkFm22xComponent *parent) {
|
||||
parent->add_on_enrollment_done_callback(
|
||||
[this](int16_t face_id, uint8_t direction) { this->trigger(face_id, direction); });
|
||||
}
|
||||
};
|
||||
|
||||
class EnrollmentFailedTrigger : public Trigger<uint8_t> {
|
||||
public:
|
||||
explicit EnrollmentFailedTrigger(HlkFm22xComponent *parent) {
|
||||
parent->add_on_enrollment_failed_callback([this](uint8_t error) { this->trigger(error); });
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class EnrollmentAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(std::string, name)
|
||||
|
||||
@@ -11,7 +11,7 @@ static const char *const TAG = "http_request";
|
||||
void HttpRequestComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"HTTP Request:\n"
|
||||
" Timeout: %ums\n"
|
||||
" Timeout: %" PRIu32 "ms\n"
|
||||
" User-Agent: %s\n"
|
||||
" Follow redirects: %s\n"
|
||||
" Redirect limit: %d",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
namespace esphome::http_request {
|
||||
|
||||
static const char *const TAG = "http_request.idf";
|
||||
static constexpr uint32_t ERROR_DURATION_MS = 1000;
|
||||
|
||||
struct UserData {
|
||||
const std::vector<std::string> &lower_case_collect_headers;
|
||||
@@ -57,7 +58,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
||||
const std::vector<Header> &request_headers,
|
||||
const std::vector<std::string> &lower_case_collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
this->status_momentary_error("failed", ERROR_DURATION_MS);
|
||||
ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");
|
||||
return nullptr;
|
||||
}
|
||||
@@ -74,7 +75,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
||||
} else if (method == "PATCH") {
|
||||
method_idf = HTTP_METHOD_PATCH;
|
||||
} else {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
this->status_momentary_error("failed", ERROR_DURATION_MS);
|
||||
ESP_LOGE(TAG, "HTTP Request failed; Unsupported method");
|
||||
return nullptr;
|
||||
}
|
||||
@@ -112,6 +113,11 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
||||
config.event_handler = http_event_handler;
|
||||
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
if (client == nullptr) {
|
||||
this->status_momentary_error("failed", ERROR_DURATION_MS);
|
||||
ESP_LOGE(TAG, "HTTP Request failed; client could not be initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpContainerIDF> container = std::make_shared<HttpContainerIDF>(client);
|
||||
container->set_parent(this);
|
||||
@@ -129,7 +135,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
||||
|
||||
esp_err_t err = esp_http_client_open(client, body_len);
|
||||
if (err != ESP_OK) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
this->status_momentary_error("failed", ERROR_DURATION_MS);
|
||||
ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err));
|
||||
esp_http_client_cleanup(client);
|
||||
return nullptr;
|
||||
@@ -151,7 +157,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
||||
}
|
||||
|
||||
if (err != ESP_OK) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
this->status_momentary_error("failed", ERROR_DURATION_MS);
|
||||
ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err));
|
||||
esp_http_client_cleanup(client);
|
||||
return nullptr;
|
||||
@@ -176,7 +182,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
||||
err = esp_http_client_set_redirection(client);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_http_client_set_redirection failed: %s", esp_err_to_name(err));
|
||||
this->status_momentary_error("failed", 1000);
|
||||
this->status_momentary_error("failed", ERROR_DURATION_MS);
|
||||
esp_http_client_cleanup(client);
|
||||
return nullptr;
|
||||
}
|
||||
@@ -189,7 +195,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
||||
err = esp_http_client_open(client, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_http_client_open failed: %s", esp_err_to_name(err));
|
||||
this->status_momentary_error("failed", 1000);
|
||||
this->status_momentary_error("failed", ERROR_DURATION_MS);
|
||||
esp_http_client_cleanup(client);
|
||||
return nullptr;
|
||||
}
|
||||
@@ -214,7 +220,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
||||
}
|
||||
|
||||
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code);
|
||||
this->status_momentary_error("failed", 1000);
|
||||
this->status_momentary_error("failed", ERROR_DURATION_MS);
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "hub75_component.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome::hub75 {
|
||||
@@ -58,7 +60,7 @@ void HUB75Display::dump_config() {
|
||||
config_.pins.oe, config_.pins.clk);
|
||||
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Clock Speed: %u MHz\n"
|
||||
" Clock Speed: %" PRIu32 " MHz\n"
|
||||
" Latch Blanking: %i\n"
|
||||
" Clock Phase: %s\n"
|
||||
" Min Refresh Rate: %i Hz\n"
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include "infrared.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_API
|
||||
@@ -100,7 +103,7 @@ void Infrared::control(const InfraredCall &call) {
|
||||
// Zero-copy from packed protobuf data
|
||||
transmit_data->set_data_from_packed_sint32(call.get_packed_data(), call.get_packed_length(),
|
||||
call.get_packed_count());
|
||||
ESP_LOGD(TAG, "Transmitting packed raw timings: count=%u, repeat=%u", call.get_packed_count(),
|
||||
ESP_LOGD(TAG, "Transmitting packed raw timings: count=%" PRIu16 ", repeat=%" PRIu32, call.get_packed_count(),
|
||||
call.get_repeat_count());
|
||||
} else if (call.is_base64url()) {
|
||||
// Decode base64url (URL-safe) into transmit buffer
|
||||
@@ -113,16 +116,16 @@ void Infrared::control(const InfraredCall &call) {
|
||||
for (int32_t timing : transmit_data->get_data()) {
|
||||
int32_t abs_timing = timing < 0 ? -timing : timing;
|
||||
if (abs_timing > max_timing_us) {
|
||||
ESP_LOGE(TAG, "Invalid timing value: %d µs (max %d)", timing, max_timing_us);
|
||||
ESP_LOGE(TAG, "Invalid timing value: %" PRId32 " µs (max %" PRId32 ")", timing, max_timing_us);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ESP_LOGD(TAG, "Transmitting base64url raw timings: count=%zu, repeat=%u", transmit_data->get_data().size(),
|
||||
ESP_LOGD(TAG, "Transmitting base64url raw timings: count=%zu, repeat=%" PRIu32, transmit_data->get_data().size(),
|
||||
call.get_repeat_count());
|
||||
} else {
|
||||
// From vector (lambdas/automations)
|
||||
transmit_data->set_data(call.get_raw_timings());
|
||||
ESP_LOGD(TAG, "Transmitting raw timings: count=%zu, repeat=%u", call.get_raw_timings().size(),
|
||||
ESP_LOGD(TAG, "Transmitting raw timings: count=%zu, repeat=%" PRIu32, call.get_raw_timings().size(),
|
||||
call.get_repeat_count());
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include <hal/gpio_hal.h>
|
||||
|
||||
namespace esphome {
|
||||
@@ -193,7 +195,7 @@ void Inkplate::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Greyscale: %s\n"
|
||||
" Partial Updating: %s\n"
|
||||
" Full Update Every: %d",
|
||||
" Full Update Every: %" PRIu32,
|
||||
YESNO(this->greyscale_), YESNO(this->partial_updating_), this->full_update_every_);
|
||||
// Log pins
|
||||
LOG_PIN(" CKV Pin: ", this->ckv_pin_);
|
||||
@@ -306,7 +308,7 @@ void Inkplate::fill(Color color) {
|
||||
// If clipping is active, fall back to base implementation
|
||||
if (this->get_clipping().is_set()) {
|
||||
Display::fill(color);
|
||||
ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time);
|
||||
ESP_LOGV(TAG, "Fill finished (%" PRIu32 "ms)", millis() - start_time);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -329,12 +331,12 @@ void Inkplate::display() {
|
||||
this->display3b_();
|
||||
} else {
|
||||
if (this->partial_updating_ && this->partial_update_()) {
|
||||
ESP_LOGV(TAG, "Display finished (partial) (%ums)", millis() - start_time);
|
||||
ESP_LOGV(TAG, "Display finished (partial) (%" PRIu32 "ms)", millis() - start_time);
|
||||
return;
|
||||
}
|
||||
this->display1b_();
|
||||
}
|
||||
ESP_LOGV(TAG, "Display finished (full) (%ums)", millis() - start_time);
|
||||
ESP_LOGV(TAG, "Display finished (full) (%" PRIu32 "ms)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate::display1b_() {
|
||||
@@ -409,7 +411,7 @@ void Inkplate::display1b_() {
|
||||
|
||||
uint32_t clock = (1UL << this->cl_pin_->get_pin());
|
||||
uint32_t data_mask = this->get_data_pin_mask_();
|
||||
ESP_LOGV(TAG, "Display1b start loops (%ums)", millis() - start_time);
|
||||
ESP_LOGV(TAG, "Display1b start loops (%" PRIu32 "ms)", millis() - start_time);
|
||||
|
||||
for (uint8_t k = 0; k < rep; k++) {
|
||||
buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1];
|
||||
@@ -440,7 +442,7 @@ void Inkplate::display1b_() {
|
||||
}
|
||||
delayMicroseconds(230);
|
||||
}
|
||||
ESP_LOGV(TAG, "Display1b first loop x %d (%ums)", 4, millis() - start_time);
|
||||
ESP_LOGV(TAG, "Display1b first loop x %d (%" PRIu32 "ms)", 4, millis() - start_time);
|
||||
|
||||
buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1];
|
||||
vscan_start_();
|
||||
@@ -469,7 +471,7 @@ void Inkplate::display1b_() {
|
||||
vscan_end_();
|
||||
}
|
||||
delayMicroseconds(230);
|
||||
ESP_LOGV(TAG, "Display1b second loop (%ums)", millis() - start_time);
|
||||
ESP_LOGV(TAG, "Display1b second loop (%" PRIu32 "ms)", millis() - start_time);
|
||||
|
||||
if (this->model_ == INKPLATE_6_PLUS) {
|
||||
clean_fast_(2, 2);
|
||||
@@ -495,13 +497,13 @@ void Inkplate::display1b_() {
|
||||
vscan_end_();
|
||||
}
|
||||
delayMicroseconds(230);
|
||||
ESP_LOGV(TAG, "Display1b third loop (%ums)", millis() - start_time);
|
||||
ESP_LOGV(TAG, "Display1b third loop (%" PRIu32 "ms)", millis() - start_time);
|
||||
}
|
||||
vscan_start_();
|
||||
eink_off_();
|
||||
this->block_partial_ = false;
|
||||
this->partial_updates_ = 0;
|
||||
ESP_LOGV(TAG, "Display1b finished (%ums)", millis() - start_time);
|
||||
ESP_LOGV(TAG, "Display1b finished (%" PRIu32 "ms)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate::display3b_() {
|
||||
@@ -614,7 +616,7 @@ void Inkplate::display3b_() {
|
||||
clean_fast_(3, 1);
|
||||
vscan_start_();
|
||||
eink_off_();
|
||||
ESP_LOGV(TAG, "Display3b finished (%ums)", millis() - start_time);
|
||||
ESP_LOGV(TAG, "Display3b finished (%" PRIu32 "ms)", millis() - start_time);
|
||||
}
|
||||
|
||||
bool Inkplate::partial_update_() {
|
||||
@@ -641,7 +643,7 @@ bool Inkplate::partial_update_() {
|
||||
this->partial_buffer_2_[n--] = LUTW[diffw & 0x0F] & LUTB[diffb & 0x0F];
|
||||
}
|
||||
}
|
||||
ESP_LOGV(TAG, "Partial update buffer built after (%ums)", millis() - start_time);
|
||||
ESP_LOGV(TAG, "Partial update buffer built after (%" PRIu32 "ms)", millis() - start_time);
|
||||
|
||||
int rep = (this->model_ == INKPLATE_6_V2) ? 6 : 5;
|
||||
|
||||
@@ -667,7 +669,7 @@ bool Inkplate::partial_update_() {
|
||||
vscan_end_();
|
||||
}
|
||||
delayMicroseconds(230);
|
||||
ESP_LOGV(TAG, "Partial update loop k=%d (%ums)", k, millis() - start_time);
|
||||
ESP_LOGV(TAG, "Partial update loop k=%d (%" PRIu32 "ms)", k, millis() - start_time);
|
||||
}
|
||||
clean_fast_(2, 2);
|
||||
clean_fast_(3, 1);
|
||||
@@ -675,7 +677,7 @@ bool Inkplate::partial_update_() {
|
||||
eink_off_();
|
||||
|
||||
memcpy(this->buffer_, this->partial_buffer_, this->get_buffer_length_());
|
||||
ESP_LOGV(TAG, "Partial update finished (%ums)", millis() - start_time);
|
||||
ESP_LOGV(TAG, "Partial update finished (%" PRIu32 "ms)", millis() - start_time);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -730,7 +732,7 @@ void Inkplate::clean() {
|
||||
clean_fast_(0, 8); // Black to Black
|
||||
clean_fast_(2, 1); // Black to White
|
||||
clean_fast_(1, 10); // White to White
|
||||
ESP_LOGV(TAG, "Clean finished (%ums)", millis() - start_time);
|
||||
ESP_LOGV(TAG, "Clean finished (%" PRIu32 "ms)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate::clean_fast_(uint8_t c, uint8_t rep) {
|
||||
@@ -773,9 +775,9 @@ void Inkplate::clean_fast_(uint8_t c, uint8_t rep) {
|
||||
vscan_end_();
|
||||
}
|
||||
delayMicroseconds(230);
|
||||
ESP_LOGV(TAG, "Clean fast rep loop %d finished (%ums)", k, millis() - start_time);
|
||||
ESP_LOGV(TAG, "Clean fast rep loop %d finished (%" PRIu32 "ms)", k, millis() - start_time);
|
||||
}
|
||||
ESP_LOGV(TAG, "Clean fast finished (%ums)", millis() - start_time);
|
||||
ESP_LOGV(TAG, "Clean fast finished (%" PRIu32 "ms)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate::pins_z_state_() {
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace internal_temperature {
|
||||
namespace esphome::internal_temperature {
|
||||
|
||||
class InternalTemperatureSensor : public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
#if defined(USE_ESP32) || (defined(USE_ZEPHYR) && defined(USE_NRF52))
|
||||
void setup() override;
|
||||
#endif // USE_ESP32 || (USE_ZEPHYR && USE_NRF52)
|
||||
void dump_config() override;
|
||||
|
||||
void update() override;
|
||||
};
|
||||
|
||||
} // namespace internal_temperature
|
||||
} // namespace esphome
|
||||
} // namespace esphome::internal_temperature
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifdef USE_BK72XX
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "internal_temperature.h"
|
||||
|
||||
extern "C" {
|
||||
uint32_t temp_single_get_current_temperature(uint32_t *temp_value);
|
||||
}
|
||||
|
||||
namespace esphome::internal_temperature {
|
||||
|
||||
static const char *const TAG = "internal_temperature.bk72xx";
|
||||
|
||||
void InternalTemperatureSensor::update() {
|
||||
float temperature = NAN;
|
||||
bool success = false;
|
||||
|
||||
uint32_t raw, result;
|
||||
result = temp_single_get_current_temperature(&raw);
|
||||
success = (result == 0);
|
||||
#if defined(USE_LIBRETINY_VARIANT_BK7231N)
|
||||
temperature = raw * -0.38f + 156.0f;
|
||||
#elif defined(USE_LIBRETINY_VARIANT_BK7231T)
|
||||
temperature = raw * 0.04f;
|
||||
#else // USE_LIBRETINY_VARIANT
|
||||
temperature = raw * 0.128f;
|
||||
#endif // USE_LIBRETINY_VARIANT
|
||||
|
||||
if (success && std::isfinite(temperature)) {
|
||||
this->publish_state(temperature);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Ignoring invalid temperature (success=%d, value=%.1f)", success, temperature);
|
||||
if (!this->has_state()) {
|
||||
this->publish_state(NAN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::internal_temperature
|
||||
|
||||
#endif // USE_BK72XX
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user