mirror of
https://github.com/esphome/esphome.git
synced 2025-11-11 04:15:52 +00:00
Compare commits
61 Commits
api_shrink
...
parition_c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f19bbbd1c5 | ||
|
|
0f136a888c | ||
|
|
6feaa8dd13 | ||
|
|
4c3931363f | ||
|
|
b47e89a7d5 | ||
|
|
c17a31a8f8 | ||
|
|
fbbdad75f6 | ||
|
|
7abb6d4998 | ||
|
|
1dabe83d04 | ||
|
|
0d735dc259 | ||
|
|
7b86e1feb0 | ||
|
|
d516627957 | ||
|
|
fb1c67490a | ||
|
|
8b9600b930 | ||
|
|
cbb98c4050 | ||
|
|
e7ff56f1cd | ||
|
|
7705a5de06 | ||
|
|
77ab096b59 | ||
|
|
26a3ec41d6 | ||
|
|
3bcbfe8d97 | ||
|
|
870b2c4f84 | ||
|
|
5f9c7a70ff | ||
|
|
f7179d4255 | ||
|
|
eb0558ca3f | ||
|
|
5585355263 | ||
|
|
e468ca4881 | ||
|
|
4c078dea2c | ||
|
|
783dbd1e6b | ||
|
|
b49619d9bf | ||
|
|
a290b88cd6 | ||
|
|
b61027607f | ||
|
|
f55c872180 | ||
|
|
9a2fc8aa51 | ||
|
|
c77bb3b269 | ||
|
|
79d1a558af | ||
|
|
a5bf55b6ac | ||
|
|
85d2565f25 | ||
|
|
4f08f0750a | ||
|
|
3c41e080c5 | ||
|
|
7c30d57391 | ||
|
|
182e106bfa | ||
|
|
d0b399d771 | ||
|
|
5d20e3a3b4 | ||
|
|
ba5fa7c10a | ||
|
|
5cdb891b58 | ||
|
|
26607713bb | ||
|
|
895d76ca03 | ||
|
|
74187845b7 | ||
|
|
822eacfd77 | ||
|
|
ab5d8f67ae | ||
|
|
83f30a64ed | ||
|
|
5eea7bdb44 | ||
|
|
bdfd88441a | ||
|
|
20b6e0d5c2 | ||
|
|
ce5e608863 | ||
|
|
aa5795c019 | ||
|
|
00c0854323 | ||
|
|
be006ecadd | ||
|
|
b08419fa47 | ||
|
|
d36ef050a9 | ||
|
|
df53ff7afe |
@@ -51,7 +51,79 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
|
|
||||||
* **Naming Conventions:**
|
* **Naming Conventions:**
|
||||||
* **Python:** Follows PEP 8. Use clear, descriptive names following snake_case.
|
* **Python:** Follows PEP 8. Use clear, descriptive names following snake_case.
|
||||||
* **C++:** Follows the Google C++ Style Guide.
|
* **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
|
||||||
|
|
||||||
* **Component Structure:**
|
* **Component Structure:**
|
||||||
* **Standard Files:**
|
* **Standard Files:**
|
||||||
@@ -368,3 +440,45 @@ 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`.
|
* **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`.
|
* **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.
|
* **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
|
||||||
|
```
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ ci:
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.14.3
|
rev: v0.14.4
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
|||||||
@@ -290,6 +290,7 @@ esphome/components/mcp23x17_base/* @jesserockz
|
|||||||
esphome/components/mcp23xxx_base/* @jesserockz
|
esphome/components/mcp23xxx_base/* @jesserockz
|
||||||
esphome/components/mcp2515/* @danielschramm @mvturnho
|
esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||||
esphome/components/mcp3204/* @rsumner
|
esphome/components/mcp3204/* @rsumner
|
||||||
|
esphome/components/mcp3221/* @philippderdiedas
|
||||||
esphome/components/mcp4461/* @p1ngb4ck
|
esphome/components/mcp4461/* @p1ngb4ck
|
||||||
esphome/components/mcp4728/* @berfenger
|
esphome/components/mcp4728/* @berfenger
|
||||||
esphome/components/mcp47a1/* @jesserockz
|
esphome/components/mcp47a1/* @jesserockz
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include <utility>
|
|
||||||
|
|
||||||
#include "alarm_control_panel.h"
|
#include "alarm_control_panel.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/controller_registry.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
@@ -34,6 +36,9 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
|
|||||||
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
|
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
|
||||||
this->current_state_ = state;
|
this->current_state_ = state;
|
||||||
this->state_callback_.call();
|
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) {
|
if (state == ACP_STATE_TRIGGERED) {
|
||||||
this->triggered_callback_.call();
|
this->triggered_callback_.call();
|
||||||
} else if (state == ACP_STATE_ARMING) {
|
} else if (state == ACP_STATE_ARMING) {
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
esp32=8, # More RAM, can buffer more
|
esp32=8, # More RAM, can buffer more
|
||||||
rp2040=5, # Limited RAM
|
rp2040=5, # Limited RAM
|
||||||
bk72xx=8, # Moderate RAM
|
bk72xx=8, # Moderate RAM
|
||||||
|
nrf52=8, # Moderate RAM
|
||||||
rtl87xx=8, # Moderate RAM
|
rtl87xx=8, # Moderate RAM
|
||||||
host=16, # Abundant resources
|
host=16, # Abundant resources
|
||||||
ln882x=8, # Moderate RAM
|
ln882x=8, # Moderate RAM
|
||||||
@@ -244,6 +245,9 @@ async def to_code(config):
|
|||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
# Track controller registration for StaticVector sizing
|
||||||
|
CORE.register_controller()
|
||||||
|
|
||||||
cg.add(var.set_port(config[CONF_PORT]))
|
cg.add(var.set_port(config[CONF_PORT]))
|
||||||
if config[CONF_PASSWORD]:
|
if config[CONF_PASSWORD]:
|
||||||
cg.add_define("USE_API_PASSWORD")
|
cg.add_define("USE_API_PASSWORD")
|
||||||
|
|||||||
@@ -2147,7 +2147,7 @@ message ListEntitiesEventResponse {
|
|||||||
EntityCategory entity_category = 7;
|
EntityCategory entity_category = 7;
|
||||||
string device_class = 8;
|
string device_class = 8;
|
||||||
|
|
||||||
repeated string event_types = 9;
|
repeated string event_types = 9 [(container_pointer_no_template) = "FixedVector<const char *>"];
|
||||||
uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"];
|
uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"];
|
||||||
}
|
}
|
||||||
message EventResponse {
|
message EventResponse {
|
||||||
|
|||||||
@@ -1310,8 +1310,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
|
|||||||
auto *event = static_cast<event::Event *>(entity);
|
auto *event = static_cast<event::Event *>(entity);
|
||||||
ListEntitiesEventResponse msg;
|
ListEntitiesEventResponse msg;
|
||||||
msg.set_device_class(event->get_device_class_ref());
|
msg.set_device_class(event->get_device_class_ref());
|
||||||
for (const auto &event_type : event->get_event_types())
|
msg.event_types = &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,
|
return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
is_single);
|
is_single);
|
||||||
}
|
}
|
||||||
@@ -1468,6 +1467,8 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
|||||||
static constexpr auto MANUFACTURER = StringRef::from_lit("Beken");
|
static constexpr auto MANUFACTURER = StringRef::from_lit("Beken");
|
||||||
#elif defined(USE_LN882X)
|
#elif defined(USE_LN882X)
|
||||||
static constexpr auto MANUFACTURER = StringRef::from_lit("Lightning");
|
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)
|
#elif defined(USE_RTL87XX)
|
||||||
static constexpr auto MANUFACTURER = StringRef::from_lit("Realtek");
|
static constexpr auto MANUFACTURER = StringRef::from_lit("Realtek");
|
||||||
#elif defined(USE_HOST)
|
#elif defined(USE_HOST)
|
||||||
|
|||||||
@@ -2877,8 +2877,8 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_bool(6, this->disabled_by_default);
|
buffer.encode_bool(6, this->disabled_by_default);
|
||||||
buffer.encode_uint32(7, static_cast<uint32_t>(this->entity_category));
|
buffer.encode_uint32(7, static_cast<uint32_t>(this->entity_category));
|
||||||
buffer.encode_string(8, this->device_class_ref_);
|
buffer.encode_string(8, this->device_class_ref_);
|
||||||
for (auto &it : this->event_types) {
|
for (const char *it : *this->event_types) {
|
||||||
buffer.encode_string(9, it, true);
|
buffer.encode_string(9, it, strlen(it), true);
|
||||||
}
|
}
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
buffer.encode_uint32(10, this->device_id);
|
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_bool(1, this->disabled_by_default);
|
||||||
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
|
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
|
||||||
size.add_length(1, this->device_class_ref_.size());
|
size.add_length(1, this->device_class_ref_.size());
|
||||||
if (!this->event_types.empty()) {
|
if (!this->event_types->empty()) {
|
||||||
for (const auto &it : this->event_types) {
|
for (const char *it : *this->event_types) {
|
||||||
size.add_length_force(1, it.size());
|
size.add_length_force(1, strlen(it));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
|
|||||||
@@ -2788,7 +2788,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage {
|
|||||||
#endif
|
#endif
|
||||||
StringRef device_class_ref_{};
|
StringRef device_class_ref_{};
|
||||||
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||||
std::vector<std::string> event_types{};
|
const FixedVector<const char *> *event_types{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(ProtoSize &size) const override;
|
void calculate_size(ProtoSize &size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#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, "disabled_by_default", this->disabled_by_default);
|
||||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||||
dump_field(out, "device_class", this->device_class_ref_);
|
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);
|
dump_field(out, "event_types", it, 4);
|
||||||
}
|
}
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/controller_registry.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/util.h"
|
#include "esphome/core/util.h"
|
||||||
@@ -34,7 +35,7 @@ APIServer::APIServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void APIServer::setup() {
|
void APIServer::setup() {
|
||||||
this->setup_controller();
|
ControllerRegistry::register_controller(this);
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
uint32_t hash = 88491486UL;
|
uint32_t hash = 88491486UL;
|
||||||
@@ -269,7 +270,7 @@ bool APIServer::check_password(const uint8_t *password_data, size_t password_len
|
|||||||
|
|
||||||
void APIServer::handle_disconnect(APIConnection *conn) {}
|
void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||||
|
|
||||||
// Macro for entities without extra parameters
|
// Macro for controller update dispatch
|
||||||
#define API_DISPATCH_UPDATE(entity_type, entity_name) \
|
#define API_DISPATCH_UPDATE(entity_type, entity_name) \
|
||||||
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
|
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||||
if (obj->is_internal()) \
|
if (obj->is_internal()) \
|
||||||
@@ -278,15 +279,6 @@ void APIServer::handle_disconnect(APIConnection *conn) {}
|
|||||||
c->send_##entity_name##_state(obj); \
|
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
|
#ifdef USE_BINARY_SENSOR
|
||||||
API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor)
|
API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor)
|
||||||
#endif
|
#endif
|
||||||
@@ -304,15 +296,15 @@ API_DISPATCH_UPDATE(light::LightState, light)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(sensor::Sensor, sensor, float state)
|
API_DISPATCH_UPDATE(sensor::Sensor, sensor)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(switch_::Switch, switch, bool state)
|
API_DISPATCH_UPDATE(switch_::Switch, switch)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(text_sensor::TextSensor, text_sensor, const std::string &state)
|
API_DISPATCH_UPDATE(text_sensor::TextSensor, text_sensor)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
@@ -320,7 +312,7 @@ API_DISPATCH_UPDATE(climate::Climate, climate)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(number::Number, number, float state)
|
API_DISPATCH_UPDATE(number::Number, number)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_DATETIME_DATE
|
#ifdef USE_DATETIME_DATE
|
||||||
@@ -336,11 +328,11 @@ API_DISPATCH_UPDATE(datetime::DateTimeEntity, datetime)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(text::Text, text, const std::string &state)
|
API_DISPATCH_UPDATE(text::Text, text)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(select::Select, select, const std::string &state, size_t index)
|
API_DISPATCH_UPDATE(select::Select, select)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_LOCK
|
#ifdef USE_LOCK
|
||||||
@@ -356,12 +348,13 @@ API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
// Event is a special case - it's the only entity that passes extra parameters to the send method
|
// Event is a special case - unlike other entities with simple state fields,
|
||||||
void APIServer::on_event(event::Event *obj, const std::string &event_type) {
|
// events store their state in a member accessed via obj->get_last_event_type()
|
||||||
|
void APIServer::on_event(event::Event *obj) {
|
||||||
if (obj->is_internal())
|
if (obj->is_internal())
|
||||||
return;
|
return;
|
||||||
for (auto &c : this->clients_)
|
for (auto &c : this->clients_)
|
||||||
c->send_event(obj, event_type);
|
c->send_event(obj, obj->get_last_event_type());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -72,19 +72,19 @@ class APIServer : public Component, public Controller {
|
|||||||
void on_light_update(light::LightState *obj) override;
|
void on_light_update(light::LightState *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
void on_sensor_update(sensor::Sensor *obj, float state) override;
|
void on_sensor_update(sensor::Sensor *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
void on_switch_update(switch_::Switch *obj, bool state) override;
|
void on_switch_update(switch_::Switch *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) override;
|
void on_text_sensor_update(text_sensor::TextSensor *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
void on_climate_update(climate::Climate *obj) override;
|
void on_climate_update(climate::Climate *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
void on_number_update(number::Number *obj, float state) override;
|
void on_number_update(number::Number *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_DATETIME_DATE
|
#ifdef USE_DATETIME_DATE
|
||||||
void on_date_update(datetime::DateEntity *obj) override;
|
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;
|
void on_datetime_update(datetime::DateTimeEntity *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
void on_text_update(text::Text *obj, const std::string &state) override;
|
void on_text_update(text::Text *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
void on_select_update(select::Select *obj, const std::string &state, size_t index) override;
|
void on_select_update(select::Select *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_LOCK
|
#ifdef USE_LOCK
|
||||||
void on_lock_update(lock::Lock *obj) override;
|
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;
|
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
void on_event(event::Event *obj, const std::string &event_type) override;
|
void on_event(event::Event *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_UPDATE
|
#ifdef USE_UPDATE
|
||||||
void on_update(update::UpdateEntity *obj) override;
|
void on_update(update::UpdateEntity *obj) override;
|
||||||
|
|||||||
@@ -9,11 +9,11 @@
|
|||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
|
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceDynamic<Ts...> {
|
||||||
public:
|
public:
|
||||||
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
|
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
|
||||||
void (T::*callback)(Ts...))
|
void (T::*callback)(Ts...))
|
||||||
: UserServiceBase<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
|
: UserServiceDynamic<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT
|
void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ template<typename T> T get_execute_arg_value(const ExecuteServiceArgument &arg);
|
|||||||
|
|
||||||
template<typename T> enums::ServiceArgType to_service_arg_type();
|
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 {
|
template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
||||||
public:
|
public:
|
||||||
UserServiceBase(std::string name, const std::array<std::string, sizeof...(Ts)> &arg_names)
|
UserServiceBase(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
|
||||||
: name_(std::move(name)), arg_names_(arg_names) {
|
: name_(name), arg_names_(arg_names) {
|
||||||
this->key_ = fnv1_hash(this->name_);
|
this->key_ = fnv1_hash(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
ListEntitiesServicesResponse encode_list_service_response() override {
|
ListEntitiesServicesResponse encode_list_service_response() override {
|
||||||
@@ -47,7 +49,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
|||||||
bool execute_service(const ExecuteServiceRequest &req) override {
|
bool execute_service(const ExecuteServiceRequest &req) override {
|
||||||
if (req.key != this->key_)
|
if (req.key != this->key_)
|
||||||
return false;
|
return false;
|
||||||
if (req.args.size() != this->arg_names_.size())
|
if (req.args.size() != sizeof...(Ts))
|
||||||
return false;
|
return false;
|
||||||
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
|
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
|
||||||
return true;
|
return true;
|
||||||
@@ -59,14 +61,60 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
|||||||
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string name_;
|
// Pointers to string literals in flash - no heap allocation
|
||||||
|
const char *name_;
|
||||||
|
std::array<const char *, sizeof...(Ts)> arg_names_;
|
||||||
uint32_t key_{0};
|
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)
|
||||||
|
: name_(std::move(name)), arg_names_(arg_names) {
|
||||||
|
this->key_ = fnv1_hash(this->name_.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
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]))...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heap-allocated strings for runtime-generated names
|
||||||
|
std::string name_;
|
||||||
std::array<std::string, sizeof...(Ts)> arg_names_;
|
std::array<std::string, sizeof...(Ts)> arg_names_;
|
||||||
|
uint32_t key_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
|
template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
|
||||||
public:
|
public:
|
||||||
UserServiceTrigger(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
|
// Constructor for static names (YAML-defined services - used by code generator)
|
||||||
|
UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
|
||||||
: UserServiceBase<Ts...>(name, arg_names) {}
|
: UserServiceBase<Ts...>(name, arg_names) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include "binary_sensor.h"
|
#include "binary_sensor.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/controller_registry.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -37,6 +39,9 @@ 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
|
// Note that set_state_ de-dups and will only trigger callbacks if the state has actually changed
|
||||||
if (this->set_state_(new_state)) {
|
if (this->set_state_(new_state)) {
|
||||||
ESP_LOGD(TAG, "'%s': New state is %s", this->get_name().c_str(), ONOFF(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() {
|
void CalibrationNumber::setup() {
|
||||||
float value = 0.0f;
|
float value = 0.0f;
|
||||||
if (this->restore_value_) {
|
if (this->restore_value_) {
|
||||||
this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
|
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash());
|
||||||
if (!this->pref_.load(&value)) {
|
if (!this->pref_.load(&value)) {
|
||||||
value = 0.0f;
|
value = 0.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from esphome.const import (
|
|||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_VALUE,
|
CONF_VALUE,
|
||||||
)
|
)
|
||||||
|
from esphome.core import ID
|
||||||
|
|
||||||
AUTO_LOAD = ["esp32_ble_client"]
|
AUTO_LOAD = ["esp32_ble_client"]
|
||||||
CODEOWNERS = ["@buxtronix", "@clydebarrow"]
|
CODEOWNERS = ["@buxtronix", "@clydebarrow"]
|
||||||
@@ -198,7 +199,12 @@ async def ble_write_to_code(config, action_id, template_arg, args):
|
|||||||
templ = await cg.templatable(value, args, cg.std_vector.template(cg.uint8))
|
templ = await cg.templatable(value, args, cg.std_vector.template(cg.uint8))
|
||||||
cg.add(var.set_value_template(templ))
|
cg.add(var.set_value_template(templ))
|
||||||
else:
|
else:
|
||||||
cg.add(var.set_value_simple(value))
|
# 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)))
|
||||||
|
|
||||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||||
cg.add(
|
cg.add(
|
||||||
|
|||||||
@@ -96,11 +96,8 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
|||||||
BLEClientWriteAction(BLEClient *ble_client) {
|
BLEClientWriteAction(BLEClient *ble_client) {
|
||||||
ble_client->register_ble_node(this);
|
ble_client->register_ble_node(this);
|
||||||
ble_client_ = ble_client;
|
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_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_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); }
|
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||||
@@ -110,17 +107,14 @@ 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_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||||
|
|
||||||
void set_value_template(std::vector<uint8_t> (*func)(Ts...)) {
|
void set_value_template(std::vector<uint8_t> (*func)(Ts...)) {
|
||||||
this->destroy_simple_value_();
|
this->value_.func = func;
|
||||||
this->value_.template_func = func;
|
this->len_ = -1; // Sentinel value indicates template mode
|
||||||
this->has_simple_value_ = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_value_simple(const std::vector<uint8_t> &value) {
|
// Store pointer to static data in flash (no RAM copy)
|
||||||
if (!this->has_simple_value_) {
|
void set_value_simple(const uint8_t *data, size_t len) {
|
||||||
this->construct_simple_value_();
|
this->value_.data = data;
|
||||||
}
|
this->len_ = len; // Length >= 0 indicates static mode
|
||||||
this->value_.simple = value;
|
|
||||||
this->has_simple_value_ = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void play(const Ts &...x) override {}
|
void play(const Ts &...x) override {}
|
||||||
@@ -128,7 +122,14 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
|||||||
void play_complex(const Ts &...x) override {
|
void play_complex(const Ts &...x) override {
|
||||||
this->num_running_++;
|
this->num_running_++;
|
||||||
this->var_ = std::make_tuple(x...);
|
this->var_ = std::make_tuple(x...);
|
||||||
auto value = this->has_simple_value_ ? this->value_.simple : this->value_.template_func(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...);
|
||||||
|
}
|
||||||
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
|
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
|
||||||
if (!write(value))
|
if (!write(value))
|
||||||
this->play_next_(x...);
|
this->play_next_(x...);
|
||||||
@@ -201,21 +202,11 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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_;
|
BLEClient *ble_client_;
|
||||||
bool has_simple_value_ = true;
|
ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
|
||||||
union Value {
|
union Value {
|
||||||
std::vector<uint8_t> simple;
|
std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
|
||||||
std::vector<uint8_t> (*template_func)(Ts...);
|
const uint8_t *data; // Pointer to static data in flash
|
||||||
Value() {} // trivial constructor
|
|
||||||
~Value() {} // trivial destructor - we manage lifetime via discriminator
|
|
||||||
} value_;
|
} value_;
|
||||||
espbt::ESPBTUUID service_uuid_;
|
espbt::ESPBTUUID service_uuid_;
|
||||||
espbt::ESPBTUUID char_uuid_;
|
espbt::ESPBTUUID char_uuid_;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from esphome import automation
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_DATA, CONF_ID, CONF_TRIGGER_ID
|
from esphome.const import CONF_DATA, CONF_ID, CONF_TRIGGER_ID
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE, ID
|
||||||
|
|
||||||
CODEOWNERS = ["@mvturnho", "@danielschramm"]
|
CODEOWNERS = ["@mvturnho", "@danielschramm"]
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
@@ -176,5 +176,8 @@ async def canbus_action_to_code(config, action_id, template_arg, args):
|
|||||||
else:
|
else:
|
||||||
if isinstance(data, bytes):
|
if isinstance(data, bytes):
|
||||||
data = [int(x) for x in data]
|
data = [int(x) for x in data]
|
||||||
cg.add(var.set_data_static(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)))
|
||||||
return var
|
return var
|
||||||
|
|||||||
@@ -112,13 +112,16 @@ class Canbus : public Component {
|
|||||||
|
|
||||||
template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public Parented<Canbus> {
|
template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public Parented<Canbus> {
|
||||||
public:
|
public:
|
||||||
void set_data_template(const std::function<std::vector<uint8_t>(Ts...)> func) {
|
void set_data_template(std::vector<uint8_t> (*func)(Ts...)) {
|
||||||
this->data_func_ = func;
|
// Stateless lambdas (generated by ESPHome) implicitly convert to function pointers
|
||||||
this->static_ = false;
|
this->data_.func = func;
|
||||||
|
this->len_ = -1; // Sentinel value indicates template mode
|
||||||
}
|
}
|
||||||
void set_data_static(const std::vector<uint8_t> &data) {
|
|
||||||
this->data_static_ = data;
|
// Store pointer to static data in flash (no RAM copy)
|
||||||
this->static_ = true;
|
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_can_id(uint32_t can_id) { this->can_id_ = can_id; }
|
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
|
||||||
@@ -133,21 +136,26 @@ 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 can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
|
||||||
auto use_extended_id =
|
auto use_extended_id =
|
||||||
this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_;
|
this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_;
|
||||||
if (this->static_) {
|
std::vector<uint8_t> data;
|
||||||
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, this->data_static_);
|
if (this->len_ >= 0) {
|
||||||
|
// Static mode: copy from flash to vector
|
||||||
|
data.assign(this->data_.data, this->data_.data + this->len_);
|
||||||
} else {
|
} else {
|
||||||
auto val = this->data_func_(x...);
|
// Template mode: call function
|
||||||
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, val);
|
data = this->data_.func(x...);
|
||||||
}
|
}
|
||||||
|
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
optional<uint32_t> can_id_{};
|
optional<uint32_t> can_id_{};
|
||||||
optional<bool> use_extended_id_{};
|
optional<bool> use_extended_id_{};
|
||||||
bool remote_transmission_request_{false};
|
bool remote_transmission_request_{false};
|
||||||
bool static_{false};
|
ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
|
||||||
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
|
union Data {
|
||||||
std::vector<uint8_t> data_static_{};
|
std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
|
||||||
|
const uint8_t *data; // Pointer to static data in flash
|
||||||
|
} data_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CanbusTrigger : public Trigger<std::vector<uint8_t>, uint32_t, bool>, public Component {
|
class CanbusTrigger : public Trigger<std::vector<uint8_t>, uint32_t, bool>, public Component {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include "climate.h"
|
#include "climate.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/controller_registry.h"
|
||||||
#include "esphome/core/macros.h"
|
#include "esphome/core/macros.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -463,6 +465,9 @@ void Climate::publish_state() {
|
|||||||
|
|
||||||
// Send state to frontend
|
// Send state to frontend
|
||||||
this->state_callback_.call(*this);
|
this->state_callback_.call(*this);
|
||||||
|
#if defined(USE_CLIMATE) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
|
ControllerRegistry::notify_climate_update(this);
|
||||||
|
#endif
|
||||||
// Save state
|
// Save state
|
||||||
this->save_state_();
|
this->save_state_();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ BYTE_ORDER_BIG = "big_endian"
|
|||||||
CONF_COLOR_DEPTH = "color_depth"
|
CONF_COLOR_DEPTH = "color_depth"
|
||||||
CONF_DRAW_ROUNDING = "draw_rounding"
|
CONF_DRAW_ROUNDING = "draw_rounding"
|
||||||
CONF_ENABLED = "enabled"
|
CONF_ENABLED = "enabled"
|
||||||
|
CONF_IGNORE_NOT_FOUND = "ignore_not_found"
|
||||||
CONF_ON_RECEIVE = "on_receive"
|
CONF_ON_RECEIVE = "on_receive"
|
||||||
CONF_ON_STATE_CHANGE = "on_state_change"
|
CONF_ON_STATE_CHANGE = "on_state_change"
|
||||||
CONF_REQUEST_HEADERS = "request_headers"
|
CONF_REQUEST_HEADERS = "request_headers"
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
#include "cover.h"
|
#include "cover.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/controller_registry.h"
|
||||||
|
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -169,6 +173,9 @@ void Cover::publish_state(bool save) {
|
|||||||
ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation));
|
ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation));
|
||||||
|
|
||||||
this->state_callback_.call();
|
this->state_callback_.call();
|
||||||
|
#if defined(USE_COVER) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
|
ControllerRegistry::notify_cover_update(this);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (save) {
|
if (save) {
|
||||||
CoverRestoreState restore{};
|
CoverRestoreState restore{};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "date_entity.h"
|
#include "date_entity.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/controller_registry.h"
|
||||||
#ifdef USE_DATETIME_DATE
|
#ifdef USE_DATETIME_DATE
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
@@ -32,6 +33,9 @@ void DateEntity::publish_state() {
|
|||||||
this->set_has_state(true);
|
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_);
|
ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
|
||||||
this->state_callback_.call();
|
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); }
|
DateCall DateEntity::make_call() { return DateCall(this); }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "datetime_entity.h"
|
#include "datetime_entity.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/controller_registry.h"
|
||||||
#ifdef USE_DATETIME_DATETIME
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
@@ -48,6 +49,9 @@ void DateTimeEntity::publish_state() {
|
|||||||
ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_,
|
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->month_, this->day_, this->hour_, this->minute_, this->second_);
|
||||||
this->state_callback_.call();
|
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); }
|
DateTimeCall DateTimeEntity::make_call() { return DateTimeCall(this); }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "time_entity.h"
|
#include "time_entity.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/controller_registry.h"
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
@@ -29,6 +30,9 @@ void TimeEntity::publish_state() {
|
|||||||
ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_,
|
ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_,
|
||||||
this->second_);
|
this->second_);
|
||||||
this->state_callback_.call();
|
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); }
|
TimeCall TimeEntity::make_call() { return TimeCall(this); }
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ async def to_code(config):
|
|||||||
zephyr_add_prj_conf("SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL", True)
|
zephyr_add_prj_conf("SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL", True)
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
cg.add_define("USE_DEBUG")
|
||||||
|
|
||||||
|
|
||||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace demo {
|
|||||||
|
|
||||||
class DemoSelect : public select::Select, public Component {
|
class DemoSelect : public select::Select, public Component {
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override { this->publish_state(value); }
|
void control(size_t index) override { this->publish_state(index); }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace demo
|
} // namespace demo
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace es8388 {
|
namespace es8388 {
|
||||||
|
|
||||||
void ADCInputMicSelect::control(const std::string &value) {
|
void ADCInputMicSelect::control(size_t index) {
|
||||||
this->publish_state(value);
|
this->publish_state(index);
|
||||||
this->parent_->set_adc_input_mic(static_cast<AdcInputMicLine>(this->index_of(value).value()));
|
this->parent_->set_adc_input_mic(static_cast<AdcInputMicLine>(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace es8388
|
} // namespace es8388
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace es8388 {
|
|||||||
|
|
||||||
class ADCInputMicSelect : public select::Select, public Parented<ES8388> {
|
class ADCInputMicSelect : public select::Select, public Parented<ES8388> {
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override;
|
void control(size_t index) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace es8388
|
} // namespace es8388
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace es8388 {
|
namespace es8388 {
|
||||||
|
|
||||||
void DacOutputSelect::control(const std::string &value) {
|
void DacOutputSelect::control(size_t index) {
|
||||||
this->publish_state(value);
|
this->publish_state(index);
|
||||||
this->parent_->set_dac_output(static_cast<DacOutputLine>(this->index_of(value).value()));
|
this->parent_->set_dac_output(static_cast<DacOutputLine>(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace es8388
|
} // namespace es8388
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace es8388 {
|
|||||||
|
|
||||||
class DacOutputSelect : public select::Select, public Parented<ES8388> {
|
class DacOutputSelect : public select::Select, public Parented<ES8388> {
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override;
|
void control(size_t index) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace es8388
|
} // namespace es8388
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "event.h"
|
#include "event.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/controller_registry.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -8,11 +9,11 @@ namespace event {
|
|||||||
static const char *const TAG = "event";
|
static const char *const TAG = "event";
|
||||||
|
|
||||||
void Event::trigger(const std::string &event_type) {
|
void Event::trigger(const std::string &event_type) {
|
||||||
// Linear search - faster than std::set for small datasets (1-5 items typical)
|
// Linear search with strcmp - faster than std::set for small datasets (1-5 items typical)
|
||||||
const std::string *found = nullptr;
|
const char *found = nullptr;
|
||||||
for (const auto &type : this->types_) {
|
for (const char *type : this->types_) {
|
||||||
if (type == event_type) {
|
if (strcmp(type, event_type.c_str()) == 0) {
|
||||||
found = &type;
|
found = type;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,9 +21,28 @@ 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());
|
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
last_event_type = found;
|
this->last_event_type_ = found;
|
||||||
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), last_event_type->c_str());
|
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), this->last_event_type_);
|
||||||
this->event_callback_.call(event_type);
|
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) {
|
void Event::add_on_event_callback(std::function<void(const std::string &event_type)> &&callback) {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/entity_base.h"
|
#include "esphome/core/entity_base.h"
|
||||||
@@ -22,16 +24,39 @@ namespace event {
|
|||||||
|
|
||||||
class Event : public EntityBase, public EntityBase_DeviceClass {
|
class Event : public EntityBase, public EntityBase_DeviceClass {
|
||||||
public:
|
public:
|
||||||
const std::string *last_event_type;
|
|
||||||
|
|
||||||
void trigger(const std::string &event_type);
|
void trigger(const std::string &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_; }
|
/// 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 add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
|
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
CallbackManager<void(const std::string &event_type)> event_callback_;
|
CallbackManager<void(const std::string &event_type)> event_callback_;
|
||||||
FixedVector<std::string> types_;
|
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};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace event
|
} // namespace event
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include "fan.h"
|
#include "fan.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/controller_registry.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -181,6 +183,9 @@ void Fan::publish_state() {
|
|||||||
ESP_LOGD(TAG, " Preset Mode: %s", preset);
|
ESP_LOGD(TAG, " Preset Mode: %s", preset);
|
||||||
}
|
}
|
||||||
this->state_callback_.call();
|
this->state_callback_.call();
|
||||||
|
#if defined(USE_FAN) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
|
ControllerRegistry::notify_fan_update(this);
|
||||||
|
#endif
|
||||||
this->save_state_();
|
this->save_state_();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ void GDK101Component::dump_config() {
|
|||||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||||
}
|
}
|
||||||
#ifdef USE_SENSOR
|
#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 1 minute", this->rad_1m_sensor_);
|
||||||
LOG_SENSOR(" ", "Average Radaition Dose per 10 minutes", this->rad_10m_sensor_);
|
LOG_SENSOR(" ", "Average Radaition Dose per 10 minutes", this->rad_10m_sensor_);
|
||||||
LOG_SENSOR(" ", "Status", this->status_sensor_);
|
LOG_SENSOR(" ", "Status", this->status_sensor_);
|
||||||
@@ -72,6 +71,10 @@ void GDK101Component::dump_config() {
|
|||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
LOG_BINARY_SENSOR(" ", "Vibration Status", this->vibration_binary_sensor_);
|
LOG_BINARY_SENSOR(" ", "Vibration Status", this->vibration_binary_sensor_);
|
||||||
#endif // USE_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; }
|
float GDK101Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
@@ -153,18 +156,18 @@ bool GDK101Component::read_status_(uint8_t *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool GDK101Component::read_fw_version_(uint8_t *data) {
|
bool GDK101Component::read_fw_version_(uint8_t *data) {
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
if (this->fw_version_sensor_ != nullptr) {
|
if (this->fw_version_text_sensor_ != nullptr) {
|
||||||
if (!this->read_bytes(GDK101_REG_READ_FIRMWARE, data, 2)) {
|
if (!this->read_bytes(GDK101_REG_READ_FIRMWARE, data, 2)) {
|
||||||
ESP_LOGE(TAG, "Updating GDK101 failed!");
|
ESP_LOGE(TAG, "Updating GDK101 failed!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float fw_version = data[0] + (data[1] / 10.0f);
|
const std::string fw_version_str = str_sprintf("%d.%d", data[0], data[1]);
|
||||||
|
|
||||||
this->fw_version_sensor_->publish_state(fw_version);
|
this->fw_version_text_sensor_->publish_state(fw_version_str);
|
||||||
}
|
}
|
||||||
#endif // USE_SENSOR
|
#endif // USE_TEXT_SENSOR
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
#endif // USE_BINARY_SENSOR
|
#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"
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -25,12 +28,14 @@ class GDK101Component : public PollingComponent, public i2c::I2CDevice {
|
|||||||
SUB_SENSOR(rad_1m)
|
SUB_SENSOR(rad_1m)
|
||||||
SUB_SENSOR(rad_10m)
|
SUB_SENSOR(rad_10m)
|
||||||
SUB_SENSOR(status)
|
SUB_SENSOR(status)
|
||||||
SUB_SENSOR(fw_version)
|
|
||||||
SUB_SENSOR(measurement_duration)
|
SUB_SENSOR(measurement_duration)
|
||||||
#endif // USE_SENSOR
|
#endif // USE_SENSOR
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
SUB_BINARY_SENSOR(vibration)
|
SUB_BINARY_SENSOR(vibration)
|
||||||
#endif // USE_BINARY_SENSOR
|
#endif // USE_BINARY_SENSOR
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
SUB_TEXT_SENSOR(fw_version)
|
||||||
|
#endif // USE_TEXT_SENSOR
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
|||||||
@@ -40,9 +40,8 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
device_class=DEVICE_CLASS_EMPTY,
|
device_class=DEVICE_CLASS_EMPTY,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_VERSION): sensor.sensor_schema(
|
cv.Optional(CONF_VERSION): cv.invalid(
|
||||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
"The 'version' option has been moved to the `text_sensor` component."
|
||||||
accuracy_decimals=1,
|
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_STATUS): sensor.sensor_schema(
|
cv.Optional(CONF_STATUS): sensor.sensor_schema(
|
||||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
@@ -71,10 +70,6 @@ async def to_code(config):
|
|||||||
sens = await sensor.new_sensor(radiation_dose_per_10m)
|
sens = await sensor.new_sensor(radiation_dose_per_10m)
|
||||||
cg.add(hub.set_rad_10m_sensor(sens))
|
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):
|
if status_config := config.get(CONF_STATUS):
|
||||||
sens = await sensor.new_sensor(status_config)
|
sens = await sensor.new_sensor(status_config)
|
||||||
cg.add(hub.set_status_sensor(sens))
|
cg.add(hub.set_status_sensor(sens))
|
||||||
|
|||||||
23
esphome/components/gdk101/text_sensor.py
Normal file
23
esphome/components/gdk101/text_sensor.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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 =
|
std::string valstr =
|
||||||
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
|
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
|
||||||
if (this->units_) {
|
if (this->units_) {
|
||||||
valstr += trace->sensor_->get_unit_of_measurement();
|
valstr += trace->sensor_->get_unit_of_measurement_ref();
|
||||||
}
|
}
|
||||||
this->font_value_->measure(valstr.c_str(), &fw, &fos, &fbl, &fh);
|
this->font_value_->measure(valstr.c_str(), &fw, &fos, &fbl, &fh);
|
||||||
if (fw > valw)
|
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 =
|
std::string valstr =
|
||||||
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
|
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
|
||||||
if (legend_->units_) {
|
if (legend_->units_) {
|
||||||
valstr += trace->sensor_->get_unit_of_measurement();
|
valstr += trace->sensor_->get_unit_of_measurement_ref();
|
||||||
}
|
}
|
||||||
buff->printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr.c_str());
|
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());
|
ESP_LOGV(TAG, " value: %s", valstr.c_str());
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <climits>
|
||||||
#include "abstract_aqi_calculator.h"
|
#include "abstract_aqi_calculator.h"
|
||||||
// https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf
|
// https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace hm3301 {
|
namespace hm3301 {
|
||||||
@@ -16,16 +16,15 @@ class AQICalculator : public AbstractAQICalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static const int AMOUNT_OF_LEVELS = 7;
|
static const int AMOUNT_OF_LEVELS = 6;
|
||||||
|
|
||||||
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200},
|
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
|
||||||
{201, 300}, {301, 400}, {401, 500}};
|
|
||||||
|
|
||||||
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 35}, {36, 55}, {56, 150},
|
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55},
|
||||||
{151, 250}, {251, 350}, {351, 500}};
|
{56, 125}, {126, 225}, {226, INT_MAX}};
|
||||||
|
|
||||||
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254}, {255, 354},
|
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
|
||||||
{355, 424}, {425, 504}, {505, 604}};
|
{255, 354}, {355, 424}, {425, INT_MAX}};
|
||||||
|
|
||||||
int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
||||||
int grid_index = get_grid_index_(value, array);
|
int grid_index = get_grid_index_(value, array);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#include "esphome/core/log.h"
|
|
||||||
|
|
||||||
#include "light_output.h"
|
|
||||||
#include "light_state.h"
|
#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 "transformers.h"
|
#include "transformers.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -137,7 +138,12 @@ void LightState::loop() {
|
|||||||
|
|
||||||
float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
|
float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
|
||||||
|
|
||||||
void LightState::publish_state() { this->remote_values_callback_.call(); }
|
void LightState::publish_state() {
|
||||||
|
this->remote_values_callback_.call();
|
||||||
|
#if defined(USE_LIGHT) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
|
ControllerRegistry::notify_light_update(this);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
LightOutput *LightState::get_output() const { return this->output_; }
|
LightOutput *LightState::get_output() const { return this->output_; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include "lock.h"
|
#include "lock.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/controller_registry.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -53,6 +55,9 @@ void Lock::publish_state(LockState state) {
|
|||||||
this->rtc_.save(&this->state);
|
this->rtc_.save(&this->state);
|
||||||
ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), lock_state_to_string(state));
|
ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), lock_state_to_string(state));
|
||||||
this->state_callback_.call();
|
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)); }
|
void Lock::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import re
|
|||||||
from esphome import config_validation as cv
|
from esphome import config_validation as cv
|
||||||
from esphome.const import CONF_ARGS, CONF_FORMAT
|
from esphome.const import CONF_ARGS, CONF_FORMAT
|
||||||
|
|
||||||
|
CONF_IF_NAN = "if_nan"
|
||||||
|
|
||||||
lv_uses = {
|
lv_uses = {
|
||||||
"USER_DATA",
|
"USER_DATA",
|
||||||
"LOG",
|
"LOG",
|
||||||
@@ -21,23 +23,48 @@ lv_fonts_used = set()
|
|||||||
esphome_fonts_used = set()
|
esphome_fonts_used = set()
|
||||||
lvgl_components_required = set()
|
lvgl_components_required = set()
|
||||||
|
|
||||||
|
# noqa
|
||||||
def validate_printf(value):
|
f_regex = re.compile(
|
||||||
cfmt = r"""
|
r"""
|
||||||
( # start of capture group 1
|
( # start of capture group 1
|
||||||
% # literal "%"
|
% # literal "%"
|
||||||
(?:[-+0 #]{0,5}) # optional flags
|
[-+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
|
||||||
(?:\d+|\*)? # width
|
(?:\d+|\*)? # width
|
||||||
(?:\.(?:\d+|\*))? # precision
|
(?:\.(?:\d+|\*))? # precision
|
||||||
(?:h|l|ll|w|I|I32|I64)? # size
|
(?:h|l|ll|w|I|I32|I64)? # size
|
||||||
[cCdiouxXeEfgGaAnpsSZ] # type
|
[cCdiouxXeEfgGaAnpsSZ] # type
|
||||||
)
|
)
|
||||||
""" # noqa
|
""",
|
||||||
matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.VERBOSE)
|
flags=re.VERBOSE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_printf(value):
|
||||||
|
format_string = value[CONF_FORMAT]
|
||||||
|
matches = c_regex.findall(format_string)
|
||||||
if len(matches) != len(value[CONF_ARGS]):
|
if len(matches) != len(value[CONF_ARGS]):
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"Found {len(matches)} printf-patterns ({', '.join(matches)}), but {len(value[CONF_ARGS])} args were given!"
|
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
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,13 @@ from .defines import (
|
|||||||
call_lambda,
|
call_lambda,
|
||||||
literal,
|
literal,
|
||||||
)
|
)
|
||||||
from .helpers import add_lv_use, esphome_fonts_used, lv_fonts_used, requires_component
|
from .helpers import (
|
||||||
|
CONF_IF_NAN,
|
||||||
|
add_lv_use,
|
||||||
|
esphome_fonts_used,
|
||||||
|
lv_fonts_used,
|
||||||
|
requires_component,
|
||||||
|
)
|
||||||
from .types import lv_font_t, lv_gradient_t
|
from .types import lv_font_t, lv_gradient_t
|
||||||
|
|
||||||
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
|
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
|
||||||
@@ -412,7 +418,13 @@ class TextValidator(LValidator):
|
|||||||
str_args = [str(x) for x in value[CONF_ARGS]]
|
str_args = [str(x) for x in value[CONF_ARGS]]
|
||||||
arg_expr = cg.RawExpression(",".join(str_args))
|
arg_expr = cg.RawExpression(",".join(str_args))
|
||||||
format_str = cpp_string_escape(format_str)
|
format_str = cpp_string_escape(format_str)
|
||||||
return literal(f"str_sprintf({format_str}, {arg_expr}).c_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)
|
||||||
if time_format := value.get(CONF_TIME_FORMAT):
|
if time_format := value.get(CONF_TIME_FORMAT):
|
||||||
source = value[CONF_TIME]
|
source = value[CONF_TIME]
|
||||||
if isinstance(source, Lambda):
|
if isinstance(source, Lambda):
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from esphome.core.config import StartupTrigger
|
|||||||
|
|
||||||
from . import defines as df, lv_validation as lvalid
|
from . import defines as df, lv_validation as lvalid
|
||||||
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR
|
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR
|
||||||
from .helpers import requires_component, validate_printf
|
from .helpers import CONF_IF_NAN, requires_component, validate_printf
|
||||||
from .layout import (
|
from .layout import (
|
||||||
FLEX_OBJ_SCHEMA,
|
FLEX_OBJ_SCHEMA,
|
||||||
GRID_CELL_SCHEMA,
|
GRID_CELL_SCHEMA,
|
||||||
@@ -54,6 +54,7 @@ PRINTF_TEXT_SCHEMA = cv.All(
|
|||||||
{
|
{
|
||||||
cv.Required(CONF_FORMAT): cv.string,
|
cv.Required(CONF_FORMAT): cv.string,
|
||||||
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
|
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
|
||||||
|
cv.Optional(CONF_IF_NAN): cv.string,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
validate_printf,
|
validate_printf,
|
||||||
|
|||||||
@@ -41,16 +41,16 @@ class LVGLSelect : public select::Select, public Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void publish() {
|
void publish() {
|
||||||
this->publish_state(this->widget_->get_selected_text());
|
auto index = this->widget_->get_selected_index();
|
||||||
|
this->publish_state(index);
|
||||||
if (this->restore_) {
|
if (this->restore_) {
|
||||||
auto index = this->widget_->get_selected_index();
|
|
||||||
this->pref_.save(&index);
|
this->pref_.save(&index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override {
|
void control(size_t index) override {
|
||||||
this->widget_->set_selected_text(value, this->anim_);
|
this->widget_->set_selected_index(index, this->anim_);
|
||||||
this->publish();
|
this->publish();
|
||||||
}
|
}
|
||||||
void set_options_() {
|
void set_options_() {
|
||||||
|
|||||||
1
esphome/components/mcp3221/__init__.py
Normal file
1
esphome/components/mcp3221/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CODEOWNERS = ["@philippderdiedas"]
|
||||||
31
esphome/components/mcp3221/mcp3221_sensor.cpp
Normal file
31
esphome/components/mcp3221/mcp3221_sensor.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#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
|
||||||
28
esphome/components/mcp3221/mcp3221_sensor.h
Normal file
28
esphome/components/mcp3221/mcp3221_sensor.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#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
|
||||||
49
esphome/components/mcp3221/sensor.py
Normal file
49
esphome/components/mcp3221/sensor.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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,8 +37,6 @@ MDNS_STATIC_CONST_CHAR(SERVICE_TCP, "_tcp");
|
|||||||
MDNS_STATIC_CONST_CHAR(VALUE_VERSION, ESPHOME_VERSION);
|
MDNS_STATIC_CONST_CHAR(VALUE_VERSION, ESPHOME_VERSION);
|
||||||
|
|
||||||
void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services) {
|
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
|
// 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.
|
// in mdns/__init__.py. If you add a new service here, update both locations.
|
||||||
|
|
||||||
@@ -179,7 +177,7 @@ void MDNSComponent::dump_config() {
|
|||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
"mDNS:\n"
|
"mDNS:\n"
|
||||||
" Hostname: %s",
|
" Hostname: %s",
|
||||||
this->hostname_.c_str());
|
App.get_name().c_str());
|
||||||
#ifdef USE_MDNS_STORE_SERVICES
|
#ifdef USE_MDNS_STORE_SERVICES
|
||||||
ESP_LOGV(TAG, " Services:");
|
ESP_LOGV(TAG, " Services:");
|
||||||
for (const auto &service : this->services_) {
|
for (const auto &service : this->services_) {
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ class MDNSComponent : public Component {
|
|||||||
#ifdef USE_MDNS_STORE_SERVICES
|
#ifdef USE_MDNS_STORE_SERVICES
|
||||||
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services_{};
|
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services_{};
|
||||||
#endif
|
#endif
|
||||||
std::string hostname_;
|
|
||||||
void compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services);
|
void compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#if defined(USE_ESP32) && defined(USE_MDNS)
|
#if defined(USE_ESP32) && defined(USE_MDNS)
|
||||||
|
|
||||||
#include <mdns.h>
|
#include <mdns.h>
|
||||||
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "mdns_component.h"
|
#include "mdns_component.h"
|
||||||
@@ -27,8 +28,9 @@ void MDNSComponent::setup() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mdns_hostname_set(this->hostname_.c_str());
|
const char *hostname = App.get_name().c_str();
|
||||||
mdns_instance_name_set(this->hostname_.c_str());
|
mdns_hostname_set(hostname);
|
||||||
|
mdns_instance_name_set(hostname);
|
||||||
|
|
||||||
for (const auto &service : services) {
|
for (const auto &service : services) {
|
||||||
auto txt_records = std::make_unique<mdns_txt_item_t[]>(service.txt_records.size());
|
auto txt_records = std::make_unique<mdns_txt_item_t[]>(service.txt_records.size());
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <ESP8266mDNS.h>
|
#include <ESP8266mDNS.h>
|
||||||
#include "esphome/components/network/ip_address.h"
|
#include "esphome/components/network/ip_address.h"
|
||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "mdns_component.h"
|
#include "mdns_component.h"
|
||||||
@@ -20,7 +21,7 @@ void MDNSComponent::setup() {
|
|||||||
this->compile_records_(services);
|
this->compile_records_(services);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
MDNS.begin(this->hostname_.c_str());
|
MDNS.begin(App.get_name().c_str());
|
||||||
|
|
||||||
for (const auto &service : services) {
|
for (const auto &service : services) {
|
||||||
// Strip the leading underscore from the proto and service_type. While it is
|
// Strip the leading underscore from the proto and service_type. While it is
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "esphome/components/network/ip_address.h"
|
#include "esphome/components/network/ip_address.h"
|
||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "mdns_component.h"
|
#include "mdns_component.h"
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ void MDNSComponent::setup() {
|
|||||||
this->compile_records_(services);
|
this->compile_records_(services);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
MDNS.begin(this->hostname_.c_str());
|
MDNS.begin(App.get_name().c_str());
|
||||||
|
|
||||||
for (const auto &service : services) {
|
for (const auto &service : services) {
|
||||||
// Strip the leading underscore from the proto and service_type. While it is
|
// Strip the leading underscore from the proto and service_type. While it is
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "esphome/components/network/ip_address.h"
|
#include "esphome/components/network/ip_address.h"
|
||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "mdns_component.h"
|
#include "mdns_component.h"
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ void MDNSComponent::setup() {
|
|||||||
this->compile_records_(services);
|
this->compile_records_(services);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
MDNS.begin(this->hostname_.c_str());
|
MDNS.begin(App.get_name().c_str());
|
||||||
|
|
||||||
for (const auto &service : services) {
|
for (const auto &service : services) {
|
||||||
// Strip the leading underscore from the proto and service_type. While it is
|
// Strip the leading underscore from the proto and service_type. While it is
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "media_player.h"
|
#include "media_player.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/controller_registry.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -148,7 +149,12 @@ void MediaPlayer::add_on_state_callback(std::function<void()> &&callback) {
|
|||||||
this->state_callback_.add(std::move(callback));
|
this->state_callback_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaPlayer::publish_state() { this->state_callback_.call(); }
|
void MediaPlayer::publish_state() {
|
||||||
|
this->state_callback_.call();
|
||||||
|
#if defined(USE_MEDIA_PLAYER) && defined(USE_CONTROLLER_REGISTRY)
|
||||||
|
ControllerRegistry::notify_media_player_update(this);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace media_player
|
} // namespace media_player
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
|
|||||||
|
|
||||||
if (map_it != this->mapping_.cend()) {
|
if (map_it != this->mapping_.cend()) {
|
||||||
size_t idx = std::distance(this->mapping_.cbegin(), map_it);
|
size_t idx = std::distance(this->mapping_.cbegin(), map_it);
|
||||||
new_state = std::string(this->option_at(idx));
|
ESP_LOGV(TAG, "Found option %s for value %lld", this->option_at(idx), value);
|
||||||
ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value);
|
this->publish_state(idx);
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGE(TAG, "No option found for mapping %lld", value);
|
ESP_LOGE(TAG, "No option found for mapping %lld", value);
|
||||||
}
|
}
|
||||||
@@ -40,19 +41,16 @@ void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModbusSelect::control(const std::string &value) {
|
void ModbusSelect::control(size_t index) {
|
||||||
auto idx = this->index_of(value);
|
optional<int64_t> mapval = this->mapping_[index];
|
||||||
if (!idx.has_value()) {
|
const char *option = this->option_at(index);
|
||||||
ESP_LOGW(TAG, "Invalid option '%s'", value.c_str());
|
ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, option);
|
||||||
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;
|
std::vector<uint16_t> data;
|
||||||
|
|
||||||
if (this->write_transform_func_.has_value()) {
|
if (this->write_transform_func_.has_value()) {
|
||||||
auto val = (*this->write_transform_func_)(this, value, *mapval, data);
|
// Transform func requires string parameter for backward compatibility
|
||||||
|
auto val = (*this->write_transform_func_)(this, std::string(option), *mapval, data);
|
||||||
if (val.has_value()) {
|
if (val.has_value()) {
|
||||||
mapval = *val;
|
mapval = *val;
|
||||||
ESP_LOGV(TAG, "write_lambda returned mapping value %lld", *mapval);
|
ESP_LOGV(TAG, "write_lambda returned mapping value %lld", *mapval);
|
||||||
@@ -85,7 +83,7 @@ void ModbusSelect::control(const std::string &value) {
|
|||||||
this->parent_->queue_command(write_cmd);
|
this->parent_->queue_command(write_cmd);
|
||||||
|
|
||||||
if (this->optimistic_)
|
if (this->optimistic_)
|
||||||
this->publish_state(value);
|
this->publish_state(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace modbus_controller
|
} // namespace modbus_controller
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
|
|||||||
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
||||||
void control(const std::string &value) override;
|
void control(size_t index) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::vector<int64_t> mapping_{};
|
std::vector<int64_t> mapping_{};
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ void MQTTAlarmControlPanelComponent::setup() {
|
|||||||
} else if (strcasecmp(payload.c_str(), "TRIGGERED") == 0) {
|
} else if (strcasecmp(payload.c_str(), "TRIGGERED") == 0) {
|
||||||
call.triggered();
|
call.triggered();
|
||||||
} else {
|
} 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();
|
call.perform();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,9 +30,12 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MQTTBinarySensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
void MQTTBinarySensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||||
if (!this->binary_sensor_->get_device_class().empty())
|
const auto device_class = this->binary_sensor_->get_device_class_ref();
|
||||||
root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class();
|
if (!device_class.empty()) {
|
||||||
|
root[MQTT_DEVICE_CLASS] = device_class;
|
||||||
|
}
|
||||||
|
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||||
if (this->binary_sensor_->is_status_binary_sensor())
|
if (this->binary_sensor_->is_status_binary_sensor())
|
||||||
root[MQTT_PAYLOAD_ON] = mqtt::global_mqtt_client->get_availability().payload_available;
|
root[MQTT_PAYLOAD_ON] = mqtt::global_mqtt_client->get_availability().payload_available;
|
||||||
if (this->binary_sensor_->is_status_binary_sensor())
|
if (this->binary_sensor_->is_status_binary_sensor())
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ void MQTTButtonComponent::setup() {
|
|||||||
if (payload == "PRESS") {
|
if (payload == "PRESS") {
|
||||||
this->button_->press();
|
this->button_->press();
|
||||||
} else {
|
} 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);
|
this->status_momentary_warning("state", 5000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -33,8 +33,9 @@ void MQTTButtonComponent::dump_config() {
|
|||||||
void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||||
config.state_topic = false;
|
config.state_topic = false;
|
||||||
if (!this->button_->get_device_class().empty()) {
|
const auto device_class = this->button_->get_device_class_ref();
|
||||||
root[MQTT_DEVICE_CLASS] = this->button_->get_device_class();
|
if (!device_class.empty()) {
|
||||||
|
root[MQTT_DEVICE_CLASS] = device_class;
|
||||||
}
|
}
|
||||||
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,11 +64,11 @@ bool MQTTComponent::send_discovery_() {
|
|||||||
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
|
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
|
||||||
|
|
||||||
if (discovery_info.clean) {
|
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);
|
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
|
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||||
return global_mqtt_client->publish_json(
|
return global_mqtt_client->publish_json(
|
||||||
@@ -85,12 +85,16 @@ bool MQTTComponent::send_discovery_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fields from EntityBase
|
// 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;
|
root[MQTT_ENABLED_BY_DEFAULT] = false;
|
||||||
if (!this->get_icon().empty())
|
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||||
root[MQTT_ICON] = this->get_icon();
|
const auto icon_ref = this->get_icon_ref_();
|
||||||
|
if (!icon_ref.empty()) {
|
||||||
|
root[MQTT_ICON] = icon_ref;
|
||||||
|
}
|
||||||
|
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||||
|
|
||||||
const auto entity_category = this->get_entity()->get_entity_category();
|
const auto entity_category = this->get_entity()->get_entity_category();
|
||||||
switch (entity_category) {
|
switch (entity_category) {
|
||||||
@@ -122,7 +126,7 @@ bool MQTTComponent::send_discovery_() {
|
|||||||
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
|
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
|
||||||
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
|
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
|
||||||
char friendly_name_hash[9];
|
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
|
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;
|
root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash;
|
||||||
} else {
|
} else {
|
||||||
@@ -184,7 +188,7 @@ bool MQTTComponent::is_discovery_enabled() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string MQTTComponent::get_default_object_id_() 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) {
|
void MQTTComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) {
|
||||||
@@ -268,9 +272,9 @@ void MQTTComponent::schedule_resend_state() { this->resend_state_ = true; }
|
|||||||
bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connected(); }
|
bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connected(); }
|
||||||
|
|
||||||
// Pull these properties from EntityBase if not overridden
|
// Pull these properties from EntityBase if not overridden
|
||||||
std::string MQTTComponent::friendly_name() const { return this->get_entity()->get_name(); }
|
std::string MQTTComponent::friendly_name_() const { return this->get_entity()->get_name(); }
|
||||||
std::string MQTTComponent::get_icon() const { return this->get_entity()->get_icon(); }
|
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(); }
|
bool MQTTComponent::is_disabled_by_default_() const { return this->get_entity()->is_disabled_by_default(); }
|
||||||
bool MQTTComponent::is_internal() {
|
bool MQTTComponent::is_internal() {
|
||||||
if (this->has_custom_state_topic_) {
|
if (this->has_custom_state_topic_) {
|
||||||
// If the custom state_topic is null, return true as it is internal and should not publish
|
// 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;
|
virtual const EntityBase *get_entity() const = 0;
|
||||||
|
|
||||||
/// Get the friendly name of this MQTT component.
|
/// Get the friendly name of this MQTT component.
|
||||||
virtual std::string friendly_name() const;
|
std::string friendly_name_() const;
|
||||||
|
|
||||||
/// Get the icon field of this component
|
/// Get the icon field of this component as StringRef
|
||||||
virtual std::string get_icon() const;
|
StringRef get_icon_ref_() const;
|
||||||
|
|
||||||
/// Get whether the underlying Entity is disabled by default
|
/// Get whether the underlying Entity is disabled by default
|
||||||
virtual bool is_disabled_by_default() const;
|
bool is_disabled_by_default_() const;
|
||||||
|
|
||||||
/// Get the MQTT topic that new states will be shared to.
|
/// Get the MQTT topic that new states will be shared to.
|
||||||
std::string get_state_topic_() const;
|
std::string get_state_topic_() const;
|
||||||
|
|||||||
@@ -67,9 +67,12 @@ void MQTTCoverComponent::dump_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||||
if (!this->cover_->get_device_class().empty())
|
const auto device_class = this->cover_->get_device_class_ref();
|
||||||
root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class();
|
if (!device_class.empty()) {
|
||||||
|
root[MQTT_DEVICE_CLASS] = device_class;
|
||||||
|
}
|
||||||
|
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||||
|
|
||||||
auto traits = this->cover_->get_traits();
|
auto traits = this->cover_->get_traits();
|
||||||
if (traits.get_is_assumed_state()) {
|
if (traits.get_is_assumed_state()) {
|
||||||
|
|||||||
@@ -21,8 +21,12 @@ void MQTTEventComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConf
|
|||||||
for (const auto &event_type : this->event_->get_event_types())
|
for (const auto &event_type : this->event_->get_event_types())
|
||||||
event_types.add(event_type);
|
event_types.add(event_type);
|
||||||
|
|
||||||
if (!this->event_->get_device_class().empty())
|
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||||
root[MQTT_DEVICE_CLASS] = this->event_->get_device_class();
|
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)
|
||||||
|
|
||||||
config.command_topic = false;
|
config.command_topic = false;
|
||||||
}
|
}
|
||||||
@@ -34,8 +38,8 @@ void MQTTEventComponent::setup() {
|
|||||||
void MQTTEventComponent::dump_config() {
|
void MQTTEventComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "MQTT Event '%s': ", this->event_->get_name().c_str());
|
ESP_LOGCONFIG(TAG, "MQTT Event '%s': ", this->event_->get_name().c_str());
|
||||||
ESP_LOGCONFIG(TAG, "Event Types: ");
|
ESP_LOGCONFIG(TAG, "Event Types: ");
|
||||||
for (const auto &event_type : this->event_->get_event_types()) {
|
for (const char *event_type : this->event_->get_event_types()) {
|
||||||
ESP_LOGCONFIG(TAG, "- %s", event_type.c_str());
|
ESP_LOGCONFIG(TAG, "- %s", event_type);
|
||||||
}
|
}
|
||||||
LOG_MQTT_COMPONENT(true, true);
|
LOG_MQTT_COMPONENT(true, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,15 +24,15 @@ void MQTTFanComponent::setup() {
|
|||||||
auto val = parse_on_off(payload.c_str());
|
auto val = parse_on_off(payload.c_str());
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case PARSE_ON:
|
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();
|
this->state_->turn_on().perform();
|
||||||
break;
|
break;
|
||||||
case PARSE_OFF:
|
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();
|
this->state_->turn_off().perform();
|
||||||
break;
|
break;
|
||||||
case PARSE_TOGGLE:
|
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();
|
this->state_->toggle().perform();
|
||||||
break;
|
break;
|
||||||
case PARSE_NONE:
|
case PARSE_NONE:
|
||||||
@@ -48,11 +48,11 @@ void MQTTFanComponent::setup() {
|
|||||||
auto val = parse_on_off(payload.c_str(), "forward", "reverse");
|
auto val = parse_on_off(payload.c_str(), "forward", "reverse");
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case PARSE_ON:
|
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();
|
this->state_->make_call().set_direction(fan::FanDirection::FORWARD).perform();
|
||||||
break;
|
break;
|
||||||
case PARSE_OFF:
|
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();
|
this->state_->make_call().set_direction(fan::FanDirection::REVERSE).perform();
|
||||||
break;
|
break;
|
||||||
case PARSE_TOGGLE:
|
case PARSE_TOGGLE:
|
||||||
@@ -75,11 +75,11 @@ void MQTTFanComponent::setup() {
|
|||||||
auto val = parse_on_off(payload.c_str(), "oscillate_on", "oscillate_off");
|
auto val = parse_on_off(payload.c_str(), "oscillate_on", "oscillate_off");
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case PARSE_ON:
|
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();
|
this->state_->make_call().set_oscillating(true).perform();
|
||||||
break;
|
break;
|
||||||
case PARSE_OFF:
|
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();
|
this->state_->make_call().set_oscillating(false).perform();
|
||||||
break;
|
break;
|
||||||
case PARSE_TOGGLE:
|
case PARSE_TOGGLE:
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ void MQTTLockComponent::setup() {
|
|||||||
} else if (strcasecmp(payload.c_str(), "OPEN") == 0) {
|
} else if (strcasecmp(payload.c_str(), "OPEN") == 0) {
|
||||||
this->lock_->open();
|
this->lock_->open();
|
||||||
} else {
|
} 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);
|
this->status_momentary_warning("state", 5000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -44,8 +44,11 @@ void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon
|
|||||||
root[MQTT_MIN] = traits.get_min_value();
|
root[MQTT_MIN] = traits.get_min_value();
|
||||||
root[MQTT_MAX] = traits.get_max_value();
|
root[MQTT_MAX] = traits.get_max_value();
|
||||||
root[MQTT_STEP] = traits.get_step();
|
root[MQTT_STEP] = traits.get_step();
|
||||||
if (!this->number_->traits.get_unit_of_measurement().empty())
|
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||||
root[MQTT_UNIT_OF_MEASUREMENT] = this->number_->traits.get_unit_of_measurement();
|
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;
|
||||||
|
}
|
||||||
switch (this->number_->traits.get_mode()) {
|
switch (this->number_->traits.get_mode()) {
|
||||||
case NUMBER_MODE_AUTO:
|
case NUMBER_MODE_AUTO:
|
||||||
break;
|
break;
|
||||||
@@ -56,8 +59,11 @@ void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon
|
|||||||
root[MQTT_MODE] = "slider";
|
root[MQTT_MODE] = "slider";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!this->number_->traits.get_device_class().empty())
|
const auto device_class = this->number_->traits.get_device_class_ref();
|
||||||
root[MQTT_DEVICE_CLASS] = this->number_->traits.get_device_class();
|
if (!device_class.empty()) {
|
||||||
|
root[MQTT_DEVICE_CLASS] = device_class;
|
||||||
|
}
|
||||||
|
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||||
|
|
||||||
config.command_topic = true;
|
config.command_topic = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,13 +44,17 @@ void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire
|
|||||||
void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; }
|
void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; }
|
||||||
|
|
||||||
void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||||
if (!this->sensor_->get_device_class().empty()) {
|
const auto device_class = this->sensor_->get_device_class_ref();
|
||||||
root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class();
|
if (!device_class.empty()) {
|
||||||
|
root[MQTT_DEVICE_CLASS] = device_class;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->sensor_->get_unit_of_measurement().empty())
|
const auto unit_of_measurement = this->sensor_->get_unit_of_measurement_ref();
|
||||||
root[MQTT_UNIT_OF_MEASUREMENT] = this->sensor_->get_unit_of_measurement();
|
if (!unit_of_measurement.empty()) {
|
||||||
|
root[MQTT_UNIT_OF_MEASUREMENT] = unit_of_measurement;
|
||||||
|
}
|
||||||
|
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||||
|
|
||||||
if (this->get_expire_after() > 0)
|
if (this->get_expire_after() > 0)
|
||||||
root[MQTT_EXPIRE_AFTER] = this->get_expire_after() / 1000;
|
root[MQTT_EXPIRE_AFTER] = this->get_expire_after() / 1000;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ void MQTTSwitchComponent::setup() {
|
|||||||
break;
|
break;
|
||||||
case PARSE_NONE:
|
case PARSE_NONE:
|
||||||
default:
|
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);
|
this->status_momentary_warning("state", 5000);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,12 @@ using namespace esphome::text_sensor;
|
|||||||
|
|
||||||
MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {}
|
MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {}
|
||||||
void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||||
if (!this->sensor_->get_device_class().empty()) {
|
const auto device_class = this->sensor_->get_device_class_ref();
|
||||||
root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class();
|
if (!device_class.empty()) {
|
||||||
|
root[MQTT_DEVICE_CLASS] = device_class;
|
||||||
}
|
}
|
||||||
|
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||||
config.command_topic = false;
|
config.command_topic = false;
|
||||||
}
|
}
|
||||||
void MQTTTextSensor::setup() {
|
void MQTTTextSensor::setup() {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ void MQTTUpdateComponent::setup() {
|
|||||||
if (payload == "INSTALL") {
|
if (payload == "INSTALL") {
|
||||||
this->update_->perform();
|
this->update_->perform();
|
||||||
} else {
|
} 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);
|
this->status_momentary_warning("state", 5000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -49,10 +49,12 @@ void MQTTValveComponent::dump_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||||
if (!this->valve_->get_device_class().empty()) {
|
const auto device_class = this->valve_->get_device_class_ref();
|
||||||
root[MQTT_DEVICE_CLASS] = this->valve_->get_device_class();
|
if (!device_class.empty()) {
|
||||||
|
root[MQTT_DEVICE_CLASS] = device_class;
|
||||||
}
|
}
|
||||||
|
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||||
|
|
||||||
auto traits = this->valve_->get_traits();
|
auto traits = this->valve_->get_traits();
|
||||||
if (traits.get_is_assumed_state()) {
|
if (traits.get_is_assumed_state()) {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include "number.h"
|
#include "number.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/controller_registry.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -32,6 +34,9 @@ void Number::publish_state(float state) {
|
|||||||
this->state = state;
|
this->state = state;
|
||||||
ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state);
|
ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state);
|
||||||
this->state_callback_.call(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) {
|
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
|
from esphome.components.mdns import MDNSComponent, enable_mdns_storage
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_CHANNEL, CONF_ENABLE_IPV6, CONF_ID, CONF_USE_ADDRESS
|
from esphome.const import CONF_CHANNEL, CONF_ENABLE_IPV6, CONF_ID, CONF_USE_ADDRESS
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE, TimePeriodMilliseconds
|
||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ from .const import (
|
|||||||
CONF_NETWORK_KEY,
|
CONF_NETWORK_KEY,
|
||||||
CONF_NETWORK_NAME,
|
CONF_NETWORK_NAME,
|
||||||
CONF_PAN_ID,
|
CONF_PAN_ID,
|
||||||
|
CONF_POLL_PERIOD,
|
||||||
CONF_PSKC,
|
CONF_PSKC,
|
||||||
CONF_SRP_ID,
|
CONF_SRP_ID,
|
||||||
CONF_TLV,
|
CONF_TLV,
|
||||||
@@ -89,7 +90,7 @@ def set_sdkconfig_options(config):
|
|||||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", True)
|
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", True)
|
||||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT_MAX_SERVICES", 5)
|
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT_MAX_SERVICES", 5)
|
||||||
|
|
||||||
# TODO: Add suport for sleepy end devices
|
# TODO: Add suport for synchronized sleepy end devices (SSED)
|
||||||
add_idf_sdkconfig_option(f"CONFIG_OPENTHREAD_{config.get(CONF_DEVICE_TYPE)}", True)
|
add_idf_sdkconfig_option(f"CONFIG_OPENTHREAD_{config.get(CONF_DEVICE_TYPE)}", True)
|
||||||
|
|
||||||
|
|
||||||
@@ -113,6 +114,17 @@ _CONNECTION_SCHEMA = cv.Schema(
|
|||||||
def _validate(config: ConfigType) -> ConfigType:
|
def _validate(config: ConfigType) -> ConfigType:
|
||||||
if CONF_USE_ADDRESS not in config:
|
if CONF_USE_ADDRESS not in config:
|
||||||
config[CONF_USE_ADDRESS] = f"{CORE.name}.local"
|
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
|
return config
|
||||||
|
|
||||||
|
|
||||||
@@ -135,6 +147,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_FORCE_DATASET): cv.boolean,
|
cv.Optional(CONF_FORCE_DATASET): cv.boolean,
|
||||||
cv.Optional(CONF_TLV): cv.string_strict,
|
cv.Optional(CONF_TLV): cv.string_strict,
|
||||||
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||||
|
cv.Optional(CONF_POLL_PERIOD): cv.positive_time_period_milliseconds,
|
||||||
}
|
}
|
||||||
).extend(_CONNECTION_SCHEMA),
|
).extend(_CONNECTION_SCHEMA),
|
||||||
cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV),
|
cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV),
|
||||||
@@ -167,6 +180,8 @@ async def to_code(config):
|
|||||||
ot = cg.new_Pvariable(config[CONF_ID])
|
ot = cg.new_Pvariable(config[CONF_ID])
|
||||||
cg.add(ot.set_use_address(config[CONF_USE_ADDRESS]))
|
cg.add(ot.set_use_address(config[CONF_USE_ADDRESS]))
|
||||||
await cg.register_component(ot, config)
|
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])
|
srp = cg.new_Pvariable(config[CONF_SRP_ID])
|
||||||
mdns_component = await cg.get_variable(config[CONF_MDNS_ID])
|
mdns_component = await cg.get_variable(config[CONF_MDNS_ID])
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ CONF_MESH_LOCAL_PREFIX = "mesh_local_prefix"
|
|||||||
CONF_NETWORK_NAME = "network_name"
|
CONF_NETWORK_NAME = "network_name"
|
||||||
CONF_NETWORK_KEY = "network_key"
|
CONF_NETWORK_KEY = "network_key"
|
||||||
CONF_PAN_ID = "pan_id"
|
CONF_PAN_ID = "pan_id"
|
||||||
|
CONF_POLL_PERIOD = "poll_period"
|
||||||
CONF_PSKC = "pskc"
|
CONF_PSKC = "pskc"
|
||||||
CONF_SRP_ID = "srp_id"
|
CONF_SRP_ID = "srp_id"
|
||||||
CONF_TLV = "tlv"
|
CONF_TLV = "tlv"
|
||||||
|
|||||||
@@ -29,6 +29,23 @@ OpenThreadComponent *global_openthread_component = // NOLINT(cppcoreguidelines-
|
|||||||
|
|
||||||
OpenThreadComponent::OpenThreadComponent() { global_openthread_component = this; }
|
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() {
|
bool OpenThreadComponent::is_connected() {
|
||||||
auto lock = InstanceLock::try_acquire(100);
|
auto lock = InstanceLock::try_acquire(100);
|
||||||
if (!lock) {
|
if (!lock) {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class OpenThreadComponent : public Component {
|
|||||||
public:
|
public:
|
||||||
OpenThreadComponent();
|
OpenThreadComponent();
|
||||||
~OpenThreadComponent();
|
~OpenThreadComponent();
|
||||||
|
void dump_config() override;
|
||||||
void setup() override;
|
void setup() override;
|
||||||
bool teardown() override;
|
bool teardown() override;
|
||||||
float get_setup_priority() const override { return setup_priority::WIFI; }
|
float get_setup_priority() const override { return setup_priority::WIFI; }
|
||||||
@@ -35,6 +36,9 @@ class OpenThreadComponent : public Component {
|
|||||||
|
|
||||||
const char *get_use_address() const;
|
const char *get_use_address() const;
|
||||||
void set_use_address(const char *use_address);
|
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:
|
protected:
|
||||||
std::optional<otIp6Address> get_omr_address_(InstanceLock &lock);
|
std::optional<otIp6Address> get_omr_address_(InstanceLock &lock);
|
||||||
@@ -46,6 +50,9 @@ class OpenThreadComponent : public Component {
|
|||||||
// Stores a pointer to a string literal (static storage duration).
|
// Stores a pointer to a string literal (static storage duration).
|
||||||
// ONLY set from Python-generated code with string literals - never dynamic strings.
|
// ONLY set from Python-generated code with string literals - never dynamic strings.
|
||||||
const char *use_address_{""};
|
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)
|
extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|||||||
@@ -105,6 +105,32 @@ void OpenThreadComponent::ot_main() {
|
|||||||
esp_cli_custom_command_init();
|
esp_cli_custom_command_init();
|
||||||
#endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
#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
|
// Run the main loop
|
||||||
#if CONFIG_OPENTHREAD_CLI
|
#if CONFIG_OPENTHREAD_CLI
|
||||||
esp_openthread_cli_create_task();
|
esp_openthread_cli_create_task();
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor
|
|||||||
stream->print(ESPHOME_F("\",name=\""));
|
stream->print(ESPHOME_F("\",name=\""));
|
||||||
stream->print(relabel_name_(obj).c_str());
|
stream->print(relabel_name_(obj).c_str());
|
||||||
stream->print(ESPHOME_F("\",unit=\""));
|
stream->print(ESPHOME_F("\",unit=\""));
|
||||||
stream->print(obj->get_unit_of_measurement().c_str());
|
stream->print(obj->get_unit_of_measurement_ref().c_str());
|
||||||
stream->print(ESPHOME_F("\"} "));
|
stream->print(ESPHOME_F("\"} "));
|
||||||
stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str());
|
stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str());
|
||||||
stream->print(ESPHOME_F("\n"));
|
stream->print(ESPHOME_F("\n"));
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import logging
|
|||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.components.const import CONF_IGNORE_NOT_FOUND
|
||||||
from esphome.components.esp32 import (
|
from esphome.components.esp32 import (
|
||||||
CONF_CPU_FREQUENCY,
|
CONF_CPU_FREQUENCY,
|
||||||
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES,
|
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES,
|
||||||
@@ -123,6 +124,7 @@ def get_config_schema(config):
|
|||||||
cv.Optional(CONF_ENABLE_ECC, default=False): cv.boolean,
|
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_SPEED, default=speeds[0]): cv.one_of(*speeds, upper=True),
|
||||||
cv.Optional(CONF_DISABLED, default=False): cv.boolean,
|
cv.Optional(CONF_DISABLED, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_IGNORE_NOT_FOUND, default=True): cv.boolean,
|
||||||
}
|
}
|
||||||
)(config)
|
)(config)
|
||||||
|
|
||||||
@@ -147,7 +149,9 @@ async def to_code(config):
|
|||||||
add_idf_sdkconfig_option("CONFIG_SPIRAM", True)
|
add_idf_sdkconfig_option("CONFIG_SPIRAM", True)
|
||||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_USE", 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_USE_CAPS_ALLOC", True)
|
||||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_IGNORE_NOTFOUND", True)
|
add_idf_sdkconfig_option(
|
||||||
|
"CONFIG_SPIRAM_IGNORE_NOTFOUND", config[CONF_IGNORE_NOT_FOUND]
|
||||||
|
)
|
||||||
|
|
||||||
add_idf_sdkconfig_option(f"CONFIG_SPIRAM_MODE_{SDK_MODES[config[CONF_MODE]]}", 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_WAND_ID,
|
||||||
CONF_ZERO,
|
CONF_ZERO,
|
||||||
)
|
)
|
||||||
from esphome.core import coroutine
|
from esphome.core import ID, coroutine
|
||||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||||
from esphome.util import Registry, SimpleRegistry
|
from esphome.util import Registry, SimpleRegistry
|
||||||
|
|
||||||
@@ -2104,7 +2104,9 @@ async def abbwelcome_action(var, config, args):
|
|||||||
)
|
)
|
||||||
cg.add(var.set_data_template(template_))
|
cg.add(var.set_data_template(template_))
|
||||||
else:
|
else:
|
||||||
cg.add(var.set_data_static(data_))
|
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_)))
|
||||||
|
|
||||||
|
|
||||||
# Mirage
|
# Mirage
|
||||||
|
|||||||
@@ -214,10 +214,13 @@ template<typename... Ts> class ABBWelcomeAction : public RemoteTransmitterAction
|
|||||||
TEMPLATABLE_VALUE(uint8_t, message_type)
|
TEMPLATABLE_VALUE(uint8_t, message_type)
|
||||||
TEMPLATABLE_VALUE(uint8_t, message_id)
|
TEMPLATABLE_VALUE(uint8_t, message_id)
|
||||||
TEMPLATABLE_VALUE(bool, auto_message_id)
|
TEMPLATABLE_VALUE(bool, auto_message_id)
|
||||||
void set_data_static(std::vector<uint8_t> data) { data_static_ = std::move(data); }
|
void set_data_template(std::vector<uint8_t> (*func)(Ts...)) {
|
||||||
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) {
|
this->data_.func = func;
|
||||||
this->data_func_ = func;
|
this->len_ = -1; // Sentinel value indicates template mode
|
||||||
has_data_func_ = true;
|
}
|
||||||
|
void set_data_static(const uint8_t *data, size_t len) {
|
||||||
|
this->data_.data = data;
|
||||||
|
this->len_ = len; // Length >= 0 indicates static mode
|
||||||
}
|
}
|
||||||
void encode(RemoteTransmitData *dst, Ts... x) override {
|
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||||
ABBWelcomeData data;
|
ABBWelcomeData data;
|
||||||
@@ -228,19 +231,25 @@ template<typename... Ts> class ABBWelcomeAction : public RemoteTransmitterAction
|
|||||||
data.set_message_type(this->message_type_.value(x...));
|
data.set_message_type(this->message_type_.value(x...));
|
||||||
data.set_message_id(this->message_id_.value(x...));
|
data.set_message_id(this->message_id_.value(x...));
|
||||||
data.auto_message_id = this->auto_message_id_.value(x...);
|
data.auto_message_id = this->auto_message_id_.value(x...);
|
||||||
if (has_data_func_) {
|
std::vector<uint8_t> data_vec;
|
||||||
data.set_data(this->data_func_(x...));
|
if (this->len_ >= 0) {
|
||||||
|
// Static mode: copy from flash to vector
|
||||||
|
data_vec.assign(this->data_.data, this->data_.data + this->len_);
|
||||||
} else {
|
} else {
|
||||||
data.set_data(this->data_static_);
|
// Template mode: call function
|
||||||
|
data_vec = this->data_.func(x...);
|
||||||
}
|
}
|
||||||
|
data.set_data(data_vec);
|
||||||
data.finalize();
|
data.finalize();
|
||||||
ABBWelcomeProtocol().encode(dst, data);
|
ABBWelcomeProtocol().encode(dst, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
|
ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
|
||||||
std::vector<uint8_t> data_static_{};
|
union Data {
|
||||||
bool has_data_func_{false};
|
std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
|
||||||
|
const uint8_t *data; // Pointer to static data in flash
|
||||||
|
} data_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace remote_base
|
} // namespace remote_base
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ static const uint16_t FALLBACK_FREQUENCY = 64767U; // To use with frequency = 0
|
|||||||
static const uint32_t MICROSECONDS_IN_SECONDS = 1000000UL;
|
static const uint32_t MICROSECONDS_IN_SECONDS = 1000000UL;
|
||||||
static const uint16_t PRONTO_DEFAULT_GAP = 45000;
|
static const uint16_t PRONTO_DEFAULT_GAP = 45000;
|
||||||
static const uint16_t MARK_EXCESS_MICROS = 20;
|
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) {
|
static uint16_t to_frequency_k_hz(uint16_t code) {
|
||||||
if (code == 0)
|
if (code == 0)
|
||||||
@@ -225,18 +226,18 @@ optional<ProntoData> ProntoProtocol::decode(RemoteReceiveData src) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ProntoProtocol::dump(const ProntoData &data) {
|
void ProntoProtocol::dump(const ProntoData &data) {
|
||||||
std::string rest;
|
|
||||||
|
|
||||||
rest = data.data;
|
|
||||||
ESP_LOGI(TAG, "Received Pronto: data=");
|
ESP_LOGI(TAG, "Received Pronto: data=");
|
||||||
while (true) {
|
|
||||||
ESP_LOGI(TAG, "%s", rest.substr(0, 230).c_str());
|
const char *ptr = data.data.c_str();
|
||||||
if (rest.size() > 230) {
|
size_t remaining = data.data.size();
|
||||||
rest = rest.substr(230);
|
|
||||||
} else {
|
// Log in chunks, always logging at least once (even for empty string)
|
||||||
break;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace remote_base
|
} // namespace remote_base
|
||||||
|
|||||||
@@ -42,17 +42,20 @@ class RawTrigger : public Trigger<RawTimings>, public Component, public RemoteRe
|
|||||||
|
|
||||||
template<typename... Ts> class RawAction : public RemoteTransmitterActionBase<Ts...> {
|
template<typename... Ts> class RawAction : public RemoteTransmitterActionBase<Ts...> {
|
||||||
public:
|
public:
|
||||||
void set_code_template(std::function<RawTimings(Ts...)> func) { this->code_func_ = func; }
|
void set_code_template(RawTimings (*func)(Ts...)) {
|
||||||
|
this->code_.func = func;
|
||||||
|
this->len_ = -1; // Sentinel value indicates template mode
|
||||||
|
}
|
||||||
void set_code_static(const int32_t *code, size_t len) {
|
void set_code_static(const int32_t *code, size_t len) {
|
||||||
this->code_static_ = code;
|
this->code_.data = code;
|
||||||
this->code_static_len_ = len;
|
this->len_ = len; // Length >= 0 indicates static mode
|
||||||
}
|
}
|
||||||
TEMPLATABLE_VALUE(uint32_t, carrier_frequency);
|
TEMPLATABLE_VALUE(uint32_t, carrier_frequency);
|
||||||
|
|
||||||
void encode(RemoteTransmitData *dst, Ts... x) override {
|
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||||
if (this->code_static_ != nullptr) {
|
if (this->len_ >= 0) {
|
||||||
for (size_t i = 0; i < this->code_static_len_; i++) {
|
for (size_t i = 0; i < static_cast<size_t>(this->len_); i++) {
|
||||||
auto val = this->code_static_[i];
|
auto val = this->code_.data[i];
|
||||||
if (val < 0) {
|
if (val < 0) {
|
||||||
dst->space(static_cast<uint32_t>(-val));
|
dst->space(static_cast<uint32_t>(-val));
|
||||||
} else {
|
} else {
|
||||||
@@ -60,15 +63,17 @@ template<typename... Ts> class RawAction : public RemoteTransmitterActionBase<Ts
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dst->set_data(this->code_func_(x...));
|
dst->set_data(this->code_.func(x...));
|
||||||
}
|
}
|
||||||
dst->set_carrier_frequency(this->carrier_frequency_.value(x...));
|
dst->set_carrier_frequency(this->carrier_frequency_.value(x...));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::function<RawTimings(Ts...)> code_func_{nullptr};
|
ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
|
||||||
const int32_t *code_static_{nullptr};
|
union Code {
|
||||||
int32_t code_static_len_{0};
|
RawTimings (*func)(Ts...);
|
||||||
|
const int32_t *data;
|
||||||
|
} code_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RawDumper : public RemoteReceiverDumperBase {
|
class RawDumper : public RemoteReceiverDumperBase {
|
||||||
|
|||||||
@@ -3,12 +3,9 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace seeed_mr24hpc1 {
|
namespace seeed_mr24hpc1 {
|
||||||
|
|
||||||
void ExistenceBoundarySelect::control(const std::string &value) {
|
void ExistenceBoundarySelect::control(size_t index) {
|
||||||
this->publish_state(value);
|
this->publish_state(index);
|
||||||
auto index = this->index_of(value);
|
this->parent_->set_existence_boundary(index);
|
||||||
if (index.has_value()) {
|
|
||||||
this->parent_->set_existence_boundary(index.value());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace seeed_mr24hpc1
|
} // namespace seeed_mr24hpc1
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class ExistenceBoundarySelect : public select::Select, public Parented<MR24HPC1C
|
|||||||
ExistenceBoundarySelect() = default;
|
ExistenceBoundarySelect() = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override;
|
void control(size_t index) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace seeed_mr24hpc1
|
} // namespace seeed_mr24hpc1
|
||||||
|
|||||||
@@ -3,12 +3,9 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace seeed_mr24hpc1 {
|
namespace seeed_mr24hpc1 {
|
||||||
|
|
||||||
void MotionBoundarySelect::control(const std::string &value) {
|
void MotionBoundarySelect::control(size_t index) {
|
||||||
this->publish_state(value);
|
this->publish_state(index);
|
||||||
auto index = this->index_of(value);
|
this->parent_->set_motion_boundary(index);
|
||||||
if (index.has_value()) {
|
|
||||||
this->parent_->set_motion_boundary(index.value());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace seeed_mr24hpc1
|
} // namespace seeed_mr24hpc1
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class MotionBoundarySelect : public select::Select, public Parented<MR24HPC1Comp
|
|||||||
MotionBoundarySelect() = default;
|
MotionBoundarySelect() = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override;
|
void control(size_t index) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace seeed_mr24hpc1
|
} // namespace seeed_mr24hpc1
|
||||||
|
|||||||
@@ -3,12 +3,9 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace seeed_mr24hpc1 {
|
namespace seeed_mr24hpc1 {
|
||||||
|
|
||||||
void SceneModeSelect::control(const std::string &value) {
|
void SceneModeSelect::control(size_t index) {
|
||||||
this->publish_state(value);
|
this->publish_state(index);
|
||||||
auto index = this->index_of(value);
|
this->parent_->set_scene_mode(index);
|
||||||
if (index.has_value()) {
|
|
||||||
this->parent_->set_scene_mode(index.value());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace seeed_mr24hpc1
|
} // namespace seeed_mr24hpc1
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class SceneModeSelect : public select::Select, public Parented<MR24HPC1Component
|
|||||||
SceneModeSelect() = default;
|
SceneModeSelect() = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override;
|
void control(size_t index) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace seeed_mr24hpc1
|
} // namespace seeed_mr24hpc1
|
||||||
|
|||||||
@@ -3,12 +3,9 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace seeed_mr24hpc1 {
|
namespace seeed_mr24hpc1 {
|
||||||
|
|
||||||
void UnmanTimeSelect::control(const std::string &value) {
|
void UnmanTimeSelect::control(size_t index) {
|
||||||
this->publish_state(value);
|
this->publish_state(index);
|
||||||
auto index = this->index_of(value);
|
this->parent_->set_unman_time(index);
|
||||||
if (index.has_value()) {
|
|
||||||
this->parent_->set_unman_time(index.value());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace seeed_mr24hpc1
|
} // namespace seeed_mr24hpc1
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class UnmanTimeSelect : public select::Select, public Parented<MR24HPC1Component
|
|||||||
UnmanTimeSelect() = default;
|
UnmanTimeSelect() = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override;
|
void control(size_t index) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace seeed_mr24hpc1
|
} // namespace seeed_mr24hpc1
|
||||||
|
|||||||
@@ -3,12 +3,9 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace seeed_mr60fda2 {
|
namespace seeed_mr60fda2 {
|
||||||
|
|
||||||
void HeightThresholdSelect::control(const std::string &value) {
|
void HeightThresholdSelect::control(size_t index) {
|
||||||
this->publish_state(value);
|
this->publish_state(index);
|
||||||
auto index = this->index_of(value);
|
this->parent_->set_height_threshold(index);
|
||||||
if (index.has_value()) {
|
|
||||||
this->parent_->set_height_threshold(index.value());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace seeed_mr60fda2
|
} // namespace seeed_mr60fda2
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class HeightThresholdSelect : public select::Select, public Parented<MR60FDA2Com
|
|||||||
HeightThresholdSelect() = default;
|
HeightThresholdSelect() = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override;
|
void control(size_t index) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace seeed_mr60fda2
|
} // namespace seeed_mr60fda2
|
||||||
|
|||||||
@@ -3,12 +3,9 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace seeed_mr60fda2 {
|
namespace seeed_mr60fda2 {
|
||||||
|
|
||||||
void InstallHeightSelect::control(const std::string &value) {
|
void InstallHeightSelect::control(size_t index) {
|
||||||
this->publish_state(value);
|
this->publish_state(index);
|
||||||
auto index = this->index_of(value);
|
this->parent_->set_install_height(index);
|
||||||
if (index.has_value()) {
|
|
||||||
this->parent_->set_install_height(index.value());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace seeed_mr60fda2
|
} // namespace seeed_mr60fda2
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class InstallHeightSelect : public select::Select, public Parented<MR60FDA2Compo
|
|||||||
InstallHeightSelect() = default;
|
InstallHeightSelect() = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override;
|
void control(size_t index) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace seeed_mr60fda2
|
} // namespace seeed_mr60fda2
|
||||||
|
|||||||
@@ -3,12 +3,9 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace seeed_mr60fda2 {
|
namespace seeed_mr60fda2 {
|
||||||
|
|
||||||
void SensitivitySelect::control(const std::string &value) {
|
void SensitivitySelect::control(size_t index) {
|
||||||
this->publish_state(value);
|
this->publish_state(index);
|
||||||
auto index = this->index_of(value);
|
this->parent_->set_sensitivity(index);
|
||||||
if (index.has_value()) {
|
|
||||||
this->parent_->set_sensitivity(index.value());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace seeed_mr60fda2
|
} // namespace seeed_mr60fda2
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user