mirror of
https://github.com/esphome/esphome.git
synced 2026-10-11 05:44:15 +00:00
[tuya] Build on_datapoint_update with build_trigger_callback (#20453)
This commit is contained in:
@@ -2,7 +2,8 @@ from esphome import automation, pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import time, uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT, CONF_TIME_ID, CONF_TRIGGER_ID
|
||||
from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT, CONF_TIME_ID
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
DOMAIN = "tuya"
|
||||
@@ -17,6 +18,8 @@ tuya_ns = cg.esphome_ns.namespace("tuya")
|
||||
TuyaDatapointType = tuya_ns.enum("TuyaDatapointType", is_class=True)
|
||||
Tuya = tuya_ns.class_("Tuya", cg.Component, uart.UARTDevice)
|
||||
|
||||
TuyaDatapoint = tuya_ns.struct("TuyaDatapoint")
|
||||
|
||||
DPTYPE_ANY = "any"
|
||||
DPTYPE_RAW = "raw"
|
||||
DPTYPE_BOOL = "bool"
|
||||
@@ -26,60 +29,18 @@ DPTYPE_STRING = "string"
|
||||
DPTYPE_ENUM = "enum"
|
||||
DPTYPE_BITMASK = "bitmask"
|
||||
|
||||
# Automation argument type, plus the expected TuyaDatapointType and the field forwarded
|
||||
DATAPOINT_TYPES = {
|
||||
DPTYPE_ANY: tuya_ns.struct("TuyaDatapoint"),
|
||||
DPTYPE_RAW: cg.std_vector.template(cg.uint8),
|
||||
DPTYPE_BOOL: cg.bool_,
|
||||
DPTYPE_INT: cg.int_,
|
||||
DPTYPE_UINT: cg.uint32,
|
||||
DPTYPE_STRING: cg.std_string,
|
||||
DPTYPE_ENUM: cg.uint8,
|
||||
DPTYPE_BITMASK: cg.uint32,
|
||||
DPTYPE_ANY: (TuyaDatapoint, None, None),
|
||||
DPTYPE_RAW: (cg.std_vector.template(cg.uint8), TuyaDatapointType.RAW, "value_raw"),
|
||||
DPTYPE_BOOL: (cg.bool_, TuyaDatapointType.BOOLEAN, "value_bool"),
|
||||
DPTYPE_INT: (cg.int_, TuyaDatapointType.INTEGER, "value_int"),
|
||||
DPTYPE_UINT: (cg.uint32, TuyaDatapointType.INTEGER, "value_uint"),
|
||||
DPTYPE_STRING: (cg.std_string, TuyaDatapointType.STRING, "value_string"),
|
||||
DPTYPE_ENUM: (cg.uint8, TuyaDatapointType.ENUM, "value_enum"),
|
||||
DPTYPE_BITMASK: (cg.uint32, TuyaDatapointType.BITMASK, "value_bitmask"),
|
||||
}
|
||||
|
||||
DATAPOINT_TRIGGERS = {
|
||||
DPTYPE_ANY: tuya_ns.class_(
|
||||
"TuyaDatapointUpdateTrigger",
|
||||
automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_ANY]),
|
||||
),
|
||||
DPTYPE_RAW: tuya_ns.class_(
|
||||
"TuyaRawDatapointUpdateTrigger",
|
||||
automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_RAW]),
|
||||
),
|
||||
DPTYPE_BOOL: tuya_ns.class_(
|
||||
"TuyaBoolDatapointUpdateTrigger",
|
||||
automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_BOOL]),
|
||||
),
|
||||
DPTYPE_INT: tuya_ns.class_(
|
||||
"TuyaIntDatapointUpdateTrigger",
|
||||
automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_INT]),
|
||||
),
|
||||
DPTYPE_UINT: tuya_ns.class_(
|
||||
"TuyaUIntDatapointUpdateTrigger",
|
||||
automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_UINT]),
|
||||
),
|
||||
DPTYPE_STRING: tuya_ns.class_(
|
||||
"TuyaStringDatapointUpdateTrigger",
|
||||
automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_STRING]),
|
||||
),
|
||||
DPTYPE_ENUM: tuya_ns.class_(
|
||||
"TuyaEnumDatapointUpdateTrigger",
|
||||
automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_ENUM]),
|
||||
),
|
||||
DPTYPE_BITMASK: tuya_ns.class_(
|
||||
"TuyaBitmaskDatapointUpdateTrigger",
|
||||
automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_BITMASK]),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def assign_declare_id(value):
|
||||
value = value.copy()
|
||||
value[CONF_TRIGGER_ID] = cv.declare_id(
|
||||
DATAPOINT_TRIGGERS[value[CONF_DATAPOINT_TYPE]]
|
||||
)(value[CONF_TRIGGER_ID].id)
|
||||
return value
|
||||
|
||||
|
||||
CONF_TUYA_ID = "tuya_id"
|
||||
CONFIG_SCHEMA = (
|
||||
@@ -93,15 +54,11 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_STATUS_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_ON_DATAPOINT_UPDATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DATAPOINT_TRIGGERS[DPTYPE_ANY]
|
||||
),
|
||||
cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_DATAPOINT_TYPE, default=DPTYPE_ANY): cv.one_of(
|
||||
*DATAPOINT_TRIGGERS, lower=True
|
||||
*DATAPOINT_TYPES, lower=True
|
||||
),
|
||||
},
|
||||
extra_validators=assign_declare_id,
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -124,9 +81,11 @@ async def to_code(config):
|
||||
for dp in config[CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS]:
|
||||
cg.add(var.add_ignore_mcu_update_on_datapoints(dp))
|
||||
for conf in config.get(CONF_ON_DATAPOINT_UPDATE, []):
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], var, conf[CONF_SENSOR_DATAPOINT]
|
||||
)
|
||||
await automation.build_automation(
|
||||
trigger, [(DATAPOINT_TYPES[conf[CONF_DATAPOINT_TYPE]], "x")], conf
|
||||
type_, expected, field = DATAPOINT_TYPES[conf[CONF_DATAPOINT_TYPE]]
|
||||
forward = None
|
||||
if expected is not None:
|
||||
forward = [getattr(MockObj("x", ".").expect_type(expected), field)]
|
||||
callback = await automation.build_trigger_callback(
|
||||
[(type_, "x")], conf, params=[(TuyaDatapoint, "x")], forward=forward
|
||||
)
|
||||
cg.add(var.register_listener(conf[CONF_SENSOR_DATAPOINT], callback))
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "automation.h"
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "tuya.automation");
|
||||
|
||||
namespace esphome::tuya {
|
||||
|
||||
void check_expected_datapoint(const TuyaDatapoint &dp, TuyaDatapointType expected) {
|
||||
if (dp.type != expected) {
|
||||
ESP_LOGW(TAG, "Tuya sensor %u expected datapoint type %#02hhX but got %#02hhX", dp.id,
|
||||
static_cast<uint8_t>(expected), static_cast<uint8_t>(dp.type));
|
||||
}
|
||||
}
|
||||
|
||||
TuyaRawDatapointUpdateTrigger::TuyaRawDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) {
|
||||
parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) {
|
||||
check_expected_datapoint(dp, TuyaDatapointType::RAW);
|
||||
this->trigger(dp.value_raw);
|
||||
});
|
||||
}
|
||||
|
||||
TuyaBoolDatapointUpdateTrigger::TuyaBoolDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) {
|
||||
parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) {
|
||||
check_expected_datapoint(dp, TuyaDatapointType::BOOLEAN);
|
||||
this->trigger(dp.value_bool);
|
||||
});
|
||||
}
|
||||
|
||||
TuyaIntDatapointUpdateTrigger::TuyaIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) {
|
||||
parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) {
|
||||
check_expected_datapoint(dp, TuyaDatapointType::INTEGER);
|
||||
this->trigger(dp.value_int);
|
||||
});
|
||||
}
|
||||
|
||||
TuyaUIntDatapointUpdateTrigger::TuyaUIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) {
|
||||
parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) {
|
||||
check_expected_datapoint(dp, TuyaDatapointType::INTEGER);
|
||||
this->trigger(dp.value_uint);
|
||||
});
|
||||
}
|
||||
|
||||
TuyaStringDatapointUpdateTrigger::TuyaStringDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) {
|
||||
parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) {
|
||||
check_expected_datapoint(dp, TuyaDatapointType::STRING);
|
||||
this->trigger(dp.value_string);
|
||||
});
|
||||
}
|
||||
|
||||
TuyaEnumDatapointUpdateTrigger::TuyaEnumDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) {
|
||||
parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) {
|
||||
check_expected_datapoint(dp, TuyaDatapointType::ENUM);
|
||||
this->trigger(dp.value_enum);
|
||||
});
|
||||
}
|
||||
|
||||
TuyaBitmaskDatapointUpdateTrigger::TuyaBitmaskDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) {
|
||||
parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) {
|
||||
check_expected_datapoint(dp, TuyaDatapointType::BITMASK);
|
||||
this->trigger(dp.value_bitmask);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace esphome::tuya
|
||||
@@ -1,53 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "tuya.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::tuya {
|
||||
|
||||
class TuyaDatapointUpdateTrigger final : public Trigger<TuyaDatapoint> {
|
||||
public:
|
||||
explicit TuyaDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) {
|
||||
parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { this->trigger(dp); });
|
||||
}
|
||||
};
|
||||
|
||||
class TuyaRawDatapointUpdateTrigger final : public Trigger<std::vector<uint8_t>> {
|
||||
public:
|
||||
explicit TuyaRawDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id);
|
||||
};
|
||||
|
||||
class TuyaBoolDatapointUpdateTrigger final : public Trigger<bool> {
|
||||
public:
|
||||
explicit TuyaBoolDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id);
|
||||
};
|
||||
|
||||
class TuyaIntDatapointUpdateTrigger final : public Trigger<int> {
|
||||
public:
|
||||
explicit TuyaIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id);
|
||||
};
|
||||
|
||||
class TuyaUIntDatapointUpdateTrigger final : public Trigger<uint32_t> {
|
||||
public:
|
||||
explicit TuyaUIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id);
|
||||
};
|
||||
|
||||
class TuyaStringDatapointUpdateTrigger final : public Trigger<std::string> {
|
||||
public:
|
||||
explicit TuyaStringDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id);
|
||||
};
|
||||
|
||||
class TuyaEnumDatapointUpdateTrigger final : public Trigger<uint8_t> {
|
||||
public:
|
||||
explicit TuyaEnumDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id);
|
||||
};
|
||||
|
||||
class TuyaBitmaskDatapointUpdateTrigger final : public Trigger<uint32_t> {
|
||||
public:
|
||||
explicit TuyaBitmaskDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id);
|
||||
};
|
||||
|
||||
} // namespace esphome::tuya
|
||||
@@ -482,7 +482,7 @@ void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) {
|
||||
// Run through listeners
|
||||
for (auto &listener : this->listeners_) {
|
||||
if (listener.datapoint_id == datapoint.id)
|
||||
listener.on_datapoint(datapoint);
|
||||
listener.on_datapoint.call(datapoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -796,20 +796,27 @@ void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datap
|
||||
this->send_command_(TuyaCommand{.cmd = TuyaCommandType::DATAPOINT_DELIVER, .payload = buffer});
|
||||
}
|
||||
|
||||
void Tuya::register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func) {
|
||||
auto listener = TuyaDatapointListener{
|
||||
void Tuya::register_listener_(uint8_t datapoint_id, Callback<void(const TuyaDatapoint &)> func) {
|
||||
this->listeners_.push_back(TuyaDatapointListener{
|
||||
.datapoint_id = datapoint_id,
|
||||
.on_datapoint = func,
|
||||
};
|
||||
this->listeners_.push_back(listener);
|
||||
});
|
||||
|
||||
// Run through existing datapoints
|
||||
for (auto &datapoint : this->datapoints_) {
|
||||
if (datapoint.id == datapoint_id)
|
||||
func(datapoint);
|
||||
func.call(datapoint);
|
||||
}
|
||||
}
|
||||
|
||||
const TuyaDatapoint &TuyaDatapoint::expect_type(TuyaDatapointType expected) const {
|
||||
if (this->type != expected) {
|
||||
ESP_LOGW(TAG, "Tuya sensor %u expected datapoint type %#02hhX but got %#02hhX", this->id,
|
||||
static_cast<uint8_t>(expected), static_cast<uint8_t>(this->type));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
TuyaInitState Tuya::get_init_state() { return this->init_state_; }
|
||||
|
||||
} // namespace esphome::tuya
|
||||
|
||||
@@ -37,11 +37,14 @@ struct TuyaDatapoint {
|
||||
};
|
||||
std::string value_string;
|
||||
std::vector<uint8_t> value_raw;
|
||||
|
||||
/// Log a warning when this datapoint is not of type `expected`, and return it.
|
||||
const TuyaDatapoint &expect_type(TuyaDatapointType expected) const;
|
||||
};
|
||||
|
||||
struct TuyaDatapointListener {
|
||||
uint8_t datapoint_id;
|
||||
std::function<void(TuyaDatapoint)> on_datapoint;
|
||||
Callback<void(const TuyaDatapoint &)> on_datapoint;
|
||||
};
|
||||
|
||||
enum class TuyaCommandType : uint8_t {
|
||||
@@ -91,7 +94,9 @@ class Tuya final : public Component, public uart::UARTDevice {
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func);
|
||||
template<typename F> void register_listener(uint8_t datapoint_id, F &&func) {
|
||||
this->register_listener_(datapoint_id, Callback<void(const TuyaDatapoint &)>::create(std::forward<F>(func)));
|
||||
}
|
||||
void set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value);
|
||||
void set_boolean_datapoint_value(uint8_t datapoint_id, bool value);
|
||||
void set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value);
|
||||
@@ -117,6 +122,7 @@ class Tuya final : public Component, public uart::UARTDevice {
|
||||
}
|
||||
|
||||
protected:
|
||||
void register_listener_(uint8_t datapoint_id, Callback<void(const TuyaDatapoint &)> func);
|
||||
void handle_char_(uint8_t c);
|
||||
void handle_datapoints_(const uint8_t *buffer, size_t len);
|
||||
optional<TuyaDatapoint> get_datapoint_(uint8_t datapoint_id);
|
||||
|
||||
@@ -10,7 +10,50 @@ tuya:
|
||||
- sensor_datapoint: 6
|
||||
datapoint_type: raw
|
||||
then:
|
||||
- logger.log: Datapoint 6 updated
|
||||
- logger.log:
|
||||
format: "Datapoint 6 updated, %u bytes"
|
||||
args: [x.size()]
|
||||
- sensor_datapoint: 1
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Datapoint %u updated"
|
||||
args: [x.id]
|
||||
- sensor_datapoint: 1
|
||||
datapoint_type: bool
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Datapoint 1 is %s"
|
||||
args: ['x ? "on" : "off"']
|
||||
- sensor_datapoint: 3
|
||||
datapoint_type: int
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Datapoint 3 is %d"
|
||||
args: [x]
|
||||
- sensor_datapoint: 3
|
||||
datapoint_type: uint
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Datapoint 3 is %u"
|
||||
args: [x]
|
||||
- sensor_datapoint: 7
|
||||
datapoint_type: string
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Datapoint 7 is %s"
|
||||
args: [x.c_str()]
|
||||
- sensor_datapoint: 8
|
||||
datapoint_type: enum
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Datapoint 8 is %u"
|
||||
args: [x]
|
||||
- sensor_datapoint: 9
|
||||
datapoint_type: bitmask
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Datapoint 9 is %u"
|
||||
args: [x]
|
||||
|
||||
binary_sensor:
|
||||
- platform: tuya
|
||||
|
||||
Reference in New Issue
Block a user