mirror of
https://github.com/esphome/esphome.git
synced 2025-11-11 12:25:47 +00:00
Compare commits
1 Commits
ethernet_m
...
api_shrink
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90e4d15fd9 |
@@ -51,79 +51,7 @@ This document provides essential context for AI models interacting with this pro
|
||||
|
||||
* **Naming Conventions:**
|
||||
* **Python:** Follows PEP 8. Use clear, descriptive names following snake_case.
|
||||
* **C++:** Follows the Google C++ Style Guide with these specifics (following clang-tidy conventions):
|
||||
- Function, method, and variable names: `lower_snake_case`
|
||||
- Class/struct/enum names: `UpperCamelCase`
|
||||
- Top-level constants (global/namespace scope): `UPPER_SNAKE_CASE`
|
||||
- Function-local constants: `lower_snake_case`
|
||||
- Protected/private fields: `lower_snake_case_with_trailing_underscore_`
|
||||
- Favor descriptive names over abbreviations
|
||||
|
||||
* **C++ Field Visibility:**
|
||||
* **Prefer `protected`:** Use `protected` for most class fields to enable extensibility and testing. Fields should be `lower_snake_case_with_trailing_underscore_`.
|
||||
* **Use `private` for safety-critical cases:** Use `private` visibility when direct field access could introduce bugs or violate invariants:
|
||||
1. **Pointer lifetime issues:** When setters validate and store pointers from known lists to prevent dangling references.
|
||||
```cpp
|
||||
// Helper to find matching string in vector and return its pointer
|
||||
inline const char *vector_find(const std::vector<const char *> &vec, const char *value) {
|
||||
for (const char *item : vec) {
|
||||
if (strcmp(item, value) == 0)
|
||||
return item;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class ClimateDevice {
|
||||
public:
|
||||
void set_custom_fan_modes(std::initializer_list<const char *> modes) {
|
||||
this->custom_fan_modes_ = modes;
|
||||
this->active_custom_fan_mode_ = nullptr; // Reset when modes change
|
||||
}
|
||||
bool set_custom_fan_mode(const char *mode) {
|
||||
// Find mode in supported list and store that pointer (not the input pointer)
|
||||
const char *validated_mode = vector_find(this->custom_fan_modes_, mode);
|
||||
if (validated_mode != nullptr) {
|
||||
this->active_custom_fan_mode_ = validated_mode;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private:
|
||||
std::vector<const char *> custom_fan_modes_; // Pointers to string literals in flash
|
||||
const char *active_custom_fan_mode_{nullptr}; // Must point to entry in custom_fan_modes_
|
||||
};
|
||||
```
|
||||
2. **Invariant coupling:** When multiple fields must remain synchronized to prevent buffer overflows or data corruption.
|
||||
```cpp
|
||||
class Buffer {
|
||||
public:
|
||||
void resize(size_t new_size) {
|
||||
auto new_data = std::make_unique<uint8_t[]>(new_size);
|
||||
if (this->data_) {
|
||||
std::memcpy(new_data.get(), this->data_.get(), std::min(this->size_, new_size));
|
||||
}
|
||||
this->data_ = std::move(new_data);
|
||||
this->size_ = new_size; // Must stay in sync with data_
|
||||
}
|
||||
private:
|
||||
std::unique_ptr<uint8_t[]> data_;
|
||||
size_t size_{0}; // Must match allocated size of data_
|
||||
};
|
||||
```
|
||||
3. **Resource management:** When setters perform cleanup or registration operations that derived classes might skip.
|
||||
* **Provide `protected` accessor methods:** When derived classes need controlled access to `private` members.
|
||||
|
||||
* **C++ Preprocessor Directives:**
|
||||
* **Avoid `#define` for constants:** Using `#define` for constants is discouraged and should be replaced with `const` variables or enums.
|
||||
* **Use `#define` only for:**
|
||||
- Conditional compilation (`#ifdef`, `#ifndef`)
|
||||
- Compile-time sizes calculated during Python code generation (e.g., configuring `std::array` or `StaticVector` dimensions via `cg.add_define()`)
|
||||
|
||||
* **C++ Additional Conventions:**
|
||||
* **Member access:** Prefix all class member access with `this->` (e.g., `this->value_` not `value_`)
|
||||
* **Indentation:** Use spaces (two per indentation level), not tabs
|
||||
* **Type aliases:** Prefer `using type_t = int;` over `typedef int type_t;`
|
||||
* **Line length:** Wrap lines at no more than 120 characters
|
||||
* **C++:** Follows the Google C++ Style Guide.
|
||||
|
||||
* **Component Structure:**
|
||||
* **Standard Files:**
|
||||
@@ -172,7 +100,8 @@ This document provides essential context for AI models interacting with this pro
|
||||
|
||||
* **C++ Class Pattern:**
|
||||
```cpp
|
||||
namespace esphome::my_component {
|
||||
namespace esphome {
|
||||
namespace my_component {
|
||||
|
||||
class MyComponent : public Component {
|
||||
public:
|
||||
@@ -188,7 +117,8 @@ This document provides essential context for AI models interacting with this pro
|
||||
int param_{0};
|
||||
};
|
||||
|
||||
} // namespace esphome::my_component
|
||||
} // namespace my_component
|
||||
} // namespace esphome
|
||||
```
|
||||
|
||||
* **Common Component Examples:**
|
||||
@@ -438,45 +368,3 @@ This document provides essential context for AI models interacting with this pro
|
||||
* **Python:** When adding a new Python dependency, add it to the appropriate `requirements*.txt` file and `pyproject.toml`.
|
||||
* **C++ / PlatformIO:** When adding a new C++ dependency, add it to `platformio.ini` and use `cg.add_library`.
|
||||
* **Build Flags:** Use `cg.add_build_flag(...)` to add compiler flags.
|
||||
|
||||
## 8. Public API and Breaking Changes
|
||||
|
||||
* **Public C++ API:**
|
||||
* **Components**: Only documented features at [esphome.io](https://esphome.io) are public API. Undocumented `public` members are internal.
|
||||
* **Core/Base Classes** (`esphome/core/`, `Component`, `Sensor`, etc.): All `public` members are public API.
|
||||
* **Components with Global Accessors** (`global_api_server`, etc.): All `public` members are public API (except config setters).
|
||||
|
||||
* **Public Python API:**
|
||||
* All documented configuration options at [esphome.io](https://esphome.io) are public API.
|
||||
* Python code in `esphome/core/` actively used by existing core components is considered stable API.
|
||||
* Other Python code is internal unless explicitly documented for external component use.
|
||||
|
||||
* **Breaking Changes Policy:**
|
||||
* Aim for **6-month deprecation window** when possible
|
||||
* Clean breaks allowed for: signature changes, deep refactorings, resource constraints
|
||||
* Must document migration path in PR description (generates release notes)
|
||||
* Blog post required for core/base class changes or significant architectural changes
|
||||
* Full details: https://developers.esphome.io/contributing/code/#public-api-and-breaking-changes
|
||||
|
||||
* **Breaking Change Checklist:**
|
||||
- [ ] Clear justification (RAM/flash savings, architectural improvement)
|
||||
- [ ] Explored non-breaking alternatives
|
||||
- [ ] Added deprecation warnings if possible (use `ESPDEPRECATED` macro for C++)
|
||||
- [ ] Documented migration path in PR description with before/after examples
|
||||
- [ ] Updated all internal usage and esphome-docs
|
||||
- [ ] Tested backward compatibility during deprecation period
|
||||
|
||||
* **Deprecation Pattern (C++):**
|
||||
```cpp
|
||||
// Remove before 2026.6.0
|
||||
ESPDEPRECATED("Use new_method() instead. Removed in 2026.6.0", "2025.12.0")
|
||||
void old_method() { this->new_method(); }
|
||||
```
|
||||
|
||||
* **Deprecation Pattern (Python):**
|
||||
```python
|
||||
# Remove before 2026.6.0
|
||||
if CONF_OLD_KEY in config:
|
||||
_LOGGER.warning(f"'{CONF_OLD_KEY}' deprecated, use '{CONF_NEW_KEY}'. Removed in 2026.6.0")
|
||||
config[CONF_NEW_KEY] = config.pop(CONF_OLD_KEY) # Auto-migrate
|
||||
```
|
||||
|
||||
@@ -21,7 +21,7 @@ permissions:
|
||||
jobs:
|
||||
request-codeowner-reviews:
|
||||
name: Run
|
||||
if: ${{ github.repository == 'esphome/esphome' && !github.event.pull_request.draft }}
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Request reviews from component codeowners
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.4
|
||||
rev: v0.14.3
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -206,7 +206,6 @@ esphome/components/hdc2010/* @optimusprimespace @ssieb
|
||||
esphome/components/he60r/* @clydebarrow
|
||||
esphome/components/heatpumpir/* @rob-deutsch
|
||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||
esphome/components/hlk_fm22x/* @OnFreund
|
||||
esphome/components/hm3301/* @freekode
|
||||
esphome/components/hmac_md5/* @dwmw2
|
||||
esphome/components/homeassistant/* @esphome/core @OttoWinter
|
||||
@@ -291,7 +290,6 @@ esphome/components/mcp23x17_base/* @jesserockz
|
||||
esphome/components/mcp23xxx_base/* @jesserockz
|
||||
esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||
esphome/components/mcp3204/* @rsumner
|
||||
esphome/components/mcp3221/* @philippderdiedas
|
||||
esphome/components/mcp4461/* @p1ngb4ck
|
||||
esphome/components/mcp4728/* @berfenger
|
||||
esphome/components/mcp47a1/* @jesserockz
|
||||
@@ -396,7 +394,6 @@ esphome/components/rpi_dpi_rgb/* @clydebarrow
|
||||
esphome/components/rtl87xx/* @kuba2k2
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/runtime_stats/* @bdraco
|
||||
esphome/components/rx8130/* @beormund
|
||||
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
||||
esphome/components/scd4x/* @martgras @sjtrny
|
||||
esphome/components/script/* @esphome/core
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#include "alarm_control_panel.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "alarm_control_panel.h"
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -36,9 +34,6 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
|
||||
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
|
||||
this->current_state_ = state;
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_alarm_control_panel_update(this);
|
||||
#endif
|
||||
if (state == ACP_STATE_TRIGGERED) {
|
||||
this->triggered_callback_.call();
|
||||
} else if (state == ACP_STATE_ARMING) {
|
||||
|
||||
@@ -227,7 +227,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
esp32=8, # More RAM, can buffer more
|
||||
rp2040=5, # Limited RAM
|
||||
bk72xx=8, # Moderate RAM
|
||||
nrf52=8, # Moderate RAM
|
||||
rtl87xx=8, # Moderate RAM
|
||||
host=16, # Abundant resources
|
||||
ln882x=8, # Moderate RAM
|
||||
@@ -245,9 +244,6 @@ async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
# Track controller registration for StaticVector sizing
|
||||
CORE.register_controller()
|
||||
|
||||
cg.add(var.set_port(config[CONF_PORT]))
|
||||
if config[CONF_PASSWORD]:
|
||||
cg.add_define("USE_API_PASSWORD")
|
||||
|
||||
@@ -2147,7 +2147,7 @@ message ListEntitiesEventResponse {
|
||||
EntityCategory entity_category = 7;
|
||||
string device_class = 8;
|
||||
|
||||
repeated string event_types = 9 [(container_pointer_no_template) = "FixedVector<const char *>"];
|
||||
repeated string event_types = 9;
|
||||
uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
message EventResponse {
|
||||
|
||||
@@ -193,6 +193,11 @@ void APIConnection::loop() {
|
||||
if (!this->deferred_batch_.empty()) {
|
||||
this->process_batch_();
|
||||
}
|
||||
// Release excess capacity from initial entity flood
|
||||
// deferred_batch_ grew up to MAX_INITIAL_PER_BATCH (24) items, now shrink to free up to ~384 bytes
|
||||
this->deferred_batch_.items.shrink_to_fit();
|
||||
// shared_write_buffer_ grew up to ~1.5KB during initial state, now shrink to free up to ~1.4KB
|
||||
this->parent_->get_shared_buffer_ref().shrink_to_fit();
|
||||
// Now that everything is sent, enable immediate sending for future state changes
|
||||
this->flags_.should_try_send_immediately = true;
|
||||
}
|
||||
@@ -1310,7 +1315,8 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
|
||||
auto *event = static_cast<event::Event *>(entity);
|
||||
ListEntitiesEventResponse msg;
|
||||
msg.set_device_class(event->get_device_class_ref());
|
||||
msg.event_types = &event->get_event_types();
|
||||
for (const auto &event_type : event->get_event_types())
|
||||
msg.event_types.push_back(event_type);
|
||||
return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||
is_single);
|
||||
}
|
||||
@@ -1467,8 +1473,6 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
static constexpr auto MANUFACTURER = StringRef::from_lit("Beken");
|
||||
#elif defined(USE_LN882X)
|
||||
static constexpr auto MANUFACTURER = StringRef::from_lit("Lightning");
|
||||
#elif defined(USE_NRF52)
|
||||
static constexpr auto MANUFACTURER = StringRef::from_lit("Nordic Semiconductor");
|
||||
#elif defined(USE_RTL87XX)
|
||||
static constexpr auto MANUFACTURER = StringRef::from_lit("Realtek");
|
||||
#elif defined(USE_HOST)
|
||||
|
||||
@@ -2877,8 +2877,8 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_uint32(7, static_cast<uint32_t>(this->entity_category));
|
||||
buffer.encode_string(8, this->device_class_ref_);
|
||||
for (const char *it : *this->event_types) {
|
||||
buffer.encode_string(9, it, strlen(it), true);
|
||||
for (auto &it : this->event_types) {
|
||||
buffer.encode_string(9, it, true);
|
||||
}
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(10, this->device_id);
|
||||
@@ -2894,9 +2894,9 @@ void ListEntitiesEventResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_bool(1, this->disabled_by_default);
|
||||
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
|
||||
size.add_length(1, this->device_class_ref_.size());
|
||||
if (!this->event_types->empty()) {
|
||||
for (const char *it : *this->event_types) {
|
||||
size.add_length_force(1, strlen(it));
|
||||
if (!this->event_types.empty()) {
|
||||
for (const auto &it : this->event_types) {
|
||||
size.add_length_force(1, it.size());
|
||||
}
|
||||
}
|
||||
#ifdef USE_DEVICES
|
||||
|
||||
@@ -2788,7 +2788,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage {
|
||||
#endif
|
||||
StringRef device_class_ref_{};
|
||||
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||
const FixedVector<const char *> *event_types{};
|
||||
std::vector<std::string> event_types{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
|
||||
@@ -2053,7 +2053,7 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const {
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
dump_field(out, "device_class", this->device_class_ref_);
|
||||
for (const auto &it : *this->event_types) {
|
||||
for (const auto &it : this->event_types) {
|
||||
dump_field(out, "event_types", it, 4);
|
||||
}
|
||||
#ifdef USE_DEVICES
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
@@ -35,7 +34,7 @@ APIServer::APIServer() {
|
||||
}
|
||||
|
||||
void APIServer::setup() {
|
||||
ControllerRegistry::register_controller(this);
|
||||
this->setup_controller();
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
uint32_t hash = 88491486UL;
|
||||
@@ -270,7 +269,7 @@ bool APIServer::check_password(const uint8_t *password_data, size_t password_len
|
||||
|
||||
void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||
|
||||
// Macro for controller update dispatch
|
||||
// Macro for entities without extra parameters
|
||||
#define API_DISPATCH_UPDATE(entity_type, entity_name) \
|
||||
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
if (obj->is_internal()) \
|
||||
@@ -279,6 +278,15 @@ void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||
c->send_##entity_name##_state(obj); \
|
||||
}
|
||||
|
||||
// Macro for entities with extra parameters (but parameters not used in send)
|
||||
#define API_DISPATCH_UPDATE_IGNORE_PARAMS(entity_type, entity_name, ...) \
|
||||
void APIServer::on_##entity_name##_update(entity_type *obj, __VA_ARGS__) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
if (obj->is_internal()) \
|
||||
return; \
|
||||
for (auto &c : this->clients_) \
|
||||
c->send_##entity_name##_state(obj); \
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor)
|
||||
#endif
|
||||
@@ -296,15 +304,15 @@ API_DISPATCH_UPDATE(light::LightState, light)
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
API_DISPATCH_UPDATE(sensor::Sensor, sensor)
|
||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(sensor::Sensor, sensor, float state)
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
API_DISPATCH_UPDATE(switch_::Switch, switch)
|
||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(switch_::Switch, switch, bool state)
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
API_DISPATCH_UPDATE(text_sensor::TextSensor, text_sensor)
|
||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(text_sensor::TextSensor, text_sensor, const std::string &state)
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
@@ -312,7 +320,7 @@ API_DISPATCH_UPDATE(climate::Climate, climate)
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
API_DISPATCH_UPDATE(number::Number, number)
|
||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(number::Number, number, float state)
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
@@ -328,11 +336,11 @@ API_DISPATCH_UPDATE(datetime::DateTimeEntity, datetime)
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
API_DISPATCH_UPDATE(text::Text, text)
|
||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(text::Text, text, const std::string &state)
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
API_DISPATCH_UPDATE(select::Select, select)
|
||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(select::Select, select, const std::string &state, size_t index)
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
@@ -348,13 +356,12 @@ API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player)
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
// Event is a special case - unlike other entities with simple state fields,
|
||||
// events store their state in a member accessed via obj->get_last_event_type()
|
||||
void APIServer::on_event(event::Event *obj) {
|
||||
// Event is a special case - it's the only entity that passes extra parameters to the send method
|
||||
void APIServer::on_event(event::Event *obj, const std::string &event_type) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_event(obj, obj->get_last_event_type());
|
||||
c->send_event(obj, event_type);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -72,19 +72,19 @@ class APIServer : public Component, public Controller {
|
||||
void on_light_update(light::LightState *obj) override;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
void on_sensor_update(sensor::Sensor *obj) override;
|
||||
void on_sensor_update(sensor::Sensor *obj, float state) override;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void on_switch_update(switch_::Switch *obj) override;
|
||||
void on_switch_update(switch_::Switch *obj, bool state) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void on_text_sensor_update(text_sensor::TextSensor *obj) override;
|
||||
void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
void on_climate_update(climate::Climate *obj) override;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
void on_number_update(number::Number *obj) override;
|
||||
void on_number_update(number::Number *obj, float state) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void on_date_update(datetime::DateEntity *obj) override;
|
||||
@@ -96,10 +96,10 @@ class APIServer : public Component, public Controller {
|
||||
void on_datetime_update(datetime::DateTimeEntity *obj) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
void on_text_update(text::Text *obj) override;
|
||||
void on_text_update(text::Text *obj, const std::string &state) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
void on_select_update(select::Select *obj) override;
|
||||
void on_select_update(select::Select *obj, const std::string &state, size_t index) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
void on_lock_update(lock::Lock *obj) override;
|
||||
@@ -141,7 +141,7 @@ class APIServer : public Component, public Controller {
|
||||
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
void on_event(event::Event *obj) override;
|
||||
void on_event(event::Event *obj, const std::string &event_type) override;
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
void on_update(update::UpdateEntity *obj) override;
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
namespace esphome::api {
|
||||
|
||||
#ifdef USE_API_SERVICES
|
||||
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceDynamic<Ts...> {
|
||||
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
|
||||
public:
|
||||
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
|
||||
void (T::*callback)(Ts...))
|
||||
: UserServiceDynamic<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
|
||||
: UserServiceBase<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
|
||||
|
||||
protected:
|
||||
void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT
|
||||
|
||||
@@ -23,57 +23,11 @@ template<typename T> T get_execute_arg_value(const ExecuteServiceArgument &arg);
|
||||
|
||||
template<typename T> enums::ServiceArgType to_service_arg_type();
|
||||
|
||||
// Base class for YAML-defined services (most common case)
|
||||
// Stores only pointers to string literals in flash - no heap allocation
|
||||
template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
||||
public:
|
||||
UserServiceBase(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
|
||||
: name_(name), arg_names_(arg_names) {
|
||||
this->key_ = fnv1_hash(name);
|
||||
}
|
||||
|
||||
ListEntitiesServicesResponse encode_list_service_response() override {
|
||||
ListEntitiesServicesResponse msg;
|
||||
msg.set_name(StringRef(this->name_));
|
||||
msg.key = this->key_;
|
||||
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
|
||||
msg.args.init(sizeof...(Ts));
|
||||
for (size_t i = 0; i < sizeof...(Ts); i++) {
|
||||
auto &arg = msg.args.emplace_back();
|
||||
arg.type = arg_types[i];
|
||||
arg.set_name(StringRef(this->arg_names_[i]));
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
bool execute_service(const ExecuteServiceRequest &req) override {
|
||||
if (req.key != this->key_)
|
||||
return false;
|
||||
if (req.args.size() != sizeof...(Ts))
|
||||
return false;
|
||||
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void execute(Ts... x) = 0;
|
||||
template<typename ArgsContainer, int... S> void execute_(const ArgsContainer &args, seq<S...> type) {
|
||||
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
||||
}
|
||||
|
||||
// Pointers to string literals in flash - no heap allocation
|
||||
const char *name_;
|
||||
std::array<const char *, sizeof...(Ts)> arg_names_;
|
||||
uint32_t key_{0};
|
||||
};
|
||||
|
||||
// Separate class for custom_api_device services (rare case)
|
||||
// Stores copies of runtime-generated names
|
||||
template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor {
|
||||
public:
|
||||
UserServiceDynamic(std::string name, const std::array<std::string, sizeof...(Ts)> &arg_names)
|
||||
UserServiceBase(std::string name, const std::array<std::string, sizeof...(Ts)> &arg_names)
|
||||
: name_(std::move(name)), arg_names_(arg_names) {
|
||||
this->key_ = fnv1_hash(this->name_.c_str());
|
||||
this->key_ = fnv1_hash(this->name_);
|
||||
}
|
||||
|
||||
ListEntitiesServicesResponse encode_list_service_response() override {
|
||||
@@ -93,7 +47,7 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
|
||||
bool execute_service(const ExecuteServiceRequest &req) override {
|
||||
if (req.key != this->key_)
|
||||
return false;
|
||||
if (req.args.size() != sizeof...(Ts))
|
||||
if (req.args.size() != this->arg_names_.size())
|
||||
return false;
|
||||
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
|
||||
return true;
|
||||
@@ -105,16 +59,14 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
|
||||
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
||||
}
|
||||
|
||||
// Heap-allocated strings for runtime-generated names
|
||||
std::string name_;
|
||||
std::array<std::string, sizeof...(Ts)> arg_names_;
|
||||
uint32_t key_{0};
|
||||
std::array<std::string, sizeof...(Ts)> arg_names_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
|
||||
public:
|
||||
// Constructor for static names (YAML-defined services - used by code generator)
|
||||
UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
|
||||
UserServiceTrigger(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
|
||||
: UserServiceBase<Ts...>(name, arg_names) {}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#include "binary_sensor.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -39,9 +37,6 @@ void BinarySensor::send_state_internal(bool new_state) {
|
||||
// Note that set_state_ de-dups and will only trigger callbacks if the state has actually changed
|
||||
if (this->set_state_(new_state)) {
|
||||
ESP_LOGD(TAG, "'%s': New state is %s", this->get_name().c_str(), ONOFF(new_state));
|
||||
#if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_binary_sensor_update(this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ static const char *const TAG = "bl0940.number";
|
||||
void CalibrationNumber::setup() {
|
||||
float value = 0.0f;
|
||||
if (this->restore_value_) {
|
||||
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash());
|
||||
this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
|
||||
if (!this->pref_.load(&value)) {
|
||||
value = 0.0f;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ from esphome.const import (
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_VALUE,
|
||||
)
|
||||
from esphome.core import ID
|
||||
|
||||
AUTO_LOAD = ["esp32_ble_client"]
|
||||
CODEOWNERS = ["@buxtronix", "@clydebarrow"]
|
||||
@@ -199,12 +198,7 @@ async def ble_write_to_code(config, action_id, template_arg, args):
|
||||
templ = await cg.templatable(value, args, cg.std_vector.template(cg.uint8))
|
||||
cg.add(var.set_value_template(templ))
|
||||
else:
|
||||
# Generate static array in flash to avoid RAM copy
|
||||
if isinstance(value, bytes):
|
||||
value = list(value)
|
||||
arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8)
|
||||
arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*value))
|
||||
cg.add(var.set_value_simple(arr, len(value)))
|
||||
cg.add(var.set_value_simple(value))
|
||||
|
||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
|
||||
@@ -96,8 +96,11 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
BLEClientWriteAction(BLEClient *ble_client) {
|
||||
ble_client->register_ble_node(this);
|
||||
ble_client_ = ble_client;
|
||||
this->construct_simple_value_();
|
||||
}
|
||||
|
||||
~BLEClientWriteAction() { this->destroy_simple_value_(); }
|
||||
|
||||
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
@@ -107,14 +110,17 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
|
||||
void set_value_template(std::vector<uint8_t> (*func)(Ts...)) {
|
||||
this->value_.func = func;
|
||||
this->len_ = -1; // Sentinel value indicates template mode
|
||||
this->destroy_simple_value_();
|
||||
this->value_.template_func = func;
|
||||
this->has_simple_value_ = false;
|
||||
}
|
||||
|
||||
// Store pointer to static data in flash (no RAM copy)
|
||||
void set_value_simple(const uint8_t *data, size_t len) {
|
||||
this->value_.data = data;
|
||||
this->len_ = len; // Length >= 0 indicates static mode
|
||||
void set_value_simple(const std::vector<uint8_t> &value) {
|
||||
if (!this->has_simple_value_) {
|
||||
this->construct_simple_value_();
|
||||
}
|
||||
this->value_.simple = value;
|
||||
this->has_simple_value_ = true;
|
||||
}
|
||||
|
||||
void play(const Ts &...x) override {}
|
||||
@@ -122,14 +128,7 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
void play_complex(const Ts &...x) override {
|
||||
this->num_running_++;
|
||||
this->var_ = std::make_tuple(x...);
|
||||
std::vector<uint8_t> value;
|
||||
if (this->len_ >= 0) {
|
||||
// Static mode: copy from flash to vector
|
||||
value.assign(this->value_.data, this->value_.data + this->len_);
|
||||
} else {
|
||||
// Template mode: call function
|
||||
value = this->value_.func(x...);
|
||||
}
|
||||
auto value = this->has_simple_value_ ? this->value_.simple : this->value_.template_func(x...);
|
||||
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
|
||||
if (!write(value))
|
||||
this->play_next_(x...);
|
||||
@@ -202,11 +201,21 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
}
|
||||
|
||||
private:
|
||||
void construct_simple_value_() { new (&this->value_.simple) std::vector<uint8_t>(); }
|
||||
|
||||
void destroy_simple_value_() {
|
||||
if (this->has_simple_value_) {
|
||||
this->value_.simple.~vector();
|
||||
}
|
||||
}
|
||||
|
||||
BLEClient *ble_client_;
|
||||
ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
|
||||
bool has_simple_value_ = true;
|
||||
union Value {
|
||||
std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
|
||||
const uint8_t *data; // Pointer to static data in flash
|
||||
std::vector<uint8_t> simple;
|
||||
std::vector<uint8_t> (*template_func)(Ts...);
|
||||
Value() {} // trivial constructor
|
||||
~Value() {} // trivial destructor - we manage lifetime via discriminator
|
||||
} value_;
|
||||
espbt::ESPBTUUID service_uuid_;
|
||||
espbt::ESPBTUUID char_uuid_;
|
||||
|
||||
@@ -4,7 +4,7 @@ from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DATA, CONF_ID, CONF_TRIGGER_ID
|
||||
from esphome.core import CORE, ID
|
||||
from esphome.core import CORE
|
||||
|
||||
CODEOWNERS = ["@mvturnho", "@danielschramm"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -176,8 +176,5 @@ async def canbus_action_to_code(config, action_id, template_arg, args):
|
||||
else:
|
||||
if isinstance(data, bytes):
|
||||
data = [int(x) for x in data]
|
||||
# Generate static array in flash to avoid RAM copy
|
||||
arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8)
|
||||
arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data))
|
||||
cg.add(var.set_data_static(arr, len(data)))
|
||||
cg.add(var.set_data_static(data))
|
||||
return var
|
||||
|
||||
@@ -112,16 +112,13 @@ class Canbus : public Component {
|
||||
|
||||
template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public Parented<Canbus> {
|
||||
public:
|
||||
void set_data_template(std::vector<uint8_t> (*func)(Ts...)) {
|
||||
// Stateless lambdas (generated by ESPHome) implicitly convert to function pointers
|
||||
this->data_.func = func;
|
||||
this->len_ = -1; // Sentinel value indicates template mode
|
||||
void set_data_template(const std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||
this->data_func_ = func;
|
||||
this->static_ = false;
|
||||
}
|
||||
|
||||
// Store pointer to static data in flash (no RAM copy)
|
||||
void set_data_static(const uint8_t *data, size_t len) {
|
||||
this->data_.data = data;
|
||||
this->len_ = len; // Length >= 0 indicates static mode
|
||||
void set_data_static(const std::vector<uint8_t> &data) {
|
||||
this->data_static_ = data;
|
||||
this->static_ = true;
|
||||
}
|
||||
|
||||
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
|
||||
@@ -136,26 +133,21 @@ template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public P
|
||||
auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
|
||||
auto use_extended_id =
|
||||
this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_;
|
||||
std::vector<uint8_t> data;
|
||||
if (this->len_ >= 0) {
|
||||
// Static mode: copy from flash to vector
|
||||
data.assign(this->data_.data, this->data_.data + this->len_);
|
||||
if (this->static_) {
|
||||
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, this->data_static_);
|
||||
} else {
|
||||
// Template mode: call function
|
||||
data = this->data_.func(x...);
|
||||
auto val = this->data_func_(x...);
|
||||
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, val);
|
||||
}
|
||||
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, data);
|
||||
}
|
||||
|
||||
protected:
|
||||
optional<uint32_t> can_id_{};
|
||||
optional<bool> use_extended_id_{};
|
||||
bool remote_transmission_request_{false};
|
||||
ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
|
||||
union Data {
|
||||
std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
|
||||
const uint8_t *data; // Pointer to static data in flash
|
||||
} data_;
|
||||
bool static_{false};
|
||||
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
|
||||
std::vector<uint8_t> data_static_{};
|
||||
};
|
||||
|
||||
class CanbusTrigger : public Trigger<std::vector<uint8_t>, uint32_t, bool>, public Component {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#include "climate.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/macros.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -465,9 +463,6 @@ void Climate::publish_state() {
|
||||
|
||||
// Send state to frontend
|
||||
this->state_callback_.call(*this);
|
||||
#if defined(USE_CLIMATE) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_climate_update(this);
|
||||
#endif
|
||||
// Save state
|
||||
this->save_state_();
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ BYTE_ORDER_BIG = "big_endian"
|
||||
CONF_COLOR_DEPTH = "color_depth"
|
||||
CONF_DRAW_ROUNDING = "draw_rounding"
|
||||
CONF_ENABLED = "enabled"
|
||||
CONF_IGNORE_NOT_FOUND = "ignore_not_found"
|
||||
CONF_ON_RECEIVE = "on_receive"
|
||||
CONF_ON_STATE_CHANGE = "on_state_change"
|
||||
CONF_REQUEST_HEADERS = "request_headers"
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
#include "cover.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
|
||||
#include <strings.h>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -173,9 +169,6 @@ void Cover::publish_state(bool save) {
|
||||
ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation));
|
||||
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_COVER) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_cover_update(this);
|
||||
#endif
|
||||
|
||||
if (save) {
|
||||
CoverRestoreState restore{};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "date_entity.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
@@ -33,9 +32,6 @@ void DateEntity::publish_state() {
|
||||
this->set_has_state(true);
|
||||
ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_DATETIME_DATE) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_date_update(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
DateCall DateEntity::make_call() { return DateCall(this); }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "datetime_entity.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
@@ -49,9 +48,6 @@ void DateTimeEntity::publish_state() {
|
||||
ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_,
|
||||
this->month_, this->day_, this->hour_, this->minute_, this->second_);
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_DATETIME_DATETIME) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_datetime_update(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
DateTimeCall DateTimeEntity::make_call() { return DateTimeCall(this); }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "time_entity.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
|
||||
#ifdef USE_DATETIME_TIME
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
@@ -30,9 +29,6 @@ void TimeEntity::publish_state() {
|
||||
ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_,
|
||||
this->second_);
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_DATETIME_TIME) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_time_update(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
TimeCall TimeEntity::make_call() { return TimeCall(this); }
|
||||
|
||||
@@ -59,7 +59,6 @@ async def to_code(config):
|
||||
zephyr_add_prj_conf("SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL", True)
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
cg.add_define("USE_DEBUG")
|
||||
|
||||
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace demo {
|
||||
|
||||
class DemoSelect : public select::Select, public Component {
|
||||
protected:
|
||||
void control(size_t index) override { this->publish_state(index); }
|
||||
void control(const std::string &value) override { this->publish_state(value); }
|
||||
};
|
||||
|
||||
} // namespace demo
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace esphome {
|
||||
namespace es8388 {
|
||||
|
||||
void ADCInputMicSelect::control(size_t index) {
|
||||
this->publish_state(index);
|
||||
this->parent_->set_adc_input_mic(static_cast<AdcInputMicLine>(index));
|
||||
void ADCInputMicSelect::control(const std::string &value) {
|
||||
this->publish_state(value);
|
||||
this->parent_->set_adc_input_mic(static_cast<AdcInputMicLine>(this->index_of(value).value()));
|
||||
}
|
||||
|
||||
} // namespace es8388
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace es8388 {
|
||||
|
||||
class ADCInputMicSelect : public select::Select, public Parented<ES8388> {
|
||||
protected:
|
||||
void control(size_t index) override;
|
||||
void control(const std::string &value) override;
|
||||
};
|
||||
|
||||
} // namespace es8388
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace esphome {
|
||||
namespace es8388 {
|
||||
|
||||
void DacOutputSelect::control(size_t index) {
|
||||
this->publish_state(index);
|
||||
this->parent_->set_dac_output(static_cast<DacOutputLine>(index));
|
||||
void DacOutputSelect::control(const std::string &value) {
|
||||
this->publish_state(value);
|
||||
this->parent_->set_dac_output(static_cast<DacOutputLine>(this->index_of(value).value()));
|
||||
}
|
||||
|
||||
} // namespace es8388
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace es8388 {
|
||||
|
||||
class DacOutputSelect : public select::Select, public Parented<ES8388> {
|
||||
protected:
|
||||
void control(size_t index) override;
|
||||
void control(const std::string &value) override;
|
||||
};
|
||||
|
||||
} // namespace es8388
|
||||
|
||||
@@ -383,7 +383,6 @@ async def to_code(config):
|
||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
|
||||
if CONF_MANUAL_IP in config:
|
||||
cg.add_define("USE_ETHERNET_MANUAL_IP")
|
||||
cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP])))
|
||||
|
||||
# Add compile-time define for PHY types with specific code
|
||||
|
||||
@@ -418,6 +418,8 @@ void EthernetComponent::dump_config() {
|
||||
|
||||
float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; }
|
||||
|
||||
bool EthernetComponent::can_proceed() { return this->is_connected(); }
|
||||
|
||||
network::IPAddresses EthernetComponent::get_ip_addresses() {
|
||||
network::IPAddresses addresses;
|
||||
esp_netif_ip_info_t ip;
|
||||
@@ -550,14 +552,11 @@ void EthernetComponent::start_connect_() {
|
||||
}
|
||||
|
||||
esp_netif_ip_info_t info;
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
info.ip = this->manual_ip_->static_ip;
|
||||
info.gw = this->manual_ip_->gateway;
|
||||
info.netmask = this->manual_ip_->subnet;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
} else {
|
||||
info.ip.addr = 0;
|
||||
info.gw.addr = 0;
|
||||
info.netmask.addr = 0;
|
||||
@@ -578,7 +577,6 @@ void EthernetComponent::start_connect_() {
|
||||
err = esp_netif_set_ip_info(this->eth_netif_, &info);
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
|
||||
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
LwIPLock lock;
|
||||
if (this->manual_ip_->dns1.is_set()) {
|
||||
@@ -591,9 +589,7 @@ void EthernetComponent::start_connect_() {
|
||||
d = this->manual_ip_->dns2;
|
||||
dns_setserver(1, &d);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
} else {
|
||||
err = esp_netif_dhcpc_start(this->eth_netif_);
|
||||
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC start error");
|
||||
@@ -691,9 +687,7 @@ void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode) { this->cl
|
||||
void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); }
|
||||
#endif
|
||||
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
|
||||
#endif
|
||||
|
||||
// set_use_address() is guaranteed to be called during component setup by Python code generation,
|
||||
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
|
||||
|
||||
@@ -58,6 +58,7 @@ class EthernetComponent : public Component {
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
bool can_proceed() override;
|
||||
void on_powerdown() override { powerdown(); }
|
||||
bool is_connected();
|
||||
|
||||
@@ -82,9 +83,7 @@ class EthernetComponent : public Component {
|
||||
void add_phy_register(PHYRegister register_value);
|
||||
#endif
|
||||
void set_type(EthernetType type);
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
void set_manual_ip(const ManualIP &manual_ip);
|
||||
#endif
|
||||
void set_fixed_mac(const std::array<uint8_t, 6> &mac) { this->fixed_mac_ = mac; }
|
||||
|
||||
network::IPAddresses get_ip_addresses();
|
||||
@@ -139,9 +138,7 @@ class EthernetComponent : public Component {
|
||||
uint8_t mdc_pin_{23};
|
||||
uint8_t mdio_pin_{18};
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
optional<ManualIP> manual_ip_{};
|
||||
#endif
|
||||
uint32_t connect_begin_;
|
||||
|
||||
// Group all uint8_t types together (enums and bools)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "event.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -9,11 +8,11 @@ namespace event {
|
||||
static const char *const TAG = "event";
|
||||
|
||||
void Event::trigger(const std::string &event_type) {
|
||||
// Linear search with strcmp - faster than std::set for small datasets (1-5 items typical)
|
||||
const char *found = nullptr;
|
||||
for (const char *type : this->types_) {
|
||||
if (strcmp(type, event_type.c_str()) == 0) {
|
||||
found = type;
|
||||
// Linear search - faster than std::set for small datasets (1-5 items typical)
|
||||
const std::string *found = nullptr;
|
||||
for (const auto &type : this->types_) {
|
||||
if (type == event_type) {
|
||||
found = &type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -21,28 +20,9 @@ void Event::trigger(const std::string &event_type) {
|
||||
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str());
|
||||
return;
|
||||
}
|
||||
this->last_event_type_ = found;
|
||||
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), this->last_event_type_);
|
||||
last_event_type = found;
|
||||
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), last_event_type->c_str());
|
||||
this->event_callback_.call(event_type);
|
||||
#if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_event(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Event::set_event_types(const FixedVector<const char *> &event_types) {
|
||||
this->types_.init(event_types.size());
|
||||
for (const char *type : event_types) {
|
||||
this->types_.push_back(type);
|
||||
}
|
||||
this->last_event_type_ = nullptr; // Reset when types change
|
||||
}
|
||||
|
||||
void Event::set_event_types(const std::vector<const char *> &event_types) {
|
||||
this->types_.init(event_types.size());
|
||||
for (const char *type : event_types) {
|
||||
this->types_.push_back(type);
|
||||
}
|
||||
this->last_event_type_ = nullptr; // Reset when types change
|
||||
}
|
||||
|
||||
void Event::add_on_event_callback(std::function<void(const std::string &event_type)> &&callback) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
@@ -24,39 +22,16 @@ namespace event {
|
||||
|
||||
class Event : public EntityBase, public EntityBase_DeviceClass {
|
||||
public:
|
||||
const std::string *last_event_type;
|
||||
|
||||
void trigger(const std::string &event_type);
|
||||
|
||||
/// Set the event types supported by this event (from initializer list).
|
||||
void set_event_types(std::initializer_list<const char *> event_types) {
|
||||
this->types_ = event_types;
|
||||
this->last_event_type_ = nullptr; // Reset when types change
|
||||
}
|
||||
/// Set the event types supported by this event (from FixedVector).
|
||||
void set_event_types(const FixedVector<const char *> &event_types);
|
||||
/// Set the event types supported by this event (from vector).
|
||||
void set_event_types(const std::vector<const char *> &event_types);
|
||||
|
||||
// Deleted overloads to catch incorrect std::string usage at compile time with clear error messages
|
||||
void set_event_types(std::initializer_list<std::string> event_types) = delete;
|
||||
void set_event_types(const FixedVector<std::string> &event_types) = delete;
|
||||
void set_event_types(const std::vector<std::string> &event_types) = delete;
|
||||
|
||||
/// Return the event types supported by this event.
|
||||
const FixedVector<const char *> &get_event_types() const { return this->types_; }
|
||||
|
||||
/// Return the last triggered event type (pointer to string in types_), or nullptr if no event triggered yet.
|
||||
const char *get_last_event_type() const { return this->last_event_type_; }
|
||||
|
||||
void set_event_types(const std::initializer_list<std::string> &event_types) { this->types_ = event_types; }
|
||||
const FixedVector<std::string> &get_event_types() const { return this->types_; }
|
||||
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
|
||||
|
||||
protected:
|
||||
CallbackManager<void(const std::string &event_type)> event_callback_;
|
||||
FixedVector<const char *> types_;
|
||||
|
||||
private:
|
||||
/// Last triggered event type - must point to entry in types_ to ensure valid lifetime.
|
||||
/// Set by trigger() after validation, reset to nullptr when types_ changes.
|
||||
const char *last_event_type_{nullptr};
|
||||
FixedVector<std::string> types_;
|
||||
};
|
||||
|
||||
} // namespace event
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#include "fan.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -183,9 +181,6 @@ void Fan::publish_state() {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", preset);
|
||||
}
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_FAN) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_fan_update(this);
|
||||
#endif
|
||||
this->save_state_();
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ void GDK101Component::dump_config() {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "Firmware Version", this->fw_version_sensor_);
|
||||
LOG_SENSOR(" ", "Average Radaition Dose per 1 minute", this->rad_1m_sensor_);
|
||||
LOG_SENSOR(" ", "Average Radaition Dose per 10 minutes", this->rad_10m_sensor_);
|
||||
LOG_SENSOR(" ", "Status", this->status_sensor_);
|
||||
@@ -71,10 +72,6 @@ void GDK101Component::dump_config() {
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
LOG_BINARY_SENSOR(" ", "Vibration Status", this->vibration_binary_sensor_);
|
||||
#endif // USE_BINARY_SENSOR
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
LOG_TEXT_SENSOR(" ", "Firmware Version", this->fw_version_text_sensor_);
|
||||
#endif // USE_TEXT_SENSOR
|
||||
}
|
||||
|
||||
float GDK101Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
@@ -156,18 +153,18 @@ bool GDK101Component::read_status_(uint8_t *data) {
|
||||
}
|
||||
|
||||
bool GDK101Component::read_fw_version_(uint8_t *data) {
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->fw_version_text_sensor_ != nullptr) {
|
||||
#ifdef USE_SENSOR
|
||||
if (this->fw_version_sensor_ != nullptr) {
|
||||
if (!this->read_bytes(GDK101_REG_READ_FIRMWARE, data, 2)) {
|
||||
ESP_LOGE(TAG, "Updating GDK101 failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string fw_version_str = str_sprintf("%d.%d", data[0], data[1]);
|
||||
const float fw_version = data[0] + (data[1] / 10.0f);
|
||||
|
||||
this->fw_version_text_sensor_->publish_state(fw_version_str);
|
||||
this->fw_version_sensor_->publish_state(fw_version);
|
||||
}
|
||||
#endif // USE_TEXT_SENSOR
|
||||
#endif // USE_SENSOR
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif // USE_BINARY_SENSOR
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif // USE_TEXT_SENSOR
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -28,14 +25,12 @@ class GDK101Component : public PollingComponent, public i2c::I2CDevice {
|
||||
SUB_SENSOR(rad_1m)
|
||||
SUB_SENSOR(rad_10m)
|
||||
SUB_SENSOR(status)
|
||||
SUB_SENSOR(fw_version)
|
||||
SUB_SENSOR(measurement_duration)
|
||||
#endif // USE_SENSOR
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
SUB_BINARY_SENSOR(vibration)
|
||||
#endif // USE_BINARY_SENSOR
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
SUB_TEXT_SENSOR(fw_version)
|
||||
#endif // USE_TEXT_SENSOR
|
||||
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
@@ -40,8 +40,9 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_VERSION): cv.invalid(
|
||||
"The 'version' option has been moved to the `text_sensor` component."
|
||||
cv.Optional(CONF_VERSION): sensor.sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
cv.Optional(CONF_STATUS): sensor.sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
@@ -70,6 +71,10 @@ async def to_code(config):
|
||||
sens = await sensor.new_sensor(radiation_dose_per_10m)
|
||||
cg.add(hub.set_rad_10m_sensor(sens))
|
||||
|
||||
if version_config := config.get(CONF_VERSION):
|
||||
sens = await sensor.new_sensor(version_config)
|
||||
cg.add(hub.set_fw_version_sensor(sens))
|
||||
|
||||
if status_config := config.get(CONF_STATUS):
|
||||
sens = await sensor.new_sensor(status_config)
|
||||
cg.add(hub.set_status_sensor(sens))
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_VERSION, ENTITY_CATEGORY_DIAGNOSTIC, ICON_CHIP
|
||||
|
||||
from . import CONF_GDK101_ID, GDK101Component
|
||||
|
||||
DEPENDENCIES = ["gdk101"]
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_GDK101_ID): cv.use_id(GDK101Component),
|
||||
cv.Required(CONF_VERSION): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_GDK101_ID])
|
||||
var = await text_sensor.new_text_sensor(config[CONF_VERSION])
|
||||
cg.add(hub.set_fw_version_text_sensor(var))
|
||||
@@ -235,7 +235,7 @@ void GraphLegend::init(Graph *g) {
|
||||
std::string valstr =
|
||||
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
|
||||
if (this->units_) {
|
||||
valstr += trace->sensor_->get_unit_of_measurement_ref();
|
||||
valstr += trace->sensor_->get_unit_of_measurement();
|
||||
}
|
||||
this->font_value_->measure(valstr.c_str(), &fw, &fos, &fbl, &fh);
|
||||
if (fw > valw)
|
||||
@@ -371,7 +371,7 @@ void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_of
|
||||
std::string valstr =
|
||||
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
|
||||
if (legend_->units_) {
|
||||
valstr += trace->sensor_->get_unit_of_measurement_ref();
|
||||
valstr += trace->sensor_->get_unit_of_measurement();
|
||||
}
|
||||
buff->printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr.c_str());
|
||||
ESP_LOGV(TAG, " value: %s", valstr.c_str());
|
||||
|
||||
@@ -1,247 +0,0 @@
|
||||
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_DIRECTION,
|
||||
CONF_ID,
|
||||
CONF_NAME,
|
||||
CONF_ON_ENROLLMENT_DONE,
|
||||
CONF_ON_ENROLLMENT_FAILED,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@OnFreund"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
AUTO_LOAD = ["binary_sensor", "sensor", "text_sensor"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_HLK_FM22X_ID = "hlk_fm22x_id"
|
||||
CONF_FACE_ID = "face_id"
|
||||
CONF_ON_FACE_SCAN_MATCHED = "on_face_scan_matched"
|
||||
CONF_ON_FACE_SCAN_UNMATCHED = "on_face_scan_unmatched"
|
||||
CONF_ON_FACE_SCAN_INVALID = "on_face_scan_invalid"
|
||||
CONF_ON_FACE_INFO = "on_face_info"
|
||||
|
||||
hlk_fm22x_ns = cg.esphome_ns.namespace("hlk_fm22x")
|
||||
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)
|
||||
ScanAction = hlk_fm22x_ns.class_("ScanAction", automation.Action)
|
||||
ResetAction = hlk_fm22x_ns.class_("ResetAction", automation.Action)
|
||||
|
||||
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_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
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("50ms"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, 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
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
for conf in config.get(CONF_ON_FACE_INFO, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger,
|
||||
[
|
||||
(cg.int16, "status"),
|
||||
(cg.int16, "left"),
|
||||
(cg.int16, "top"),
|
||||
(cg.int16, "right"),
|
||||
(cg.int16, "bottom"),
|
||||
(cg.int16, "yaw"),
|
||||
(cg.int16, "pitch"),
|
||||
(cg.int16, "roll"),
|
||||
],
|
||||
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.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)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"hlk_fm22x.enroll",
|
||||
EnrollmentAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
|
||||
cv.Required(CONF_NAME): cv.templatable(cv.string),
|
||||
cv.Required(CONF_DIRECTION): cv.templatable(cv.uint8_t),
|
||||
},
|
||||
key=CONF_NAME,
|
||||
),
|
||||
)
|
||||
async def hlk_fm22x_enroll_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
template_ = await cg.templatable(config[CONF_NAME], args, cg.std_string)
|
||||
cg.add(var.set_name(template_))
|
||||
template_ = await cg.templatable(config[CONF_DIRECTION], args, cg.uint8)
|
||||
cg.add(var.set_direction(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"hlk_fm22x.delete",
|
||||
DeleteAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
|
||||
cv.Required(CONF_FACE_ID): cv.templatable(cv.uint16_t),
|
||||
},
|
||||
key=CONF_FACE_ID,
|
||||
),
|
||||
)
|
||||
async def hlk_fm22x_delete_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
template_ = await cg.templatable(config[CONF_FACE_ID], args, cg.int16)
|
||||
cg.add(var.set_face_id(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"hlk_fm22x.delete_all",
|
||||
DeleteAllAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def hlk_fm22x_delete_all_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"hlk_fm22x.scan",
|
||||
ScanAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def hlk_fm22x_scan_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"hlk_fm22x.reset",
|
||||
ResetAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def hlk_fm22x_reset_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
@@ -1,21 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ICON, ICON_KEY_PLUS
|
||||
|
||||
from . import CONF_HLK_FM22X_ID, HlkFm22xComponent
|
||||
|
||||
DEPENDENCIES = ["hlk_fm22x"]
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend(
|
||||
{
|
||||
cv.GenerateID(CONF_HLK_FM22X_ID): cv.use_id(HlkFm22xComponent),
|
||||
cv.Optional(CONF_ICON, default=ICON_KEY_PLUS): cv.icon,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_HLK_FM22X_ID])
|
||||
var = await binary_sensor.new_binary_sensor(config)
|
||||
cg.add(hub.set_enrolling_binary_sensor(var))
|
||||
@@ -1,325 +0,0 @@
|
||||
#include "hlk_fm22x.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome::hlk_fm22x {
|
||||
|
||||
static const char *const TAG = "hlk_fm22x";
|
||||
|
||||
void HlkFm22xComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up HLK-FM22X...");
|
||||
this->set_enrolling_(false);
|
||||
while (this->available()) {
|
||||
this->read();
|
||||
}
|
||||
this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_STATUS); });
|
||||
}
|
||||
|
||||
void HlkFm22xComponent::update() {
|
||||
if (this->active_command_ != HlkFm22xCommand::NONE) {
|
||||
if (this->wait_cycles_ > 600) {
|
||||
ESP_LOGE(TAG, "Command 0x%.2X timed out", this->active_command_);
|
||||
if (HlkFm22xCommand::RESET == this->active_command_) {
|
||||
this->mark_failed();
|
||||
} else {
|
||||
this->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
this->recv_command_();
|
||||
}
|
||||
|
||||
void HlkFm22xComponent::enroll_face(const std::string &name, HlkFm22xFaceDirection direction) {
|
||||
if (name.length() > 31) {
|
||||
ESP_LOGE(TAG, "enroll_face(): name too long '%s'", name.c_str());
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Starting enrollment for %s", name.c_str());
|
||||
std::array<uint8_t, 35> data{};
|
||||
data[0] = 0; // admin
|
||||
std::copy(name.begin(), name.end(), data.begin() + 1);
|
||||
// Remaining bytes are already zero-initialized
|
||||
data[33] = (uint8_t) direction;
|
||||
data[34] = 10; // timeout
|
||||
this->send_command_(HlkFm22xCommand::ENROLL, data.data(), data.size());
|
||||
this->set_enrolling_(true);
|
||||
}
|
||||
|
||||
void HlkFm22xComponent::scan_face() {
|
||||
ESP_LOGI(TAG, "Verify face");
|
||||
static const uint8_t DATA[] = {0, 0};
|
||||
this->send_command_(HlkFm22xCommand::VERIFY, DATA, sizeof(DATA));
|
||||
}
|
||||
|
||||
void HlkFm22xComponent::delete_face(int16_t face_id) {
|
||||
ESP_LOGI(TAG, "Deleting face in slot %d", face_id);
|
||||
const uint8_t data[] = {(uint8_t) (face_id >> 8), (uint8_t) (face_id & 0xFF)};
|
||||
this->send_command_(HlkFm22xCommand::DELETE_FACE, data, sizeof(data));
|
||||
}
|
||||
|
||||
void HlkFm22xComponent::delete_all_faces() {
|
||||
ESP_LOGI(TAG, "Deleting all stored faces");
|
||||
this->send_command_(HlkFm22xCommand::DELETE_ALL_FACES);
|
||||
}
|
||||
|
||||
void HlkFm22xComponent::get_face_count_() {
|
||||
ESP_LOGD(TAG, "Getting face count");
|
||||
this->send_command_(HlkFm22xCommand::GET_ALL_FACE_IDS);
|
||||
}
|
||||
|
||||
void HlkFm22xComponent::reset() {
|
||||
ESP_LOGI(TAG, "Resetting module");
|
||||
this->active_command_ = HlkFm22xCommand::NONE;
|
||||
this->wait_cycles_ = 0;
|
||||
this->set_enrolling_(false);
|
||||
this->send_command_(HlkFm22xCommand::RESET);
|
||||
}
|
||||
|
||||
void HlkFm22xComponent::send_command_(HlkFm22xCommand command, const uint8_t *data, size_t size) {
|
||||
ESP_LOGV(TAG, "Send command: 0x%.2X", command);
|
||||
if (this->active_command_ != HlkFm22xCommand::NONE) {
|
||||
ESP_LOGW(TAG, "Command 0x%.2X already active", this->active_command_);
|
||||
return;
|
||||
}
|
||||
this->wait_cycles_ = 0;
|
||||
this->active_command_ = command;
|
||||
while (this->available())
|
||||
this->read();
|
||||
this->write((uint8_t) (START_CODE >> 8));
|
||||
this->write((uint8_t) (START_CODE & 0xFF));
|
||||
this->write((uint8_t) command);
|
||||
uint16_t data_size = size;
|
||||
this->write((uint8_t) (data_size >> 8));
|
||||
this->write((uint8_t) (data_size & 0xFF));
|
||||
|
||||
uint8_t checksum = 0;
|
||||
checksum ^= (uint8_t) command;
|
||||
checksum ^= (data_size >> 8);
|
||||
checksum ^= (data_size & 0xFF);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
this->write(data[i]);
|
||||
checksum ^= data[i];
|
||||
}
|
||||
|
||||
this->write(checksum);
|
||||
this->active_command_ = command;
|
||||
this->wait_cycles_ = 0;
|
||||
}
|
||||
|
||||
void HlkFm22xComponent::recv_command_() {
|
||||
uint8_t byte, checksum = 0;
|
||||
uint16_t length = 0;
|
||||
|
||||
if (this->available() < 7) {
|
||||
++this->wait_cycles_;
|
||||
return;
|
||||
}
|
||||
this->wait_cycles_ = 0;
|
||||
|
||||
if ((this->read() != (uint8_t) (START_CODE >> 8)) || (this->read() != (uint8_t) (START_CODE & 0xFF))) {
|
||||
ESP_LOGE(TAG, "Invalid start code");
|
||||
return;
|
||||
}
|
||||
|
||||
byte = this->read();
|
||||
checksum ^= byte;
|
||||
HlkFm22xResponseType response_type = (HlkFm22xResponseType) byte;
|
||||
|
||||
byte = this->read();
|
||||
checksum ^= byte;
|
||||
length = byte << 8;
|
||||
byte = this->read();
|
||||
checksum ^= byte;
|
||||
length |= byte;
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
data.reserve(length);
|
||||
for (uint16_t idx = 0; idx < length; ++idx) {
|
||||
byte = this->read();
|
||||
checksum ^= byte;
|
||||
data.push_back(byte);
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type, format_hex_pretty(data).c_str());
|
||||
|
||||
byte = this->read();
|
||||
if (byte != checksum) {
|
||||
ESP_LOGE(TAG, "Invalid checksum for data. Calculated: 0x%.2X, Received: 0x%.2X", checksum, byte);
|
||||
return;
|
||||
}
|
||||
switch (response_type) {
|
||||
case HlkFm22xResponseType::NOTE:
|
||||
this->handle_note_(data);
|
||||
break;
|
||||
case HlkFm22xResponseType::REPLY:
|
||||
this->handle_reply_(data);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unexpected response type: 0x%.2X", response_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HlkFm22xComponent::handle_note_(const std::vector<uint8_t> &data) {
|
||||
switch (data[0]) {
|
||||
case HlkFm22xNoteType::FACE_STATE:
|
||||
if (data.size() < 17) {
|
||||
ESP_LOGE(TAG, "Invalid face note data size: %u", data.size());
|
||||
break;
|
||||
}
|
||||
{
|
||||
int16_t info[8];
|
||||
uint8_t offset = 1;
|
||||
for (int16_t &i : info) {
|
||||
i = ((int16_t) data[offset + 1] << 8) | data[offset];
|
||||
offset += 2;
|
||||
}
|
||||
ESP_LOGV(TAG, "Face state: status: %d, left: %d, top: %d, right: %d, bottom: %d, yaw: %d, pitch: %d, roll: %d",
|
||||
info[0], info[1], info[2], info[3], info[4], info[5], info[6], info[7]);
|
||||
this->face_info_callback_.call(info[0], info[1], info[2], info[3], info[4], info[5], info[6], info[7]);
|
||||
}
|
||||
break;
|
||||
case HlkFm22xNoteType::READY:
|
||||
ESP_LOGE(TAG, "Command 0x%.2X timed out", this->active_command_);
|
||||
switch (this->active_command_) {
|
||||
case HlkFm22xCommand::ENROLL:
|
||||
this->set_enrolling_(false);
|
||||
this->enrollment_failed_callback_.call(HlkFm22xResult::FAILED4_TIMEOUT);
|
||||
break;
|
||||
case HlkFm22xCommand::VERIFY:
|
||||
this->face_scan_invalid_callback_.call(HlkFm22xResult::FAILED4_TIMEOUT);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->active_command_ = HlkFm22xCommand::NONE;
|
||||
this->wait_cycles_ = 0;
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unhandled note: 0x%.2X", data[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HlkFm22xComponent::handle_reply_(const std::vector<uint8_t> &data) {
|
||||
auto expected = this->active_command_;
|
||||
this->active_command_ = HlkFm22xCommand::NONE;
|
||||
if (data[0] != (uint8_t) expected) {
|
||||
ESP_LOGE(TAG, "Unexpected response command. Expected: 0x%.2X, Received: 0x%.2X", expected, data[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data[1] != HlkFm22xResult::SUCCESS) {
|
||||
ESP_LOGE(TAG, "Command <0x%.2X> failed. Error: 0x%.2X", data[0], data[1]);
|
||||
switch (expected) {
|
||||
case HlkFm22xCommand::ENROLL:
|
||||
this->set_enrolling_(false);
|
||||
this->enrollment_failed_callback_.call(data[1]);
|
||||
break;
|
||||
case HlkFm22xCommand::VERIFY:
|
||||
if (data[1] == HlkFm22xResult::REJECTED) {
|
||||
this->face_scan_unmatched_callback_.call();
|
||||
} else {
|
||||
this->face_scan_invalid_callback_.call(data[1]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (expected) {
|
||||
case HlkFm22xCommand::VERIFY: {
|
||||
int16_t face_id = ((int16_t) data[2] << 8) | data[3];
|
||||
std::string name(data.begin() + 4, data.begin() + 36);
|
||||
ESP_LOGD(TAG, "Face verified. ID: %d, name: %s", face_id, name.c_str());
|
||||
if (this->last_face_id_sensor_ != nullptr) {
|
||||
this->last_face_id_sensor_->publish_state(face_id);
|
||||
}
|
||||
if (this->last_face_name_text_sensor_ != nullptr) {
|
||||
this->last_face_name_text_sensor_->publish_state(name);
|
||||
}
|
||||
this->face_scan_matched_callback_.call(face_id, name);
|
||||
break;
|
||||
}
|
||||
case HlkFm22xCommand::ENROLL: {
|
||||
int16_t face_id = ((int16_t) data[2] << 8) | data[3];
|
||||
HlkFm22xFaceDirection direction = (HlkFm22xFaceDirection) data[4];
|
||||
ESP_LOGI(TAG, "Face enrolled. ID: %d, Direction: 0x%.2X", face_id, direction);
|
||||
this->enrollment_done_callback_.call(face_id, (uint8_t) direction);
|
||||
this->set_enrolling_(false);
|
||||
this->defer([this]() { this->get_face_count_(); });
|
||||
break;
|
||||
}
|
||||
case HlkFm22xCommand::GET_STATUS:
|
||||
if (this->status_sensor_ != nullptr) {
|
||||
this->status_sensor_->publish_state(data[2]);
|
||||
}
|
||||
this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_VERSION); });
|
||||
break;
|
||||
case HlkFm22xCommand::GET_VERSION:
|
||||
if (this->version_text_sensor_ != nullptr) {
|
||||
std::string version(data.begin() + 2, data.end());
|
||||
this->version_text_sensor_->publish_state(version);
|
||||
}
|
||||
this->defer([this]() { this->get_face_count_(); });
|
||||
break;
|
||||
case HlkFm22xCommand::GET_ALL_FACE_IDS:
|
||||
if (this->face_count_sensor_ != nullptr) {
|
||||
this->face_count_sensor_->publish_state(data[2]);
|
||||
}
|
||||
break;
|
||||
case HlkFm22xCommand::DELETE_FACE:
|
||||
ESP_LOGI(TAG, "Deleted face");
|
||||
break;
|
||||
case HlkFm22xCommand::DELETE_ALL_FACES:
|
||||
ESP_LOGI(TAG, "Deleted all faces");
|
||||
break;
|
||||
case HlkFm22xCommand::RESET:
|
||||
ESP_LOGI(TAG, "Module reset");
|
||||
this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_STATUS); });
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unhandled command: 0x%.2X", this->active_command_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HlkFm22xComponent::set_enrolling_(bool enrolling) {
|
||||
if (this->enrolling_binary_sensor_ != nullptr) {
|
||||
this->enrolling_binary_sensor_->publish_state(enrolling);
|
||||
}
|
||||
}
|
||||
|
||||
void HlkFm22xComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "HLK_FM22X:");
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
if (this->version_text_sensor_) {
|
||||
LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %s", this->version_text_sensor_->get_state().c_str());
|
||||
}
|
||||
if (this->enrolling_binary_sensor_) {
|
||||
LOG_BINARY_SENSOR(" ", "Enrolling", this->enrolling_binary_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %s", this->enrolling_binary_sensor_->state ? "ON" : "OFF");
|
||||
}
|
||||
if (this->face_count_sensor_) {
|
||||
LOG_SENSOR(" ", "Face Count", this->face_count_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->face_count_sensor_->get_state());
|
||||
}
|
||||
if (this->status_sensor_) {
|
||||
LOG_SENSOR(" ", "Status", this->status_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->status_sensor_->get_state());
|
||||
}
|
||||
if (this->last_face_id_sensor_) {
|
||||
LOG_SENSOR(" ", "Last Face ID", this->last_face_id_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %u", (int16_t) this->last_face_id_sensor_->get_state());
|
||||
}
|
||||
if (this->last_face_name_text_sensor_) {
|
||||
LOG_TEXT_SENSOR(" ", "Last Face Name", this->last_face_name_text_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %s", this->last_face_name_text_sensor_->get_state().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::hlk_fm22x
|
||||
@@ -1,224 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::hlk_fm22x {
|
||||
|
||||
static const uint16_t START_CODE = 0xEFAA;
|
||||
enum HlkFm22xCommand {
|
||||
NONE = 0x00,
|
||||
RESET = 0x10,
|
||||
GET_STATUS = 0x11,
|
||||
VERIFY = 0x12,
|
||||
ENROLL = 0x13,
|
||||
DELETE_FACE = 0x20,
|
||||
DELETE_ALL_FACES = 0x21,
|
||||
GET_ALL_FACE_IDS = 0x24,
|
||||
GET_VERSION = 0x30,
|
||||
GET_SERIAL_NUMBER = 0x93,
|
||||
};
|
||||
|
||||
enum HlkFm22xResponseType {
|
||||
REPLY = 0x00,
|
||||
NOTE = 0x01,
|
||||
IMAGE = 0x02,
|
||||
};
|
||||
|
||||
enum HlkFm22xNoteType {
|
||||
READY = 0x00,
|
||||
FACE_STATE = 0x01,
|
||||
};
|
||||
|
||||
enum HlkFm22xResult {
|
||||
SUCCESS = 0x00,
|
||||
REJECTED = 0x01,
|
||||
ABORTED = 0x02,
|
||||
FAILED4_CAMERA = 0x04,
|
||||
FAILED4_UNKNOWNREASON = 0x05,
|
||||
FAILED4_INVALIDPARAM = 0x06,
|
||||
FAILED4_NOMEMORY = 0x07,
|
||||
FAILED4_UNKNOWNUSER = 0x08,
|
||||
FAILED4_MAXUSER = 0x09,
|
||||
FAILED4_FACEENROLLED = 0x0A,
|
||||
FAILED4_LIVENESSCHECK = 0x0C,
|
||||
FAILED4_TIMEOUT = 0x0D,
|
||||
FAILED4_AUTHORIZATION = 0x0E,
|
||||
FAILED4_READ_FILE = 0x13,
|
||||
FAILED4_WRITE_FILE = 0x14,
|
||||
FAILED4_NO_ENCRYPT = 0x15,
|
||||
FAILED4_NO_RGBIMAGE = 0x17,
|
||||
FAILED4_JPGPHOTO_LARGE = 0x18,
|
||||
FAILED4_JPGPHOTO_SMALL = 0x19,
|
||||
};
|
||||
|
||||
enum HlkFm22xFaceDirection {
|
||||
FACE_DIRECTION_UNDEFINED = 0x00,
|
||||
FACE_DIRECTION_MIDDLE = 0x01,
|
||||
FACE_DIRECTION_RIGHT = 0x02,
|
||||
FACE_DIRECTION_LEFT = 0x04,
|
||||
FACE_DIRECTION_DOWN = 0x08,
|
||||
FACE_DIRECTION_UP = 0x10,
|
||||
};
|
||||
|
||||
class HlkFm22xComponent : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_face_count_sensor(sensor::Sensor *face_count_sensor) { this->face_count_sensor_ = face_count_sensor; }
|
||||
void set_status_sensor(sensor::Sensor *status_sensor) { this->status_sensor_ = status_sensor; }
|
||||
void set_last_face_id_sensor(sensor::Sensor *last_face_id_sensor) {
|
||||
this->last_face_id_sensor_ = last_face_id_sensor;
|
||||
}
|
||||
void set_last_face_name_text_sensor(text_sensor::TextSensor *last_face_name_text_sensor) {
|
||||
this->last_face_name_text_sensor_ = last_face_name_text_sensor;
|
||||
}
|
||||
void set_enrolling_binary_sensor(binary_sensor::BinarySensor *enrolling_binary_sensor) {
|
||||
this->enrolling_binary_sensor_ = enrolling_binary_sensor;
|
||||
}
|
||||
void set_version_text_sensor(text_sensor::TextSensor *version_text_sensor) {
|
||||
this->version_text_sensor_ = version_text_sensor;
|
||||
}
|
||||
void add_on_face_scan_matched_callback(std::function<void(int16_t, std::string)> callback) {
|
||||
this->face_scan_matched_callback_.add(std::move(callback));
|
||||
}
|
||||
void add_on_face_scan_unmatched_callback(std::function<void()> callback) {
|
||||
this->face_scan_unmatched_callback_.add(std::move(callback));
|
||||
}
|
||||
void add_on_face_scan_invalid_callback(std::function<void(uint8_t)> callback) {
|
||||
this->face_scan_invalid_callback_.add(std::move(callback));
|
||||
}
|
||||
void add_on_face_info_callback(
|
||||
std::function<void(int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t)> callback) {
|
||||
this->face_info_callback_.add(std::move(callback));
|
||||
}
|
||||
void add_on_enrollment_done_callback(std::function<void(int16_t, uint8_t)> callback) {
|
||||
this->enrollment_done_callback_.add(std::move(callback));
|
||||
}
|
||||
void add_on_enrollment_failed_callback(std::function<void(uint8_t)> callback) {
|
||||
this->enrollment_failed_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void enroll_face(const std::string &name, HlkFm22xFaceDirection direction);
|
||||
void scan_face();
|
||||
void delete_face(int16_t face_id);
|
||||
void delete_all_faces();
|
||||
void reset();
|
||||
|
||||
protected:
|
||||
void get_face_count_();
|
||||
void send_command_(HlkFm22xCommand command, const uint8_t *data = nullptr, size_t size = 0);
|
||||
void recv_command_();
|
||||
void handle_note_(const std::vector<uint8_t> &data);
|
||||
void handle_reply_(const std::vector<uint8_t> &data);
|
||||
void set_enrolling_(bool enrolling);
|
||||
|
||||
HlkFm22xCommand active_command_ = HlkFm22xCommand::NONE;
|
||||
uint16_t wait_cycles_ = 0;
|
||||
sensor::Sensor *face_count_sensor_{nullptr};
|
||||
sensor::Sensor *status_sensor_{nullptr};
|
||||
sensor::Sensor *last_face_id_sensor_{nullptr};
|
||||
binary_sensor::BinarySensor *enrolling_binary_sensor_{nullptr};
|
||||
text_sensor::TextSensor *last_face_name_text_sensor_{nullptr};
|
||||
text_sensor::TextSensor *version_text_sensor_{nullptr};
|
||||
CallbackManager<void(uint8_t)> face_scan_invalid_callback_;
|
||||
CallbackManager<void(int16_t, std::string)> face_scan_matched_callback_;
|
||||
CallbackManager<void()> face_scan_unmatched_callback_;
|
||||
CallbackManager<void(int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t)> face_info_callback_;
|
||||
CallbackManager<void(int16_t, uint8_t)> enrollment_done_callback_;
|
||||
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)
|
||||
TEMPLATABLE_VALUE(uint8_t, direction)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto name = this->name_.value(x...);
|
||||
auto direction = (HlkFm22xFaceDirection) this->direction_.value(x...);
|
||||
this->parent_->enroll_face(name, direction);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class DeleteAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(int16_t, face_id)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto face_id = this->face_id_.value(x...);
|
||||
this->parent_->delete_face(face_id);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class DeleteAllAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->delete_all_faces(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ScanAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->scan_face(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ResetAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->reset(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::hlk_fm22x
|
||||
@@ -1,47 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_STATUS, ENTITY_CATEGORY_DIAGNOSTIC, ICON_ACCOUNT
|
||||
|
||||
from . import CONF_HLK_FM22X_ID, HlkFm22xComponent
|
||||
|
||||
DEPENDENCIES = ["hlk_fm22x"]
|
||||
|
||||
CONF_FACE_COUNT = "face_count"
|
||||
CONF_LAST_FACE_ID = "last_face_id"
|
||||
ICON_FACE = "mdi:face-recognition"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_HLK_FM22X_ID): cv.use_id(HlkFm22xComponent),
|
||||
cv.Optional(CONF_FACE_COUNT): sensor.sensor_schema(
|
||||
icon=ICON_FACE,
|
||||
accuracy_decimals=0,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_STATUS): sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_LAST_FACE_ID): sensor.sensor_schema(
|
||||
icon=ICON_ACCOUNT,
|
||||
accuracy_decimals=0,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_HLK_FM22X_ID])
|
||||
|
||||
for key in [
|
||||
CONF_FACE_COUNT,
|
||||
CONF_STATUS,
|
||||
CONF_LAST_FACE_ID,
|
||||
]:
|
||||
if key not in config:
|
||||
continue
|
||||
conf = config[key]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(getattr(hub, f"set_{key}_sensor")(sens))
|
||||
@@ -1,42 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_VERSION,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_ACCOUNT,
|
||||
ICON_RESTART,
|
||||
)
|
||||
|
||||
from . import CONF_HLK_FM22X_ID, HlkFm22xComponent
|
||||
|
||||
DEPENDENCIES = ["hlk_fm22x"]
|
||||
|
||||
CONF_LAST_FACE_NAME = "last_face_name"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_HLK_FM22X_ID): cv.use_id(HlkFm22xComponent),
|
||||
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
|
||||
icon=ICON_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_LAST_FACE_NAME): text_sensor.text_sensor_schema(
|
||||
icon=ICON_ACCOUNT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_HLK_FM22X_ID])
|
||||
for key in [
|
||||
CONF_VERSION,
|
||||
CONF_LAST_FACE_NAME,
|
||||
]:
|
||||
if key not in config:
|
||||
continue
|
||||
conf = config[key]
|
||||
sens = await text_sensor.new_text_sensor(conf)
|
||||
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include <climits>
|
||||
|
||||
#include "abstract_aqi_calculator.h"
|
||||
// https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf
|
||||
// https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf
|
||||
|
||||
namespace esphome {
|
||||
namespace hm3301 {
|
||||
@@ -16,15 +16,16 @@ class AQICalculator : public AbstractAQICalculator {
|
||||
}
|
||||
|
||||
protected:
|
||||
static const int AMOUNT_OF_LEVELS = 6;
|
||||
static const int AMOUNT_OF_LEVELS = 7;
|
||||
|
||||
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
|
||||
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200},
|
||||
{201, 300}, {301, 400}, {401, 500}};
|
||||
|
||||
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55},
|
||||
{56, 125}, {126, 225}, {226, INT_MAX}};
|
||||
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 35}, {36, 55}, {56, 150},
|
||||
{151, 250}, {251, 350}, {351, 500}};
|
||||
|
||||
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
|
||||
{255, 354}, {355, 424}, {425, INT_MAX}};
|
||||
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254}, {255, 354},
|
||||
{355, 424}, {425, 504}, {505, 604}};
|
||||
|
||||
int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
||||
int grid_index = get_grid_index_(value, array);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#include "light_state.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "light_output.h"
|
||||
#include "light_state.h"
|
||||
#include "transformers.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -138,12 +137,7 @@ void LightState::loop() {
|
||||
|
||||
float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
|
||||
|
||||
void LightState::publish_state() {
|
||||
this->remote_values_callback_.call();
|
||||
#if defined(USE_LIGHT) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_light_update(this);
|
||||
#endif
|
||||
}
|
||||
void LightState::publish_state() { this->remote_values_callback_.call(); }
|
||||
|
||||
LightOutput *LightState::get_output() const { return this->output_; }
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#include "lock.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -55,9 +53,6 @@ void Lock::publish_state(LockState state) {
|
||||
this->rtc_.save(&this->state);
|
||||
ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), lock_state_to_string(state));
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_LOCK) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_lock_update(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Lock::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import importlib
|
||||
import logging
|
||||
import pkgutil
|
||||
|
||||
from esphome.automation import build_automation, validate_automation
|
||||
from esphome.automation import build_automation, register_action, validate_automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_COLOR_DEPTH, CONF_DRAW_ROUNDING
|
||||
from esphome.components.display import Display
|
||||
@@ -27,8 +25,8 @@ from esphome.cpp_generator import MockObj
|
||||
from esphome.final_validate import full_config
|
||||
from esphome.helpers import write_file_if_changed
|
||||
|
||||
from . import defines as df, helpers, lv_validation as lvalid, widgets
|
||||
from .automation import disp_update, focused_widgets, refreshed_widgets
|
||||
from . import defines as df, helpers, lv_validation as lvalid
|
||||
from .automation import disp_update, focused_widgets, refreshed_widgets, update_to_code
|
||||
from .defines import add_define
|
||||
from .encoders import (
|
||||
ENCODERS_CONFIG,
|
||||
@@ -47,6 +45,7 @@ from .schemas import (
|
||||
WIDGET_TYPES,
|
||||
any_widget_schema,
|
||||
container_schema,
|
||||
create_modify_schema,
|
||||
obj_schema,
|
||||
)
|
||||
from .styles import add_top_layer, styles_to_code, theme_to_code
|
||||
@@ -55,6 +54,7 @@ from .trigger import add_on_boot_triggers, generate_triggers
|
||||
from .types import (
|
||||
FontEngine,
|
||||
IdleTrigger,
|
||||
ObjUpdateAction,
|
||||
PlainTrigger,
|
||||
lv_font_t,
|
||||
lv_group_t,
|
||||
@@ -69,23 +69,33 @@ from .widgets import (
|
||||
set_obj_properties,
|
||||
styles_used,
|
||||
)
|
||||
|
||||
# Import only what we actually use directly in this file
|
||||
from .widgets.animimg import animimg_spec
|
||||
from .widgets.arc import arc_spec
|
||||
from .widgets.button import button_spec
|
||||
from .widgets.buttonmatrix import buttonmatrix_spec
|
||||
from .widgets.canvas import canvas_spec
|
||||
from .widgets.checkbox import checkbox_spec
|
||||
from .widgets.container import container_spec
|
||||
from .widgets.dropdown import dropdown_spec
|
||||
from .widgets.img import img_spec
|
||||
from .widgets.keyboard import keyboard_spec
|
||||
from .widgets.label import label_spec
|
||||
from .widgets.led import led_spec
|
||||
from .widgets.line import line_spec
|
||||
from .widgets.lv_bar import bar_spec
|
||||
from .widgets.meter import meter_spec
|
||||
from .widgets.msgbox import MSGBOX_SCHEMA, msgboxes_to_code
|
||||
from .widgets.obj import obj_spec # Used in LVGL_SCHEMA
|
||||
from .widgets.page import ( # page_spec used in LVGL_SCHEMA
|
||||
add_pages,
|
||||
generate_page_triggers,
|
||||
page_spec,
|
||||
)
|
||||
|
||||
# Widget registration happens via WidgetType.__init__ in individual widget files
|
||||
# The imports below trigger creation of the widget types
|
||||
# Action registration (lvgl.{widget}.update) happens automatically
|
||||
# in the WidgetType.__init__ method
|
||||
|
||||
for module_info in pkgutil.iter_modules(widgets.__path__):
|
||||
importlib.import_module(f".widgets.{module_info.name}", package=__package__)
|
||||
from .widgets.obj import obj_spec
|
||||
from .widgets.page import add_pages, generate_page_triggers, page_spec
|
||||
from .widgets.qrcode import qr_code_spec
|
||||
from .widgets.roller import roller_spec
|
||||
from .widgets.slider import slider_spec
|
||||
from .widgets.spinbox import spinbox_spec
|
||||
from .widgets.spinner import spinner_spec
|
||||
from .widgets.switch import switch_spec
|
||||
from .widgets.tabview import tabview_spec
|
||||
from .widgets.textarea import textarea_spec
|
||||
from .widgets.tileview import tileview_spec
|
||||
|
||||
DOMAIN = "lvgl"
|
||||
DEPENDENCIES = ["display"]
|
||||
@@ -93,6 +103,41 @@ AUTO_LOAD = ["key_provider"]
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
for w_type in (
|
||||
label_spec,
|
||||
obj_spec,
|
||||
button_spec,
|
||||
bar_spec,
|
||||
slider_spec,
|
||||
arc_spec,
|
||||
line_spec,
|
||||
spinner_spec,
|
||||
led_spec,
|
||||
animimg_spec,
|
||||
checkbox_spec,
|
||||
img_spec,
|
||||
switch_spec,
|
||||
tabview_spec,
|
||||
buttonmatrix_spec,
|
||||
meter_spec,
|
||||
dropdown_spec,
|
||||
roller_spec,
|
||||
textarea_spec,
|
||||
spinbox_spec,
|
||||
keyboard_spec,
|
||||
tileview_spec,
|
||||
qr_code_spec,
|
||||
canvas_spec,
|
||||
container_spec,
|
||||
):
|
||||
WIDGET_TYPES[w_type.name] = w_type
|
||||
|
||||
for w_type in WIDGET_TYPES.values():
|
||||
register_action(
|
||||
f"lvgl.{w_type.name}.update",
|
||||
ObjUpdateAction,
|
||||
create_modify_schema(w_type),
|
||||
)(update_to_code)
|
||||
|
||||
SIMPLE_TRIGGERS = (
|
||||
df.CONF_ON_PAUSE,
|
||||
@@ -357,15 +402,6 @@ def add_hello_world(config):
|
||||
return config
|
||||
|
||||
|
||||
def _theme_schema(value):
|
||||
return cv.Schema(
|
||||
{
|
||||
cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA)
|
||||
for name, w in WIDGET_TYPES.items()
|
||||
}
|
||||
)(value)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = final_validation
|
||||
|
||||
LVGL_SCHEMA = cv.All(
|
||||
@@ -418,7 +454,12 @@ LVGL_SCHEMA = cv.All(
|
||||
cv.Optional(
|
||||
df.CONF_TRANSPARENCY_KEY, default=0x000400
|
||||
): lvalid.lv_color,
|
||||
cv.Optional(df.CONF_THEME): _theme_schema,
|
||||
cv.Optional(df.CONF_THEME): cv.Schema(
|
||||
{
|
||||
cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA)
|
||||
for name, w in WIDGET_TYPES.items()
|
||||
}
|
||||
),
|
||||
cv.Optional(df.CONF_GRADIENTS): GRADIENT_SCHEMA,
|
||||
cv.Optional(df.CONF_TOUCHSCREENS, default=None): touchscreen_schema,
|
||||
cv.Optional(df.CONF_ENCODERS, default=None): ENCODERS_CONFIG,
|
||||
|
||||
@@ -3,8 +3,6 @@ import re
|
||||
from esphome import config_validation as cv
|
||||
from esphome.const import CONF_ARGS, CONF_FORMAT
|
||||
|
||||
CONF_IF_NAN = "if_nan"
|
||||
|
||||
lv_uses = {
|
||||
"USER_DATA",
|
||||
"LOG",
|
||||
@@ -23,48 +21,23 @@ lv_fonts_used = set()
|
||||
esphome_fonts_used = set()
|
||||
lvgl_components_required = set()
|
||||
|
||||
# noqa
|
||||
f_regex = re.compile(
|
||||
r"""
|
||||
|
||||
def validate_printf(value):
|
||||
cfmt = r"""
|
||||
( # start of capture group 1
|
||||
% # literal "%"
|
||||
[-+0 #]{0,5} # optional flags
|
||||
(?:\d+|\*)? # width
|
||||
(?:\.(?:\d+|\*))? # precision
|
||||
(?:h|l|ll|w|I|I32|I64)? # size
|
||||
f # type
|
||||
)
|
||||
""",
|
||||
flags=re.VERBOSE,
|
||||
)
|
||||
# noqa
|
||||
c_regex = re.compile(
|
||||
r"""
|
||||
( # start of capture group 1
|
||||
% # literal "%"
|
||||
[-+0 #]{0,5} # optional flags
|
||||
(?:[-+0 #]{0,5}) # optional flags
|
||||
(?:\d+|\*)? # width
|
||||
(?:\.(?:\d+|\*))? # precision
|
||||
(?:h|l|ll|w|I|I32|I64)? # size
|
||||
[cCdiouxXeEfgGaAnpsSZ] # type
|
||||
)
|
||||
""",
|
||||
flags=re.VERBOSE,
|
||||
)
|
||||
|
||||
|
||||
def validate_printf(value):
|
||||
format_string = value[CONF_FORMAT]
|
||||
matches = c_regex.findall(format_string)
|
||||
""" # noqa
|
||||
matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.VERBOSE)
|
||||
if len(matches) != len(value[CONF_ARGS]):
|
||||
raise cv.Invalid(
|
||||
f"Found {len(matches)} printf-patterns ({', '.join(matches)}), but {len(value[CONF_ARGS])} args were given!"
|
||||
)
|
||||
|
||||
if value.get(CONF_IF_NAN) and len(f_regex.findall(format_string)) != 1:
|
||||
raise cv.Invalid(
|
||||
"Use of 'if_nan' requires a single valid printf-pattern of type %f"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
|
||||
@@ -33,13 +33,7 @@ from .defines import (
|
||||
call_lambda,
|
||||
literal,
|
||||
)
|
||||
from .helpers import (
|
||||
CONF_IF_NAN,
|
||||
add_lv_use,
|
||||
esphome_fonts_used,
|
||||
lv_fonts_used,
|
||||
requires_component,
|
||||
)
|
||||
from .helpers import add_lv_use, esphome_fonts_used, lv_fonts_used, requires_component
|
||||
from .types import lv_font_t, lv_gradient_t
|
||||
|
||||
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
|
||||
@@ -418,13 +412,7 @@ class TextValidator(LValidator):
|
||||
str_args = [str(x) for x in value[CONF_ARGS]]
|
||||
arg_expr = cg.RawExpression(",".join(str_args))
|
||||
format_str = cpp_string_escape(format_str)
|
||||
sprintf_str = f"str_sprintf({format_str}, {arg_expr}).c_str()"
|
||||
if nanval := value.get(CONF_IF_NAN):
|
||||
nanval = cpp_string_escape(nanval)
|
||||
return literal(
|
||||
f"(std::isfinite({arg_expr}) ? {sprintf_str} : {nanval})"
|
||||
)
|
||||
return literal(sprintf_str)
|
||||
return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()")
|
||||
if time_format := value.get(CONF_TIME_FORMAT):
|
||||
source = value[CONF_TIME]
|
||||
if isinstance(source, Lambda):
|
||||
|
||||
@@ -20,7 +20,7 @@ from esphome.core.config import StartupTrigger
|
||||
|
||||
from . import defines as df, lv_validation as lvalid
|
||||
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR
|
||||
from .helpers import CONF_IF_NAN, requires_component, validate_printf
|
||||
from .helpers import requires_component, validate_printf
|
||||
from .layout import (
|
||||
FLEX_OBJ_SCHEMA,
|
||||
GRID_CELL_SCHEMA,
|
||||
@@ -54,7 +54,6 @@ PRINTF_TEXT_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_FORMAT): cv.string,
|
||||
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
|
||||
cv.Optional(CONF_IF_NAN): cv.string,
|
||||
},
|
||||
),
|
||||
validate_printf,
|
||||
@@ -411,10 +410,6 @@ def any_widget_schema(extras=None):
|
||||
Dynamically generate schemas for all possible LVGL widgets. This is what implements the ability to have a list of any kind of
|
||||
widget under the widgets: key.
|
||||
|
||||
This uses lazy evaluation - the schema is built when called during validation,
|
||||
not at import time. This allows external components to register widgets
|
||||
before schema validation begins.
|
||||
|
||||
:param extras: Additional schema to be applied to each generated one
|
||||
:return: A validator for the Widgets key
|
||||
"""
|
||||
|
||||
@@ -41,16 +41,16 @@ class LVGLSelect : public select::Select, public Component {
|
||||
}
|
||||
|
||||
void publish() {
|
||||
auto index = this->widget_->get_selected_index();
|
||||
this->publish_state(index);
|
||||
this->publish_state(this->widget_->get_selected_text());
|
||||
if (this->restore_) {
|
||||
auto index = this->widget_->get_selected_index();
|
||||
this->pref_.save(&index);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void control(size_t index) override {
|
||||
this->widget_->set_selected_index(index, this->anim_);
|
||||
void control(const std::string &value) override {
|
||||
this->widget_->set_selected_text(value, this->anim_);
|
||||
this->publish();
|
||||
}
|
||||
void set_options_() {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import sys
|
||||
|
||||
from esphome import automation, codegen as cg
|
||||
from esphome.automation import register_action
|
||||
from esphome.config_validation import Schema
|
||||
from esphome.const import CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_TEXT, CONF_VALUE
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.cpp_generator import MockObj, MockObjClass
|
||||
from esphome.cpp_types import esphome_ns
|
||||
|
||||
@@ -126,16 +124,13 @@ class WidgetType:
|
||||
schema=None,
|
||||
modify_schema=None,
|
||||
lv_name=None,
|
||||
is_mock: bool = False,
|
||||
):
|
||||
"""
|
||||
:param name: The widget name, e.g. "bar"
|
||||
:param w_type: The C type of the widget
|
||||
:param parts: What parts this widget supports
|
||||
:param schema: The config schema for defining a widget
|
||||
:param modify_schema: A schema to update the widget, defaults to the same as the schema
|
||||
:param lv_name: The name of the LVGL widget in the LVGL library, if different from the name
|
||||
:param is_mock: Whether this widget is a mock widget, i.e. not a real LVGL widget
|
||||
:param modify_schema: A schema to update the widget
|
||||
"""
|
||||
self.name = name
|
||||
self.lv_name = lv_name or name
|
||||
@@ -151,22 +146,6 @@ class WidgetType:
|
||||
self.modify_schema = modify_schema
|
||||
self.mock_obj = MockObj(f"lv_{self.lv_name}", "_")
|
||||
|
||||
# Local import to avoid circular import
|
||||
from .automation import update_to_code
|
||||
from .schemas import WIDGET_TYPES, create_modify_schema
|
||||
|
||||
if not is_mock:
|
||||
if self.name in WIDGET_TYPES:
|
||||
raise EsphomeError(f"Duplicate definition of widget type '{self.name}'")
|
||||
WIDGET_TYPES[self.name] = self
|
||||
|
||||
# Register the update action automatically
|
||||
register_action(
|
||||
f"lvgl.{self.name}.update",
|
||||
ObjUpdateAction,
|
||||
create_modify_schema(self),
|
||||
)(update_to_code)
|
||||
|
||||
@property
|
||||
def animated(self):
|
||||
return False
|
||||
|
||||
@@ -213,14 +213,17 @@ class LvScrActType(WidgetType):
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("lv_scr_act()", lv_obj_t, (), is_mock=True)
|
||||
super().__init__("lv_scr_act()", lv_obj_t, ())
|
||||
|
||||
async def to_code(self, w, config: dict):
|
||||
return []
|
||||
|
||||
|
||||
lv_scr_act_spec = LvScrActType()
|
||||
|
||||
|
||||
def get_scr_act(lv_comp: MockObj) -> Widget:
|
||||
return Widget.create(None, lv_comp.get_scr_act(), LvScrActType(), {})
|
||||
return Widget.create(None, lv_comp.get_scr_act(), lv_scr_act_spec, {})
|
||||
|
||||
|
||||
def get_widget_generator(wid):
|
||||
|
||||
@@ -2,7 +2,7 @@ from esphome import automation
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_RANGE_FROM, CONF_RANGE_TO, CONF_STEP, CONF_VALUE
|
||||
|
||||
from ..automation import action_to_code
|
||||
from ..automation import action_to_code, update_to_code
|
||||
from ..defines import (
|
||||
CONF_CURSOR,
|
||||
CONF_DECIMAL_PLACES,
|
||||
@@ -171,3 +171,17 @@ async def spinbox_decrement(config, action_id, template_arg, args):
|
||||
lv.spinbox_decrement(w.obj)
|
||||
|
||||
return await action_to_code(widgets, do_increment, action_id, template_arg, args)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.spinbox.update",
|
||||
ObjUpdateAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(lv_spinbox_t),
|
||||
cv.Required(CONF_VALUE): lv_float,
|
||||
}
|
||||
),
|
||||
)
|
||||
async def spinbox_update_to_code(config, action_id, template_arg, args):
|
||||
return await update_to_code(config, action_id, template_arg, args)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
CODEOWNERS = ["@philippderdiedas"]
|
||||
@@ -1,31 +0,0 @@
|
||||
#include "mcp3221_sensor.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp3221 {
|
||||
|
||||
static const char *const TAG = "mcp3221";
|
||||
|
||||
float MCP3221Sensor::sample() {
|
||||
uint8_t data[2];
|
||||
if (this->read(data, 2) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Read failed");
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
|
||||
uint16_t value = encode_uint16(data[0], data[1]);
|
||||
float voltage = value * this->reference_voltage_ / 4096.0f;
|
||||
|
||||
return voltage;
|
||||
}
|
||||
|
||||
void MCP3221Sensor::update() {
|
||||
float v = this->sample();
|
||||
this->publish_state(v);
|
||||
}
|
||||
|
||||
} // namespace mcp3221
|
||||
} // namespace esphome
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp3221 {
|
||||
|
||||
class MCP3221Sensor : public sensor::Sensor,
|
||||
public PollingComponent,
|
||||
public voltage_sampler::VoltageSampler,
|
||||
public i2c::I2CDevice {
|
||||
public:
|
||||
void set_reference_voltage(float reference_voltage) { this->reference_voltage_ = reference_voltage; }
|
||||
void update() override;
|
||||
float sample() override;
|
||||
|
||||
protected:
|
||||
float reference_voltage_;
|
||||
};
|
||||
|
||||
} // namespace mcp3221
|
||||
} // namespace esphome
|
||||
@@ -1,49 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor, voltage_sampler
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_REFERENCE_VOLTAGE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_SCALE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["voltage_sampler"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
|
||||
mcp3221_ns = cg.esphome_ns.namespace("mcp3221")
|
||||
MCP3221Sensor = mcp3221_ns.class_(
|
||||
"MCP3221Sensor",
|
||||
sensor.Sensor,
|
||||
voltage_sampler.VoltageSampler,
|
||||
cg.PollingComponent,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
MCP3221Sensor,
|
||||
icon=ICON_SCALE,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_REFERENCE_VOLTAGE, default="3.3V"): cv.voltage,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x48))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
cg.add(var.set_reference_voltage(config[CONF_REFERENCE_VOLTAGE]))
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
@@ -37,6 +37,8 @@ MDNS_STATIC_CONST_CHAR(SERVICE_TCP, "_tcp");
|
||||
MDNS_STATIC_CONST_CHAR(VALUE_VERSION, ESPHOME_VERSION);
|
||||
|
||||
void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services) {
|
||||
this->hostname_ = App.get_name();
|
||||
|
||||
// IMPORTANT: The #ifdef blocks below must match COMPONENTS_WITH_MDNS_SERVICES
|
||||
// in mdns/__init__.py. If you add a new service here, update both locations.
|
||||
|
||||
@@ -177,7 +179,7 @@ void MDNSComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"mDNS:\n"
|
||||
" Hostname: %s",
|
||||
App.get_name().c_str());
|
||||
this->hostname_.c_str());
|
||||
#ifdef USE_MDNS_STORE_SERVICES
|
||||
ESP_LOGV(TAG, " Services:");
|
||||
for (const auto &service : this->services_) {
|
||||
|
||||
@@ -76,6 +76,7 @@ class MDNSComponent : public Component {
|
||||
#ifdef USE_MDNS_STORE_SERVICES
|
||||
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services_{};
|
||||
#endif
|
||||
std::string hostname_;
|
||||
void compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#if defined(USE_ESP32) && defined(USE_MDNS)
|
||||
|
||||
#include <mdns.h>
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "mdns_component.h"
|
||||
@@ -28,9 +27,8 @@ void MDNSComponent::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
const char *hostname = App.get_name().c_str();
|
||||
mdns_hostname_set(hostname);
|
||||
mdns_instance_name_set(hostname);
|
||||
mdns_hostname_set(this->hostname_.c_str());
|
||||
mdns_instance_name_set(this->hostname_.c_str());
|
||||
|
||||
for (const auto &service : services) {
|
||||
auto txt_records = std::make_unique<mdns_txt_item_t[]>(service.txt_records.size());
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <ESP8266mDNS.h>
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "mdns_component.h"
|
||||
@@ -21,7 +20,7 @@ void MDNSComponent::setup() {
|
||||
this->compile_records_(services);
|
||||
#endif
|
||||
|
||||
MDNS.begin(App.get_name().c_str());
|
||||
MDNS.begin(this->hostname_.c_str());
|
||||
|
||||
for (const auto &service : services) {
|
||||
// Strip the leading underscore from the proto and service_type. While it is
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "mdns_component.h"
|
||||
|
||||
@@ -21,7 +20,7 @@ void MDNSComponent::setup() {
|
||||
this->compile_records_(services);
|
||||
#endif
|
||||
|
||||
MDNS.begin(App.get_name().c_str());
|
||||
MDNS.begin(this->hostname_.c_str());
|
||||
|
||||
for (const auto &service : services) {
|
||||
// Strip the leading underscore from the proto and service_type. While it is
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "mdns_component.h"
|
||||
|
||||
@@ -21,7 +20,7 @@ void MDNSComponent::setup() {
|
||||
this->compile_records_(services);
|
||||
#endif
|
||||
|
||||
MDNS.begin(App.get_name().c_str());
|
||||
MDNS.begin(this->hostname_.c_str());
|
||||
|
||||
for (const auto &service : services) {
|
||||
// Strip the leading underscore from the proto and service_type. While it is
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "media_player.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -149,12 +148,7 @@ void MediaPlayer::add_on_state_callback(std::function<void()> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void MediaPlayer::publish_state() {
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_MEDIA_PLAYER) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_media_player_update(this);
|
||||
#endif
|
||||
}
|
||||
void MediaPlayer::publish_state() { this->state_callback_.call(); }
|
||||
|
||||
} // namespace media_player
|
||||
} // namespace esphome
|
||||
|
||||
@@ -28,9 +28,8 @@ void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
|
||||
|
||||
if (map_it != this->mapping_.cend()) {
|
||||
size_t idx = std::distance(this->mapping_.cbegin(), map_it);
|
||||
ESP_LOGV(TAG, "Found option %s for value %lld", this->option_at(idx), value);
|
||||
this->publish_state(idx);
|
||||
return;
|
||||
new_state = std::string(this->option_at(idx));
|
||||
ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "No option found for mapping %lld", value);
|
||||
}
|
||||
@@ -41,16 +40,19 @@ void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
|
||||
}
|
||||
}
|
||||
|
||||
void ModbusSelect::control(size_t index) {
|
||||
optional<int64_t> mapval = this->mapping_[index];
|
||||
const char *option = this->option_at(index);
|
||||
ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, option);
|
||||
void ModbusSelect::control(const std::string &value) {
|
||||
auto idx = this->index_of(value);
|
||||
if (!idx.has_value()) {
|
||||
ESP_LOGW(TAG, "Invalid option '%s'", value.c_str());
|
||||
return;
|
||||
}
|
||||
optional<int64_t> mapval = this->mapping_[idx.value()];
|
||||
ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, value.c_str());
|
||||
|
||||
std::vector<uint16_t> data;
|
||||
|
||||
if (this->write_transform_func_.has_value()) {
|
||||
// Transform func requires string parameter for backward compatibility
|
||||
auto val = (*this->write_transform_func_)(this, std::string(option), *mapval, data);
|
||||
auto val = (*this->write_transform_func_)(this, value, *mapval, data);
|
||||
if (val.has_value()) {
|
||||
mapval = *val;
|
||||
ESP_LOGV(TAG, "write_lambda returned mapping value %lld", *mapval);
|
||||
@@ -83,7 +85,7 @@ void ModbusSelect::control(size_t index) {
|
||||
this->parent_->queue_command(write_cmd);
|
||||
|
||||
if (this->optimistic_)
|
||||
this->publish_state(index);
|
||||
this->publish_state(value);
|
||||
}
|
||||
|
||||
} // namespace modbus_controller
|
||||
|
||||
@@ -38,7 +38,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
|
||||
|
||||
void dump_config() override;
|
||||
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
||||
void control(size_t index) override;
|
||||
void control(const std::string &value) override;
|
||||
|
||||
protected:
|
||||
std::vector<int64_t> mapping_{};
|
||||
|
||||
@@ -36,7 +36,7 @@ void MQTTAlarmControlPanelComponent::setup() {
|
||||
} else if (strcasecmp(payload.c_str(), "TRIGGERED") == 0) {
|
||||
call.triggered();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s': Received unknown command payload %s", this->friendly_name_().c_str(), payload.c_str());
|
||||
ESP_LOGW(TAG, "'%s': Received unknown command payload %s", this->friendly_name().c_str(), payload.c_str());
|
||||
}
|
||||
call.perform();
|
||||
});
|
||||
|
||||
@@ -30,12 +30,9 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor
|
||||
}
|
||||
|
||||
void MQTTBinarySensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
const auto device_class = this->binary_sensor_->get_device_class_ref();
|
||||
if (!device_class.empty()) {
|
||||
root[MQTT_DEVICE_CLASS] = device_class;
|
||||
}
|
||||
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
if (!this->binary_sensor_->get_device_class().empty())
|
||||
root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class();
|
||||
if (this->binary_sensor_->is_status_binary_sensor())
|
||||
root[MQTT_PAYLOAD_ON] = mqtt::global_mqtt_client->get_availability().payload_available;
|
||||
if (this->binary_sensor_->is_status_binary_sensor())
|
||||
|
||||
@@ -20,7 +20,7 @@ void MQTTButtonComponent::setup() {
|
||||
if (payload == "PRESS") {
|
||||
this->button_->press();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str());
|
||||
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str());
|
||||
this->status_momentary_warning("state", 5000);
|
||||
}
|
||||
});
|
||||
@@ -33,9 +33,8 @@ void MQTTButtonComponent::dump_config() {
|
||||
void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
config.state_topic = false;
|
||||
const auto device_class = this->button_->get_device_class_ref();
|
||||
if (!device_class.empty()) {
|
||||
root[MQTT_DEVICE_CLASS] = device_class;
|
||||
if (!this->button_->get_device_class().empty()) {
|
||||
root[MQTT_DEVICE_CLASS] = this->button_->get_device_class();
|
||||
}
|
||||
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
}
|
||||
|
||||
@@ -64,11 +64,11 @@ bool MQTTComponent::send_discovery_() {
|
||||
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
|
||||
|
||||
if (discovery_info.clean) {
|
||||
ESP_LOGV(TAG, "'%s': Cleaning discovery", this->friendly_name_().c_str());
|
||||
ESP_LOGV(TAG, "'%s': Cleaning discovery", this->friendly_name().c_str());
|
||||
return global_mqtt_client->publish(this->get_discovery_topic_(discovery_info), "", 0, this->qos_, true);
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "'%s': Sending discovery", this->friendly_name_().c_str());
|
||||
ESP_LOGV(TAG, "'%s': Sending discovery", this->friendly_name().c_str());
|
||||
|
||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
return global_mqtt_client->publish_json(
|
||||
@@ -85,16 +85,12 @@ bool MQTTComponent::send_discovery_() {
|
||||
}
|
||||
|
||||
// Fields from EntityBase
|
||||
root[MQTT_NAME] = this->get_entity()->has_own_name() ? this->friendly_name_() : "";
|
||||
root[MQTT_NAME] = this->get_entity()->has_own_name() ? this->friendly_name() : "";
|
||||
|
||||
if (this->is_disabled_by_default_())
|
||||
if (this->is_disabled_by_default())
|
||||
root[MQTT_ENABLED_BY_DEFAULT] = false;
|
||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
const auto icon_ref = this->get_icon_ref_();
|
||||
if (!icon_ref.empty()) {
|
||||
root[MQTT_ICON] = icon_ref;
|
||||
}
|
||||
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
if (!this->get_icon().empty())
|
||||
root[MQTT_ICON] = this->get_icon();
|
||||
|
||||
const auto entity_category = this->get_entity()->get_entity_category();
|
||||
switch (entity_category) {
|
||||
@@ -126,7 +122,7 @@ bool MQTTComponent::send_discovery_() {
|
||||
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
|
||||
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
|
||||
char friendly_name_hash[9];
|
||||
sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name_()));
|
||||
sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name()));
|
||||
friendly_name_hash[8] = 0; // ensure the hash-string ends with null
|
||||
root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash;
|
||||
} else {
|
||||
@@ -188,7 +184,7 @@ bool MQTTComponent::is_discovery_enabled() const {
|
||||
}
|
||||
|
||||
std::string MQTTComponent::get_default_object_id_() const {
|
||||
return str_sanitize(str_snake_case(this->friendly_name_()));
|
||||
return str_sanitize(str_snake_case(this->friendly_name()));
|
||||
}
|
||||
|
||||
void MQTTComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) {
|
||||
@@ -272,9 +268,9 @@ void MQTTComponent::schedule_resend_state() { this->resend_state_ = true; }
|
||||
bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connected(); }
|
||||
|
||||
// Pull these properties from EntityBase if not overridden
|
||||
std::string MQTTComponent::friendly_name_() const { return this->get_entity()->get_name(); }
|
||||
StringRef MQTTComponent::get_icon_ref_() const { return this->get_entity()->get_icon_ref(); }
|
||||
bool MQTTComponent::is_disabled_by_default_() const { return this->get_entity()->is_disabled_by_default(); }
|
||||
std::string MQTTComponent::friendly_name() const { return this->get_entity()->get_name(); }
|
||||
std::string MQTTComponent::get_icon() const { return this->get_entity()->get_icon(); }
|
||||
bool MQTTComponent::is_disabled_by_default() const { return this->get_entity()->is_disabled_by_default(); }
|
||||
bool MQTTComponent::is_internal() {
|
||||
if (this->has_custom_state_topic_) {
|
||||
// If the custom state_topic is null, return true as it is internal and should not publish
|
||||
|
||||
@@ -165,13 +165,13 @@ class MQTTComponent : public Component {
|
||||
virtual const EntityBase *get_entity() const = 0;
|
||||
|
||||
/// Get the friendly name of this MQTT component.
|
||||
std::string friendly_name_() const;
|
||||
virtual std::string friendly_name() const;
|
||||
|
||||
/// Get the icon field of this component as StringRef
|
||||
StringRef get_icon_ref_() const;
|
||||
/// Get the icon field of this component
|
||||
virtual std::string get_icon() const;
|
||||
|
||||
/// Get whether the underlying Entity is disabled by default
|
||||
bool is_disabled_by_default_() const;
|
||||
virtual bool is_disabled_by_default() const;
|
||||
|
||||
/// Get the MQTT topic that new states will be shared to.
|
||||
std::string get_state_topic_() const;
|
||||
|
||||
@@ -67,12 +67,9 @@ void MQTTCoverComponent::dump_config() {
|
||||
}
|
||||
}
|
||||
void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
const auto device_class = this->cover_->get_device_class_ref();
|
||||
if (!device_class.empty()) {
|
||||
root[MQTT_DEVICE_CLASS] = device_class;
|
||||
}
|
||||
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
if (!this->cover_->get_device_class().empty())
|
||||
root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class();
|
||||
|
||||
auto traits = this->cover_->get_traits();
|
||||
if (traits.get_is_assumed_state()) {
|
||||
|
||||
@@ -21,12 +21,8 @@ void MQTTEventComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConf
|
||||
for (const auto &event_type : this->event_->get_event_types())
|
||||
event_types.add(event_type);
|
||||
|
||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
const auto device_class = this->event_->get_device_class_ref();
|
||||
if (!device_class.empty()) {
|
||||
root[MQTT_DEVICE_CLASS] = device_class;
|
||||
}
|
||||
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
if (!this->event_->get_device_class().empty())
|
||||
root[MQTT_DEVICE_CLASS] = this->event_->get_device_class();
|
||||
|
||||
config.command_topic = false;
|
||||
}
|
||||
@@ -38,8 +34,8 @@ void MQTTEventComponent::setup() {
|
||||
void MQTTEventComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MQTT Event '%s': ", this->event_->get_name().c_str());
|
||||
ESP_LOGCONFIG(TAG, "Event Types: ");
|
||||
for (const char *event_type : this->event_->get_event_types()) {
|
||||
ESP_LOGCONFIG(TAG, "- %s", event_type);
|
||||
for (const auto &event_type : this->event_->get_event_types()) {
|
||||
ESP_LOGCONFIG(TAG, "- %s", event_type.c_str());
|
||||
}
|
||||
LOG_MQTT_COMPONENT(true, true);
|
||||
}
|
||||
|
||||
@@ -24,15 +24,15 @@ void MQTTFanComponent::setup() {
|
||||
auto val = parse_on_off(payload.c_str());
|
||||
switch (val) {
|
||||
case PARSE_ON:
|
||||
ESP_LOGD(TAG, "'%s' Turning Fan ON.", this->friendly_name_().c_str());
|
||||
ESP_LOGD(TAG, "'%s' Turning Fan ON.", this->friendly_name().c_str());
|
||||
this->state_->turn_on().perform();
|
||||
break;
|
||||
case PARSE_OFF:
|
||||
ESP_LOGD(TAG, "'%s' Turning Fan OFF.", this->friendly_name_().c_str());
|
||||
ESP_LOGD(TAG, "'%s' Turning Fan OFF.", this->friendly_name().c_str());
|
||||
this->state_->turn_off().perform();
|
||||
break;
|
||||
case PARSE_TOGGLE:
|
||||
ESP_LOGD(TAG, "'%s' Toggling Fan.", this->friendly_name_().c_str());
|
||||
ESP_LOGD(TAG, "'%s' Toggling Fan.", this->friendly_name().c_str());
|
||||
this->state_->toggle().perform();
|
||||
break;
|
||||
case PARSE_NONE:
|
||||
@@ -48,11 +48,11 @@ void MQTTFanComponent::setup() {
|
||||
auto val = parse_on_off(payload.c_str(), "forward", "reverse");
|
||||
switch (val) {
|
||||
case PARSE_ON:
|
||||
ESP_LOGD(TAG, "'%s': Setting direction FORWARD", this->friendly_name_().c_str());
|
||||
ESP_LOGD(TAG, "'%s': Setting direction FORWARD", this->friendly_name().c_str());
|
||||
this->state_->make_call().set_direction(fan::FanDirection::FORWARD).perform();
|
||||
break;
|
||||
case PARSE_OFF:
|
||||
ESP_LOGD(TAG, "'%s': Setting direction REVERSE", this->friendly_name_().c_str());
|
||||
ESP_LOGD(TAG, "'%s': Setting direction REVERSE", this->friendly_name().c_str());
|
||||
this->state_->make_call().set_direction(fan::FanDirection::REVERSE).perform();
|
||||
break;
|
||||
case PARSE_TOGGLE:
|
||||
@@ -75,11 +75,11 @@ void MQTTFanComponent::setup() {
|
||||
auto val = parse_on_off(payload.c_str(), "oscillate_on", "oscillate_off");
|
||||
switch (val) {
|
||||
case PARSE_ON:
|
||||
ESP_LOGD(TAG, "'%s': Setting oscillating ON", this->friendly_name_().c_str());
|
||||
ESP_LOGD(TAG, "'%s': Setting oscillating ON", this->friendly_name().c_str());
|
||||
this->state_->make_call().set_oscillating(true).perform();
|
||||
break;
|
||||
case PARSE_OFF:
|
||||
ESP_LOGD(TAG, "'%s': Setting oscillating OFF", this->friendly_name_().c_str());
|
||||
ESP_LOGD(TAG, "'%s': Setting oscillating OFF", this->friendly_name().c_str());
|
||||
this->state_->make_call().set_oscillating(false).perform();
|
||||
break;
|
||||
case PARSE_TOGGLE:
|
||||
|
||||
@@ -24,7 +24,7 @@ void MQTTLockComponent::setup() {
|
||||
} else if (strcasecmp(payload.c_str(), "OPEN") == 0) {
|
||||
this->lock_->open();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str());
|
||||
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str());
|
||||
this->status_momentary_warning("state", 5000);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -44,11 +44,8 @@ void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon
|
||||
root[MQTT_MIN] = traits.get_min_value();
|
||||
root[MQTT_MAX] = traits.get_max_value();
|
||||
root[MQTT_STEP] = traits.get_step();
|
||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
const auto unit_of_measurement = this->number_->traits.get_unit_of_measurement_ref();
|
||||
if (!unit_of_measurement.empty()) {
|
||||
root[MQTT_UNIT_OF_MEASUREMENT] = unit_of_measurement;
|
||||
}
|
||||
if (!this->number_->traits.get_unit_of_measurement().empty())
|
||||
root[MQTT_UNIT_OF_MEASUREMENT] = this->number_->traits.get_unit_of_measurement();
|
||||
switch (this->number_->traits.get_mode()) {
|
||||
case NUMBER_MODE_AUTO:
|
||||
break;
|
||||
@@ -59,11 +56,8 @@ void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon
|
||||
root[MQTT_MODE] = "slider";
|
||||
break;
|
||||
}
|
||||
const auto device_class = this->number_->traits.get_device_class_ref();
|
||||
if (!device_class.empty()) {
|
||||
root[MQTT_DEVICE_CLASS] = device_class;
|
||||
}
|
||||
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
if (!this->number_->traits.get_device_class().empty())
|
||||
root[MQTT_DEVICE_CLASS] = this->number_->traits.get_device_class();
|
||||
|
||||
config.command_topic = true;
|
||||
}
|
||||
|
||||
@@ -44,17 +44,13 @@ void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire
|
||||
void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; }
|
||||
|
||||
void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
const auto device_class = this->sensor_->get_device_class_ref();
|
||||
if (!device_class.empty()) {
|
||||
root[MQTT_DEVICE_CLASS] = device_class;
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
if (!this->sensor_->get_device_class().empty()) {
|
||||
root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class();
|
||||
}
|
||||
|
||||
const auto unit_of_measurement = this->sensor_->get_unit_of_measurement_ref();
|
||||
if (!unit_of_measurement.empty()) {
|
||||
root[MQTT_UNIT_OF_MEASUREMENT] = unit_of_measurement;
|
||||
}
|
||||
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
if (!this->sensor_->get_unit_of_measurement().empty())
|
||||
root[MQTT_UNIT_OF_MEASUREMENT] = this->sensor_->get_unit_of_measurement();
|
||||
|
||||
if (this->get_expire_after() > 0)
|
||||
root[MQTT_EXPIRE_AFTER] = this->get_expire_after() / 1000;
|
||||
|
||||
@@ -29,7 +29,7 @@ void MQTTSwitchComponent::setup() {
|
||||
break;
|
||||
case PARSE_NONE:
|
||||
default:
|
||||
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str());
|
||||
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str());
|
||||
this->status_momentary_warning("state", 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -15,12 +15,10 @@ using namespace esphome::text_sensor;
|
||||
|
||||
MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {}
|
||||
void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
const auto device_class = this->sensor_->get_device_class_ref();
|
||||
if (!device_class.empty()) {
|
||||
root[MQTT_DEVICE_CLASS] = device_class;
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
if (!this->sensor_->get_device_class().empty()) {
|
||||
root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class();
|
||||
}
|
||||
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
config.command_topic = false;
|
||||
}
|
||||
void MQTTTextSensor::setup() {
|
||||
|
||||
@@ -20,7 +20,7 @@ void MQTTUpdateComponent::setup() {
|
||||
if (payload == "INSTALL") {
|
||||
this->update_->perform();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s': Received unknown update payload: %s", this->friendly_name_().c_str(), payload.c_str());
|
||||
ESP_LOGW(TAG, "'%s': Received unknown update payload: %s", this->friendly_name().c_str(), payload.c_str());
|
||||
this->status_momentary_warning("state", 5000);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -49,12 +49,10 @@ void MQTTValveComponent::dump_config() {
|
||||
}
|
||||
}
|
||||
void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
const auto device_class = this->valve_->get_device_class_ref();
|
||||
if (!device_class.empty()) {
|
||||
root[MQTT_DEVICE_CLASS] = device_class;
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
if (!this->valve_->get_device_class().empty()) {
|
||||
root[MQTT_DEVICE_CLASS] = this->valve_->get_device_class();
|
||||
}
|
||||
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
|
||||
auto traits = this->valve_->get_traits();
|
||||
if (traits.get_is_assumed_state()) {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#include "number.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -34,9 +32,6 @@ void Number::publish_state(float state) {
|
||||
this->state = state;
|
||||
ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state);
|
||||
this->state_callback_.call(state);
|
||||
#if defined(USE_NUMBER) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_number_update(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Number::add_on_state_callback(std::function<void(float)> &&callback) {
|
||||
|
||||
@@ -9,7 +9,7 @@ from esphome.components.esp32 import (
|
||||
from esphome.components.mdns import MDNSComponent, enable_mdns_storage
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ENABLE_IPV6, CONF_ID, CONF_USE_ADDRESS
|
||||
from esphome.core import CORE, TimePeriodMilliseconds
|
||||
from esphome.core import CORE
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
@@ -22,7 +22,6 @@ from .const import (
|
||||
CONF_NETWORK_KEY,
|
||||
CONF_NETWORK_NAME,
|
||||
CONF_PAN_ID,
|
||||
CONF_POLL_PERIOD,
|
||||
CONF_PSKC,
|
||||
CONF_SRP_ID,
|
||||
CONF_TLV,
|
||||
@@ -90,7 +89,7 @@ def set_sdkconfig_options(config):
|
||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", True)
|
||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT_MAX_SERVICES", 5)
|
||||
|
||||
# TODO: Add suport for synchronized sleepy end devices (SSED)
|
||||
# TODO: Add suport for sleepy end devices
|
||||
add_idf_sdkconfig_option(f"CONFIG_OPENTHREAD_{config.get(CONF_DEVICE_TYPE)}", True)
|
||||
|
||||
|
||||
@@ -114,17 +113,6 @@ _CONNECTION_SCHEMA = cv.Schema(
|
||||
def _validate(config: ConfigType) -> ConfigType:
|
||||
if CONF_USE_ADDRESS not in config:
|
||||
config[CONF_USE_ADDRESS] = f"{CORE.name}.local"
|
||||
device_type = config.get(CONF_DEVICE_TYPE)
|
||||
poll_period = config.get(CONF_POLL_PERIOD)
|
||||
if (
|
||||
device_type == "FTD"
|
||||
and poll_period
|
||||
and poll_period > TimePeriodMilliseconds(milliseconds=0)
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_POLL_PERIOD} can only be used with {CONF_DEVICE_TYPE}: MTD"
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -147,7 +135,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_FORCE_DATASET): cv.boolean,
|
||||
cv.Optional(CONF_TLV): cv.string_strict,
|
||||
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||
cv.Optional(CONF_POLL_PERIOD): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
).extend(_CONNECTION_SCHEMA),
|
||||
cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV),
|
||||
@@ -180,8 +167,6 @@ async def to_code(config):
|
||||
ot = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(ot.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
await cg.register_component(ot, config)
|
||||
if (poll_period := config.get(CONF_POLL_PERIOD)) is not None:
|
||||
cg.add(ot.set_poll_period(poll_period))
|
||||
|
||||
srp = cg.new_Pvariable(config[CONF_SRP_ID])
|
||||
mdns_component = await cg.get_variable(config[CONF_MDNS_ID])
|
||||
|
||||
@@ -6,7 +6,6 @@ CONF_MESH_LOCAL_PREFIX = "mesh_local_prefix"
|
||||
CONF_NETWORK_NAME = "network_name"
|
||||
CONF_NETWORK_KEY = "network_key"
|
||||
CONF_PAN_ID = "pan_id"
|
||||
CONF_POLL_PERIOD = "poll_period"
|
||||
CONF_PSKC = "pskc"
|
||||
CONF_SRP_ID = "srp_id"
|
||||
CONF_TLV = "tlv"
|
||||
|
||||
@@ -29,23 +29,6 @@ OpenThreadComponent *global_openthread_component = // NOLINT(cppcoreguidelines-
|
||||
|
||||
OpenThreadComponent::OpenThreadComponent() { global_openthread_component = this; }
|
||||
|
||||
void OpenThreadComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Open Thread:");
|
||||
#if CONFIG_OPENTHREAD_FTD
|
||||
ESP_LOGCONFIG(TAG, " Device Type: FTD");
|
||||
#elif CONFIG_OPENTHREAD_MTD
|
||||
ESP_LOGCONFIG(TAG, " Device Type: MTD");
|
||||
// TBD: Synchronized Sleepy End Device
|
||||
if (this->poll_period > 0) {
|
||||
ESP_LOGCONFIG(TAG, " Device is configured as Sleepy End Device (SED)");
|
||||
uint32_t duration = this->poll_period / 1000;
|
||||
ESP_LOGCONFIG(TAG, " Poll Period: %" PRIu32 "s", duration);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Device is configured as Minimal End Device (MED)");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool OpenThreadComponent::is_connected() {
|
||||
auto lock = InstanceLock::try_acquire(100);
|
||||
if (!lock) {
|
||||
|
||||
@@ -22,7 +22,6 @@ class OpenThreadComponent : public Component {
|
||||
public:
|
||||
OpenThreadComponent();
|
||||
~OpenThreadComponent();
|
||||
void dump_config() override;
|
||||
void setup() override;
|
||||
bool teardown() override;
|
||||
float get_setup_priority() const override { return setup_priority::WIFI; }
|
||||
@@ -36,9 +35,6 @@ class OpenThreadComponent : public Component {
|
||||
|
||||
const char *get_use_address() const;
|
||||
void set_use_address(const char *use_address);
|
||||
#if CONFIG_OPENTHREAD_MTD
|
||||
void set_poll_period(uint32_t poll_period) { this->poll_period = poll_period; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
std::optional<otIp6Address> get_omr_address_(InstanceLock &lock);
|
||||
@@ -50,9 +46,6 @@ class OpenThreadComponent : public Component {
|
||||
// Stores a pointer to a string literal (static storage duration).
|
||||
// ONLY set from Python-generated code with string literals - never dynamic strings.
|
||||
const char *use_address_{""};
|
||||
#if CONFIG_OPENTHREAD_MTD
|
||||
uint32_t poll_period{0};
|
||||
#endif
|
||||
};
|
||||
|
||||
extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -105,32 +105,6 @@ void OpenThreadComponent::ot_main() {
|
||||
esp_cli_custom_command_init();
|
||||
#endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
||||
|
||||
otLinkModeConfig link_mode_config = {0};
|
||||
#if CONFIG_OPENTHREAD_FTD
|
||||
link_mode_config.mRxOnWhenIdle = true;
|
||||
link_mode_config.mDeviceType = true;
|
||||
link_mode_config.mNetworkData = true;
|
||||
#elif CONFIG_OPENTHREAD_MTD
|
||||
if (this->poll_period > 0) {
|
||||
if (otLinkSetPollPeriod(esp_openthread_get_instance(), this->poll_period) != OT_ERROR_NONE) {
|
||||
ESP_LOGE(TAG, "Failed to set OpenThread pollperiod.");
|
||||
}
|
||||
uint32_t link_polling_period = otLinkGetPollPeriod(esp_openthread_get_instance());
|
||||
ESP_LOGD(TAG, "Link Polling Period: %d", link_polling_period);
|
||||
}
|
||||
link_mode_config.mRxOnWhenIdle = this->poll_period == 0;
|
||||
link_mode_config.mDeviceType = false;
|
||||
link_mode_config.mNetworkData = false;
|
||||
#endif
|
||||
|
||||
if (otThreadSetLinkMode(esp_openthread_get_instance(), link_mode_config) != OT_ERROR_NONE) {
|
||||
ESP_LOGE(TAG, "Failed to set OpenThread linkmode.");
|
||||
}
|
||||
link_mode_config = otThreadGetLinkMode(esp_openthread_get_instance());
|
||||
ESP_LOGD(TAG, "Link Mode Device Type: %s", link_mode_config.mDeviceType ? "true" : "false");
|
||||
ESP_LOGD(TAG, "Link Mode Network Data: %s", link_mode_config.mNetworkData ? "true" : "false");
|
||||
ESP_LOGD(TAG, "Link Mode RX On When Idle: %s", link_mode_config.mRxOnWhenIdle ? "true" : "false");
|
||||
|
||||
// Run the main loop
|
||||
#if CONFIG_OPENTHREAD_CLI
|
||||
esp_openthread_cli_create_task();
|
||||
|
||||
@@ -158,7 +158,7 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\",unit=\""));
|
||||
stream->print(obj->get_unit_of_measurement_ref().c_str());
|
||||
stream->print(obj->get_unit_of_measurement().c_str());
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str());
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
|
||||
@@ -2,7 +2,6 @@ import logging
|
||||
import textwrap
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_IGNORE_NOT_FOUND
|
||||
from esphome.components.esp32 import (
|
||||
CONF_CPU_FREQUENCY,
|
||||
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES,
|
||||
@@ -124,7 +123,6 @@ def get_config_schema(config):
|
||||
cv.Optional(CONF_ENABLE_ECC, default=False): cv.boolean,
|
||||
cv.Optional(CONF_SPEED, default=speeds[0]): cv.one_of(*speeds, upper=True),
|
||||
cv.Optional(CONF_DISABLED, default=False): cv.boolean,
|
||||
cv.Optional(CONF_IGNORE_NOT_FOUND, default=True): cv.boolean,
|
||||
}
|
||||
)(config)
|
||||
|
||||
@@ -149,9 +147,7 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM", True)
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_USE", True)
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_USE_CAPS_ALLOC", True)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_SPIRAM_IGNORE_NOTFOUND", config[CONF_IGNORE_NOT_FOUND]
|
||||
)
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_IGNORE_NOTFOUND", True)
|
||||
|
||||
add_idf_sdkconfig_option(f"CONFIG_SPIRAM_MODE_{SDK_MODES[config[CONF_MODE]]}", True)
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ from esphome.const import (
|
||||
CONF_WAND_ID,
|
||||
CONF_ZERO,
|
||||
)
|
||||
from esphome.core import ID, coroutine
|
||||
from esphome.core import coroutine
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.util import Registry, SimpleRegistry
|
||||
|
||||
@@ -2104,9 +2104,7 @@ async def abbwelcome_action(var, config, args):
|
||||
)
|
||||
cg.add(var.set_data_template(template_))
|
||||
else:
|
||||
arr_id = ID(f"{var.base}_data", is_declaration=True, type=cg.uint8)
|
||||
arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data_))
|
||||
cg.add(var.set_data_static(arr, len(data_)))
|
||||
cg.add(var.set_data_static(data_))
|
||||
|
||||
|
||||
# Mirage
|
||||
|
||||
@@ -214,13 +214,10 @@ template<typename... Ts> class ABBWelcomeAction : public RemoteTransmitterAction
|
||||
TEMPLATABLE_VALUE(uint8_t, message_type)
|
||||
TEMPLATABLE_VALUE(uint8_t, message_id)
|
||||
TEMPLATABLE_VALUE(bool, auto_message_id)
|
||||
void set_data_template(std::vector<uint8_t> (*func)(Ts...)) {
|
||||
this->data_.func = func;
|
||||
this->len_ = -1; // Sentinel value indicates template mode
|
||||
}
|
||||
void set_data_static(const uint8_t *data, size_t len) {
|
||||
this->data_.data = data;
|
||||
this->len_ = len; // Length >= 0 indicates static mode
|
||||
void set_data_static(std::vector<uint8_t> data) { data_static_ = std::move(data); }
|
||||
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||
this->data_func_ = func;
|
||||
has_data_func_ = true;
|
||||
}
|
||||
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||
ABBWelcomeData data;
|
||||
@@ -231,25 +228,19 @@ template<typename... Ts> class ABBWelcomeAction : public RemoteTransmitterAction
|
||||
data.set_message_type(this->message_type_.value(x...));
|
||||
data.set_message_id(this->message_id_.value(x...));
|
||||
data.auto_message_id = this->auto_message_id_.value(x...);
|
||||
std::vector<uint8_t> data_vec;
|
||||
if (this->len_ >= 0) {
|
||||
// Static mode: copy from flash to vector
|
||||
data_vec.assign(this->data_.data, this->data_.data + this->len_);
|
||||
if (has_data_func_) {
|
||||
data.set_data(this->data_func_(x...));
|
||||
} else {
|
||||
// Template mode: call function
|
||||
data_vec = this->data_.func(x...);
|
||||
data.set_data(this->data_static_);
|
||||
}
|
||||
data.set_data(data_vec);
|
||||
data.finalize();
|
||||
ABBWelcomeProtocol().encode(dst, data);
|
||||
}
|
||||
|
||||
protected:
|
||||
ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
|
||||
union Data {
|
||||
std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
|
||||
const uint8_t *data; // Pointer to static data in flash
|
||||
} data_;
|
||||
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
|
||||
std::vector<uint8_t> data_static_{};
|
||||
bool has_data_func_{false};
|
||||
};
|
||||
|
||||
} // namespace remote_base
|
||||
|
||||
@@ -71,7 +71,6 @@ static const uint16_t FALLBACK_FREQUENCY = 64767U; // To use with frequency = 0
|
||||
static const uint32_t MICROSECONDS_IN_SECONDS = 1000000UL;
|
||||
static const uint16_t PRONTO_DEFAULT_GAP = 45000;
|
||||
static const uint16_t MARK_EXCESS_MICROS = 20;
|
||||
static constexpr size_t PRONTO_LOG_CHUNK_SIZE = 230;
|
||||
|
||||
static uint16_t to_frequency_k_hz(uint16_t code) {
|
||||
if (code == 0)
|
||||
@@ -226,18 +225,18 @@ optional<ProntoData> ProntoProtocol::decode(RemoteReceiveData src) {
|
||||
}
|
||||
|
||||
void ProntoProtocol::dump(const ProntoData &data) {
|
||||
std::string rest;
|
||||
|
||||
rest = data.data;
|
||||
ESP_LOGI(TAG, "Received Pronto: data=");
|
||||
|
||||
const char *ptr = data.data.c_str();
|
||||
size_t remaining = data.data.size();
|
||||
|
||||
// Log in chunks, always logging at least once (even for empty string)
|
||||
do {
|
||||
size_t chunk_size = remaining < PRONTO_LOG_CHUNK_SIZE ? remaining : PRONTO_LOG_CHUNK_SIZE;
|
||||
ESP_LOGI(TAG, "%.*s", (int) chunk_size, ptr);
|
||||
ptr += chunk_size;
|
||||
remaining -= chunk_size;
|
||||
} while (remaining > 0);
|
||||
while (true) {
|
||||
ESP_LOGI(TAG, "%s", rest.substr(0, 230).c_str());
|
||||
if (rest.size() > 230) {
|
||||
rest = rest.substr(230);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace remote_base
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user