mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
[zigbee] Improve basic cluster and add manufacturer and app version setting (#20519)
This commit is contained in:
@@ -3,6 +3,7 @@ from typing import Any
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_MANUFACTURER
|
||||
from esphome.components.esp32 import DOMAIN as ESP32_DOMAIN, only_on_variant
|
||||
from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32C5,
|
||||
@@ -14,14 +15,19 @@ from esphome.components.esp32.const import (
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ESPHOME,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_MODEL,
|
||||
CONF_NAME,
|
||||
CONF_ON_START,
|
||||
CONF_POLLING_INTERVAL,
|
||||
CONF_PROJECT,
|
||||
CONF_VERSION,
|
||||
__version__ as ESPHOME_VERSION,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .const import (
|
||||
@@ -36,6 +42,8 @@ from .const import (
|
||||
CONF_USE_DEVICE_TYPE,
|
||||
CONF_WIPE_ON_BOOT,
|
||||
KEY_ZIGBEE,
|
||||
KEY_ZIGBEE_AREA_STR,
|
||||
KEY_ZIGBEE_VERSION_STR,
|
||||
POWER_SOURCE,
|
||||
REPORT,
|
||||
ZigbeeComponent,
|
||||
@@ -143,12 +151,33 @@ def _validate_router_sleepy(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
def _validate_ascii(value):
|
||||
"""Validate that a string contains only ascii characters."""
|
||||
if not isinstance(value, str):
|
||||
raise cv.Invalid("expected string")
|
||||
if not value.isascii():
|
||||
raise cv.Invalid("Only ASCII characters are allowed")
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(ZigbeeComponent),
|
||||
cv.Optional(CONF_MODEL, default=CORE.name): cv.All(
|
||||
cv.string, cv.Length(max=31)
|
||||
cv.string,
|
||||
cv.Length(max=31),
|
||||
_validate_ascii,
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_MANUFACTURER, default="esphome", visibility=cv.Visibility.YAML_ONLY
|
||||
): cv.All(
|
||||
cv.string,
|
||||
cv.Length(max=31),
|
||||
_validate_ascii,
|
||||
),
|
||||
cv.Optional(CONF_VERSION, default=0): cv.All(
|
||||
cv.int_range(min=0x00, max=0xFF),
|
||||
),
|
||||
cv.Optional(CONF_ROUTER, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_JOIN): automation.validate_automation({}),
|
||||
@@ -207,7 +236,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
def validate_number_of_ep(config: ConfigType) -> ConfigType:
|
||||
if not CORE.is_nrf52:
|
||||
return config
|
||||
if KEY_ZIGBEE not in CORE.data:
|
||||
if KEY_EP_NUMBER not in CORE.data.get(KEY_ZIGBEE, {}):
|
||||
raise cv.Invalid("At least one zigbee device need to be included")
|
||||
count = len(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER])
|
||||
if count == 1:
|
||||
@@ -220,7 +249,38 @@ def validate_number_of_ep(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
def final_validate_strings(config: ConfigType) -> ConfigType:
|
||||
version_str = ESPHOME_VERSION
|
||||
if (
|
||||
proj_version := fv.full_config.get()
|
||||
.get(CONF_ESPHOME, {})
|
||||
.get(CONF_PROJECT, {})
|
||||
.get(CONF_VERSION)
|
||||
):
|
||||
version_str = proj_version.encode("ascii", "ignore")[:15].decode()
|
||||
if version_str != proj_version:
|
||||
_LOGGER.warning(
|
||||
"esphome.project.version %s was changed to %s for use in Zigbee attributes. Use only a maximum of 15 ASCII characters",
|
||||
proj_version,
|
||||
version_str,
|
||||
)
|
||||
area_str = ""
|
||||
if CORE.area:
|
||||
area_str = CORE.area.encode("ascii", "ignore")[:15].decode()
|
||||
if area_str != CORE.area:
|
||||
_LOGGER.warning(
|
||||
"esphome.area %s was changed to %s for use in Zigbee attributes. Use only a maximum of 15 ASCII characters",
|
||||
CORE.area,
|
||||
area_str,
|
||||
)
|
||||
data: dict[str, Any] = CORE.data.setdefault(KEY_ZIGBEE, {})
|
||||
data[KEY_ZIGBEE_VERSION_STR] = version_str
|
||||
data[KEY_ZIGBEE_AREA_STR] = area_str
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
final_validate_strings,
|
||||
validate_number_of_ep,
|
||||
final_validate_esp32,
|
||||
)
|
||||
|
||||
@@ -85,6 +85,8 @@ POWER_SOURCE = {
|
||||
}
|
||||
|
||||
KEY_ZIGBEE = "zigbee"
|
||||
KEY_ZIGBEE_AREA_STR = "zigbee_area_str"
|
||||
KEY_ZIGBEE_VERSION_STR = "zigbee_version_str"
|
||||
|
||||
# BACnet engineering units mapping (ZCL uses BACnet unit codes)
|
||||
# See: https://github.com/zigpy/zha/blob/dev/zha/application/platforms/number/bacnet.py
|
||||
|
||||
@@ -238,7 +238,8 @@ void ZigbeeComponent::add_cluster(uint8_t endpoint_id, uint16_t cluster_id, uint
|
||||
ESP_LOGD(TAG, "Endpoint %u: Added cluster 0x%04X with role %u", endpoint_id, cluster_id, role);
|
||||
}
|
||||
|
||||
void ZigbeeComponent::set_basic_cluster(const char *model, const char *manufacturer, uint8_t power_source) {
|
||||
void ZigbeeComponent::set_basic_cluster(const char *model, const char *manufacturer, const char *location,
|
||||
const char *sw_build_id, uint8_t power_source, uint8_t app_version) {
|
||||
char date_buf[16];
|
||||
time_t time_val = App.get_build_time();
|
||||
struct tm *timeinfo = localtime(&time_val);
|
||||
@@ -247,7 +248,10 @@ void ZigbeeComponent::set_basic_cluster(const char *model, const char *manufactu
|
||||
.model = get_zcl_string(model, 31),
|
||||
.manufacturer = get_zcl_string(manufacturer, 31),
|
||||
.date = get_zcl_string(date_buf, 15),
|
||||
.location = get_zcl_string(location, 15),
|
||||
.sw_build_id = get_zcl_string(sw_build_id, 15),
|
||||
.power_source = power_source,
|
||||
.app_version = app_version,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -267,6 +271,16 @@ void ZigbeeComponent::update_basic_cluster_(ezb_af_ep_desc_t ep_desc) {
|
||||
ezb_zcl_basic_cluster_desc_add_attr(cluster_desc, EZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID,
|
||||
this->basic_cluster_data_.model);
|
||||
ezb_zcl_basic_cluster_desc_add_attr(cluster_desc, EZB_ZCL_ATTR_BASIC_DATE_CODE_ID, this->basic_cluster_data_.date);
|
||||
ezb_zcl_basic_cluster_desc_add_attr(cluster_desc, EZB_ZCL_ATTR_BASIC_APPLICATION_VERSION_ID,
|
||||
&(this->basic_cluster_data_.app_version));
|
||||
uint8_t stack_version = 0;
|
||||
uint8_t hw_version = 0;
|
||||
ezb_zcl_basic_cluster_desc_add_attr(cluster_desc, EZB_ZCL_ATTR_BASIC_STACK_VERSION_ID, &stack_version);
|
||||
ezb_zcl_basic_cluster_desc_add_attr(cluster_desc, EZB_ZCL_ATTR_BASIC_HW_VERSION_ID, &hw_version);
|
||||
ezb_zcl_basic_cluster_desc_add_attr(cluster_desc, EZB_ZCL_ATTR_BASIC_LOCATION_DESCRIPTION_ID,
|
||||
this->basic_cluster_data_.location);
|
||||
ezb_zcl_basic_cluster_desc_add_attr(cluster_desc, EZB_ZCL_ATTR_BASIC_SW_BUILD_ID_ID,
|
||||
this->basic_cluster_data_.sw_build_id);
|
||||
}
|
||||
|
||||
bool ZigbeeComponent::register_device() {
|
||||
@@ -420,37 +434,35 @@ void ZigbeeComponent::loop() {
|
||||
}
|
||||
|
||||
void ZigbeeComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Zigbee\n"
|
||||
" Model: %.*s\n"
|
||||
" Manufacturer: %.*s\n"
|
||||
#ifdef CONFIG_ZB_ZCZR
|
||||
" Router: %s\n"
|
||||
#else
|
||||
" Sleepy end device: %s\n"
|
||||
" Poll interval: %" PRIu32 " ms\n"
|
||||
#endif
|
||||
" App Version: %u",
|
||||
this->basic_cluster_data_.model[0], reinterpret_cast<const char *>(this->basic_cluster_data_.model + 1),
|
||||
this->basic_cluster_data_.manufacturer[0],
|
||||
reinterpret_cast<const char *>(this->basic_cluster_data_.manufacturer + 1),
|
||||
#ifdef CONFIG_ZB_ZCZR
|
||||
YESNO(this->device_role_ == EZB_NWK_DEVICE_TYPE_ROUTER),
|
||||
#else
|
||||
YESNO(this->sleepy_), this->keep_alive_,
|
||||
#endif
|
||||
this->basic_cluster_data_.app_version);
|
||||
if (esp_zigbee_lock_acquire(10 / portTICK_PERIOD_MS)) {
|
||||
ESP_LOGCONFIG(
|
||||
TAG,
|
||||
"Zigbee\n"
|
||||
" Model: %.*s\n"
|
||||
#ifdef CONFIG_ZB_ZCZR
|
||||
" Router: %s\n"
|
||||
#else
|
||||
" Sleepy end device: %s\n"
|
||||
" Poll interval: %" PRIu32 " ms\n"
|
||||
#endif
|
||||
" Device is joined to the network: %s\n"
|
||||
" Current channel: %d\n"
|
||||
" Short addr: 0x%04X\n"
|
||||
" Short pan id: 0x%04X",
|
||||
this->basic_cluster_data_.model[0], reinterpret_cast<const char *>(this->basic_cluster_data_.model + 1),
|
||||
#ifdef CONFIG_ZB_ZCZR
|
||||
YESNO(this->device_role_ == EZB_NWK_DEVICE_TYPE_ROUTER),
|
||||
#else
|
||||
YESNO(this->sleepy_), this->keep_alive_,
|
||||
#endif
|
||||
YESNO(ezb_bdb_dev_joined()), ezb_nwk_get_current_channel(), ezb_nwk_get_short_address(), ezb_nwk_get_panid());
|
||||
esp_zigbee_lock_release();
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Zigbee\n"
|
||||
" Model: %.*s\n"
|
||||
" Router: %s\n",
|
||||
this->basic_cluster_data_.model[0],
|
||||
reinterpret_cast<const char *>(this->basic_cluster_data_.model + 1),
|
||||
YESNO(this->device_role_ == EZB_NWK_DEVICE_TYPE_ROUTER));
|
||||
" Device is joined to the network: %s\n"
|
||||
" Current channel: %d\n"
|
||||
" Short addr: 0x%04X\n"
|
||||
" Short pan id: 0x%04X",
|
||||
YESNO(ezb_bdb_dev_joined()), ezb_nwk_get_current_channel(), ezb_nwk_get_short_address(),
|
||||
ezb_nwk_get_panid());
|
||||
esp_zigbee_lock_release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,8 @@ class ZigbeeComponent final : public Component {
|
||||
|
||||
void set_sleepy(bool sleepy) { this->sleepy_ = sleepy; }
|
||||
void set_keep_alive(uint32_t keep_alive) { this->keep_alive_ = keep_alive; }
|
||||
void set_basic_cluster(const char *model, const char *manufacturer, uint8_t power_source);
|
||||
void set_basic_cluster(const char *model, const char *manufacturer, const char *location, const char *sw_build_id,
|
||||
uint8_t power_source, uint8_t app_version);
|
||||
void add_cluster(uint8_t endpoint_id, uint16_t cluster_id, uint8_t role);
|
||||
void create_default_cluster(uint8_t endpoint_id, uint16_t device_id);
|
||||
bool register_device();
|
||||
@@ -80,7 +81,10 @@ class ZigbeeComponent final : public Component {
|
||||
uint8_t *model;
|
||||
uint8_t *manufacturer;
|
||||
uint8_t *date;
|
||||
uint8_t *location;
|
||||
uint8_t *sw_build_id;
|
||||
uint8_t power_source;
|
||||
uint8_t app_version;
|
||||
} basic_cluster_data_;
|
||||
#ifdef CONFIG_ZB_ZED
|
||||
ezb_nwk_device_type_t device_role_ = EZB_NWK_DEVICE_TYPE_END_DEVICE;
|
||||
|
||||
@@ -4,6 +4,7 @@ import re
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_MANUFACTURER
|
||||
from esphome.components.esp32 import (
|
||||
CONF_PARTITIONS,
|
||||
add_idf_component,
|
||||
@@ -28,6 +29,7 @@ from esphome.const import (
|
||||
CONF_TYPE,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
CONF_VALUE,
|
||||
CONF_VERSION,
|
||||
CONF_WIFI,
|
||||
DEVICE_CLASS_OUTLET,
|
||||
)
|
||||
@@ -49,6 +51,8 @@ from .const import (
|
||||
CONF_SLEEPY,
|
||||
CONF_USE_DEVICE_TYPE,
|
||||
KEY_ZIGBEE,
|
||||
KEY_ZIGBEE_AREA_STR,
|
||||
KEY_ZIGBEE_VERSION_STR,
|
||||
POWER_SOURCE,
|
||||
ZigbeeAttribute,
|
||||
)
|
||||
@@ -418,12 +422,14 @@ async def esp32_to_code(config: ConfigType) -> "MockObj":
|
||||
cg.add(var.set_sleepy(True))
|
||||
if (keep_alive := config.get(CONF_POLLING_INTERVAL, 3000)) != 3000:
|
||||
cg.add(var.set_keep_alive(keep_alive))
|
||||
|
||||
cg.add(
|
||||
var.set_basic_cluster(
|
||||
config[CONF_MODEL],
|
||||
"esphome",
|
||||
config[CONF_MANUFACTURER],
|
||||
zb_data[KEY_ZIGBEE_AREA_STR],
|
||||
zb_data[KEY_ZIGBEE_VERSION_STR],
|
||||
POWER_SOURCE[config[CONF_POWER_SOURCE]],
|
||||
config[CONF_VERSION],
|
||||
)
|
||||
)
|
||||
for ep_num, ep in ep_dict.items():
|
||||
|
||||
@@ -2,6 +2,7 @@ import datetime
|
||||
import random
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_MANUFACTURER
|
||||
from esphome.components.zephyr import zephyr_add_prj_conf
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -9,7 +10,7 @@ from esphome.const import (
|
||||
CONF_MODEL,
|
||||
CONF_NAME,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
__version__,
|
||||
CONF_VERSION,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.cpp_generator import (
|
||||
@@ -27,6 +28,8 @@ from .const import (
|
||||
CONF_SLEEPY,
|
||||
CONF_WIPE_ON_BOOT,
|
||||
KEY_ZIGBEE,
|
||||
KEY_ZIGBEE_AREA_STR,
|
||||
KEY_ZIGBEE_VERSION_STR,
|
||||
POWER_SOURCE,
|
||||
AnalogAttrs,
|
||||
AnalogAttrsOutput,
|
||||
@@ -158,15 +161,16 @@ async def zephyr_to_code(config: ConfigType) -> "MockObj":
|
||||
|
||||
async def _attr_to_code(config: ConfigType) -> None:
|
||||
# Create the basic attributes structure and attribute list
|
||||
zb_data = CORE.data.get(KEY_ZIGBEE, {})
|
||||
basic_attrs = zigbee_new_variable("zigbee_basic_attrs", ZB_ZCL_BASIC_ATTRS_EXT_T)
|
||||
zigbee_new_attr_list(
|
||||
"zigbee_basic_attrib_list",
|
||||
"ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT",
|
||||
zigbee_assign(basic_attrs.zcl_version, cg.RawExpression("ZB_ZCL_VERSION")),
|
||||
zigbee_assign(basic_attrs.app_version, 0),
|
||||
zigbee_assign(basic_attrs.app_version, config[CONF_VERSION]),
|
||||
zigbee_assign(basic_attrs.stack_version, 0),
|
||||
zigbee_assign(basic_attrs.hw_version, 0),
|
||||
zigbee_set_string(basic_attrs.mf_name, "esphome"),
|
||||
zigbee_set_string(basic_attrs.mf_name, config[CONF_MANUFACTURER]),
|
||||
zigbee_set_string(basic_attrs.model_id, config[CONF_MODEL]),
|
||||
zigbee_set_string(
|
||||
basic_attrs.date_code,
|
||||
@@ -178,11 +182,11 @@ async def _attr_to_code(config: ConfigType) -> None:
|
||||
basic_attrs.power_source,
|
||||
POWER_SOURCE[config[CONF_POWER_SOURCE]],
|
||||
),
|
||||
zigbee_set_string(basic_attrs.location_id, ""),
|
||||
zigbee_set_string(basic_attrs.location_id, zb_data[KEY_ZIGBEE_AREA_STR]),
|
||||
zigbee_assign(
|
||||
basic_attrs.ph_env, cg.RawExpression("ZB_ZCL_BASIC_ENV_UNSPECIFIED")
|
||||
),
|
||||
zigbee_set_string(basic_attrs.sw_ver, __version__),
|
||||
zigbee_set_string(basic_attrs.sw_ver, zb_data[KEY_ZIGBEE_VERSION_STR]),
|
||||
)
|
||||
|
||||
# Create the identify attributes structure and attribute list
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
esphome:
|
||||
area:
|
||||
id: testing_area
|
||||
name: Testing Area
|
||||
project:
|
||||
name: esphome.test
|
||||
version: "1.1"
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
name: "Garage Door Open 1"
|
||||
|
||||
@@ -58,6 +58,8 @@ switch:
|
||||
|
||||
zigbee:
|
||||
model: zigbee_test
|
||||
manufacturer: test
|
||||
version: 1
|
||||
sleepy: true
|
||||
polling_interval: 2s
|
||||
power_source: BATTERY
|
||||
|
||||
@@ -3,6 +3,8 @@ packages:
|
||||
|
||||
zigbee:
|
||||
model: zigbee_test
|
||||
manufacturer: test
|
||||
version: 1
|
||||
wipe_on_boot: true
|
||||
on_join:
|
||||
then:
|
||||
|
||||
Reference in New Issue
Block a user