mirror of
https://github.com/esphome/esphome.git
synced 2026-01-08 19:20:51 -07:00
[nrf52, zigbee] Add sensor (#12187)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import math
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server
|
||||
from esphome.components import mqtt, web_server, zigbee
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ABOVE,
|
||||
@@ -295,6 +295,7 @@ validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||
_SENSOR_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMPONENT_SCHEMA)
|
||||
.extend(zigbee.SENSOR_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent),
|
||||
@@ -335,6 +336,7 @@ _SENSOR_SCHEMA = (
|
||||
)
|
||||
|
||||
_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("sensor"))
|
||||
_SENSOR_SCHEMA.add_extra(zigbee.validate_sensor)
|
||||
|
||||
|
||||
def sensor_schema(
|
||||
@@ -918,6 +920,8 @@ async def setup_sensor_core_(var, config):
|
||||
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||
await web_server.add_entity_config(var, web_server_config)
|
||||
|
||||
await zigbee.setup_sensor(var, config)
|
||||
|
||||
|
||||
async def register_sensor(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
|
||||
@@ -13,14 +13,16 @@ from esphome.types import ConfigType
|
||||
from .const_zephyr import (
|
||||
CONF_MAX_EP_NUMBER,
|
||||
CONF_ON_JOIN,
|
||||
CONF_POWER_SOURCE,
|
||||
CONF_WIPE_ON_BOOT,
|
||||
CONF_ZIGBEE_ID,
|
||||
KEY_EP_NUMBER,
|
||||
KEY_ZIGBEE,
|
||||
POWER_SOURCE,
|
||||
ZigbeeComponent,
|
||||
zigbee_ns,
|
||||
)
|
||||
from .zigbee_zephyr import zephyr_binary_sensor
|
||||
from .zigbee_zephyr import zephyr_binary_sensor, zephyr_sensor
|
||||
|
||||
CODEOWNERS = ["@tomaszduda23"]
|
||||
|
||||
@@ -35,6 +37,7 @@ def zigbee_set_core_data(config: ConfigType) -> ConfigType:
|
||||
|
||||
|
||||
BINARY_SENSOR_SCHEMA = cv.Schema({}).extend(zephyr_binary_sensor)
|
||||
SENSOR_SCHEMA = cv.Schema({}).extend(zephyr_sensor)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
@@ -42,9 +45,15 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(ZigbeeComponent),
|
||||
cv.Optional(CONF_ON_JOIN): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_WIPE_ON_BOOT, default=False): cv.All(
|
||||
cv.boolean,
|
||||
cv.Any(
|
||||
cv.boolean,
|
||||
cv.one_of(*["once"], lower=True),
|
||||
),
|
||||
cv.requires_component("nrf52"),
|
||||
),
|
||||
cv.Optional(CONF_POWER_SOURCE, default="DC_SOURCE"): cv.enum(
|
||||
POWER_SOURCE, upper=True
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
zigbee_set_core_data,
|
||||
@@ -86,7 +95,16 @@ async def setup_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
await zephyr_setup_binary_sensor(entity, config)
|
||||
|
||||
|
||||
def validate_binary_sensor(config: ConfigType) -> ConfigType:
|
||||
async def setup_sensor(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
if not config.get(CONF_ZIGBEE_ID) or config.get(CONF_INTERNAL):
|
||||
return
|
||||
if CORE.using_zephyr:
|
||||
from .zigbee_zephyr import zephyr_setup_sensor
|
||||
|
||||
await zephyr_setup_sensor(entity, config)
|
||||
|
||||
|
||||
def consume_endpoint(config: ConfigType) -> ConfigType:
|
||||
if not config.get(CONF_ZIGBEE_ID) or config.get(CONF_INTERNAL):
|
||||
return config
|
||||
data: dict[str, Any] = CORE.data.setdefault(KEY_ZIGBEE, {})
|
||||
@@ -95,6 +113,14 @@ def validate_binary_sensor(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
def validate_binary_sensor(config: ConfigType) -> ConfigType:
|
||||
return consume_endpoint(config)
|
||||
|
||||
|
||||
def validate_sensor(config: ConfigType) -> ConfigType:
|
||||
return consume_endpoint(config)
|
||||
|
||||
|
||||
ZIGBEE_ACTION_SCHEMA = automation.maybe_simple_id(
|
||||
cv.Schema(
|
||||
{
|
||||
|
||||
@@ -3,12 +3,24 @@ import esphome.codegen as cg
|
||||
zigbee_ns = cg.esphome_ns.namespace("zigbee")
|
||||
ZigbeeComponent = zigbee_ns.class_("ZigbeeComponent", cg.Component)
|
||||
BinaryAttrs = zigbee_ns.struct("BinaryAttrs")
|
||||
AnalogAttrs = zigbee_ns.struct("AnalogAttrs")
|
||||
|
||||
CONF_MAX_EP_NUMBER = 8
|
||||
CONF_ZIGBEE_ID = "zigbee_id"
|
||||
CONF_ON_JOIN = "on_join"
|
||||
CONF_WIPE_ON_BOOT = "wipe_on_boot"
|
||||
CONF_ZIGBEE_BINARY_SENSOR = "zigbee_binary_sensor"
|
||||
CONF_ZIGBEE_SENSOR = "zigbee_sensor"
|
||||
CONF_POWER_SOURCE = "power_source"
|
||||
POWER_SOURCE = {
|
||||
"UNKNOWN": "ZB_ZCL_BASIC_POWER_SOURCE_UNKNOWN",
|
||||
"MAINS_SINGLE_PHASE": "ZB_ZCL_BASIC_POWER_SOURCE_MAINS_SINGLE_PHASE",
|
||||
"MAINS_THREE_PHASE": "ZB_ZCL_BASIC_POWER_SOURCE_MAINS_THREE_PHASE",
|
||||
"BATTERY": "ZB_ZCL_BASIC_POWER_SOURCE_BATTERY",
|
||||
"DC_SOURCE": "ZB_ZCL_BASIC_POWER_SOURCE_DC_SOURCE",
|
||||
"EMERGENCY_MAINS_CONST": "ZB_ZCL_BASIC_POWER_SOURCE_EMERGENCY_MAINS_CONST",
|
||||
"EMERGENCY_MAINS_TRANSF": "ZB_ZCL_BASIC_POWER_SOURCE_EMERGENCY_MAINS_TRANSF",
|
||||
}
|
||||
|
||||
# Keys for CORE.data storage
|
||||
KEY_ZIGBEE = "zigbee"
|
||||
@@ -22,3 +34,4 @@ ZB_ZCL_IDENTIFY_ATTRS_T = "zb_zcl_identify_attrs_t"
|
||||
ZB_ZCL_CLUSTER_ID_BASIC = "ZB_ZCL_CLUSTER_ID_BASIC"
|
||||
ZB_ZCL_CLUSTER_ID_IDENTIFY = "ZB_ZCL_CLUSTER_ID_IDENTIFY"
|
||||
ZB_ZCL_CLUSTER_ID_BINARY_INPUT = "ZB_ZCL_CLUSTER_ID_BINARY_INPUT"
|
||||
ZB_ZCL_CLUSTER_ID_ANALOG_INPUT = "ZB_ZCL_CLUSTER_ID_ANALOG_INPUT"
|
||||
|
||||
@@ -17,9 +17,9 @@ ZigbeeBinarySensor::ZigbeeBinarySensor(binary_sensor::BinarySensor *binary_senso
|
||||
void ZigbeeBinarySensor::setup() {
|
||||
this->binary_sensor_->add_on_state_callback([this](bool state) {
|
||||
this->cluster_attributes_->present_value = state ? ZB_TRUE : ZB_FALSE;
|
||||
ESP_LOGD(TAG, "Set attribute end point: %d, present_value %d", this->end_point_,
|
||||
ESP_LOGD(TAG, "Set attribute endpoint: %d, present_value %d", this->endpoint_,
|
||||
this->cluster_attributes_->present_value);
|
||||
ZB_ZCL_SET_ATTRIBUTE(this->end_point_, ZB_ZCL_CLUSTER_ID_BINARY_INPUT, ZB_ZCL_CLUSTER_SERVER_ROLE,
|
||||
ZB_ZCL_SET_ATTRIBUTE(this->endpoint_, ZB_ZCL_CLUSTER_ID_BINARY_INPUT, ZB_ZCL_CLUSTER_SERVER_ROLE,
|
||||
ZB_ZCL_ATTR_BINARY_INPUT_PRESENT_VALUE_ID, &this->cluster_attributes_->present_value,
|
||||
ZB_FALSE);
|
||||
this->parent_->flush();
|
||||
@@ -29,8 +29,8 @@ void ZigbeeBinarySensor::setup() {
|
||||
void ZigbeeBinarySensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Zigbee Binary Sensor\n"
|
||||
" End point: %d, present_value %u",
|
||||
this->end_point_, this->cluster_attributes_->present_value);
|
||||
" Endpoint: %d, present_value %u",
|
||||
this->endpoint_, this->cluster_attributes_->present_value);
|
||||
}
|
||||
|
||||
} // namespace esphome::zigbee
|
||||
|
||||
76
esphome/components/zigbee/zigbee_sensor_zephyr.cpp
Normal file
76
esphome/components/zigbee/zigbee_sensor_zephyr.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "zigbee_sensor_zephyr.h"
|
||||
#if defined(USE_ZIGBEE) && defined(USE_NRF52) && defined(USE_SENSOR)
|
||||
#include "esphome/core/log.h"
|
||||
extern "C" {
|
||||
#include <zboss_api.h>
|
||||
#include <zboss_api_addons.h>
|
||||
#include <zb_nrf_platform.h>
|
||||
#include <zigbee/zigbee_app_utils.h>
|
||||
#include <zb_error_to_string.h>
|
||||
}
|
||||
namespace esphome::zigbee {
|
||||
|
||||
static const char *const TAG = "zigbee.sensor";
|
||||
|
||||
ZigbeeSensor::ZigbeeSensor(sensor::Sensor *sensor) : sensor_(sensor) {}
|
||||
|
||||
void ZigbeeSensor::setup() {
|
||||
this->sensor_->add_on_state_callback([this](float state) {
|
||||
this->cluster_attributes_->present_value = state;
|
||||
ESP_LOGD(TAG, "Set attribute endpoint: %d, present_value %f", this->endpoint_, state);
|
||||
ZB_ZCL_SET_ATTRIBUTE(this->endpoint_, ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, ZB_ZCL_CLUSTER_SERVER_ROLE,
|
||||
ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID,
|
||||
(zb_uint8_t *) &this->cluster_attributes_->present_value, ZB_FALSE);
|
||||
this->parent_->flush();
|
||||
});
|
||||
}
|
||||
|
||||
void ZigbeeSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Zigbee Sensor\n"
|
||||
" Endpoint: %d, present_value %f",
|
||||
this->endpoint_, this->cluster_attributes_->present_value);
|
||||
}
|
||||
|
||||
const zb_uint8_t ZB_ZCL_ANALOG_INPUT_STATUS_FLAG_MAX_VALUE = 0x0F;
|
||||
|
||||
static zb_ret_t check_value_analog_server(zb_uint16_t attr_id, zb_uint8_t endpoint,
|
||||
zb_uint8_t *value) { // NOLINT(readability-non-const-parameter)
|
||||
zb_ret_t ret = RET_OK;
|
||||
ZVUNUSED(endpoint);
|
||||
|
||||
switch (attr_id) {
|
||||
case ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID:
|
||||
ret = ZB_ZCL_CHECK_BOOL_VALUE(*value) ? RET_OK : RET_ERROR;
|
||||
break;
|
||||
case ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID:
|
||||
break;
|
||||
|
||||
case ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID:
|
||||
if (*value > ZB_ZCL_ANALOG_INPUT_STATUS_FLAG_MAX_VALUE) {
|
||||
ret = RET_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace esphome::zigbee
|
||||
|
||||
void zb_zcl_analog_input_init_server() {
|
||||
zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, ZB_ZCL_CLUSTER_SERVER_ROLE,
|
||||
esphome::zigbee::check_value_analog_server, (zb_zcl_cluster_write_attr_hook_t) NULL,
|
||||
(zb_zcl_cluster_handler_t) NULL);
|
||||
}
|
||||
|
||||
void zb_zcl_analog_input_init_client() {
|
||||
zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, ZB_ZCL_CLUSTER_CLIENT_ROLE,
|
||||
(zb_zcl_cluster_check_value_t) NULL, (zb_zcl_cluster_write_attr_hook_t) NULL,
|
||||
(zb_zcl_cluster_handler_t) NULL);
|
||||
}
|
||||
|
||||
#endif
|
||||
86
esphome/components/zigbee/zigbee_sensor_zephyr.h
Normal file
86
esphome/components/zigbee/zigbee_sensor_zephyr.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/zigbee/zigbee_zephyr.h"
|
||||
#if defined(USE_ZIGBEE) && defined(USE_NRF52) && defined(USE_SENSOR)
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
extern "C" {
|
||||
#include <zboss_api.h>
|
||||
#include <zboss_api_addons.h>
|
||||
}
|
||||
|
||||
enum {
|
||||
ZB_ZCL_ATTR_ANALOG_INPUT_DESCRIPTION_ID = 0x001C,
|
||||
ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID = 0x0051,
|
||||
ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID = 0x0055,
|
||||
ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID = 0x006F,
|
||||
ZB_ZCL_ATTR_ANALOG_INPUT_ENGINEERING_UNITS_ID = 0x0075,
|
||||
};
|
||||
|
||||
#define ZB_ZCL_ANALOG_INPUT_CLUSTER_REVISION_DEFAULT ((zb_uint16_t) 0x0001u)
|
||||
|
||||
#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_DESCRIPTION_ID(data_ptr) \
|
||||
{ \
|
||||
ZB_ZCL_ATTR_ANALOG_INPUT_DESCRIPTION_ID, ZB_ZCL_ATTR_TYPE_CHAR_STRING, ZB_ZCL_ATTR_ACCESS_READ_ONLY, \
|
||||
(ZB_ZCL_NON_MANUFACTURER_SPECIFIC), (void *) (data_ptr) \
|
||||
}
|
||||
|
||||
#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID(data_ptr) \
|
||||
{ \
|
||||
ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID, ZB_ZCL_ATTR_TYPE_BOOL, \
|
||||
ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_WRITE_OPTIONAL, (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), \
|
||||
(void *) (data_ptr) \
|
||||
}
|
||||
|
||||
#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID(data_ptr) \
|
||||
{ \
|
||||
ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID, ZB_ZCL_ATTR_TYPE_SINGLE, \
|
||||
ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_WRITE_OPTIONAL | ZB_ZCL_ATTR_ACCESS_REPORTING, \
|
||||
(ZB_ZCL_NON_MANUFACTURER_SPECIFIC), (void *) (data_ptr) \
|
||||
}
|
||||
|
||||
#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID(data_ptr) \
|
||||
{ \
|
||||
ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID, ZB_ZCL_ATTR_TYPE_8BITMAP, \
|
||||
ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_REPORTING, (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), \
|
||||
(void *) (data_ptr) \
|
||||
}
|
||||
|
||||
#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_ENGINEERING_UNITS_ID(data_ptr) \
|
||||
{ \
|
||||
ZB_ZCL_ATTR_ANALOG_INPUT_ENGINEERING_UNITS_ID, ZB_ZCL_ATTR_TYPE_16BIT_ENUM, ZB_ZCL_ATTR_ACCESS_READ_ONLY, \
|
||||
(ZB_ZCL_NON_MANUFACTURER_SPECIFIC), (void *) (data_ptr) \
|
||||
}
|
||||
|
||||
#define ESPHOME_ZB_ZCL_DECLARE_ANALOG_INPUT_ATTRIB_LIST(attr_list, out_of_service, present_value, status_flag, \
|
||||
engineering_units, description) \
|
||||
ZB_ZCL_START_DECLARE_ATTRIB_LIST_CLUSTER_REVISION(attr_list, ZB_ZCL_ANALOG_INPUT) \
|
||||
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID, (out_of_service)) \
|
||||
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID, (present_value)) \
|
||||
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID, (status_flag)) \
|
||||
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_ENGINEERING_UNITS_ID, (engineering_units)) \
|
||||
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_DESCRIPTION_ID, (description)) \
|
||||
ZB_ZCL_FINISH_DECLARE_ATTRIB_LIST
|
||||
|
||||
void zb_zcl_analog_input_init_server();
|
||||
void zb_zcl_analog_input_init_client();
|
||||
#define ZB_ZCL_CLUSTER_ID_ANALOG_INPUT_SERVER_ROLE_INIT zb_zcl_analog_input_init_server
|
||||
#define ZB_ZCL_CLUSTER_ID_ANALOG_INPUT_CLIENT_ROLE_INIT zb_zcl_analog_input_init_client
|
||||
|
||||
namespace esphome::zigbee {
|
||||
|
||||
class ZigbeeSensor : public ZigbeeEntity, public Component {
|
||||
public:
|
||||
explicit ZigbeeSensor(sensor::Sensor *sensor);
|
||||
void set_cluster_attributes(AnalogAttrs &cluster_attributes) { this->cluster_attributes_ = &cluster_attributes; }
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
AnalogAttrs *cluster_attributes_{nullptr};
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace esphome::zigbee
|
||||
#endif
|
||||
@@ -138,9 +138,26 @@ void ZigbeeComponent::setup() {
|
||||
}
|
||||
|
||||
#ifdef USE_ZIGBEE_WIPE_ON_BOOT
|
||||
erase_flash_(FIXED_PARTITION_ID(ZBOSS_NVRAM));
|
||||
erase_flash_(FIXED_PARTITION_ID(ZBOSS_PRODUCT_CONFIG));
|
||||
erase_flash_(FIXED_PARTITION_ID(SETTINGS_STORAGE));
|
||||
bool wipe = true;
|
||||
#ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC
|
||||
// unique hash to store preferences for this component
|
||||
uint32_t hash = 88498616UL;
|
||||
uint32_t wipe_value = 0;
|
||||
auto wipe_pref = global_preferences->make_preference<uint32_t>(hash, true);
|
||||
if (wipe_pref.load(&wipe_value)) {
|
||||
wipe = wipe_value != USE_ZIGBEE_WIPE_ON_BOOT_MAGIC;
|
||||
ESP_LOGD(TAG, "Wipe value in preferences %u, in firmware %u", wipe_value, USE_ZIGBEE_WIPE_ON_BOOT_MAGIC);
|
||||
}
|
||||
#endif
|
||||
if (wipe) {
|
||||
erase_flash_(FIXED_PARTITION_ID(ZBOSS_NVRAM));
|
||||
erase_flash_(FIXED_PARTITION_ID(ZBOSS_PRODUCT_CONFIG));
|
||||
erase_flash_(FIXED_PARTITION_ID(SETTINGS_STORAGE));
|
||||
#ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC
|
||||
wipe_value = USE_ZIGBEE_WIPE_ON_BOOT_MAGIC;
|
||||
wipe_pref.save(&wipe_value);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
ZB_ZCL_REGISTER_DEVICE_CB(zcl_device_cb);
|
||||
@@ -152,15 +169,54 @@ void ZigbeeComponent::setup() {
|
||||
zigbee_enable();
|
||||
}
|
||||
|
||||
void ZigbeeComponent::dump_config() {
|
||||
bool wipe = false;
|
||||
static const char *role() {
|
||||
switch (zb_get_network_role()) {
|
||||
case ZB_NWK_DEVICE_TYPE_COORDINATOR:
|
||||
return "coordinator";
|
||||
case ZB_NWK_DEVICE_TYPE_ROUTER:
|
||||
return "router";
|
||||
case ZB_NWK_DEVICE_TYPE_ED:
|
||||
return "end device";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static const char *get_wipe_on_boot() {
|
||||
#ifdef USE_ZIGBEE_WIPE_ON_BOOT
|
||||
wipe = true;
|
||||
#ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC
|
||||
return "ONCE";
|
||||
#else
|
||||
return "YES";
|
||||
#endif
|
||||
#else
|
||||
return "NO";
|
||||
#endif
|
||||
}
|
||||
|
||||
void ZigbeeComponent::dump_config() {
|
||||
char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = {0};
|
||||
zb_ieee_addr_t addr;
|
||||
zb_get_long_address(addr);
|
||||
ieee_addr_to_str(ieee_addr_buf, sizeof(ieee_addr_buf), addr);
|
||||
zb_ext_pan_id_t extended_pan_id;
|
||||
char extended_pan_id_buf[IEEE_ADDR_BUF_SIZE] = {0};
|
||||
zb_get_extended_pan_id(extended_pan_id);
|
||||
ieee_addr_to_str(extended_pan_id_buf, sizeof(extended_pan_id_buf), extended_pan_id);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Zigbee\n"
|
||||
" Wipe on boot: %s",
|
||||
YESNO(wipe));
|
||||
" Wipe on boot: %s\n"
|
||||
" Device is joined to the network: %s\n"
|
||||
" Current channel: %d\n"
|
||||
" Current page: %d\n"
|
||||
" Sleep threshold: %ums\n"
|
||||
" Role: %s\n"
|
||||
" Long addr: 0x%s\n"
|
||||
" Short addr: 0x%04X\n"
|
||||
" Long pan id: 0x%s\n"
|
||||
" Short pan id: 0x%04X",
|
||||
get_wipe_on_boot(), YESNO(zb_zdo_joined()), zb_get_current_channel(), zb_get_current_page(),
|
||||
zb_get_sleep_threshold(), role(), ieee_addr_buf, zb_get_short_address(), extended_pan_id_buf,
|
||||
zb_get_pan_id());
|
||||
}
|
||||
|
||||
static void send_attribute_report(zb_bufid_t bufid, zb_uint16_t cmd_id) {
|
||||
|
||||
@@ -28,16 +28,15 @@ extern "C" {
|
||||
ESPHOME_CAT7(zb_af_simple_desc_, ep_name, _, in_num, _, out_num, _t)
|
||||
|
||||
// needed to use ESPHOME_ZB_DECLARE_SIMPLE_DESC
|
||||
#define ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC(ep_name, ep_id, in_clust_num, out_clust_num, ...) \
|
||||
#define ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC(ep_name, ep_id, in_clust_num, out_clust_num, app_device_id, ...) \
|
||||
ESPHOME_ZB_DECLARE_SIMPLE_DESC(ep_name, in_clust_num, out_clust_num); \
|
||||
ESPHOME_ZB_AF_SIMPLE_DESC_TYPE(ep_name, in_clust_num, out_clust_num) \
|
||||
simple_desc_##ep_name = {ep_id, ZB_AF_HA_PROFILE_ID, ZB_HA_SIMPLE_SENSOR_DEVICE_ID, 0, 0, in_clust_num, \
|
||||
out_clust_num, {__VA_ARGS__}}
|
||||
simple_desc_##ep_name = {ep_id, ZB_AF_HA_PROFILE_ID, app_device_id, 0, 0, in_clust_num, out_clust_num, {__VA_ARGS__}}
|
||||
|
||||
// needed to use ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC
|
||||
#define ESPHOME_ZB_HA_DECLARE_EP(ep_name, ep_id, cluster_list, in_cluster_num, out_cluster_num, report_attr_count, \
|
||||
...) \
|
||||
ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC(ep_name, ep_id, in_cluster_num, out_cluster_num, __VA_ARGS__); \
|
||||
app_device_id, ...) \
|
||||
ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC(ep_name, ep_id, in_cluster_num, out_cluster_num, app_device_id, __VA_ARGS__); \
|
||||
ZBOSS_DEVICE_DECLARE_REPORTING_CTX(reporting_info##ep_name, report_attr_count); \
|
||||
ZB_AF_DECLARE_ENDPOINT_DESC(ep_name, ep_id, ZB_AF_HA_PROFILE_ID, 0, NULL, \
|
||||
ZB_ZCL_ARRAY_SIZE(cluster_list, zb_zcl_cluster_desc_t), cluster_list, \
|
||||
@@ -57,10 +56,8 @@ struct AnalogAttrs {
|
||||
zb_bool_t out_of_service;
|
||||
float present_value;
|
||||
zb_uint8_t status_flags;
|
||||
zb_uint16_t engineering_units;
|
||||
zb_uchar_t description[ZB_ZCL_MAX_STRING_SIZE];
|
||||
float max_present_value;
|
||||
float min_present_value;
|
||||
float resolution;
|
||||
};
|
||||
|
||||
class ZigbeeComponent : public Component {
|
||||
@@ -93,10 +90,10 @@ class ZigbeeComponent : public Component {
|
||||
class ZigbeeEntity {
|
||||
public:
|
||||
void set_parent(ZigbeeComponent *parent) { this->parent_ = parent; }
|
||||
void set_end_point(zb_uint8_t end_point) { this->end_point_ = end_point; }
|
||||
void set_endpoint(zb_uint8_t endpoint) { this->endpoint_ = endpoint; }
|
||||
|
||||
protected:
|
||||
zb_uint8_t end_point_{0};
|
||||
zb_uint8_t endpoint_{0};
|
||||
ZigbeeComponent *parent_{nullptr};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,45 @@
|
||||
from datetime import datetime
|
||||
import random
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.zephyr import zephyr_add_prj_conf
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_NAME, __version__
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_NAME,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_CENTIMETER,
|
||||
UNIT_DECIBEL,
|
||||
UNIT_HECTOPASCAL,
|
||||
UNIT_HERTZ,
|
||||
UNIT_HOUR,
|
||||
UNIT_KELVIN,
|
||||
UNIT_KILOMETER,
|
||||
UNIT_KILOWATT,
|
||||
UNIT_KILOWATT_HOURS,
|
||||
UNIT_LUX,
|
||||
UNIT_METER,
|
||||
UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||
UNIT_MILLIAMP,
|
||||
UNIT_MILLIGRAMS_PER_CUBIC_METER,
|
||||
UNIT_MILLIMETER,
|
||||
UNIT_MILLISECOND,
|
||||
UNIT_MILLIVOLT,
|
||||
UNIT_MINUTE,
|
||||
UNIT_OHM,
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
UNIT_PASCAL,
|
||||
UNIT_PERCENT,
|
||||
UNIT_SECOND,
|
||||
UNIT_VOLT,
|
||||
UNIT_WATT,
|
||||
UNIT_WATT_HOURS,
|
||||
__version__,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.cpp_generator import (
|
||||
AssignmentExpression,
|
||||
@@ -15,22 +50,63 @@ from esphome.types import ConfigType
|
||||
|
||||
from .const_zephyr import (
|
||||
CONF_ON_JOIN,
|
||||
CONF_POWER_SOURCE,
|
||||
CONF_WIPE_ON_BOOT,
|
||||
CONF_ZIGBEE_BINARY_SENSOR,
|
||||
CONF_ZIGBEE_ID,
|
||||
CONF_ZIGBEE_SENSOR,
|
||||
KEY_EP_NUMBER,
|
||||
KEY_ZIGBEE,
|
||||
POWER_SOURCE,
|
||||
ZB_ZCL_BASIC_ATTRS_EXT_T,
|
||||
ZB_ZCL_CLUSTER_ID_ANALOG_INPUT,
|
||||
ZB_ZCL_CLUSTER_ID_BASIC,
|
||||
ZB_ZCL_CLUSTER_ID_BINARY_INPUT,
|
||||
ZB_ZCL_CLUSTER_ID_IDENTIFY,
|
||||
ZB_ZCL_IDENTIFY_ATTRS_T,
|
||||
AnalogAttrs,
|
||||
BinaryAttrs,
|
||||
ZigbeeComponent,
|
||||
zigbee_ns,
|
||||
)
|
||||
|
||||
ZigbeeBinarySensor = zigbee_ns.class_("ZigbeeBinarySensor", cg.Component)
|
||||
ZigbeeSensor = zigbee_ns.class_("ZigbeeSensor", cg.Component)
|
||||
|
||||
# BACnet engineering units mapping (ZCL uses BACnet unit codes)
|
||||
# See: https://github.com/zigpy/zha/blob/dev/zha/application/platforms/number/bacnet.py
|
||||
BACNET_UNITS = {
|
||||
UNIT_CELSIUS: 62,
|
||||
UNIT_KELVIN: 63,
|
||||
UNIT_VOLT: 5,
|
||||
UNIT_MILLIVOLT: 124,
|
||||
UNIT_AMPERE: 3,
|
||||
UNIT_MILLIAMP: 2,
|
||||
UNIT_OHM: 4,
|
||||
UNIT_WATT: 47,
|
||||
UNIT_KILOWATT: 48,
|
||||
UNIT_WATT_HOURS: 18,
|
||||
UNIT_KILOWATT_HOURS: 19,
|
||||
UNIT_PASCAL: 53,
|
||||
UNIT_HECTOPASCAL: 133,
|
||||
UNIT_HERTZ: 27,
|
||||
UNIT_MILLIMETER: 30,
|
||||
UNIT_CENTIMETER: 118,
|
||||
UNIT_METER: 31,
|
||||
UNIT_KILOMETER: 193,
|
||||
UNIT_MILLISECOND: 159,
|
||||
UNIT_SECOND: 73,
|
||||
UNIT_MINUTE: 72,
|
||||
UNIT_HOUR: 71,
|
||||
UNIT_PARTS_PER_MILLION: 96,
|
||||
UNIT_PARTS_PER_BILLION: 97,
|
||||
UNIT_MICROGRAMS_PER_CUBIC_METER: 219,
|
||||
UNIT_MILLIGRAMS_PER_CUBIC_METER: 218,
|
||||
UNIT_LUX: 37,
|
||||
UNIT_DECIBEL: 199,
|
||||
UNIT_PERCENT: 98,
|
||||
}
|
||||
BACNET_UNIT_NO_UNITS = 95
|
||||
|
||||
zephyr_binary_sensor = cv.Schema(
|
||||
{
|
||||
@@ -41,6 +117,15 @@ zephyr_binary_sensor = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
zephyr_sensor = cv.Schema(
|
||||
{
|
||||
cv.OnlyWith(CONF_ZIGBEE_ID, ["nrf52", "zigbee"]): cv.use_id(ZigbeeComponent),
|
||||
cv.OnlyWith(CONF_ZIGBEE_SENSOR, ["nrf52", "zigbee"]): cv.declare_id(
|
||||
ZigbeeSensor
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def zephyr_to_code(config: ConfigType) -> None:
|
||||
zephyr_add_prj_conf("ZIGBEE", True)
|
||||
@@ -56,6 +141,10 @@ async def zephyr_to_code(config: ConfigType) -> None:
|
||||
zephyr_add_prj_conf("NET_UDP", False)
|
||||
|
||||
if config[CONF_WIPE_ON_BOOT]:
|
||||
if config[CONF_WIPE_ON_BOOT] == "once":
|
||||
cg.add_define(
|
||||
"USE_ZIGBEE_WIPE_ON_BOOT_MAGIC", random.randint(0x000001, 0xFFFFFF)
|
||||
)
|
||||
cg.add_define("USE_ZIGBEE_WIPE_ON_BOOT")
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
@@ -85,7 +174,7 @@ async def _attr_to_code(config: ConfigType) -> None:
|
||||
),
|
||||
zigbee_assign(
|
||||
basic_attrs.power_source,
|
||||
cg.RawExpression("ZB_ZCL_BASIC_POWER_SOURCE_DC_SOURCE"),
|
||||
cg.RawExpression(POWER_SOURCE[config[CONF_POWER_SOURCE]]),
|
||||
),
|
||||
zigbee_set_string(basic_attrs.location_id, ""),
|
||||
zigbee_assign(
|
||||
@@ -191,6 +280,7 @@ def zigbee_register_ep(
|
||||
report_attr_count: int,
|
||||
clusters: list[ZigbeeClusterDesc],
|
||||
slot_index: int,
|
||||
app_device_id: str,
|
||||
) -> None:
|
||||
"""Register a Zigbee endpoint."""
|
||||
in_cluster_num = sum(1 for c in clusters if c.has_attrs)
|
||||
@@ -204,7 +294,7 @@ def zigbee_register_ep(
|
||||
ep_id = slot_index + 1 # Endpoints are 1-indexed
|
||||
obj = cg.RawExpression(
|
||||
f"ESPHOME_ZB_HA_DECLARE_EP({ep_name}, {ep_id}, {cluster_list_name}, "
|
||||
f"{in_cluster_num}, {out_cluster_num}, {report_attr_count}, {', '.join(cluster_ids)})"
|
||||
f"{in_cluster_num}, {out_cluster_num}, {report_attr_count}, {app_device_id}, {', '.join(cluster_ids)})"
|
||||
)
|
||||
CORE.add_global(obj)
|
||||
|
||||
@@ -224,42 +314,102 @@ async def zephyr_setup_binary_sensor(entity: cg.MockObj, config: ConfigType) ->
|
||||
CORE.add_job(_add_binary_sensor, entity, config)
|
||||
|
||||
|
||||
async def _add_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
# Find the next available endpoint slot
|
||||
slot_index = next(
|
||||
async def zephyr_setup_sensor(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
CORE.add_job(_add_sensor, entity, config)
|
||||
|
||||
|
||||
def _slot_index() -> int:
|
||||
"""Find the next available endpoint slot"""
|
||||
slot = next(
|
||||
(i for i, v in enumerate(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER]) if v == ""), None
|
||||
)
|
||||
if slot is None:
|
||||
raise cv.Invalid(
|
||||
f"Not found empty slot, size ({len(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER])})"
|
||||
)
|
||||
return slot
|
||||
|
||||
|
||||
async def _add_zigbee_input(
|
||||
entity: cg.MockObj,
|
||||
config: ConfigType,
|
||||
component_key,
|
||||
attrs_type,
|
||||
zcl_macro: str,
|
||||
cluster_id: str,
|
||||
app_device_id: str,
|
||||
extra_field_values: dict[str, int] | None = None,
|
||||
) -> None:
|
||||
slot_index = _slot_index()
|
||||
|
||||
# Create unique names for this sensor's variables based on slot index
|
||||
prefix = f"zigbee_ep{slot_index + 1}"
|
||||
attrs_name = f"{prefix}_binary_attrs"
|
||||
attr_list_name = f"{prefix}_binary_input_attrib_list"
|
||||
attrs_name = f"{prefix}_attrs"
|
||||
attr_list_name = f"{prefix}_attrib_list"
|
||||
cluster_list_name = f"{prefix}_cluster_list"
|
||||
ep_name = f"{prefix}_ep"
|
||||
|
||||
# Create the binary attributes structure
|
||||
binary_attrs = zigbee_new_variable(attrs_name, BinaryAttrs)
|
||||
attr_list = zigbee_new_attr_list(
|
||||
attr_list_name,
|
||||
"ESPHOME_ZB_ZCL_DECLARE_BINARY_INPUT_ATTRIB_LIST",
|
||||
zigbee_assign(binary_attrs.out_of_service, 0),
|
||||
zigbee_assign(binary_attrs.present_value, 0),
|
||||
zigbee_assign(binary_attrs.status_flags, 0),
|
||||
zigbee_set_string(binary_attrs.description, config[CONF_NAME]),
|
||||
)
|
||||
# Create attribute struct
|
||||
attrs = zigbee_new_variable(attrs_name, attrs_type)
|
||||
|
||||
# Build attribute list args
|
||||
attr_args = [
|
||||
zigbee_assign(attrs.out_of_service, 0),
|
||||
zigbee_assign(attrs.present_value, 0),
|
||||
zigbee_assign(attrs.status_flags, 0),
|
||||
]
|
||||
# Add extra field assignments (e.g., engineering_units for sensors)
|
||||
if extra_field_values:
|
||||
for field_name, value in extra_field_values.items():
|
||||
attr_args.append(zigbee_assign(getattr(attrs, field_name), value))
|
||||
attr_args.append(zigbee_set_string(attrs.description, config[CONF_NAME]))
|
||||
|
||||
# Create attribute list
|
||||
attr_list = zigbee_new_attr_list(attr_list_name, zcl_macro, *attr_args)
|
||||
|
||||
# Create cluster list and register endpoint
|
||||
cluster_list_name, clusters = zigbee_new_cluster_list(
|
||||
cluster_list_name,
|
||||
[ZigbeeClusterDesc(ZB_ZCL_CLUSTER_ID_BINARY_INPUT, attr_list)],
|
||||
[ZigbeeClusterDesc(cluster_id, attr_list)],
|
||||
)
|
||||
zigbee_register_ep(
|
||||
ep_name, cluster_list_name, 2, clusters, slot_index, app_device_id
|
||||
)
|
||||
zigbee_register_ep(ep_name, cluster_list_name, 2, clusters, slot_index)
|
||||
|
||||
# Create the ZigbeeBinarySensor component
|
||||
var = cg.new_Pvariable(config[CONF_ZIGBEE_BINARY_SENSOR], entity)
|
||||
await cg.register_component(var, config)
|
||||
# Create ESPHome component
|
||||
var = cg.new_Pvariable(config[component_key], entity)
|
||||
await cg.register_component(var, {})
|
||||
|
||||
cg.add(var.set_endpoint(slot_index + 1))
|
||||
cg.add(var.set_cluster_attributes(attrs))
|
||||
|
||||
cg.add(var.set_end_point(slot_index + 1))
|
||||
cg.add(var.set_cluster_attributes(binary_attrs))
|
||||
hub = await cg.get_variable(config[CONF_ZIGBEE_ID])
|
||||
cg.add(var.set_parent(hub))
|
||||
|
||||
|
||||
async def _add_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
await _add_zigbee_input(
|
||||
entity,
|
||||
config,
|
||||
CONF_ZIGBEE_BINARY_SENSOR,
|
||||
BinaryAttrs,
|
||||
"ESPHOME_ZB_ZCL_DECLARE_BINARY_INPUT_ATTRIB_LIST",
|
||||
ZB_ZCL_CLUSTER_ID_BINARY_INPUT,
|
||||
"ZB_HA_SIMPLE_SENSOR_DEVICE_ID",
|
||||
)
|
||||
|
||||
|
||||
async def _add_sensor(entity: cg.MockObj, config: ConfigType) -> None:
|
||||
# Get BACnet engineering unit from unit_of_measurement
|
||||
unit = config.get(CONF_UNIT_OF_MEASUREMENT, "")
|
||||
bacnet_unit = BACNET_UNITS.get(unit, BACNET_UNIT_NO_UNITS)
|
||||
|
||||
await _add_zigbee_input(
|
||||
entity,
|
||||
config,
|
||||
CONF_ZIGBEE_SENSOR,
|
||||
AnalogAttrs,
|
||||
"ESPHOME_ZB_ZCL_DECLARE_ANALOG_INPUT_ATTRIB_LIST",
|
||||
ZB_ZCL_CLUSTER_ID_ANALOG_INPUT,
|
||||
"ZB_HA_CUSTOM_ATTR_DEVICE_ID",
|
||||
extra_field_values={"engineering_units": bacnet_unit},
|
||||
)
|
||||
|
||||
@@ -305,6 +305,7 @@
|
||||
#define USE_SOFTDEVICE_VERSION 1
|
||||
#define USE_ZIGBEE
|
||||
#define USE_ZIGBEE_WIPE_ON_BOOT
|
||||
#define USE_ZIGBEE_WIPE_ON_BOOT_MAGIC 1
|
||||
#define ZIGBEE_ENDPOINTS_COUNT 8
|
||||
#endif
|
||||
|
||||
|
||||
@@ -15,10 +15,14 @@ binary_sensor:
|
||||
- platform: template
|
||||
name: "Garage Door Open 7"
|
||||
internal: True
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
name: "Garage Door Open 8"
|
||||
name: "Analog 1"
|
||||
lambda: return 10.0;
|
||||
- platform: template
|
||||
name: "Garage Door Open 9"
|
||||
name: "Analog 2"
|
||||
lambda: return 11.0;
|
||||
|
||||
zigbee:
|
||||
wipe_on_boot: true
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
zigbee:
|
||||
wipe_on_boot: once
|
||||
power_source: battery
|
||||
|
||||
Reference in New Issue
Block a user