Showing preview only (227K chars total). Download the full file or copy to clipboard to get everything.
Repository: luar123/zigbee_esphome
Branch: master
Commit: 41761b894b8e
Files: 37
Total size: 215.2 KB
Directory structure:
gitextract_o48vfjhm/
├── .clang-format
├── .clang-tidy
├── .editorconfig
├── .flake8
├── .gitattributes
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .yamllint
├── README.md
├── components/
│ └── zigbee/
│ ├── __init__.py
│ ├── automation.cpp
│ ├── automation.h
│ ├── const.py
│ ├── esp_zb_event.h
│ ├── files_to_parse/
│ │ └── parse_zigbee_headers.py
│ ├── partitions_zb.csv
│ ├── time/
│ │ ├── __init__.py
│ │ ├── zigbee_time.cpp
│ │ └── zigbee_time.h
│ ├── types.py
│ ├── zigbee.cpp
│ ├── zigbee.h
│ ├── zigbee_attribute.cpp
│ ├── zigbee_attribute.h
│ ├── zigbee_const.py
│ ├── zigbee_ep.py
│ ├── zigbee_helpers.c
│ ├── zigbee_helpers.h
│ └── zigbee_pre_build.py.script
├── example_aht10_esp32h2.yaml
├── example_esp32c6.yaml
├── example_esp32h2.yaml
├── example_esp32h2_basic.yaml
├── example_time.yml
├── partitions_zb.csv
└── pyproject.toml
================================================
FILE CONTENTS
================================================
================================================
FILE: .clang-format
================================================
Language: Cpp
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: DontAlign
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 120
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
- Regex: '^<.*\.h>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IncludeIsMainRegex: '([-_](test|unittest))?$'
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 2000
PointerAlignment: Right
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
CanonicalDelimiter: ''
BasedOnStyle: google
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 2
UseTab: Never
================================================
FILE: .clang-tidy
================================================
---
Checks: >-
*,
-abseil-*,
-altera-*,
-android-*,
-boost-*,
-bugprone-easily-swappable-parameters,
-bugprone-implicit-widening-of-multiplication-result,
-bugprone-narrowing-conversions,
-bugprone-signed-char-misuse,
-cert-dcl50-cpp,
-cert-err33-c,
-cert-err58-cpp,
-cert-oop57-cpp,
-cert-str34-c,
-clang-analyzer-optin.cplusplus.UninitializedObject,
-clang-analyzer-osx.*,
-clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-ignored-optimization-argument,
-clang-diagnostic-shadow-field,
-clang-diagnostic-unused-const-variable,
-clang-diagnostic-unused-parameter,
-concurrency-*,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-prefer-member-initializer,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-type-const-cast,
-cppcoreguidelines-pro-type-cstyle-cast,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-static-cast-downcast,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions,
-cppcoreguidelines-virtual-class-destructor,
-fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects,
-fuchsia-default-arguments-declarations,
-fuchsia-default-arguments-calls,
-google-build-using-namespace,
-google-explicit-constructor,
-google-readability-braces-around-statements,
-google-readability-casting,
-google-readability-namespace-comments,
-google-readability-todo,
-google-runtime-references,
-hicpp-*,
-llvm-else-after-return,
-llvm-header-guard,
-llvm-include-order,
-llvm-qualified-auto,
-llvmlibc-*,
-misc-non-private-member-variables-in-classes,
-misc-no-recursion,
-misc-unused-parameters,
-modernize-avoid-c-arrays,
-modernize-avoid-bind,
-modernize-concat-nested-namespaces,
-modernize-return-braced-init-list,
-modernize-use-auto,
-modernize-use-default-member-init,
-modernize-use-equals-default,
-modernize-use-trailing-return-type,
-modernize-use-nodiscard,
-mpi-*,
-objc-*,
-readability-container-data-pointer,
-readability-convert-member-functions-to-static,
-readability-else-after-return,
-readability-function-cognitive-complexity,
-readability-implicit-bool-conversion,
-readability-isolate-declaration,
-readability-magic-numbers,
-readability-make-member-function-const,
-readability-redundant-string-init,
-readability-uppercase-literal-suffix,
-readability-use-anyofallof,
WarningsAsErrors: '*'
AnalyzeTemporaryDtors: false
FormatStyle: google
CheckOptions:
- key: google-readability-function-size.StatementThreshold
value: '800'
- key: google-runtime-int.TypeSuffix
value: '_t'
- key: llvm-namespace-comment.ShortNamespaceLines
value: '10'
- key: llvm-namespace-comment.SpacesBeforeComments
value: '2'
- key: modernize-loop-convert.MaxCopySize
value: '16'
- key: modernize-loop-convert.MinConfidence
value: reasonable
- key: modernize-loop-convert.NamingStyle
value: CamelCase
- key: modernize-pass-by-value.IncludeStyle
value: llvm
- key: modernize-replace-auto-ptr.IncludeStyle
value: llvm
- key: modernize-use-nullptr.NullMacros
value: 'NULL'
- key: modernize-make-unique.MakeSmartPtrFunction
value: 'make_unique'
- key: modernize-make-unique.MakeSmartPtrFunctionHeader
value: 'esphome/core/helpers.h'
- key: readability-braces-around-statements.ShortStatementLines
value: 2
- key: readability-identifier-naming.LocalVariableCase
value: 'lower_case'
- key: readability-identifier-naming.ClassCase
value: 'CamelCase'
- key: readability-identifier-naming.StructCase
value: 'CamelCase'
- key: readability-identifier-naming.EnumCase
value: 'CamelCase'
- key: readability-identifier-naming.EnumConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.StaticConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.StaticVariableCase
value: 'lower_case'
- key: readability-identifier-naming.GlobalConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.ParameterCase
value: 'lower_case'
- key: readability-identifier-naming.PrivateMemberCase
value: 'lower_case'
- key: readability-identifier-naming.PrivateMemberSuffix
value: '_'
- key: readability-identifier-naming.PrivateMethodCase
value: 'lower_case'
- key: readability-identifier-naming.PrivateMethodSuffix
value: '_'
- key: readability-identifier-naming.ClassMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ClassMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMemberSuffix
value: '_'
- key: readability-identifier-naming.FunctionCase
value: 'lower_case'
- key: readability-identifier-naming.ClassMethodCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMethodCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMethodSuffix
value: '_'
- key: readability-identifier-naming.VirtualMethodCase
value: 'lower_case'
- key: readability-identifier-naming.VirtualMethodSuffix
value: ''
- key: readability-qualified-auto.AddConstToQualified
value: 0
- key: readability-identifier-length.MinimumVariableNameLength
value: 0
- key: readability-identifier-length.MinimumParameterNameLength
value: 0
- key: readability-identifier-length.MinimumLoopCounterNameLength
value: 0
================================================
FILE: .editorconfig
================================================
root = true
# general
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
# python
[*.py]
indent_style = space
indent_size = 4
# C++
[*.{cpp,h,tcc}]
indent_style = space
indent_size = 2
# Web
[*.{js,html,css}]
indent_style = space
indent_size = 2
# YAML
[*.{yaml,yml}]
indent_style = space
indent_size = 2
quote_type = double
# JSON
[*.json]
indent_style = space
indent_size = 2
================================================
FILE: .flake8
================================================
[flake8]
max-line-length = 120
# Following 4 for black compatibility
# E501: line too long
# W503: Line break occurred before a binary operator
# E203: Whitespace before ':'
# D202 No blank lines allowed after function docstring
# TODO fix flake8
# D100 Missing docstring in public module
# D101 Missing docstring in public class
# D102 Missing docstring in public method
# D103 Missing docstring in public function
# D104 Missing docstring in public package
# D105 Missing docstring in magic method
# D107 Missing docstring in __init__
# D200 One-line docstring should fit on one line with quotes
# D205 1 blank line required between summary line and description
# D209 Multi-line docstring closing quotes should be on a separate line
# D400 First line should end with a period
# D401 First line should be in imperative mood
ignore =
E501,
W503,
E203,
D202,
D100,
D101,
D102,
D103,
D104,
D105,
D107,
D200,
D205,
D209,
D400,
D401,
exclude = api_pb2.py
================================================
FILE: .gitattributes
================================================
# Normalize line endings to LF in the repository
* text eol=lf
*.png binary
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
#github: luar123 # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
#patreon: # Replace with a single Patreon username
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
#liberapay: # Replace with a single Liberapay username
#issuehunt: # Replace with a single IssueHunt username
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
#polar: # Replace with a single Polar username
buy_me_a_coffee: luar123
#thanks_dev: # Replace with a single thanks.dev username
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .gitignore
================================================
# Gitignore settings for ESPHome
# This is an example and may include too much for your use-case.
# You can modify this file to suit your needs.
/.esphome/
/secrets.yaml
/.vscode/
__pycache__/
/components/zigbee/files_to_parse/*.c
/components/zigbee/files_to_parse/*.h
/components/zigbee/files_to_parse/zigbee_const.py
================================================
FILE: .pre-commit-config.yaml
================================================
---
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.5.4
hooks:
# Run the linter.
- id: ruff
args: [--fix]
# Run the formatter.
- id: ruff-format
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.4.2
hooks:
- id: black
args:
- --safe
- --quiet
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
hooks:
- id: flake8
additional_dependencies:
- flake8-docstrings==1.5.0
- pydocstyle==5.1.1
files: ^(esphome|tests)/.+\.py$
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
hooks:
- id: no-commit-to-branch
args:
- --branch=dev
- --branch=release
- --branch=beta
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.2
hooks:
- id: pyupgrade
args: [--py39-plus]
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.35.1
hooks:
- id: yamllint
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v13.0.1
hooks:
- id: clang-format
types_or: [c, c++]
- repo: local
hooks:
- id: pylint
name: pylint
entry: pylint
language: system
types: [python]
================================================
FILE: .yamllint
================================================
---
extends: default
ignore-from-file: .gitignore
rules:
document-start: disable
empty-lines:
level: error
max: 1
max-start: 0
max-end: 1
indentation:
level: error
spaces: 2
indent-sequences: true
check-multi-line-strings: false
line-length: disable
truthy: disable
================================================
FILE: README.md
================================================
> [!TIP]
> **New simple Mode! No more endpoint definitions needed.**
>
> I started to implement the automated endpoint definition generation, see basic mode section for details.
> [!Important]
> **Please help to collect working cluster definitions [here](https://github.com/luar123/zigbee_esphome/discussions/22).**
>
> **If something is not working please check the [troubleshooting](#troubleshooting) section first. Config validation is not complete. Always consult [Zigbee Cluster Library](https://csa-iot.org/wp-content/uploads/2022/01/07-5123-08-Zigbee-Cluster-Library-1.pdf) for cluster definitions**
# ESPHome ZigBee external component
External ZigBee component for ESPHome.
## Features
- Automated generation of zigbee definition for lights, switches, sensors and binary sensors (see basic mode)
- Definition of endpoints, clusters and attributes supported by esp-zigbee-sdk 1.6
- Set attributes action
- Manual report action
- Reset zigbee action
- Join trigger
- Attribute received trigger
- Time sync with coordinator
- Custom clusters and attributes
- (normal, binary, text) sensors, switches and lights can be connected to attributes without need for lambdas/actions
- Wifi co-existence on ESP32-C6 and ESP32-C5
- Deep-sleep should work
- Not tested: groups
- Time sync with coordinator
- Router
## Limitations
- No coordinator devices
- Attribute set action works only with numeric types and character string
- Attribute OnValue trigger works only with numeric types
- Reporting can be enabled, but not configured
- No control devices like switches ([workaround](https://github.com/luar123/zigbee_esphome/discussions/18#discussioncomment-11875376))
- Needs esp-idf >=5.1.4
- Needs esphome >=2025.7
- scenes not implemented
- Officially the zigbee stack supports only 10 endpoints. however, this is not enforced and at least for sensor endpoints more than 10 seem to work. More then 10 light endpoints will crash!
- zigbee2mqtt: Only one light is supported without creating a custom converter/definition
- zigbee2mqtt: Analog input cluster (used for sensors) is supported by 2025 October release, but ignores type and unit
- ZHA: Analog input cluster (used for sensors) without unit/type is ignored
- ZHA: Minimum reporting interval is set to high values (30s) for some sensors and can't be changed. Keep that in mind if reporting seems not to work properly.
## ToDo List (Short-Mid term)
- Light effects (through identify cluster commands)
- more components to support basic mode
## Not planned (feel free to submit a pull request)
- [Zigbee ZCL OTA Upgrade Cluster](https://docs.espressif.com/projects/esp-zigbee-sdk/en/latest/esp32/user-guide/zcl_ota_upgrade.html) and related [OTA API for ESP Zigbee SDK](https://docs.espressif.com/projects/esp-zigbee-sdk/en/latest/esp32/api-reference/esp_zigbee_ota.html) to allow OTA (over-the-air) firmware updates via Zigbee
- Coordinator devices
- Binding config in yaml
- Reporting config in yaml
- Control device support like switches ([workaround](https://github.com/luar123/zigbee_esphome/discussions/18#discussioncomment-11875376))
## Usage
Include external component:
```
external_components:
- source: github://luar123/zigbee_esphome
components: [zigbee]
zigbee:
components: all # to add all supported components
...
```
### Configuration variables
- **id** (Optional, string): Manually specify the ID for code generation.
- **name** (Optional, string): Zigbee Model Identifier in basic cluster. Used by coordinator to match custom converters. Defaults to `esphome.name`
- **manufacturer** (Optional, string): Zigbee Manufacturer Name in basic cluster. Used by coordinator to match custom converters. Defaults to `"esphome"`
- **date** (Optional, string): Date Code in basic cluster. Defaults to build time
- **power_supply** (Optional, int): Zigbee Power Source in basic cluster. Defaults to `0` = unknown
- `1` = single phase mains (USB)
- `2` = three phase mains
- `3` = battery
- **version** (Optional, int): Zigbee App Version in basic cluster. Defaults to `0`
- **area** (Optional, int): Zigbee Physical Environment in basic cluster. See ZCL. Defaults to `0` = unknown
- **router** (Optional, bool): Create a router device instead of an end device. Defaults to `false`
- **device_version** (Optional, int): Set the Home Automation Profile device version. Custom values might be needed for compatibility with some vendors. Defaults to `0`
- **trust_center_key** (Optional, bind_key): Set custom trust center key. 32 digits hex number.
- **debug** (Optional, bool): Print zigbee stack debug messages. Defaults to `false`
- **components** (Optional, string|list): `all`: add definitions for all supported components that have a name and are not marked as internal.
- None: Add no definitions (default).
- List of component ids: Add only those. Can be combined with manual definitions in endpoints
- **as_generic** (Optional, bool): Use generic/basic clusters where possible. Currently sensors and switches. Defaults to false
- **endpoints** (Optional, list): endpoint list for advanced definitions. See examples
[Todo]
### Basic mode
By adding `components: all` the endpoint definition is generated automatically. Currently [sensor](https://esphome.io/components/sensor/), [binary_sensor](https://esphome.io/components/binary_sensor/), [light](https://esphome.io/components/light/) and [switch](https://esphome.io/components/switch/) ESPHome components are supported.
Because this is an [external component](https://esphome.io/components/external_components/) the whole implementation is a bit hacky and likely to fail with some setups. Also it is not possible to tweak the generated definitions.
Each entity creates a new endpoint. For sensors the unit/type is set automatically. Please note that these definitions are not complete. Feel free to open an issue or pull request (see zigbee_ep.py)
| ESPHome Entity | Zigbee Cluster |
| --------------- | ------------------------------------------------------------------ |
| `light` | `light` |
| `switch` | `on_off`, `binary_output` |
| `binary_sensor` | `binary_input` |
| `sensor` | `analog_input` or mapped to specific (e.g. `temperature`) clusters |
**Basic example**:
```
zigbee:
id: "zb"
components: all
```
### Advanced mode
Endpoint/Cluster definitions can be defined manually. Can be combined with automated definition.
Advanced example:
```
zigbee:
id: "zb"
endpoints:
- num: 1
device_type: COLOR_DIMMABLE_LIGHT
clusters:
- id: ON_OFF
attributes:
- attribute_id: 0
type: bool
on_value:
then:
- light.control:
id: light_1
state: !lambda "return (bool)x;"
- id: LEVEL_CONTROL
attributes:
- attribute_id: 0
type: U8
on_value:
then:
- light.control:
id: light_1
brightness: !lambda "return ((float)x)/255;"
- device_type: TEMPERATURE_SENSOR
num: 2
clusters:
- id: REL_HUMIDITY_MEASUREMENT
attributes:
- attribute_id: 0
id: hum_attr
type: U16
report: true
value: 200
- id: TEMP_MEASUREMENT
attributes:
- attribute_id: 0x0
type: S16
report: true
value: 100
device: temp_sensor_id
scale: 100
- device_type: HEATING_COOLING_UNIT
num: 3
clusters:
- id: THERMOSTAT
role: "Client"
attributes:
- attribute_id: 0x0008 # PIHeatingDemand
type: U8
value: 0
on_report:
then:
# The below lambda will be called with an argument
# `ZigBeeReportData <T> x` where ZigBeeReportData is defined as
#
# template<typename T> struct ZigBeeReportData {
# // Value of the attribute sent from server side.
# T value;
# // Address of device which sent this value.
# esp_zb_zcl_addr_t src_address;
# // Number of the endpoint on device which sent this value.
# uint8_t src_endpoint;
# };
#
# And T is a C++ type matching the type of the attribute.
- lambda: |-
ESP_LOGD("main", "Received PIHeatingDemand | address type: 0x%02x; short addr: 0x%04x; ieee addr: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X",
x.src_address.addr_type, x.src_address.u.short_addr,
x.src_address.u.ieee_addr[7], x.src_address.u.ieee_addr[6],
x.src_address.u.ieee_addr[5], x.src_address.u.ieee_addr[4],
x.src_address.u.ieee_addr[3], x.src_address.u.ieee_addr[2],
x.src_address.u.ieee_addr[1], x.src_address.u.ieee_addr[0]);
- switch.control:
id: relay_1_switch
state: !lambda "return (x.value>10);"
on_join:
then:
- logger.log: "Joined network"
```
### Actions
- `zigbee.setAttr`
- `id`: id of attribute
- `value`: attribute value. (can be a `lambda`)
- only numeric or string types
- `zigbee.report`: `id` of zigbee component
- Manually send reports for all attributes with `report=true`
- `zigbee.reportAttr`: `id` of zigbee_attribute component
- Manually send report for attribute
- `zigbee.reset`: `id` of zigbee component
- Reset Zigbee Network and Device. Leave the current network and tries to join open networks.
Examples:
```
on_value:
then:
- zigbee.setAttr:
id: hum_attr
value: !lambda "return x*100;"
```
```
on_press:
then:
- zigbee.report: zb
```
### Time sync
Add a 'time' component with platform 'zigbee', e.g.:
```
zigbee:
id: "zb"
...
time:
- platform: zigbee
timezone: Europe/London
on_time_sync:
then:
- logger.log: "Synchronized system clock"
on_time:
- seconds: /10
then:
- logger.log: "Tick-tock 10 seconds"
```
## Troubleshooting
- Build errors
- Try to run `esphome clean <name.ymal>`
- Try to delete the `.esphome/build/<name>/` folder
- ESP crashes
- Try to erase completely with `esptool.py erase_flash` and flash again.
- Make sure your configuration is valid. Config validation is not complete. Always consult [Zigbee Cluster Library](https://csa-iot.org/wp-content/uploads/2022/01/07-5123-08-Zigbee-Cluster-Library-1.pdf) for cluster definitions
- Common issues are that attributes do not support reporting (try set `report: false`), use a different type, or are not readable/writable (see ZCL).
- Zigbee is not working as expected
- Whenever the cluster definition changed you need to re-interview and remove/add the device to your network.
- Sometimes it helps to power-cycle the coordinator and restarting z2m.
- Remove other endpoints. Sometimes coordinators struggle with multiple endpoints.
## Notes
- I don't have much free time to work on this right now, so feel free to fork/improve/create PRs/etc.
- At the moment, the C++ implementation is rather simple and generic. I tried to keep as much logic as possible in the python part. However, endpoints/clusters ~~/attributes~~ could also be classes, this would simplify the yaml setup but requires more sophisticated C++ code.
- There is also a project with more advanced C++ zigbee libraries for esp32 that could be used here as well: https://github.com/Muk911/esphome/tree/main/esp32c6/hello-zigbee
- [parse_zigbee_headers.py](components/zigbee/files_to_parse/parse_zigbee_headers.py) is used to create the python enums and C helper functions automatically from zigbee sdk headers.
- Deprecated [custom zigbee component](https://github.com/luar123/esphome_zb_sensor)
## Example Zigbee device
ESPHome Zigbee using only dev board or additionally [AHT10 Temperature+Humidity Sensor](https://next.esphome.io/components/sensor/aht10).
### Hardware Required
- One development board with ESP32-H2, ESP32-C5 or ESP32-C6 SoC acting as Zigbee end-device (that you will load ESPHome with the example config to).
- For example, the official [ESP32-H2-DevKitM-1](https://docs.espressif.com/projects/espressif-esp-dev-kits/en/latest/esp32h2/esp32-h2-devkitm-1/user_guide.html) development kit board.
- [AHT10 Temperature+Humidity Sensor](https://next.esphome.io/components/sensor/aht10) connected to I2C pins (SDA: 12, SCL: 22) for the aht10 example.
- A USB cable for power supply and programming.
- (Optional) A USB-C cable to get ESP32 logs from the UART USB port (UART0).
### Build ESPHome Zigbee sensor
- We will build [example_esp32h2.yaml](example_esp32h2.yaml) file.
- Check [Getting Started with the ESPHome Command Line](https://esphome.io/guides/getting_started_command_line.html) tutorial to set up your dev environment.
- Build with `esphome run example_esp32h2.yaml`.
## How to contribute
**Please submit all PRs here** and not to https://github.com/luar123/esphome/tree/zigbee
Use pre-commit hook by enabling you esphome environment first and then running `pre-commit install` in the git root foulder.
If looking to contribute to this project, then suggest follow steps in these guides + look at issues in Espressif's ESP Zigbee SDK repository:
- https://github.com/espressif/esp-zigbee-sdk/issues
- https://github.com/firstcontributions/first-contributions/blob/master/README.md
- https://github.com/firstcontributions/first-contributions/blob/master/github-desktop-tutorial.md
## External documentation and reference
> [!NOTE]
> The official documentation and reference examples for the ESP Zigbee SDK can currently be obtained from Espressif:
- [ESP32 Zigbee SDK Programming Guide](https://docs.espressif.com/projects/esp-zigbee-sdk/en/latest/esp32/)
- [ESP32-H2 Application User Guide](https://docs.espressif.com/projects/esp-zigbee-sdk/en/latest/esp32h2/application.html)
- [ESP32-C6 Application User Guide](https://docs.espressif.com/projects/esp-zigbee-sdk/en/latest/esp32c6/application.html)
- [ESP-Zigbee-SDK Github repo](https://github.com/espressif/esp-zigbee-sdk)
- [ESP-Zigbee-SDK examples](https://github.com/espressif/esp-zigbee-sdk/tree/main/examples/)
- [Zigbee HA Example](https://github.com/espressif/esp-zigbee-sdk/tree/main/examples/esp_zigbee_HA_sample)
- [Zigbee HA Light Bulb example](https://github.com/espressif/esp-zigbee-sdk/tree/main/examples/esp_zigbee_HA_sample/HA_on_off_light)
- [Zigbee HA temperature sensor example](https://github.com/espressif/esp-zigbee-sdk/tree/main/examples/esp_zigbee_HA_sample/HA_temperature_sensor)
- [Zigbee HA thermostat example](https://github.com/espressif/esp-zigbee-sdk/tree/main/examples/esp_zigbee_HA_sample/HA_thermostat)
================================================
FILE: components/zigbee/__init__.py
================================================
import datetime
import inspect
from pathlib import Path
import re
from esphome import automation
import esphome.codegen as cg
from esphome.components import text_sensor
from esphome.components.esp32 import (
CONF_PARTITIONS,
add_extra_script,
add_idf_component,
add_idf_sdkconfig_option,
only_on_variant,
)
from esphome.components.esp32.const import (
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
)
import esphome.config_validation as cv
from esphome.const import (
CONF_AP,
CONF_AREA,
CONF_COMPONENTS,
CONF_DATE,
CONF_DEBUG,
CONF_DEVICE,
CONF_ID,
CONF_LAMBDA,
CONF_MAX_LENGTH,
CONF_NAME,
CONF_ON_VALUE,
CONF_POWER_SUPPLY,
CONF_TRIGGER_ID,
CONF_TYPE,
CONF_VALUE,
CONF_VERSION,
CONF_WIFI,
)
from esphome.core import CORE, EsphomeError
import esphome.final_validate as fv
from .const import (
CONF_ACCESS,
CONF_AS_GENERIC,
CONF_ATTRIBUTE_ID,
CONF_ATTRIBUTES,
CONF_CLUSTERS,
CONF_DEVICE_TYPE,
CONF_DEVICE_VERSION,
CONF_ENDPOINTS,
CONF_MANUFACTURER,
CONF_NUM,
CONF_ON_JOIN,
CONF_ON_REPORT,
CONF_REPORT,
CONF_ROLE,
CONF_ROUTER,
CONF_SCALE,
CONF_TRUST_CENTER_KEY,
BinarySensor,
Sensor,
Switch,
)
from .types import (
ReportAction,
ReportAttrAction,
ResetZigbeeAction,
SetAttrAction,
ZigBeeAttribute,
ZigBeeComponent,
ZigBeeOnReportTrigger,
ZigBeeOnValueTrigger,
)
from .zigbee_const import ATTR_ACCESS, ATTR_TYPE, CLUSTER_ID, CLUSTER_ROLE, DEVICE_ID
from .zigbee_ep import create_ep
try:
from esphome.components.esp32 import require_vfs_select
except ImportError:
def require_vfs_select():
pass
_supports_synchronous = (
"synchronous" in inspect.signature(automation.register_action).parameters
)
def _register_action(name, action_type, schema, **kwargs):
if _supports_synchronous:
kwargs.setdefault("synchronous", True)
else:
kwargs.pop("synchronous", None)
return automation.register_action(name, action_type, schema, **kwargs)
DEPENDENCIES = ["esp32"]
_CALLBACK_AUTOMATIONS = (
automation.CallbackAutomation(CONF_ON_JOIN, "add_on_join_callback"),
)
comp_ids = 0
# dummies for upstream compatibility
BINARY_SENSOR_SCHEMA = cv.Schema({})
SENSOR_SCHEMA = cv.Schema({})
SWITCH_SCHEMA = cv.Schema({})
NUMBER_SCHEMA = cv.Schema({})
def validate_binary_sensor(x):
return x
def validate_sensor(x):
return x
def validate_switch(x):
return x
def validate_number(x):
return x
async def setup_binary_sensor(sensor, config):
pass
async def setup_sensor(sensor, config):
pass
async def setup_switch(switch, config):
pass
async def setup_number(number, config, min_value, max_value, step):
pass
def get_c_size(bits, options):
return str([n for n in options if n >= int(bits)][0])
def get_c_type(attr_type):
if attr_type == "BOOL":
return cg.bool_
if attr_type == "SINGLE":
return cg.float_
if attr_type == "DOUBLE":
return cg.double
if "STRING" in attr_type:
return cg.std_string
test = re.match(r"(^U?)(\d{1,2})(BITMAP$|BIT$|BIT_ENUM$|$)", attr_type)
if test and test.group(2):
return getattr(cg, "uint" + get_c_size(test.group(2), [8, 16, 32, 64]))
test = re.match(r"^S(\d{1,2})$", attr_type)
if test and test.group(1):
return getattr(cg, "int" + get_c_size(test.group(1), [16, 32, 64]))
raise EsphomeError(f"Zigbee: type {attr_type} not supported or implemented")
def get_cv_by_type(attr_type):
if attr_type == "BOOL":
return cv.boolean
if attr_type in ["SEMI", "SINGLE", "DOUBLE"]:
return cv.float_
if "STRING" in attr_type:
return cv.string
test = re.match(r"(^U?)(\d{1,2})(BITMAP$|BIT$|BIT_ENUM$|$)", attr_type)
if test and test.group(2):
return cv.positive_int
test = re.match(r"^S(\d{1,2})$", attr_type)
if test and test.group(1):
return cv.int_
raise cv.Invalid(f"Zigbee: type {attr_type} not supported or implemented")
def get_default_by_type(attr_type):
if "CHAR_STRING" == attr_type:
return ""
return 0
def validate_clusters(config):
for attr in config.get(CONF_ATTRIBUTES):
if isinstance(config.get(CONF_ID), int) and config.get(CONF_ID) >= 0xFC00:
if not {CONF_TYPE, CONF_ACCESS, CONF_VALUE} <= attr.keys():
raise cv.Invalid(
f"Parameters {CONF_TYPE}, {CONF_VALUE} and {CONF_ACCESS} are need for custom cluster."
)
return config
def validate_string_attributes(config):
if "CHAR_STRING" == config[CONF_TYPE]:
if CONF_MAX_LENGTH not in config.keys():
raise cv.Invalid(
f"The '{CONF_MAX_LENGTH}' parameter is mandatory for string attributes."
)
if config[CONF_MAX_LENGTH] == 0:
config[CONF_MAX_LENGTH] = len(config.get(CONF_VALUE, ""))
# Check that size of default value matches CONF_MAX_LENGTH
if len(config[CONF_VALUE]) > config[CONF_MAX_LENGTH]:
raise cv.Invalid(
"The default value is larger than the maximum length of the string attribute."
)
return config
def validate_attributes(config):
if CONF_VALUE in config:
config[CONF_VALUE] = get_cv_by_type(config[CONF_TYPE])(config[CONF_VALUE])
else:
config[CONF_VALUE] = get_default_by_type(config[CONF_TYPE])
config[CONF_ACCESS] = (
ATTR_ACCESS[config[CONF_ACCESS]] + config[CONF_REPORT] * 4
if CONF_ACCESS in config
else 0
)
validate_string_attributes(config)
if (CONF_ID not in config) and (
CONF_DEVICE in config or CONF_ON_VALUE in config or CONF_ON_REPORT in config
):
config[CONF_ID] = cv.declare_id(ZigBeeAttribute)(None)
elif all(
i not in config for i in [CONF_ID, CONF_DEVICE, CONF_ON_VALUE, CONF_ON_REPORT]
) and (config[CONF_SCALE] != 1.0 or CONF_LAMBDA in config or config[CONF_REPORT]):
raise cv.Invalid(
f"Parameters {CONF_SCALE}', '{CONF_LAMBDA}' or '{CONF_REPORT}' are not allowed without '{CONF_ID}', '{CONF_DEVICE}', '{CONF_ON_VALUE}' or '{CONF_ON_REPORT}'."
)
return config
def final_validate(config):
esp_conf = fv.full_config.get()["esp32"]
if CONF_PARTITIONS in esp_conf:
with open(
CORE.relative_config_path(esp_conf[CONF_PARTITIONS]), encoding="utf8"
) as f:
partitions = f.read()
if ("zb_storage" not in partitions) and ("zb_fct" not in partitions):
raise cv.Invalid(
"Add \n'zb_storage, data, fat, , 16K,'\n'zb_fct, data, fat, , 1K,'\n to your custom partition table."
)
else:
raise cv.Invalid(
f"Use '{CONF_PARTITIONS}' in esp32 to specify a custom partition table including zigbee partitions"
)
if CONF_WIFI in fv.full_config.get():
if CONF_AP in fv.full_config.get()[CONF_WIFI]:
raise cv.Invalid("Zigbee can't be used together with an Wifi Access Point.")
global comp_ids # noqa: PLW0603
comp_ids = len(CORE.component_ids)
return config
FINAL_VALIDATE_SCHEMA = cv.Schema(final_validate)
def _require_vfs_select(config):
"""Register VFS select requirement during config validation."""
# ZigBee uses esp_vfs_eventfd which requires VFS select support
require_vfs_select()
return config
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ZigBeeComponent),
cv.Optional(CONF_NAME): cv.string,
cv.Optional(CONF_MANUFACTURER, default="esphome"): cv.string,
cv.Optional(
CONF_DATE, default=datetime.datetime.now().strftime("%Y%m%d")
): cv.string,
cv.Optional(CONF_POWER_SUPPLY, default=0): cv.int_, # make enum
cv.Optional(CONF_VERSION, default=0): cv.int_,
cv.Optional(CONF_AREA, default=0): cv.int_, # make enum
cv.Optional(CONF_ROUTER, default=False): cv.boolean,
cv.Optional(CONF_TRUST_CENTER_KEY): cv.bind_key,
cv.Optional(CONF_DEVICE_VERSION): cv.int_,
cv.Optional(CONF_DEBUG, default=False): cv.boolean,
cv.Optional(CONF_COMPONENTS): cv.Any(
cv.one_of("all", "none", lower=True),
cv.ensure_list(cv.use_id(cg.EntityBase)),
),
cv.Optional(CONF_AS_GENERIC, default=False): cv.boolean,
cv.Optional(CONF_ENDPOINTS): cv.ensure_list(
cv.Schema(
{
cv.Required(CONF_DEVICE_TYPE): cv.enum(DEVICE_ID, upper=True),
cv.Optional(CONF_NUM): cv.int_range(1, 240),
cv.Optional(CONF_CLUSTERS): cv.ensure_list(
cv.Schema(
{
cv.Required(CONF_ID): cv.Any(
cv.enum(CLUSTER_ID, upper=True),
cv.int_range(0xFC00, 0xFFFF),
),
cv.Optional(CONF_ROLE, default="Server"): cv.enum(
CLUSTER_ROLE, upper=True
),
cv.Optional(CONF_ATTRIBUTES): cv.ensure_list(
cv.Schema(
{
cv.Optional(CONF_ID): cv.declare_id(
ZigBeeAttribute
),
cv.Required(CONF_ATTRIBUTE_ID): cv.int_,
cv.Required(CONF_TYPE): cv.enum(
ATTR_TYPE, upper=True
),
cv.Optional(CONF_ACCESS): cv.enum(
ATTR_ACCESS, upper=True
),
cv.Optional(CONF_VALUE): cv.valid,
cv.Optional(
CONF_REPORT, default=False
): cv.Any(
cv.boolean,
cv.one_of("force", lower=True),
),
cv.Optional(
CONF_ON_VALUE
): automation.validate_automation(
{
cv.GenerateID(
CONF_TRIGGER_ID
): cv.declare_id(
ZigBeeOnValueTrigger
),
}
),
cv.Optional(CONF_DEVICE): cv.use_id(
cg.EntityBase
),
cv.Optional(
CONF_SCALE, default=1.0
): cv.float_,
cv.Optional(
CONF_LAMBDA
): cv.returning_lambda,
cv.Optional(
CONF_MAX_LENGTH
): cv.int_range(0, 254),
cv.Optional(
CONF_ON_REPORT
): automation.validate_automation(
{
cv.GenerateID(
CONF_TRIGGER_ID
): cv.declare_id(
ZigBeeOnReportTrigger
),
}
),
}
),
validate_attributes,
),
},
),
validate_clusters,
),
}
),
),
cv.Optional(CONF_ON_JOIN): automation.validate_automation({}),
}
).extend(cv.COMPONENT_SCHEMA),
cv.require_framework_version(esp_idf=cv.Version(5, 1, 2)),
_require_vfs_select,
only_on_variant(
supported=[
VARIANT_ESP32H2,
VARIANT_ESP32C6,
VARIANT_ESP32C5,
]
),
)
def find_attr(conf, id):
for ep in conf[CONF_ENDPOINTS]:
for cl in ep.get(CONF_CLUSTERS, []):
for attr in cl.get(CONF_ATTRIBUTES, []):
if attr[CONF_ID] == id:
return attr
raise EsphomeError(f"Zigbee: Cannot find attribute {id}.")
async def attributes_to_code(var, ep_num, cl):
for attr in cl.get(CONF_ATTRIBUTES, []):
if attr.get(CONF_ID) is None:
cg.add(
var.add_attr(
ep_num,
CLUSTER_ID.get(cl[CONF_ID], cl[CONF_ID]),
CLUSTER_ROLE[cl[CONF_ROLE]],
attr[CONF_ATTRIBUTE_ID],
ATTR_TYPE[attr[CONF_TYPE]],
attr[CONF_ACCESS],
attr.get(CONF_MAX_LENGTH, 0),
attr[CONF_VALUE],
)
)
continue
attr_var = cg.new_Pvariable(
attr[CONF_ID],
var,
ep_num,
CLUSTER_ID.get(cl[CONF_ID], cl[CONF_ID]),
CLUSTER_ROLE[cl[CONF_ROLE]],
attr[CONF_ATTRIBUTE_ID],
ATTR_TYPE[attr[CONF_TYPE]],
attr[CONF_SCALE],
)
await cg.register_component(attr_var, attr)
cg.add(
attr_var.add_attr(
attr[CONF_ACCESS],
attr.get(CONF_MAX_LENGTH, 0),
attr[CONF_VALUE],
)
)
if attr[CONF_REPORT]:
cg.add(attr_var.set_report(attr[CONF_REPORT] == "force"))
if CONF_LAMBDA in attr:
lambda_ = await cg.process_lambda(
attr[CONF_LAMBDA],
[(cg.float_, "x")],
return_type=get_c_type(attr[CONF_TYPE]),
)
if CONF_DEVICE in attr:
device = await cg.get_variable(attr[CONF_DEVICE])
template_arg = cg.TemplateArguments(get_c_type(attr[CONF_TYPE]))
if CONF_LAMBDA in attr:
if device.base.type.inherits_from(Sensor):
lambda_ = await cg.process_lambda(
attr[CONF_LAMBDA],
[(cg.float_, "x")],
return_type=get_c_type(attr[CONF_TYPE]),
)
elif device.base.type.inherits_from(BinarySensor):
lambda_ = await cg.process_lambda(
attr[CONF_LAMBDA],
[(cg.bool_, "x")],
return_type=get_c_type(attr[CONF_TYPE]),
)
elif device.base.type.inherits_from(text_sensor.TextSensor):
lambda_ = await cg.process_lambda(
attr[CONF_LAMBDA],
[(cg.std_string, "x")],
return_type=get_c_type(attr[CONF_TYPE]),
)
elif device.base.type.inherits_from(Switch):
lambda_ = await cg.process_lambda(
attr[CONF_LAMBDA],
[(get_c_type(attr[CONF_TYPE]), "x")],
return_type=cg.bool_,
)
cg.add(attr_var.connect(template_arg, device, lambda_))
else:
cg.add(attr_var.connect(template_arg, device))
for conf in attr.get(CONF_ON_VALUE, []):
trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID],
cg.TemplateArguments(get_c_type(attr[CONF_TYPE])),
attr_var,
)
await cg.register_component(trigger, conf)
await automation.build_automation(
trigger, [(get_c_type(attr[CONF_TYPE]), "x")], conf
)
for conf in attr.get(CONF_ON_REPORT, []):
trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID],
cg.TemplateArguments(get_c_type(attr[CONF_TYPE])),
attr_var,
)
await cg.register_component(trigger, conf)
value_type = get_c_type(attr[CONF_TYPE])
automation_arg_type = "esphome::zigbee::ZigBeeReportData" + str(
cg.TemplateArguments(value_type)
)
await automation.build_automation(
trigger, [(cg.RawExpression(automation_arg_type), "x")], conf
)
async def to_code(config):
add_idf_component(
name="espressif/esp-zboss-lib",
ref="1.6.4",
)
add_idf_component(
name="espressif/esp-zigbee-lib",
ref="1.6.8",
)
add_idf_sdkconfig_option("CONFIG_ZB_ENABLED", True)
if config.get(CONF_ROUTER):
add_idf_sdkconfig_option("CONFIG_ZB_ZCZR", True)
else:
add_idf_sdkconfig_option("CONFIG_ZB_ZED", True)
add_idf_sdkconfig_option("CONFIG_ZB_RADIO_NATIVE", True)
# The pre-built Zigbee library uses esp_log_default_level which requires
# dynamic log level control to be enabled
add_idf_sdkconfig_option("CONFIG_LOG_DYNAMIC_LEVEL_CONTROL", True)
if CONF_WIFI in CORE.config:
add_idf_sdkconfig_option("CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE", 4096)
cg.add_define("CONFIG_WIFI_COEX")
if config.get(CONF_DEBUG):
add_idf_sdkconfig_option("CONFIG_ZB_DEBUG_MODE", True)
# create endpoints
ep_list, added_ids = create_ep(config, CORE.config)
if added_ids:
# update ESPHOME_COMPONENT_COUNT via pre build script
cg.add_define("ZB_ESPHOME_COMPONENT_COUNT", comp_ids + added_ids)
add_extra_script(
"pre",
"zigbee_pre_build.py",
Path(__file__).parent / "zigbee_pre_build.py.script",
)
# setup zigbee components
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if CONF_TRUST_CENTER_KEY in config:
cg.add(var.set_trust_center_key(config[CONF_TRUST_CENTER_KEY]))
if CONF_DEVICE_VERSION in config:
cg.add(var.set_device_version(config[CONF_DEVICE_VERSION]))
if CONF_NAME not in config:
config[CONF_NAME] = CORE.name or ""
cg.add(
var.set_basic_cluster(
config[CONF_NAME],
config[CONF_MANUFACTURER],
config[CONF_DATE],
config[CONF_POWER_SUPPLY],
config[CONF_VERSION],
0,
0,
CORE.area or "",
config[CONF_AREA],
)
)
for ep in ep_list:
cg.add(
var.create_default_cluster(ep[CONF_NUM], DEVICE_ID[ep[CONF_DEVICE_TYPE]])
)
for cl in ep.get(CONF_CLUSTERS, []):
cg.add(
var.add_cluster(
ep[CONF_NUM],
CLUSTER_ID.get(cl[CONF_ID], cl[CONF_ID]),
CLUSTER_ROLE[cl[CONF_ROLE]],
)
)
await attributes_to_code(var, ep[CONF_NUM], cl)
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
ZIGBEE_ACTION_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(ZigBeeComponent),
}
)
@_register_action(
"zigbee.reset",
ResetZigbeeAction,
automation.maybe_simple_id(ZIGBEE_ACTION_SCHEMA),
synchronous=True,
)
async def reset_zigbee_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@_register_action(
"zigbee.report",
ReportAction,
automation.maybe_simple_id(ZIGBEE_ACTION_SCHEMA),
synchronous=True,
)
async def report_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
ZIGBEE_ATTRIBUTE_ACTION_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(ZigBeeAttribute),
}
)
ZIGBEE_SET_ATTR_SCHEMA = cv.All(
automation.maybe_simple_id(
ZIGBEE_ATTRIBUTE_ACTION_SCHEMA.extend(
cv.Schema(
{
cv.Required(CONF_VALUE): cv.templatable(cv.valid),
}
)
)
),
)
@_register_action(
"zigbee.setAttr", SetAttrAction, ZIGBEE_SET_ATTR_SCHEMA, synchronous=True
)
async def zigbee_set_attr_to_code(config, action_id, template_arg, args):
attr = find_attr(
CORE.config["zigbee"],
config[CONF_ID],
)
template_arg = cg.TemplateArguments(
get_c_type(attr[CONF_TYPE]),
template_arg.args if template_arg.args.args else None,
)
parent = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, parent)
template_ = await cg.templatable(
config[CONF_VALUE], args, get_c_type(attr[CONF_TYPE])
)
cg.add(var.set_value(template_))
return var
@_register_action(
"zigbee.reportAttr",
ReportAttrAction,
automation.maybe_simple_id(ZIGBEE_ATTRIBUTE_ACTION_SCHEMA),
synchronous=True,
)
async def zigbee_report_attr_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
================================================
FILE: components/zigbee/automation.cpp
================================================
#include "automation.h"
#include <algorithm>
#include "esphome/core/log.h"
namespace esphome {
namespace zigbee {
/**
* @brief sRGB to R'G'B' gamma correction as outlined in
* https://en.wikipedia.org/wiki/SRGB
*
* @param linear a component value in sRGB color space
* @return float the corresponding component value in R'G'B' color space
*/
inline float gamma_correct(float linear) {
if(linear < 0.0031308f) {
return linear * 12.92f;
} else {
return (1.055f) * pow(linear, (1.0f / 2.4f)) - 0.055f;
}
}
/* The following conversions operate on xyY with Y = 1.0, hence Y
doesn't appear in the formulas. The coefficients originate from
https://en.wikipedia.org/wiki/SRGB#Primaries
*/
float get_r_from_xy(float x, float y) {
float z = 1.0f - x - y;
float X = x / y;
float Z = z / y;
float r = X * 3.2406 - 1.5372f - Z * 0.4986f;
return std::clamp(gamma_correct(r), 0.0f, 1.0f);
}
float get_g_from_xy(float x, float y) {
float z = 1.0f - x - y;
float X = x / y;
float Z = z / y;
float g = -X * 0.9689f + 1.8758f + Z * 0.0415f;
return std::clamp(gamma_correct(g), 0.0f, 1.0f);
}
float get_b_from_xy(float x, float y) {
float z = 1.0f - x - y;
float X = x / y;
float Z = z / y;
float b = X * 0.0557f - 0.2040f + Z * 1.0570f;
return std::clamp(gamma_correct(b), 0.0f, 1.0f);
}
#ifdef USE_LIGHT
void set_light_color(uint8_t ep, light::LightCall *call, uint16_t value, bool is_x) {
static std::map<uint8_t, float> x;
static std::map<uint8_t, float> y;
if (is_x) {
x[ep] = (float) value / 65536;
} else {
y[ep] = (float) value / 65536;
}
ESP_LOGD(TAG, "Set color, x: %f, y: %f", x[ep], y[ep]);
call->set_rgb(get_r_from_xy(x[ep], y[ep]), get_g_from_xy(x[ep], y[ep]), get_b_from_xy(x[ep], y[ep]));
}
#endif
} // namespace zigbee
} // namespace esphome
================================================
FILE: components/zigbee/automation.h
================================================
#pragma once
//#include <stdfloat> //deactive because not working with esp-idf 5.1.4
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/version.h"
#include "esphome/core/log.h"
#include "zigbee_attribute.h"
#include "zigbee.h"
#ifdef USE_LIGHT
#include "esphome/components/light/light_state.h"
#endif
namespace esphome {
namespace zigbee {
template<typename... Ts> class ResetZigbeeAction : public Action<Ts...>, public Parented<ZigBeeComponent> {
public:
#if ESPHOME_VERSION_CODE >= VERSION_CODE(2025, 11, 0)
void play(const Ts &...x) override { this->parent_->reset(); }
#else
void play(Ts... x) override { this->parent_->reset(); }
#endif
};
template<typename... Ts> class ReportAction : public Action<Ts...>, public Parented<ZigBeeComponent> {
public:
#if ESPHOME_VERSION_CODE >= VERSION_CODE(2025, 11, 0)
void play(const Ts &...x) override { this->parent_->report(); }
#else
void play(Ts... x) override { this->parent_->report(); }
#endif
};
template<typename... Ts> class ReportAttrAction : public Action<Ts...>, public Parented<ZigBeeAttribute> {
public:
#if ESPHOME_VERSION_CODE >= VERSION_CODE(2025, 11, 0)
void play(const Ts &...x) override { this->parent_->report(); }
#else
void play(Ts... x) override { this->parent_->report(); }
#endif
};
template<typename T, typename... Ts> class SetAttrAction : public Action<Ts...> {
public:
SetAttrAction(ZigBeeAttribute *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(T, value);
#if ESPHOME_VERSION_CODE >= VERSION_CODE(2025, 11, 0)
void play(const Ts &...x) override { this->parent_->set_attr(this->value_.value(x...)); }
#else
void play(Ts... x) override { this->parent_->set_attr(this->value_.value(x...)); }
#endif
protected:
ZigBeeAttribute *parent_;
};
template<typename Ts> class ZigBeeOnValueTrigger : public Trigger<Ts>, public Component {
public:
explicit ZigBeeOnValueTrigger(ZigBeeAttribute *parent) : parent_(parent) {}
void setup() override {
this->parent_->add_on_value_callback([this](esp_zb_zcl_attribute_t attribute) { this->on_value_(attribute); });
}
protected:
void on_value_(esp_zb_zcl_attribute_t attribute) {
if (attribute.data.type == parent_->attr_type() && attribute.data.value) {
this->trigger(get_value_by_type<Ts>(parent_->attr_type(), attribute.data.value));
}
}
ZigBeeAttribute *parent_;
};
template<typename T> struct ZigBeeReportData {
T value;
esp_zb_zcl_addr_t src_address;
uint8_t src_endpoint;
};
template<typename T> class ZigBeeOnReportTrigger : public Trigger<ZigBeeReportData<T>>, public Component {
public:
explicit ZigBeeOnReportTrigger(ZigBeeAttribute *parent) : parent_(parent) {}
void setup() override {
this->parent_->add_on_report_callback(
[this](esp_zb_zcl_attribute_t attribute, esp_zb_zcl_addr_t src_address, uint8_t src_endpoint) {
this->on_report_(attribute, src_address, src_endpoint);
});
}
protected:
void on_report_(esp_zb_zcl_attribute_t attribute, esp_zb_zcl_addr_t src_address, uint8_t src_endpoint) {
if (attribute.data.type == parent_->attr_type() && attribute.data.value) {
this->trigger(ZigBeeReportData<T>{
.value = get_value_by_type<T>(parent_->attr_type(), attribute.data.value),
.src_address = src_address,
.src_endpoint = src_endpoint,
});
}
}
ZigBeeAttribute *parent_;
};
template<class T> T get_value_by_type(uint8_t attr_type, void *data) {
switch (attr_type) {
case ESP_ZB_ZCL_ATTR_TYPE_SEMI:
return 0; //(T) * (std::float16_t *) data;
case ESP_ZB_ZCL_ATTR_TYPE_OCTET_STRING:
return 0;
case ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING:
return 0;
case ESP_ZB_ZCL_ATTR_TYPE_LONG_OCTET_STRING:
return 0;
case ESP_ZB_ZCL_ATTR_TYPE_LONG_CHAR_STRING:
return 0;
case ESP_ZB_ZCL_ATTR_TYPE_ARRAY:
return 0;
case ESP_ZB_ZCL_ATTR_TYPE_16BIT_ARRAY:
return 0;
case ESP_ZB_ZCL_ATTR_TYPE_32BIT_ARRAY:
return 0;
case ESP_ZB_ZCL_ATTR_TYPE_STRUCTURE:
return 0;
case ESP_ZB_ZCL_ATTR_TYPE_SET:
return 0;
case ESP_ZB_ZCL_ATTR_TYPE_BAG:
return 0;
case ESP_ZB_ZCL_ATTR_TYPE_TIME_OF_DAY:
return (T) * (uint32_t *) data;
case ESP_ZB_ZCL_ATTR_TYPE_DATE:
return (T) * (uint32_t *) data;
case ESP_ZB_ZCL_ATTR_TYPE_UTC_TIME:
return (T) * (uint32_t *) data;
case ESP_ZB_ZCL_ATTR_TYPE_CLUSTER_ID:
return (T) * (uint16_t *) data;
case ESP_ZB_ZCL_ATTR_TYPE_ATTRIBUTE_ID:
return (T) * (uint16_t *) data;
case ESP_ZB_ZCL_ATTR_TYPE_BACNET_OID:
return 0;
case ESP_ZB_ZCL_ATTR_TYPE_IEEE_ADDR:
return (T) * (uint64_t *) data;
case ESP_ZB_ZCL_ATTR_TYPE_128_BIT_KEY:
return 0;
default:
return *(T *) data;
}
}
float get_r_from_xy(float x, float y);
float get_g_from_xy(float x, float y);
float get_b_from_xy(float x, float y);
#ifdef USE_LIGHT
void set_light_color(uint8_t ep, light::LightCall *call, uint16_t value, bool is_x);
#endif
} // namespace zigbee
} // namespace esphome
================================================
FILE: components/zigbee/const.py
================================================
from dataclasses import dataclass
import esphome.codegen as cg
CONF_ENDPOINTS = "endpoints"
CONF_DEVICE_TYPE = "device_type"
CONF_NUM = "num"
CONF_CLUSTERS = "clusters"
CONF_ON_JOIN = "on_join"
CONF_MANUFACTURER = "manufacturer"
CONF_ATTRIBUTES = "attributes"
CONF_ROLE = "role"
CONF_ENDPOINT = "endpoint"
CONF_CLUSTER = "cluster"
CONF_REPORT = "report"
CONF_ACCESS = "access"
CONF_SCALE = "scale"
CONF_ATTRIBUTE_ID = "attribute_id"
CONF_ZIGBEE_ID = "zigbee_id"
CONF_ROUTER = "router"
CONF_AS_GENERIC = "as_generic"
CONF_ON_REPORT = "on_report"
CONF_TRUST_CENTER_KEY = "trust_center_key"
CONF_DEVICE_VERSION = "device_version"
# dummies for upstream compatibility
binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor")
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase)
sensor_ns = cg.esphome_ns.namespace("sensor")
Sensor = sensor_ns.class_("Sensor", cg.EntityBase)
switch_ns = cg.esphome_ns.namespace("switch_") # NB: "switch_" not "switch"
Switch = switch_ns.class_("Switch", cg.EntityBase)
@dataclass
class AnalogInputType:
Temp_Degrees_C = 0x00
Relative_Humidity_Percent = 0x01
Pressure_Pascal = 0x02
Flow_Liters_Per_Sec = 0x03
Percentage = 0x04
Parts_Per_Million = 0x05
Rotational_Speed_RPM = 0x06
Current_Amps = 0x07
Frequency_Hz = 0x08
Power_Watts = 0x09
Power_Kilo_Watts = 0x0A
Energy_Kilo_Watt_Hours = 0x0B
Count = 0x0C
Enthalpy_KJoules_Per_Kg = 0x0D
Time_Seconds = 0x0E
@dataclass
class BacnetUnit:
"""BACnet units."""
SQUARE_METERS = 0
SQUARE_FEET = 1
MILLIAMPERES = 2
AMPERES = 3
OHMS = 4
VOLTS = 5
KILOVOLTS = 6
MEGAVOLTS = 7
VOLT_AMPERES = 8
KILOVOLT_AMPERES = 9
MEGAVOLT_AMPERES = 10
VOLT_AMPERES_REACTIVE = 11
KILOVOLT_AMPERES_REACTIVE = 12
MEGAVOLT_AMPERES_REACTIVE = 13
DEGREES_PHASE = 14
POWER_FACTOR = 15
JOULES = 16
KILOJOULES = 17
WATT_HOURS = 18
KILOWATT_HOURS = 19
BTUS = 20
THERMS = 21
TON_HOURS = 22
JOULES_PER_KILOGRAM_DRY_AIR = 23
BTUS_PER_POUND_DRY_AIR = 24
CYCLES_PER_HOUR = 25
CYCLES_PER_MINUTE = 26
HERTZ = 27
GRAMS_OF_WATER_PER_KILOGRAM_DRY_AIR = 28
PERCENT_RELATIVE_HUMIDITY = 29
MILLIMETERS = 30
METERS = 31
INCHES = 32
FEET = 33
WATTS_PER_SQUARE_FOOT = 34
WATTS_PER_SQUARE_METER = 35
LUMENS = 36
LUXES = 37
FOOT_CANDLES = 38
KILOGRAMS = 39
POUNDS_MASS = 40
TONS = 41
KILOGRAMS_PER_SECOND = 42
KILOGRAMS_PER_MINUTE = 43
KILOGRAMS_PER_HOUR = 44
POUNDS_MASS_PER_MINUTE = 45
POUNDS_MASS_PER_HOUR = 46
WATTS = 47
KILOWATTS = 48
MEGAWATTS = 49
BTUS_PER_HOUR = 50
HORSEPOWER = 51
TONS_REFRIGERATION = 52
PASCALS = 53
KILOPASCALS = 54
BARS = 55
POUNDS_FORCE_PER_SQUARE_INCH = 56
CENTIMETERS_OF_WATER = 57
INCHES_OF_WATER = 58
MILLIMETERS_OF_MERCURY = 59
CENTIMETERS_OF_MERCURY = 60
INCHES_OF_MERCURY = 61
DEGREES_CELSIUS = 62
KELVIN = 63
DEGREES_FAHRENHEIT = 64
DEGREE_DAYS_CELSIUS = 65
DEGREE_DAYS_FAHRENHEIT = 66
YEARS = 67
MONTHS = 68
WEEKS = 69
DAYS = 70
HOURS = 71
MINUTES = 72
SECONDS = 73
METERS_PER_SECOND = 74
KILOMETERS_PER_HOUR = 75
FEET_PER_SECOND = 76
FEET_PER_MINUTE = 77
MILES_PER_HOUR = 78
CUBIC_FEET = 79
CUBIC_METERS = 80
IMPERIAL_GALLONS = 81
LITERS = 82
US_GALLONS = 83
CUBIC_FEET_PER_MINUTE = 84
CUBIC_METERS_PER_SECOND = 85
IMPERIAL_GALLONS_PER_MINUTE = 86
LITERS_PER_SECOND = 87
LITERS_PER_MINUTE = 88
US_GALLONS_PER_MINUTE = 89
DEGREES_ANGULAR = 90
DEGREES_CELSIUS_PER_HOUR = 91
DEGREES_CELSIUS_PER_MINUTE = 92
DEGREES_FAHRENHEIT_PER_HOUR = 93
DEGREES_FAHRENHEIT_PER_MINUTE = 94
NO_UNITS = 95
PARTS_PER_MILLION = 96
PARTS_PER_BILLION = 97
PERCENT = 98
PERCENT_PER_SECOND = 99
PER_MINUTE = 100
PER_SECOND = 101
PSI_PER_DEGREE_FAHRENHEIT = 102
RADIANS = 103
REVOLUTIONS_PER_MINUTE = 104
CURRENCY1 = 105
CURRENCY2 = 106
CURRENCY3 = 107
CURRENCY4 = 108
CURRENCY5 = 109
CURRENCY6 = 110
CURRENCY7 = 111
CURRENCY8 = 112
CURRENCY9 = 113
CURRENCY10 = 114
SQUARE_INCHES = 115
SQUARE_CENTIMETERS = 116
BTUS_PER_POUND = 117
CENTIMETERS = 118
POUNDS_MASS_PER_SECOND = 119
DELTA_DEGREES_FAHRENHEIT = 120
DELTA_KELVIN = 121
KILOHMS = 122
MEGOHMS = 123
MILLIVOLTS = 124
KILOJOULES_PER_KILOGRAM = 125
MEGAJOULES = 126
JOULES_PER_DEGREE_KELVIN = 127
JOULES_PER_KILOGRAM_DEGREE_KELVIN = 128
KILOHERTZ = 129
MEGAHERTZ = 130
PER_HOUR = 131
MILLIWATTS = 132
HECTOPASCALS = 133
MILLIBARS = 134
CUBIC_METERS_PER_HOUR = 135
LITERS_PER_HOUR = 136
KW_HOURS_PER_SQUARE_METER = 137
KW_HOURS_PER_SQUARE_FOOT = 138
MEGAJOULES_PER_SQUARE_METER = 139
MEGAJOULES_PER_SQUARE_FOOT = 140
WATTS_PER_SQUARE_METER_DEGREE_KELVIN = 141
CUBIC_FEET_PER_SECOND = 142
PERCENT_OBSCURATION_PER_FOOT = 143
PERCENT_OBSCURATION_PER_METER = 144
MILLIOHMS = 145
MEGAWATT_HOURS = 146
KILO_BTUS = 147
MEGA_BTUS = 148
KILOJOULES_PER_KILOGRAM_DRY_AIR = 149
MEGAJOULES_PER_KILOGRAM_DRY_AIR = 150
KILOJOULES_PER_DEGREE_KELVIN = 151
MEGAJOULES_PER_DEGREE_KELVIN = 152
NEWTON = 153
GRAMS_PER_SECOND = 154
GRAMS_PER_MINUTE = 155
TONS_PER_HOUR = 156
KILO_BTUS_PER_HOUR = 157
HUNDREDTHS_SECONDS = 158
MILLISECONDS = 159
NEWTON_METERS = 160
MILLIMETERS_PER_SECOND = 161
MILLIMETERS_PER_MINUTE = 162
METERS_PER_MINUTE = 163
METERS_PER_HOUR = 164
CUBIC_METERS_PER_MINUTE = 165
METERS_PER_SECOND_PER_SECOND = 166
AMPERES_PER_METER = 167
AMPERES_PER_SQUARE_METER = 168
AMPERE_SQUARE_METERS = 169
FARADS = 170
HENRYS = 171
OHM_METERS = 172
SIEMENS_PER_METER = 174
TESLAS = 175
VOLTS_PER_DEGREE_KELVIN = 176
VOLTS_PER_METER = 177
WEBERS = 178
CANDELAS = 179
CANDELAS_PER_SQUARE_METER = 180
KELVIN_PER_HOUR = 181
KELVIN_PER_MINUTE = 182
JOULE_SECONDS = 183
RADIANS_PER_SECOND = 184
SQUARE_METERS_PER_NEWTON = 185
KILOGRAMS_PER_CUBIC_METER = 186
NEWTON_SECONDS = 187
NEWTONS_PER_METER = 188
WATTS_PER_METER_PER_DEGREE_KELVIN = 189
MICROSIEMENS = 190
CUBIC_FEET_PER_HOUR = 191
US_GALLONS_PER_HOUR = 192
KILOMETERS = 193
MICROMETERS = 194
GRAMS = 195
MILLIGRAMS = 196
MILLILITERS = 197
MILLILITERS_PER_SECOND = 198
DECIBELS = 199
DECIBELS_MILLIVOLT = 200
DECIBELS_VOLT = 201
MILLISIEMENS = 202
WATT_REACTIVE_HOURS = 203
KILOWATT_REACTIVE_HOURS = 204
MEGAWATT_REACTIVE_HOURS = 205
MILLIMETERS_OF_WATER = 206
PER_MILLE = 207
GRAMS_PER_GRAM = 208
KILOGRAMS_PER_KILOGRAM = 209
GRAMS_PER_KILOGRAM = 210
MILLIGRAMS_PER_GRAM = 211
MILLIGRAMS_PER_KILOGRAM = 212
GRAMS_PER_MILLILITER = 213
GRAMS_PER_LITER = 214
MILLIGRAMS_PER_LITER = 215
MICROGRAMS_PER_LITER = 216
GRAMS_PER_CUBIC_METER = 217
MILLIGRAMS_PER_CUBIC_METER = 218
MICROGRAMS_PER_CUBIC_METER = 219
NANOGRAMS_PER_CUBIC_METER = 220
GRAMS_PER_CUBIC_CENTIMETER = 221
BECQUERELS = 222
KILOBECQUERELS = 223
MEGABECQUERELS = 224
GRAY = 225
MILLIGRAY = 226
MICROGRAY = 227
SIEVERTS = 228
MILLISIEVERTS = 229
MICROSIEVERTS = 230
MICROSIEVERTS_PER_HOUR = 231
DECIBELS_A = 232
NEPHELOMETRIC_TURBIDITY_UNIT = 233
PH = 234
GRAMS_PER_SQUARE_METER = 235
MINUTES_PER_DEGREE_KELVIN = 236
OHM_METER_SQUARED_PER_METER = 237
AMPERE_SECONDS = 238
VOLT_AMPERE_HOURS = 239
KILOVOLT_AMPERE_HOURS = 240
MEGAVOLT_AMPERE_HOURS = 241
VOLT_AMPERE_REACTIVE_HOURS = 242
KILOVOLT_AMPERE_REACTIVE_HOURS = 243
MEGAVOLT_AMPERE_REACTIVE_HOURS = 244
VOLT_SQUARE_HOURS = 245
AMPERE_SQUARE_HOURS = 246
JOULE_PER_HOURS = 247
CUBIC_FEET_PER_DAY = 248
CUBIC_METERS_PER_DAY = 249
WATT_HOURS_PER_CUBIC_METER = 250
JOULES_PER_CUBIC_METER = 251
MOLE_PERCENT = 252
PASCAL_SECONDS = 253
MILLION_STANDARD_CUBIC_FEET_PER_MINUTE = 254
================================================
FILE: components/zigbee/esp_zb_event.h
================================================
#pragma once
#ifdef USE_ESP32
#include <cstddef> // for offsetof
#include <cstring> // for memcpy
#include "esp_zigbee_core.h"
#include "zboss_api.h"
#include "ha/esp_zigbee_ha_standard.h"
namespace esphome::zigbee {
class ZBEvent {
public:
ZBEvent(esp_zb_device_cb_common_info_t info, esp_zb_zcl_attribute_t attribute, uint8_t *current_level) {
this->callback_id_ = ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID;
this->init_set_attr_value_data(info, attribute, current_level);
}
ZBEvent(const esp_zb_zcl_report_attr_message_t *message) {
this->callback_id_ = ESP_ZB_CORE_REPORT_ATTR_CB_ID;
this->init_report_attr_data(message);
}
ZBEvent(esp_zb_zcl_cmd_info_t info, esp_zb_zcl_read_attr_resp_variable_t *variables) {
this->callback_id_ = ESP_ZB_CORE_CMD_READ_ATTR_RESP_CB_ID;
this->init_read_attr_resp_data(info, variables);
}
~ZBEvent() { this->release(); }
ZBEvent() : event_{}, callback_id_(ESP_ZB_CORE_BASIC_RESET_TO_FACTORY_RESET_CB_ID) {}
void release() {
// Free any allocated memory within the event
switch (this->callback_id_) {
case ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID:
if (this->event_.set_attr.attribute.data.value != nullptr &&
this->event_.set_attr.attribute.data.value != this->event_.set_attr.inline_data) {
free(this->event_.set_attr.attribute.data.value);
this->event_.set_attr.attribute.data.value = nullptr;
}
break;
case ESP_ZB_CORE_REPORT_ATTR_CB_ID:
if (this->event_.report_attr.attribute.data.value != nullptr &&
this->event_.report_attr.attribute.data.value != this->event_.report_attr.inline_data) {
free(this->event_.report_attr.attribute.data.value);
this->event_.report_attr.attribute.data.value = nullptr;
}
break;
case ESP_ZB_CORE_CMD_READ_ATTR_RESP_CB_ID: {
esp_zb_zcl_read_attr_resp_variable_t *var = &(this->event_.read_attr_resp.variables);
while (var != nullptr) {
if (var->attribute.data.value != nullptr &&
var->attribute.data.value != this->event_.read_attr_resp.inline_data) {
free(var->attribute.data.value);
var->attribute.data.value = nullptr;
}
var = var->next;
}
}
// Reset the event data
this->callback_id_ = ESP_ZB_CORE_BASIC_RESET_TO_FACTORY_RESET_CB_ID; // or some invalid value
break;
default:
break;
}
}
void load_set_attr_value_event(esp_zb_device_cb_common_info_t info, esp_zb_zcl_attribute_t attribute,
uint8_t *current_level) {
this->release();
this->callback_id_ = ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID;
this->init_set_attr_value_data(info, attribute, current_level);
}
void load_report_attr_event(const esp_zb_zcl_report_attr_message_t *message) {
this->release();
this->callback_id_ = ESP_ZB_CORE_REPORT_ATTR_CB_ID;
this->init_report_attr_data(message);
}
void load_read_attr_resp_event(esp_zb_zcl_cmd_info_t info, esp_zb_zcl_read_attr_resp_variable_t *variables) {
this->release();
this->callback_id_ = ESP_ZB_CORE_CMD_READ_ATTR_RESP_CB_ID;
this->init_read_attr_resp_data(info, variables);
}
// Disable copy to prevent double-delete
ZBEvent(const ZBEvent &) = delete;
ZBEvent &operator=(const ZBEvent &) = delete;
union {
struct set_attr_event {
esp_zb_device_cb_common_info_t info;
esp_zb_zcl_attribute_t attribute;
uint8_t current_level;
bool has_current_level;
uint8_t inline_data[4]; // For small data types
} set_attr;
struct report_attr_event {
uint8_t dst_endpoint;
uint16_t cluster;
esp_zb_zcl_attribute_t attribute;
esp_zb_zcl_addr_t src_address;
uint8_t src_endpoint;
uint8_t inline_data[4]; // For small data types
} report_attr;
struct read_attr_resp_event {
esp_zb_zcl_cmd_info_t info;
esp_zb_zcl_read_attr_resp_variable_t variables;
uint8_t inline_data[4]; // For small data types
} read_attr_resp;
} event_;
esp_zb_core_action_callback_id_t callback_id_;
private:
void init_set_attr_value_data(esp_zb_device_cb_common_info_t info, esp_zb_zcl_attribute_t attribute,
uint8_t *current_level) {
this->event_.set_attr.info = info;
this->event_.set_attr.attribute = attribute;
// get attribute.data.value with correct type
this->event_.set_attr.has_current_level = (current_level != nullptr);
if (current_level != nullptr) {
this->event_.set_attr.current_level = *current_level;
}
if (attribute.data.value != nullptr) {
// Copy the attribute value to avoid dangling pointer issues
size_t value_size = this->get_attribute_value_size_(attribute.data);
if (value_size > 4) {
this->event_.set_attr.attribute.data.value = malloc(value_size);
if (this->event_.set_attr.attribute.data.value != nullptr) {
memcpy(this->event_.set_attr.attribute.data.value, attribute.data.value, value_size);
}
} else {
memcpy(this->event_.set_attr.inline_data, attribute.data.value, value_size);
this->event_.set_attr.attribute.data.value = this->event_.set_attr.inline_data;
}
}
}
void init_report_attr_data(const esp_zb_zcl_report_attr_message_t *message) {
this->event_.report_attr.dst_endpoint = message->dst_endpoint;
this->event_.report_attr.cluster = message->cluster;
this->event_.report_attr.attribute = message->attribute;
this->event_.report_attr.src_address = message->src_address;
this->event_.report_attr.src_endpoint = message->src_endpoint;
if (message->attribute.data.value != nullptr) {
// Copy the attribute value to avoid dangling pointer issues
size_t value_size = this->get_attribute_value_size_(message->attribute.data);
if (value_size > 4) {
this->event_.report_attr.attribute.data.value = malloc(value_size);
if (this->event_.report_attr.attribute.data.value != nullptr) {
memcpy(this->event_.report_attr.attribute.data.value, message->attribute.data.value, value_size);
}
} else {
memcpy(this->event_.report_attr.inline_data, message->attribute.data.value, value_size);
this->event_.report_attr.attribute.data.value = this->event_.report_attr.inline_data;
}
}
}
void init_read_attr_resp_data(esp_zb_zcl_cmd_info_t info, esp_zb_zcl_read_attr_resp_variable_t *variables) {
this->event_.read_attr_resp.info = info;
if (variables == nullptr) {
return;
}
this->event_.read_attr_resp.variables.status = variables->status;
this->event_.read_attr_resp.variables.attribute.id = variables->attribute.id;
this->event_.read_attr_resp.variables.attribute.data.type = variables->attribute.data.type;
this->event_.read_attr_resp.variables.attribute.data.size = variables->attribute.data.size;
// Note: variables is a pointer to a struct/list; deep copy needed
if (variables->attribute.data.value != nullptr) {
size_t value_size = this->get_attribute_value_size_(variables->attribute.data);
if (value_size > 4) {
this->event_.read_attr_resp.variables.attribute.data.value = malloc(value_size);
if (this->event_.read_attr_resp.variables.attribute.data.value != nullptr) {
memcpy(this->event_.read_attr_resp.variables.attribute.data.value, variables->attribute.data.value,
value_size);
}
} else {
memcpy(this->event_.read_attr_resp.inline_data, variables->attribute.data.value, value_size);
this->event_.read_attr_resp.variables.attribute.data.value = this->event_.read_attr_resp.inline_data;
}
}
esp_zb_zcl_read_attr_resp_variable_t *var = variables;
esp_zb_zcl_read_attr_resp_variable_t *var_last = &(this->event_.read_attr_resp.variables);
while (var->next != nullptr) {
var = var->next;
size_t value_size = this->get_attribute_value_size_(var->attribute.data);
var_last->next = new esp_zb_zcl_read_attr_resp_variable_t();
if (var_last->next != nullptr) {
var_last->next->attribute.id = var->attribute.id;
var_last->next->status = var->status;
var_last->next->attribute.data.type = var->attribute.data.type;
var_last->next->attribute.data.size = var->attribute.data.size;
var_last->next->attribute.data.value = malloc(value_size);
if (var_last->next->attribute.data.value != nullptr) {
memcpy(var_last->next->attribute.data.value, var->attribute.data.value, value_size);
}
var_last = var_last->next;
}
}
}
size_t get_attribute_value_size_(esp_zb_zcl_attribute_data_t data) {
// Determine attribute value size based on attribute type (and size)
uint8_t len = 0;
switch (data.type) {
case ESP_ZB_ZCL_ATTR_TYPE_BOOL:
return sizeof(bool);
case ESP_ZB_ZCL_ATTR_TYPE_U8:
return sizeof(uint8_t);
case ESP_ZB_ZCL_ATTR_TYPE_U16:
return sizeof(uint16_t);
case ESP_ZB_ZCL_ATTR_TYPE_U24:
return 3; // 24 bits = 3 bytes
case ESP_ZB_ZCL_ATTR_TYPE_U32:
return sizeof(uint32_t);
case ESP_ZB_ZCL_ATTR_TYPE_U40:
return 5; // 40 bits = 5 bytes
case ESP_ZB_ZCL_ATTR_TYPE_U48:
return 6; // 48 bits = 6 bytes
case ESP_ZB_ZCL_ATTR_TYPE_U56:
return 7; // 56 bits = 7 bytes
case ESP_ZB_ZCL_ATTR_TYPE_U64:
return sizeof(uint64_t);
case ESP_ZB_ZCL_ATTR_TYPE_S8:
return sizeof(int8_t);
case ESP_ZB_ZCL_ATTR_TYPE_S16:
return sizeof(int16_t);
case ESP_ZB_ZCL_ATTR_TYPE_S24:
return 3; // 24 bits = 3 bytes
case ESP_ZB_ZCL_ATTR_TYPE_S32:
return sizeof(int32_t);
case ESP_ZB_ZCL_ATTR_TYPE_S40:
return 5; // 40 bits = 5 bytes
case ESP_ZB_ZCL_ATTR_TYPE_S48:
return 6; // 48 bits = 6 bytes
case ESP_ZB_ZCL_ATTR_TYPE_S56:
return 7; // 56 bits = 7 bytes
case ESP_ZB_ZCL_ATTR_TYPE_S64:
return sizeof(int64_t);
case ESP_ZB_ZCL_ATTR_TYPE_8BITMAP:
return sizeof(uint8_t);
case ESP_ZB_ZCL_ATTR_TYPE_16BITMAP:
return sizeof(uint16_t);
case ESP_ZB_ZCL_ATTR_TYPE_24BITMAP:
return 3; // 24 bits = 3 bytes
case ESP_ZB_ZCL_ATTR_TYPE_32BITMAP:
return sizeof(uint32_t);
case ESP_ZB_ZCL_ATTR_TYPE_40BITMAP:
return 5; // 40 bits = 5 bytes
case ESP_ZB_ZCL_ATTR_TYPE_48BITMAP:
return 6; // 48 bits = 6 bytes
case ESP_ZB_ZCL_ATTR_TYPE_56BITMAP:
return 7; // 56 bits = 7 bytes
case ESP_ZB_ZCL_ATTR_TYPE_64BITMAP:
return sizeof(uint64_t);
case ESP_ZB_ZCL_ATTR_TYPE_8BIT:
return sizeof(uint8_t);
case ESP_ZB_ZCL_ATTR_TYPE_16BIT:
return sizeof(uint16_t);
case ESP_ZB_ZCL_ATTR_TYPE_24BIT:
return 3; // 24 bits = 3 bytes
case ESP_ZB_ZCL_ATTR_TYPE_32BIT:
return sizeof(uint32_t);
case ESP_ZB_ZCL_ATTR_TYPE_40BIT:
return 5; // 40 bits = 5 bytes
case ESP_ZB_ZCL_ATTR_TYPE_48BIT:
return 6; // 48 bits = 6 bytes
case ESP_ZB_ZCL_ATTR_TYPE_56BIT:
return 7; // 56 bits = 7 bytes
case ESP_ZB_ZCL_ATTR_TYPE_64BIT:
return sizeof(uint64_t);
case ESP_ZB_ZCL_ATTR_TYPE_8BIT_ENUM:
return sizeof(uint8_t);
case ESP_ZB_ZCL_ATTR_TYPE_16BIT_ENUM:
return sizeof(uint16_t);
case ESP_ZB_ZCL_ATTR_TYPE_SEMI:
return sizeof(uint16_t);
case ESP_ZB_ZCL_ATTR_TYPE_SINGLE:
return sizeof(float);
case ESP_ZB_ZCL_ATTR_TYPE_DOUBLE:
return sizeof(double);
case ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING:
len = *((uint8_t *) data.value); // first byte is length
return len + 1; // length byte + string
case ESP_ZB_ZCL_ATTR_TYPE_TIME_OF_DAY:
return sizeof(uint32_t);
case ESP_ZB_ZCL_ATTR_TYPE_DATE:
return sizeof(uint32_t);
case ESP_ZB_ZCL_ATTR_TYPE_UTC_TIME:
return sizeof(uint32_t);
case ESP_ZB_ZCL_ATTR_TYPE_CLUSTER_ID:
return sizeof(uint16_t);
case ESP_ZB_ZCL_ATTR_TYPE_ATTRIBUTE_ID:
return sizeof(uint16_t);
case ESP_ZB_ZCL_ATTR_TYPE_BACNET_OID:
return sizeof(uint32_t);
case ESP_ZB_ZCL_ATTR_TYPE_IEEE_ADDR:
return sizeof(uint64_t);
case ESP_ZB_ZCL_ATTR_TYPE_128_BIT_KEY:
return 16; // 128 bits = 16 bytes
default:
return 0; // Unknown type
}
}
};
} // namespace esphome::zigbee
#endif // USE_ESP32
================================================
FILE: components/zigbee/files_to_parse/parse_zigbee_headers.py
================================================
#!/usr/bin/env python3
"""
Create python enums
"""
from pycparser import c_ast, parse_file
filename = "esp_zigbee_zcl_common.h"
ast = parse_file(filename, use_cpp=True)
typedefs = [t[1].type for t in ast.children() if isinstance(t[1], c_ast.Typedef)]
my_enums = [t for t in typedefs if isinstance(t.type, c_ast.Enum)]
def print_enum(enum):
print(enum.declname + ":")
print(
"\n".join([e.name + ": " + e.value.value for e in enum.type.values.enumerators])
)
def write_profileIDs(enums):
enum = list(filter(lambda e: e.declname == "esp_zb_ha_standard_devices_t", enums))[
0
]
enum_name = "ha_standard_devices"
to_py = f'{enum_name} = cg.esphome_ns.enum("{enum.declname}")\nDEVICE_ID' + " = {\n"
for e in enum.type.values.enumerators:
to_py += f' "{e.name.removeprefix("ESP_ZB_HA_").removesuffix("_DEVICE_ID")}": {enum_name}.{e.name},\n'
to_py += f' {(e.value.value.removesuffix("U").upper().replace("X","x"))}: {enum_name}.{e.name},\n'
to_py += "}\n"
return to_py
def write_clusterIDs(enums):
enum = list(filter(lambda e: e.declname == "esp_zb_zcl_cluster_id_t", enums))[0]
enum_name = "cluster_id"
to_py = (
f'{enum_name} = cg.esphome_ns.enum("{enum.declname}")\nCLUSTER_ID' + " = {\n"
)
for e in enum.type.values.enumerators:
to_py += f' "{e.name.removeprefix("ESP_ZB_ZCL_CLUSTER_ID_")}": {enum_name}.{e.name},\n'
to_py += f' {(e.value.value.removesuffix("U").upper().replace("X","x"))}: {enum_name}.{e.name},\n'
to_py += "}\n"
return to_py
def write_clusterRoles(enums):
enum = list(filter(lambda e: e.declname == "esp_zb_zcl_cluster_role_t", enums))[0]
enum_name = "cluster_role"
to_py = (
f'{enum_name} = cg.esphome_ns.enum("{enum.declname}")\nCLUSTER_ROLE' + " = {\n"
)
for e in enum.type.values.enumerators:
to_py += f' "{e.name.removeprefix("ESP_ZB_ZCL_CLUSTER_").removesuffix("_ROLE")}": {enum_name}.{e.name},\n'
to_py += "}\n"
return to_py
def write_ZBtypes(enums):
enum = list(filter(lambda e: e.declname == "esp_zb_zcl_attr_type_t", enums))[0]
enum_name = "attr_type"
to_py = f'{enum_name} = cg.esphome_ns.enum("{enum.declname}")\nATTR_TYPE' + " = {\n"
for e in enum.type.values.enumerators:
to_py += f' "{e.name.removeprefix("ESP_ZB_ZCL_ATTR_TYPE_")}": {enum_name}.{e.name},\n'
to_py += "}\n"
return to_py
with open("zigbee_const.py", "w", encoding="utf-8") as f:
f.write("import esphome.codegen as cg\n\n")
f.write(write_profileIDs(my_enums))
f.write(write_clusterIDs(my_enums))
f.write(write_clusterRoles(my_enums))
f.write(write_ZBtypes(my_enums))
f.write(
"""ATTR_ACCESS = {
"READ_ONLY": 1,
"WRITE_ONLY": 2,
"READ_WRITE": 3,
}
"""
)
# [print_enum(e) for e in enums]
replacements_cl = {
"level_control": "level",
"multi_value": "multistate_value",
"multi_input": "multistate_input",
"multi_output": "multistate_output",
"ota_upgrade": "ota",
"illuminance_measurement": "illuminance_meas",
"temp_measurement": "temperature_meas",
"pressure_measurement": "pressure_meas",
"rel_humidity_measurement": "humidity_meas",
"electrical_measurement": "electrical_meas",
"flow_measurement": "flow_meas",
}
remove_cl = [
"rssi_location",
"green_power",
"keep_alive",
"pump_config_control",
"ballast_config",
]
def write_cluster_list_aou(enums):
enum = list(filter(lambda e: e.declname == "esp_zb_zcl_cluster_id_t", enums))[0]
ids = [e.name for e in enum.type.values.enumerators]
to_py = "esp_err_t esphome_zb_cluster_list_add_or_update_cluster(uint16_t cluster_id, esp_zb_cluster_list_t *cluster_list, esp_zb_attribute_list_t *attr_list, uint8_t role_mask) {\n esp_err_t ret;\n"
to_py += " ret = esp_zb_cluster_list_update_cluster(cluster_list, attr_list, cluster_id, role_mask);\n"
to_py += " if (ret != ESP_OK) {\n"
to_py += (
' ESP_LOGE("zigbee_helper", "Ignore previous cluster not found error");\n'
)
to_py += " switch (cluster_id) {\n"
for id in ids:
cluster_name = id.removeprefix("ESP_ZB_ZCL_CLUSTER_ID_").lower()
if cluster_name in remove_cl:
continue
for k, i in replacements_cl.items():
cluster_name = cluster_name.replace(k, i)
to_py += f" case {id}:\n"
to_py += f" ret = esp_zb_cluster_list_add_{cluster_name}_cluster(cluster_list, attr_list, role_mask);\n"
to_py += " break;\n"
to_py += " default:\n ret = esp_zb_cluster_list_add_custom_cluster(cluster_list, attr_list, role_mask);\n }\n }\n return ret;\n}\n\n"
return to_py
replacements_attrl = {
"level_control": "level",
"multi_value": "multistate_value",
"multi_input": "multistate_input",
"multi_output": "multistate_output",
"ota_upgrade": "ota",
"illuminance_measurement": "illuminance_meas",
"temp_measurement": "temperature_meas",
"pressure_measurement": "pressure_meas",
"rel_humidity_measurement": "humidity_meas",
"electrical_measurement": "electrical_meas",
"flow_measurement": "flow_meas",
}
void_list = []
def write_attr_list_create(enums):
enum = list(filter(lambda e: e.declname == "esp_zb_zcl_cluster_id_t", enums))[0]
ids = [e.name for e in enum.type.values.enumerators]
to_py = "esp_zb_attribute_list_t *esphome_zb_default_attr_list_create(uint16_t cluster_id) {\n"
to_py += " switch (cluster_id) {\n"
for id in ids:
cluster_name = id.removeprefix("ESP_ZB_ZCL_CLUSTER_ID_").lower()
if cluster_name in remove_cl:
continue
for k, i in replacements_attrl.items():
cluster_name = cluster_name.replace(k, i)
to_py += f" case {id}:\n"
if cluster_name in void_list:
to_py += f" return esp_zb_{cluster_name}_cluster_create();\n"
else:
to_py += f" return esp_zb_{cluster_name}_cluster_create(NULL);\n"
to_py += " default:\n return esp_zb_zcl_attr_list_create(cluster_id);\n }\n}\n\n"
return to_py
remove_attra = [
"rssi_location",
"green_power",
"keep_alive",
"pump_config_control",
"ballast_config",
"ias_ace",
"price",
"metering",
]
def write_attr_add(enums):
enum = list(filter(lambda e: e.declname == "esp_zb_zcl_cluster_id_t", enums))[0]
ids = [e.name for e in enum.type.values.enumerators]
to_py = "esp_err_t esphome_zb_cluster_add_attr(uint16_t cluster_id, esp_zb_attribute_list_t *attr_list, uint16_t attr_id, void *value_p) {\n"
to_py += " switch (cluster_id) {\n"
for id in ids:
cluster_name = id.removeprefix("ESP_ZB_ZCL_CLUSTER_ID_").lower()
if cluster_name in remove_attra:
continue
for k, i in replacements_cl.items():
cluster_name = cluster_name.replace(k, i)
to_py += f" case {id}:\n"
to_py += f" return esp_zb_{cluster_name}_cluster_add_attr(attr_list, attr_id, value_p);\n"
to_py += " default:\n return ESP_FAIL;\n }\n}\n\n"
return to_py
with open("c_helpers.c", "w", encoding="utf-8") as f:
f.write(write_cluster_list_aou(my_enums))
f.write(write_attr_list_create(my_enums))
f.write(write_attr_add(my_enums))
================================================
FILE: components/zigbee/partitions_zb.csv
================================================
otadata, data, ota, , 0x2000,
phy_init, data, phy, , 0x1000,
app0, app, ota_0, , 0x1B0000,
app1, app, ota_1, , 0x1B0000,
nvs, data, nvs, , 0x6D000,
zb_storage, data, fat, , 16K,
zb_fct, data, fat, , 1K,
================================================
FILE: components/zigbee/time/__init__.py
================================================
import esphome.codegen as cg
from esphome.components import time as time_
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.cpp_generator import get_variable
from ..const import CONF_ZIGBEE_ID
DEPENDENCIES = ["zigbee"]
zigbee_ns = cg.esphome_ns.namespace("zigbee")
ZigbeeTime = zigbee_ns.class_("ZigbeeTime", time_.RealTimeClock)
ZigBeeComponent = zigbee_ns.class_("ZigBeeComponent", cg.Component)
CONFIG_SCHEMA = time_.TIME_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(ZigbeeTime),
cv.GenerateID(CONF_ZIGBEE_ID): cv.use_id(ZigBeeComponent),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
cg.add_define("USE_ZIGBEE_TIME")
zb = await get_variable(config[CONF_ZIGBEE_ID])
var = cg.new_Pvariable(config[CONF_ID], zb)
await cg.register_component(var, config)
await time_.register_time(var, config)
================================================
FILE: components/zigbee/time/zigbee_time.cpp
================================================
#include "zigbee_time.h"
#include "esphome/core/log.h"
namespace esphome {
namespace zigbee {
void ZigbeeTime::send_timesync_request() {
ESP_LOGD(TAG, "Requesting time from coordinator...");
uint16_t attributes[] = {ESP_ZB_ZCL_ATTR_TIME_TIME_ID, ESP_ZB_ZCL_ATTR_TIME_TIME_STATUS_ID};
esp_zb_zcl_read_attr_cmd_t read_req;
read_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT;
read_req.attr_field = attributes;
read_req.attr_number = sizeof(attributes) / sizeof(uint16_t);
read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TIME;
read_req.zcl_basic_cmd.dst_endpoint = 1;
read_req.zcl_basic_cmd.src_endpoint = 1;
read_req.zcl_basic_cmd.dst_addr_u.addr_short = 0x0000; // coordinator
if (esp_zb_lock_acquire(30 / portTICK_PERIOD_MS)) {
esp_zb_zcl_read_attr_cmd_req(&read_req);
esp_zb_lock_release();
this->requested_ = true;
ESP_LOGD(TAG, "Sent request");
}
}
void ZigbeeTime::recieve_timesync_response(esp_zb_zcl_read_attr_resp_variable_t *variable) {
uint32_t utc = 0;
uint8_t sync_status = 0;
while (variable != nullptr) {
ESP_LOGD(TAG, "Read attribute response: status(%d), attribute(0x%x), type(0x%x), value(%d)", variable->status,
variable->attribute.id, variable->attribute.data.type,
variable->attribute.data.value ? *(uint32_t *) variable->attribute.data.value : 0);
switch (variable->attribute.id) {
case ESP_ZB_ZCL_ATTR_TIME_TIME_ID:
utc = *(uint32_t *) variable->attribute.data.value;
utc = utc + zigbee_time_offset;
ESP_LOGD(TAG, "Recieved UTC time: %d", utc);
break;
case ESP_ZB_ZCL_ATTR_TIME_TIME_STATUS_ID:
sync_status = *(uint8_t *) variable->attribute.data.value;
ESP_LOGD(TAG, "Recieved sync status time: 0x%x", sync_status);
break;
default:
ESP_LOGD(TAG, "Recieved other time property: not yet handled");
break;
}
variable = variable->next;
}
if ((utc != 0) && (sync_status & 0x3 != 0)) { /* 0x3 = either Master or Syncronized bits set */
this->set_utc_time(utc);
} else {
ESP_LOGD(TAG, "Did not recieve both time and status; clock NOT updated");
}
}
void ZigbeeTime::setup() {
ESP_LOGD(TAG, "Using Zigbee network as time source");
this->synced_ = false;
this->requested_ = false;
this->zc_->zt_ = this;
}
void ZigbeeTime::update() {
ESP_LOGD(TAG, "Updating time sync from Zigbee network...");
this->synced_ = false;
}
void ZigbeeTime::loop() {
if (this->synced_)
return;
if (this->requested_)
return;
if (zc_->is_connected()) {
this->send_timesync_request();
}
}
void ZigbeeTime::set_utc_time(uint32_t utc) {
this->synchronize_epoch_(utc);
this->synced_ = true;
this->requested_ = false;
}
} // namespace zigbee
} // namespace esphome
================================================
FILE: components/zigbee/time/zigbee_time.h
================================================
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/time/real_time_clock.h"
#include "../zigbee.h"
namespace esphome {
namespace zigbee {
static const uint32_t zigbee_time_offset =
946684800; /* Zigbee time is based on counting seconds from 1 Jan 2000 (=946684800) */
class ZigBeeComponent;
class ZigbeeTime : public time::RealTimeClock {
public:
ZigbeeTime(ZigBeeComponent *zc) : zc_(zc) {}
void setup() override;
void loop() override;
void update() override;
void set_utc_time(uint32_t utc);
void send_timesync_request();
void recieve_timesync_response(esp_zb_zcl_read_attr_resp_variable_t *variable);
protected:
ZigBeeComponent *zc_;
bool synced_;
bool requested_;
};
} // namespace zigbee
} // namespace esphome
================================================
FILE: components/zigbee/types.py
================================================
from esphome import automation
import esphome.codegen as cg
zigbee_ns = cg.esphome_ns.namespace("zigbee")
ZigBeeComponent = zigbee_ns.class_("ZigBeeComponent", cg.Component)
ZigBeeAttribute = zigbee_ns.class_("ZigBeeAttribute", cg.Component)
ZigBeeOnValueTrigger = zigbee_ns.class_(
"ZigBeeOnValueTrigger", automation.Trigger.template(int), cg.Component
)
ZigBeeOnReportTrigger = zigbee_ns.class_(
"ZigBeeOnReportTrigger", automation.Trigger.template(int), cg.Component
)
ResetZigbeeAction = zigbee_ns.class_(
"ResetZigbeeAction", automation.Action, cg.Parented.template(ZigBeeComponent)
)
SetAttrAction = zigbee_ns.class_("SetAttrAction", automation.Action)
ReportAttrAction = zigbee_ns.class_(
"ReportAttrAction", automation.Action, cg.Parented.template(ZigBeeAttribute)
)
ReportAction = zigbee_ns.class_(
"ReportAction", automation.Action, cg.Parented.template(ZigBeeComponent)
)
================================================
FILE: components/zigbee/zigbee.cpp
================================================
#include "zigbee.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_check.h"
#include "nvs_flash.h"
#include "zigbee_attribute.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "zigbee_helpers.h"
#ifdef CONFIG_WIFI_COEX
#include "esp_coexist.h"
#endif
#if !(defined ZB_ED_ROLE || defined ZB_ROUTER_ROLE)
#error Define ZB_ED_ROLE or ZB_ROUTER_ROLE in idf.py menuconfig.
#endif
namespace esphome {
namespace zigbee {
ZigBeeComponent *global_zigbee;
device_params_t coord;
/**
* Creates a ZCL string from the given input string.
*
* @param str Pointer to the input null-terminated C-style string.
* @param max_size Maximum allowable size for the resulting ZCL string. Maximum value: 254.
* @param use_max_size Optional. If true, the `max_size` is used directly,
* overriding the actual size of the input string.
* @return Pointer to a dynamically allocated ZCL string.
* NOTE: Caller is responsible for freeing the allocated memory with `delete[]`.
*
*/
uint8_t *get_zcl_string(const char *str, uint8_t max_size, bool use_max_size) {
uint8_t str_len = static_cast<uint8_t>(strlen(str));
uint8_t zcl_str_size = use_max_size ? max_size : std::min(max_size, str_len);
uint8_t *zcl_str = new uint8_t[zcl_str_size + 1]; // string + length octet
zcl_str[0] = zcl_str_size;
memcpy(zcl_str + 1, str, str_len);
return zcl_str;
}
static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) {
if (esp_zb_bdb_start_top_level_commissioning(mode_mask) != ESP_OK) {
ESP_LOGE(TAG, "Start network steering failed!");
}
}
void ZigBeeComponent::report() {
for (const auto &[_, attribute] : this->attributes_) {
attribute->report();
}
}
void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) {
static uint8_t steering_retry_count = 0;
uint32_t *p_sg_p = signal_struct->p_app_signal;
esp_err_t err_status = signal_struct->esp_err_status;
esp_zb_app_signal_type_t sig_type = (esp_zb_app_signal_type_t) *p_sg_p;
esp_zb_zdo_signal_leave_params_t *leave_params = NULL;
switch (sig_type) {
case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP:
// Notifies the application that ZBOSS framework (scheduler, buffer pool, etc.) has started, but no
// join/rejoin/formation/BDB initialization has been done yet.
ESP_LOGD(TAG, "Zigbee stack initialized");
esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_INITIALIZATION);
break;
case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START:
// Device started for the first time after the NVRAM erase
case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT:
// Device started using the NVRAM contents.
if (err_status == ESP_OK) {
ESP_LOGD(TAG, "Device started up in %sfactory-reset mode", esp_zb_bdb_is_factory_new() ? "" : "non ");
global_zigbee->started_ = true;
if (esp_zb_bdb_is_factory_new()) {
ESP_LOGD(TAG, "Start network steering");
esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
} else {
ESP_LOGD(TAG, "Device rebooted");
global_zigbee->searchBindings();
}
} else {
ESP_LOGE(TAG, "FIRST_START. Device started up in %sfactory-reset mode with an error %d (%s)",
esp_zb_bdb_is_factory_new() ? "" : "non ", err_status, esp_err_to_name(err_status));
ESP_LOGW(TAG, "Failed to initialize Zigbee stack (status: %s)", esp_err_to_name(err_status));
esp_zb_scheduler_alarm((esp_zb_callback_t) bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_INITIALIZATION,
1000);
}
break;
case ESP_ZB_BDB_SIGNAL_STEERING:
// BDB network steering completed (Network steering only)
if (err_status == ESP_OK) {
steering_retry_count = 0;
esp_zb_ieee_addr_t extended_pan_id;
esp_zb_get_extended_pan_id(extended_pan_id);
ESP_LOGI(TAG,
"Joined network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: "
"0x%04hx, Channel:%d)",
extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], extended_pan_id[3],
extended_pan_id[2], extended_pan_id[1], extended_pan_id[0], esp_zb_get_pan_id(),
esp_zb_get_current_channel());
global_zigbee->joined_ = true;
global_zigbee->enable_loop_soon_any_context();
} else {
ESP_LOGI(TAG, "Network steering was not successful (status: %s)", esp_err_to_name(err_status));
if (steering_retry_count < 10) {
steering_retry_count++;
esp_zb_scheduler_alarm((esp_zb_callback_t) bdb_start_top_level_commissioning_cb,
ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000);
} else {
esp_zb_scheduler_alarm((esp_zb_callback_t) bdb_start_top_level_commissioning_cb,
ESP_ZB_BDB_MODE_NETWORK_STEERING, 600 * 1000);
}
}
break;
case ESP_ZB_ZDO_SIGNAL_LEAVE:
leave_params = (esp_zb_zdo_signal_leave_params_t *) esp_zb_app_signal_get_params(p_sg_p);
if (leave_params->leave_type == ESP_ZB_NWK_LEAVE_TYPE_RESET) {
ESP_LOGD(TAG, "Reset device");
esp_zb_factory_reset();
} else {
ESP_LOGD(TAG, "Leave_type: %u", leave_params->leave_type);
}
break;
default:
ESP_LOGD(TAG, "ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type,
esp_err_to_name(err_status));
break;
}
}
// Recall bounded devices from the binding table after reboot
void ZigBeeComponent::bindingTableCb(const esp_zb_zdo_binding_table_info_t *table_info, void *user_ctx) {
bool done = true;
esp_zb_zdo_mgmt_bind_param_t *req = (esp_zb_zdo_mgmt_bind_param_t *) user_ctx;
esp_zb_zdp_status_t zdo_status = (esp_zb_zdp_status_t) table_info->status;
ESP_LOGD(TAG, "Binding table callback for address 0x%04x with status %d", req->dst_addr, zdo_status);
if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) {
// Print binding table log simple
ESP_LOGD(TAG, "Binding table info: total %d, index %d, count %d", table_info->total, table_info->index,
table_info->count);
if (table_info->total == 0) {
ESP_LOGD(TAG, "No binding table entries found");
free(req);
global_zigbee->connected_ = true;
return;
}
esp_zb_zdo_binding_table_record_t *record = table_info->record;
for (int i = 0; i < table_info->count; i++) {
ESP_LOGD(TAG, "Binding table record: src_endp %d, dst_endp %d, cluster_id 0x%04x, dst_addr_mode %d",
record->src_endp, record->dst_endp, record->cluster_id, record->dst_addr_mode);
zb_device_params_t *device = (zb_device_params_t *) calloc(1, sizeof(zb_device_params_t));
device->endpoint = record->dst_endp;
if (record->dst_addr_mode == ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT ||
record->dst_addr_mode == ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT) {
device->short_addr = record->dst_address.addr_short;
} else { // ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT
memcpy(device->ieee_addr, record->dst_address.addr_long, sizeof(esp_zb_ieee_addr_t));
}
ESP_LOGD(TAG,
"Device bound to EP %d -> device endpoint: %d, short addr: 0x%04x, ieee addr: "
"%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X",
record->src_endp, device->endpoint, device->short_addr, device->ieee_addr[7], device->ieee_addr[6],
device->ieee_addr[5], device->ieee_addr[4], device->ieee_addr[3], device->ieee_addr[2],
device->ieee_addr[1], device->ieee_addr[0]);
record = record->next;
}
// Continue reading the binding table
if (table_info->index + table_info->count < table_info->total) {
/* There are unreported binding table entries, request for them. */
req->start_index = table_info->index + table_info->count;
esp_zb_zdo_binding_table_req(req, bindingTableCb, req);
done = false;
}
}
if (done) {
// Print bound devices
ESP_LOGD(TAG, "Filling bounded devices finished");
free(req);
global_zigbee->connected_ = true;
}
}
void ZigBeeComponent::searchBindings() {
esp_zb_zdo_mgmt_bind_param_t *mb_req = (esp_zb_zdo_mgmt_bind_param_t *) malloc(sizeof(esp_zb_zdo_mgmt_bind_param_t));
mb_req->dst_addr = esp_zb_get_short_address();
mb_req->start_index = 0;
ESP_LOGD(TAG, "Requesting binding table for address 0x%04x", mb_req->dst_addr);
esp_zb_zdo_binding_table_req(mb_req, bindingTableCb, (void *) mb_req);
}
void load_zb_event(ZBEvent *event, esp_zb_device_cb_common_info_t info, esp_zb_zcl_attribute_t attribute,
uint8_t *current_level) {
event->load_set_attr_value_event(info, attribute, current_level);
}
void load_zb_event(ZBEvent *event, const esp_zb_zcl_report_attr_message_t *message) {
event->load_report_attr_event(message);
}
void load_zb_event(ZBEvent *event, esp_zb_zcl_cmd_info_t info, esp_zb_zcl_read_attr_resp_variable_t *variables) {
event->load_read_attr_resp_event(info, variables);
}
template<typename... Args> void enqueue_zb_event(Args... args) {
// Allocate an event from the pool
ZBEvent *event = global_zigbee->zb_event_pool_.allocate();
if (event == nullptr) {
// No events available - queue is full or we're out of memory
global_zigbee->zb_events_.increment_dropped_count();
return;
}
// Load new event data (replaces previous event)
load_zb_event(event, args...);
// Push the event to the queue
global_zigbee->zb_events_.push(event);
// Push always succeeds because we're the only producer and the pool ensures we never exceed queue size
global_zigbee->enable_loop_soon_any_context();
App.wake_loop_threadsafe();
}
// Explicit template instantiations for the friend function
template void enqueue_zb_event(esp_zb_device_cb_common_info_t info, esp_zb_zcl_attribute_t attribute,
uint8_t *current_level);
template void enqueue_zb_event(const esp_zb_zcl_report_attr_message_t *message);
template void enqueue_zb_event(esp_zb_zcl_cmd_info_t info, esp_zb_zcl_read_attr_resp_variable_t *variables);
static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message) {
ESP_RETURN_ON_FALSE(message, ESP_FAIL, TAG, "Empty message");
ESP_RETURN_ON_FALSE(message->info.status == ESP_ZB_ZCL_STATUS_SUCCESS, ESP_ERR_INVALID_ARG, TAG,
"Received message: error status(%d)", message->info.status);
ESP_LOGD(TAG, "Received message: endpoint(%d), cluster(0x%x), attribute(0x%x), data size(%d)",
message->info.dst_endpoint, message->info.cluster, message->attribute.id, message->attribute.data.size);
// if the attribute is On/Off and it is set to Off, restore the previous level
esp_zb_zcl_attr_t *current_level = nullptr;
if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) {
if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID &&
message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL && !*(bool *) message->attribute.data.value) {
ESP_LOGD(TAG, "turned off");
if (esp_zb_zcl_get_cluster(message->info.dst_endpoint, ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL,
ESP_ZB_ZCL_CLUSTER_SERVER_ROLE) != nullptr) {
current_level =
esp_zb_zcl_get_attribute(message->info.dst_endpoint, ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL,
ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID);
if (current_level) {
ESP_LOGD(TAG, "got level");
esp_zb_zcl_set_attribute_val(message->info.dst_endpoint, ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL,
ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID,
current_level->data_p, false);
}
}
}
}
if (current_level != nullptr) {
enqueue_zb_event(message->info, message->attribute, (uint8_t *) current_level->data_p);
} else {
enqueue_zb_event(message->info, message->attribute, nullptr);
}
return ESP_OK;
}
static esp_err_t zb_cmd_attribute_handler(const esp_zb_zcl_cmd_read_attr_resp_message_t *message) {
ESP_RETURN_ON_FALSE(message, ESP_FAIL, TAG, "Empty message");
ESP_RETURN_ON_FALSE(message->info.status == ESP_ZB_ZCL_STATUS_SUCCESS, ESP_ERR_INVALID_ARG, TAG,
"Received message: error status(%d)", message->info.status);
enqueue_zb_event(message->info, message->variables);
return ESP_OK;
}
static esp_err_t zb_report_attribute_handler(const esp_zb_zcl_report_attr_message_t *message) {
ESP_RETURN_ON_FALSE(message, ESP_FAIL, TAG, "Empty message");
ESP_RETURN_ON_FALSE(message->status == ESP_ZB_ZCL_STATUS_SUCCESS, ESP_ERR_INVALID_ARG, TAG,
"Received message: error status(%d)", message->status);
enqueue_zb_event(message);
return ESP_OK;
}
static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message) {
esp_err_t ret = ESP_OK;
switch (callback_id) {
case ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID:
ret = zb_attribute_handler((esp_zb_zcl_set_attr_value_message_t *) message);
break;
case ESP_ZB_CORE_CMD_READ_ATTR_RESP_CB_ID:
ret = zb_cmd_attribute_handler((esp_zb_zcl_cmd_read_attr_resp_message_t *) message);
break;
case ESP_ZB_CORE_REPORT_ATTR_CB_ID:
ret = zb_report_attribute_handler((esp_zb_zcl_report_attr_message_t *) message);
break;
case ESP_ZB_CORE_CMD_DEFAULT_RESP_CB_ID:
ESP_LOGD(TAG, "Receive Zigbee default response callback");
break;
default:
ESP_LOGW(TAG, "Receive Zigbee action(0x%x) callback", callback_id);
break;
}
return ret;
}
void ZigBeeComponent::handle_attribute(esp_zb_device_cb_common_info_t info, esp_zb_zcl_attribute_t attribute,
uint8_t *current_level) {
if (this->attributes_.find({info.dst_endpoint, info.cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, attribute.id}) !=
this->attributes_.end()) {
this->attributes_[{info.dst_endpoint, info.cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, attribute.id}]->on_value(
attribute);
// if the attribute is On/Off and it is set to Off, restore the previous level
if (info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF && current_level != nullptr) {
if (attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL &&
!*(bool *) attribute.data.value) {
ESP_LOGD(TAG, "turned off");
if (this->attributes_.find({info.dst_endpoint, ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL,
ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID}) !=
this->attributes_.end()) {
ESP_LOGD(TAG, "found level");
esp_zb_zcl_attribute_t lvl_attr = {
.id = ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID,
.data =
{
.type = ESP_ZB_ZCL_ATTR_TYPE_U8,
.size = sizeof(uint8_t),
.value = current_level,
},
};
this->attributes_[{info.dst_endpoint, ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE,
ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID}]
->on_value(lvl_attr);
ESP_LOGD(TAG, "Light set to restore-level: %d", *current_level);
}
}
}
}
}
void ZigBeeComponent::handle_report_attribute(uint8_t dst_endpoint, uint16_t cluster, esp_zb_zcl_attribute_t attribute,
esp_zb_zcl_addr_t src_address, uint8_t src_endpoint) {
auto attr = this->attributes_.find({dst_endpoint, cluster, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE, attribute.id});
if (attr == this->attributes_.end()) {
ESP_LOGD(TAG, "No attributes configured for report (endpoint %d; cluster 0x%04x; attribute id 0x%04x)",
dst_endpoint, cluster, attribute.id);
return;
}
attr->second->on_report(attribute, src_address, src_endpoint);
}
void ZigBeeComponent::handle_read_attribute_response(esp_zb_zcl_cmd_info_t info,
esp_zb_zcl_read_attr_resp_variable_t *variables) {
switch (info.cluster) {
case ESP_ZB_ZCL_CLUSTER_ID_TIME:
ESP_LOGD(TAG, "Recieved time information");
#ifdef USE_ZIGBEE_TIME
if (this->zt_ == nullptr) {
ESP_LOGD(TAG, "No time component linked to update time!");
} else {
this->zt_->recieve_timesync_response(variables);
}
#else
ESP_LOGD(TAG, "No zigbee time component included at build time!");
#endif
break;
default:
ESP_LOGD(TAG, "Attribute data recieved (but not yet handled):");
while (variables) {
ESP_LOGD(TAG, "Read attribute response: status(%d), cluster(0x%x), attribute(0x%x), type(0x%x), value(%d)",
variables->status, info.cluster, variables->attribute.id, variables->attribute.data.type,
variables->attribute.data.value ? *(uint8_t *) variables->attribute.data.value : 0);
variables = variables->next;
}
}
}
void ZigBeeComponent::create_default_cluster(uint8_t endpoint_id, esp_zb_ha_standard_devices_t device_id) {
esp_zb_cluster_list_t *cluster_list = esphome_zb_default_clusters_create(device_id);
this->endpoint_list_[endpoint_id] =
std::tuple<esp_zb_ha_standard_devices_t, esp_zb_cluster_list_t *>(device_id, cluster_list);
// Add basic cluster
this->add_cluster(endpoint_id, ESP_ZB_ZCL_CLUSTER_ID_BASIC, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
// Add identify cluster if not already present
if (esp_zb_cluster_list_get_cluster(cluster_list, ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE) ==
nullptr) {
this->add_cluster(endpoint_id, ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
}
}
void ZigBeeComponent::add_cluster(uint8_t endpoint_id, uint16_t cluster_id, uint8_t role) {
esp_zb_attribute_list_t *attr_list;
switch (cluster_id) {
case 0:
attr_list = create_basic_cluster_();
break;
default:
attr_list = esphome_zb_default_attr_list_create(cluster_id);
}
this->attribute_list_[{endpoint_id, cluster_id, role}] = attr_list;
}
void ZigBeeComponent::set_basic_cluster(std::string model, std::string manufacturer, std::string date, uint8_t power,
uint8_t app_version, uint8_t stack_version, uint8_t hw_version,
std::string area, uint8_t physical_env) {
this->basic_cluster_data_ = {
.model = model,
.manufacturer = manufacturer,
.date = date,
.power = power,
.app_version = app_version,
.stack_version = stack_version,
.hw_version = hw_version,
.area = area,
.physical_env = physical_env,
};
}
esp_zb_attribute_list_t *ZigBeeComponent::create_basic_cluster_() {
// ------------------------------ Cluster BASIC ------------------------------
esp_zb_basic_cluster_cfg_t basic_cluster_cfg = {
.zcl_version = ESP_ZB_ZCL_BASIC_ZCL_VERSION_DEFAULT_VALUE,
.power_source = this->basic_cluster_data_.power,
};
ESP_LOGD(TAG, "Model: %s", this->basic_cluster_data_.model.c_str());
ESP_LOGD(TAG, "Manufacturer: %s", this->basic_cluster_data_.manufacturer.c_str());
ESP_LOGD(TAG, "Date: %s", this->basic_cluster_data_.date.c_str());
ESP_LOGD(TAG, "Area: %s", this->basic_cluster_data_.area.c_str());
uint8_t *ManufacturerName = get_zcl_string(this->basic_cluster_data_.manufacturer.c_str(),
32); // warning: this is in format {length, 'string'} :
uint8_t *ModelIdentifier = get_zcl_string(this->basic_cluster_data_.model.c_str(), 32);
uint8_t *DateCode = get_zcl_string(this->basic_cluster_data_.date.c_str(), 16);
uint8_t *Location = get_zcl_string(this->basic_cluster_data_.area.c_str(), 16);
esp_zb_attribute_list_t *attr_list = esp_zb_basic_cluster_create(&basic_cluster_cfg);
esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_APPLICATION_VERSION_ID,
&(this->basic_cluster_data_.app_version));
esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_STACK_VERSION_ID,
&(this->basic_cluster_data_.stack_version));
esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_HW_VERSION_ID,
&(this->basic_cluster_data_.hw_version));
esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, ManufacturerName);
esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, ModelIdentifier);
esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_DATE_CODE_ID, DateCode);
esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_LOCATION_DESCRIPTION_ID, Location);
esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_PHYSICAL_ENVIRONMENT_ID,
&(this->basic_cluster_data_.physical_env));
delete[] ManufacturerName;
delete[] ModelIdentifier;
delete[] DateCode;
delete[] Location;
return attr_list;
}
esp_err_t ZigBeeComponent::create_endpoint(uint8_t endpoint_id, esp_zb_ha_standard_devices_t device_id,
esp_zb_cluster_list_t *esp_zb_cluster_list) {
// ------------------------------ Create endpoint list ------------------------------
esp_zb_endpoint_config_t endpoint_config = {.endpoint = endpoint_id,
.app_profile_id = ESP_ZB_AF_HA_PROFILE_ID,
.app_device_id = device_id,
.app_device_version = this->device_version_};
return esp_zb_ep_list_add_ep(this->esp_zb_ep_list_, esp_zb_cluster_list, endpoint_config);
}
static void esp_zb_task_(void *pvParameters) {
if (esp_zb_start(false) != ESP_OK) {
ESP_LOGE(TAG, "Could not setup Zigbee");
// this->mark_failed();
vTaskDelete(NULL);
}
if ((global_zigbee->device_role_ == ESP_ZB_DEVICE_TYPE_ED) && (global_zigbee->basic_cluster_data_.power == 0x03)) {
ESP_LOGD(TAG, "Battery powered!");
// zb_set_ed_node_descriptor(0, 1, 1); // workaround, rx_on_when_idle should be 0 for battery powered devices.
esp_zb_set_node_descriptor_power_source(0);
} else {
esp_zb_set_node_descriptor_power_source(1);
}
esp_zb_stack_main_loop();
}
void ZigBeeComponent::setup() {
global_zigbee = this;
esp_zb_platform_config_t config = {
.radio_config = ESP_ZB_DEFAULT_RADIO_CONFIG(),
.host_config = ESP_ZB_DEFAULT_HOST_CONFIG(),
};
#ifdef CONFIG_WIFI_COEX
if (esp_coex_wifi_i154_enable() != ESP_OK) {
this->mark_failed();
return;
}
#endif
// ESP_ERROR_CHECK(nvs_flash_init()); not needed, called by esp32 component
if (esp_zb_platform_config(&config) != ESP_OK) {
this->mark_failed();
return;
}
/* initialize Zigbee stack */
esp_zb_zed_cfg_t zb_zed_cfg = {
.ed_timeout = ED_AGING_TIMEOUT,
.keep_alive = ED_KEEP_ALIVE,
};
esp_zb_zczr_cfg_t zb_zczr_cfg = {
.max_children = MAX_CHILDREN,
};
esp_zb_cfg_t zb_nwk_cfg = {
.esp_zb_role = this->device_role_,
.install_code_policy = INSTALLCODE_POLICY_ENABLE,
};
#ifdef ZB_ROUTER_ROLE
zb_nwk_cfg.nwk_cfg.zczr_cfg = zb_zczr_cfg;
#else
zb_nwk_cfg.nwk_cfg.zed_cfg = zb_zed_cfg;
#endif
esp_zb_init(&zb_nwk_cfg);
if (this->custom_trust_center_key_) {
esp_zb_enable_joining_to_distributed(true);
esp_zb_secur_TC_standard_distributed_key_set(this->trustkey_);
}
esp_err_t ret;
// clusters
for (auto const &[key, val] : this->attribute_list_) {
esp_zb_cluster_list_t *esp_zb_cluster_list = std::get<1>(this->endpoint_list_[std::get<0>(key)]);
ret = esphome_zb_cluster_list_add_or_update_cluster(std::get<1>(key), esp_zb_cluster_list, val, std::get<2>(key));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not create cluster 0x%04X with role %u: %s", std::get<1>(key), std::get<2>(key),
esp_err_to_name(ret));
}
}
this->attribute_list_.clear();
// endpoints
for (auto const &[ep_id, dev] : this->endpoint_list_) {
// create_default_cluster(key, val);
if (create_endpoint(ep_id, std::get<0>(dev), std::get<1>(dev)) != ESP_OK) {
ESP_LOGE(TAG, "Could not create endpoint %u", ep_id);
}
}
// ------------------------------ Register Device ------------------------------
if (esp_zb_device_register(this->esp_zb_ep_list_) != ESP_OK) {
ESP_LOGE(TAG, "Could not register the endpoint list");
this->mark_failed();
return;
}
esp_zb_core_action_handler_register(zb_action_handler);
if (esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK) != ESP_OK) {
ESP_LOGE(TAG, "Could not setup Zigbee");
this->mark_failed();
return;
}
// reporting
for (auto &[_, attribute] : this->attributes_) {
if (attribute->report_enabled) {
esp_zb_zcl_reporting_info_t reporting_info = attribute->get_reporting_info();
ESP_LOGD(TAG, "set reporting for cluster: %u", reporting_info.cluster_id);
if (esp_zb_zcl_update_reporting_info(&reporting_info) != ESP_OK) {
ESP_LOGE(TAG, "Could not configure reporting for attribute 0x%04X in cluster 0x%04X in endpoint %u",
reporting_info.attr_id, reporting_info.cluster_id, reporting_info.ep);
}
}
}
xTaskCreate(esp_zb_task_, "Zigbee_main", 4096, NULL, 24, NULL);
this->disable_loop(); // loop is only needed for processing events, so disable until we join a network
}
void ZigBeeComponent::loop() {
// Process all pending events
ZBEvent *event = this->zb_events_.pop();
while (event != nullptr) {
// Handle the event
switch (event->callback_id_) {
case ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID:
this->handle_attribute(
event->event_.set_attr.info, event->event_.set_attr.attribute,
event->event_.set_attr.has_current_level ? &event->event_.set_attr.current_level : nullptr);
break;
case ESP_ZB_CORE_CMD_READ_ATTR_RESP_CB_ID:
this->handle_read_attribute_response(event->event_.read_attr_resp.info,
&(event->event_.read_attr_resp.variables));
break;
case ESP_ZB_CORE_REPORT_ATTR_CB_ID:
this->handle_report_attribute(event->event_.report_attr.dst_endpoint, event->event_.report_attr.cluster,
event->event_.report_attr.attribute, event->event_.report_attr.src_address,
event->event_.report_attr.src_endpoint);
break;
}
// Free the event back to the pool
this->zb_event_pool_.release(event);
// Get the next event
event = this->zb_events_.pop();
}
// Log dropped events periodically
uint16_t dropped = this->zb_events_.get_and_reset_dropped_count();
if (dropped > 0) {
ESP_LOGW(TAG, "Dropped %u Zigbee events due to buffer overflow", dropped);
}
if (this->joined_) {
this->on_join_callback_.call();
this->joined_ = false; // only call once
this->connected_ = true;
} else if (this->connected_) {
this->disable_loop(); // only disable once connected
}
}
void ZigBeeComponent::dump_config() {
char trustkey_hex[format_hex_pretty_size(sizeof(this->trustkey_))];
ESP_LOGCONFIG(TAG, "ZigBee:");
ESP_LOGCONFIG(TAG, " Device Version: %u", this->device_version_);
if (this->custom_trust_center_key_) {
ESP_LOGCONFIG(TAG, " Custom Trust Center Key: %s",
format_hex_pretty_to(trustkey_hex, this->trustkey_, sizeof(this->trustkey_), '.'));
}
for (auto const &[key, val] : this->endpoint_list_) {
ESP_LOGCONFIG(TAG, " Endpoint: %u, %d", key, std::get<0>(val));
}
}
void ZigBeeComponent::set_trust_center_key(const char *trust_center_key) {
parse_hex(trust_center_key, this->trustkey_, sizeof(this->trustkey_));
this->custom_trust_center_key_ = true;
}
} // namespace zigbee
} // namespace esphome
================================================
FILE: components/zigbee/zigbee.h
================================================
#pragma once
#include <map>
#include <tuple>
#include "esphome/core/defines.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/log.h"
#include "esphome/core/lock_free_queue.h"
#include "esphome/core/event_pool.h"
#include "esp_zb_event.h"
#include "esp_zigbee_core.h"
#include "zboss_api.h"
#include "ha/esp_zigbee_ha_standard.h"
#include "zigbee_helpers.h"
#ifdef USE_ZIGBEE_TIME
#include "time/zigbee_time.h"
#endif
namespace esphome {
namespace zigbee {
static const char *const TAG = "zigbee";
static constexpr uint8_t MAX_ZB_QUEUE_SIZE = 32;
using device_params_t = struct DeviceParamsS {
esp_zb_ieee_addr_t ieee_addr;
uint8_t endpoint;
uint16_t short_addr;
};
using zdo_info_user_ctx_t = struct ZdoInfoCtxS {
uint8_t endpoint;
uint16_t short_addr;
};
using zb_device_params_t = struct zb_device_params_s {
esp_zb_ieee_addr_t ieee_addr;
uint8_t endpoint;
uint16_t short_addr;
};
/* Zigbee configuration */
#define INSTALLCODE_POLICY_ENABLE false /* enable the install code policy for security */
#define ED_AGING_TIMEOUT ESP_ZB_ED_AGING_TIMEOUT_64MIN
#define ED_KEEP_ALIVE 3000 /* 3000 millisecond */
#define MAX_CHILDREN 10
#define ESP_ZB_PRIMARY_CHANNEL_MASK \
ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK /* Zigbee primary channel mask use in the example */
#define ESP_ZB_DEFAULT_RADIO_CONFIG() \
{ .radio_mode = ZB_RADIO_MODE_NATIVE, }
#define ESP_ZB_DEFAULT_HOST_CONFIG() \
{ .host_connection_mode = ZB_HOST_CONNECTION_MODE_NONE, }
template<class T> T get_value_by_type(uint8_t attr_type, void *data);
uint8_t *get_zcl_string(const char *str, uint8_t max_size, bool use_max_size = false);
class ZigBeeAttribute;
class ZigbeeTime;
class ZigBeeComponent : public Component {
public:
void setup() override;
void loop() override;
void dump_config() override;
esp_err_t create_endpoint(uint8_t endpoint_id, esp_zb_ha_standard_devices_t device_id,
esp_zb_cluster_list_t *esp_zb_cluster_list);
void set_basic_cluster(std::string model, std::string manufacturer, std::string date, uint8_t power,
uint8_t app_version, uint8_t stack_version, uint8_t hw_version, std::string area,
uint8_t physical_env);
void set_trust_center_key(const char *trust_center_key);
void set_device_version(uint8_t version) { this->device_version_ = version; }
void add_cluster(uint8_t endpoint_id, uint16_t cluster_id, uint8_t role);
void create_default_cluster(uint8_t endpoint_id, esp_zb_ha_standard_devices_t device_id);
template<typename T>
void add_attr(ZigBeeAttribute *attr, uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, uint16_t attr_id,
uint8_t attr_type, uint8_t attr_access, uint8_t max_size, T value);
template<typename T>
void add_attr(uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, uint16_t attr_id, uint8_t attr_type,
uint8_t attr_access, uint8_t max_size, T value);
void handle_attribute(esp_zb_device_cb_common_info_t info, esp_zb_zcl_attribute_t attribute, uint8_t *current_level);
void handle_report_attribute(uint8_t dst_endpoint, uint16_t cluster, esp_zb_zcl_attribute_t attribute,
esp_zb_zcl_addr_t src_address, uint8_t src_endpoint);
void handle_read_attribute_response(esp_zb_zcl_cmd_info_t info, esp_zb_zcl_read_attr_resp_variable_t *variables);
void searchBindings();
static void bindingTableCb(const esp_zb_zdo_binding_table_info_t *table_info, void *user_ctx);
void reset() {
esp_zb_lock_acquire(portMAX_DELAY);
esp_zb_factory_reset();
esp_zb_lock_release();
}
void report();
#ifdef USE_ZIGBEE_TIME
ZigbeeTime *zt_{nullptr};
#endif
template<typename F> void add_on_join_callback(F &&callback) {
this->on_join_callback_.add(std::forward<F>(callback));
}
bool is_started() { return this->started_; }
bool is_connected() { return this->connected_; }
bool connected_ = false;
bool started_ = false;
bool joined_ = false;
CallbackManager<void()> on_join_callback_{};
struct {
std::string model;
std::string manufacturer;
std::string date;
uint8_t power;
uint8_t app_version;
uint8_t stack_version;
uint8_t hw_version;
std::string area;
uint8_t physical_env;
} basic_cluster_data_;
#ifdef ZB_ED_ROLE
esp_zb_nwk_device_type_t device_role_ = ESP_ZB_DEVICE_TYPE_ED;
#else
esp_zb_nwk_device_type_t device_role_ = ESP_ZB_DEVICE_TYPE_ROUTER;
#endif
protected:
template<typename... Args> friend void enqueue_zb_event(Args... args);
esphome::LockFreeQueue<ZBEvent, MAX_ZB_QUEUE_SIZE> zb_events_;
esphome::EventPool<ZBEvent, MAX_ZB_QUEUE_SIZE> zb_event_pool_;
esp_zb_attribute_list_t *create_basic_cluster_();
template<typename T>
void add_attr_(ZigBeeAttribute *attr, uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, uint16_t attr_id,
uint8_t attr_type, uint8_t attr_access, T *value_p);
std::map<uint8_t, std::tuple<esp_zb_ha_standard_devices_t, esp_zb_cluster_list_t *>> endpoint_list_;
std::map<std::tuple<uint8_t, uint16_t, uint8_t>, esp_zb_attribute_list_t *> attribute_list_;
std::map<std::tuple<uint8_t, uint16_t, uint8_t, uint16_t>, ZigBeeAttribute *> attributes_;
esp_zb_ep_list_t *esp_zb_ep_list_ = esp_zb_ep_list_create();
uint8_t ident_time_;
bool custom_trust_center_key_ = false;
uint8_t trustkey_[16] = {0};
uint8_t device_version_ = 0;
};
extern "C" void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct);
extern "C" void zb_set_ed_node_descriptor(bool power_src, bool rx_on_when_idle, bool alloc_addr);
template<typename T>
void ZigBeeComponent::add_attr(uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, uint16_t attr_id,
uint8_t attr_type, uint8_t attr_access, uint8_t max_size, T value) {
this->add_attr<T>(nullptr, endpoint_id, cluster_id, role, attr_id, attr_type, attr_access, max_size, value);
}
template<typename T>
void ZigBeeComponent::add_attr(ZigBeeAttribute *attr, uint8_t endpoint_id, uint16_t cluster_id, uint8_t role,
uint16_t attr_id, uint8_t attr_type, uint8_t attr_access, uint8_t max_size, T value) {
// The size byte of the zcl_str must be set to the maximum value,
// even though the initial string may be shorter.
if constexpr (std::is_same<T, std::string>::value) {
auto zcl_str = get_zcl_string(value.c_str(), max_size, true);
add_attr_(attr, endpoint_id, cluster_id, role, attr_id, attr_type, attr_access, zcl_str);
delete[] zcl_str;
} else if constexpr (std::is_convertible<T, const char *>::value) {
auto zcl_str = get_zcl_string(value, max_size, true);
add_attr_(attr, endpoint_id, cluster_id, role, attr_id, attr_type, attr_access, zcl_str);
delete[] zcl_str;
} else {
add_attr_(attr, endpoint_id, cluster_id, role, attr_id, attr_type, attr_access, &value);
}
}
template<typename T>
void ZigBeeComponent::add_attr_(ZigBeeAttribute *attr, uint8_t endpoint_id, uint16_t cluster_id, uint8_t role,
uint16_t attr_id, uint8_t attr_type, uint8_t attr_access, T *value_p) {
esp_zb_attribute_list_t *attr_list = this->attribute_list_[{endpoint_id, cluster_id, role}];
esp_err_t ret =
esphome_zb_cluster_add_or_update_attr(cluster_id, attr_list, attr_id, attr_type, attr_access, value_p);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not add attribute 0x%04X to cluster 0x%04X in endpoint %u: %s", attr_id, cluster_id,
endpoint_id, esp_err_to_name(ret));
}
if (attr != nullptr) {
this->attributes_[{endpoint_id, cluster_id, role, attr_id}] = attr;
}
}
} // namespace zigbee
} // namespace esphome
================================================
FILE: components/zigbee/zigbee_attribute.cpp
================================================
#include "zigbee_attribute.h"
namespace esphome {
namespace zigbee {
void ZigBeeAttribute::set_attr_() {
if (!this->zb_->is_connected()) {
return;
}
if (esp_zb_lock_acquire(20 / portTICK_PERIOD_MS)) {
esp_zb_zcl_status_t state = esp_zb_zcl_set_attribute_val(this->endpoint_id_, this->cluster_id_, this->role_,
this->attr_id_, this->value_p, false);
if (this->force_report_) {
this->report_(true);
}
this->set_attr_requested_ = false;
// Check for error
if (state != ESP_ZB_ZCL_STATUS_SUCCESS) {
ESP_LOGE(TAG, "Setting attribute failed: %s", esp_err_to_name(state));
}
ESP_LOGD(TAG, "Attribute set!");
esp_zb_lock_release();
}
}
void ZigBeeAttribute::report_() { this->report_(false); }
void ZigBeeAttribute::report_(bool has_lock) {
if (!this->zb_->is_connected()) {
return;
}
if (has_lock or esp_zb_lock_acquire(20 / portTICK_PERIOD_MS)) {
esp_zb_zcl_report_attr_cmd_t cmd = {
.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT,
.direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_CLI,
};
cmd.zcl_basic_cmd.dst_addr_u.addr_short = 0x0000;
cmd.zcl_basic_cmd.dst_endpoint = 1;
cmd.zcl_basic_cmd.src_endpoint = this->endpoint_id_;
cmd.clusterID = this->cluster_id_;
cmd.attributeID = this->attr_id_;
// cmd.cluster_role = reporting_info.cluster_role;
esp_zb_zcl_report_attr_cmd_req(&cmd);
this->report_requested_ = false;
if (!has_lock) {
esp_zb_lock_release();
}
}
}
esp_zb_zcl_reporting_info_t ZigBeeAttribute::get_reporting_info() {
esp_zb_zcl_reporting_info_t reporting_info = {
.direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_SRV,
.ep = this->endpoint_id_,
.cluster_id = this->cluster_id_,
.cluster_role = this->role_,
.attr_id = this->attr_id_,
.manuf_code = ESP_ZB_ZCL_ATTR_NON_MANUFACTURER_SPECIFIC,
};
reporting_info.dst.profile_id = ESP_ZB_AF_HA_PROFILE_ID;
reporting_info.u.send_info.min_interval = 10; /*!< Actual minimum reporting interval */
reporting_info.u.send_info.max_interval = 0; /*!< Actual maximum reporting interval */
reporting_info.u.send_info.def_min_interval = 10; /*!< Default minimum reporting interval */
reporting_info.u.send_info.def_max_interval = 0; /*!< Default maximum reporting interval */
reporting_info.u.send_info.delta.s16 = 0; /*!< Actual reportable change */
return reporting_info;
}
void ZigBeeAttribute::set_report(bool force) {
this->report_enabled = true;
this->force_report_ = force;
}
void ZigBeeAttribute::report() {
this->report_requested_ = true;
this->enable_loop();
}
void ZigBeeAttribute::loop() {
if (this->set_attr_requested_) {
this->set_attr_();
}
if (this->report_requested_) {
this->report_();
}
if (!this->report_requested_ && !this->set_attr_requested_) {
this->disable_loop();
}
}
} // namespace zigbee
} // namespace esphome
================================================
FILE: components/zigbee/zigbee_attribute.h
================================================
#pragma once
#include <type_traits>
#include "zigbee.h"
#include "esp_zigbee_core.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
#ifdef USE_SWITCH
#include "esphome/components/switch/switch.h"
#endif
#ifdef USE_LIGHT
#include "esphome/components/light/light_state.h"
#endif
namespace esphome {
namespace zigbee {
#ifdef USE_LIGHT
void set_light_color(uint8_t ep, light::LightCall *call, uint16_t value, bool is_x);
#endif
class ZigBeeAttribute : public Component {
public:
ZigBeeAttribute(ZigBeeComponent *parent, uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, uint16_t attr_id,
uint8_t attr_type, float scale)
: zb_(parent),
endpoint_id_(endpoint_id),
cluster_id_(cluster_id),
role_(role),
attr_id_(attr_id),
attr_type_(attr_type),
scale_(scale) {}
// void dump_config() override;
void loop() override;
template<typename T> void add_attr(uint8_t attr_access, uint8_t max_size, T value);
esp_zb_zcl_reporting_info_t get_reporting_info();
void set_report(bool force);
void report();
template<typename T> void set_attr(const T &value);
uint8_t attr_type() { return attr_type_; }
template<typename F> void add_on_value_callback(F &&callback) { on_value_callback_.add(std::forward<F>(callback)); }
void on_value(esp_zb_zcl_attribute_t attribute) { this->on_value_callback_.call(attribute); }
void add_on_report_callback(
std::function<void(esp_zb_zcl_attribute_t attribute, esp_zb_zcl_addr_t src_address, uint8_t src_endpoint)>
callback) {
on_report_callback_.add(std::move(callback));
}
void on_report(esp_zb_zcl_attribute_t attribute, esp_zb_zcl_addr_t src_address, uint8_t src_endpoint) {
this->on_report_callback_.call(attribute, src_address, src_endpoint);
}
bool report_enabled = false;
#ifdef USE_SENSOR
template<typename T> void connect(sensor::Sensor *sensor);
template<typename T> void connect(sensor::Sensor *sensor, std::function<T(float)> &&f);
#endif
#ifdef USE_BINARY_SENSOR
template<typename T> void connect(binary_sensor::BinarySensor *sensor);
template<typename T> void connect(binary_sensor::BinarySensor *sensor, std::function<T(bool)> &&f);
#endif
#ifdef USE_TEXT_SENSOR
template<typename T> void connect(text_sensor::TextSensor *sensor);
template<typename T> void connect(text_sensor::TextSensor *sensor, std::function<T(std::string)> &&f);
#endif
#ifdef USE_SWITCH
template<typename T> void connect(switch_::Switch *device);
template<typename T> void connect(switch_::Switch *device, std::function<bool(T)> &&f);
#endif
#ifdef USE_LIGHT
template<typename T> void connect(light::LightState *device);
#endif
protected:
void set_attr_();
void report_();
void report_(bool has_lock);
ZigBeeComponent *zb_;
uint8_t endpoint_id_;
uint16_t cluster_id_;
uint8_t role_;
uint16_t attr_id_;
uint8_t attr_type_;
uint8_t max_size_;
float scale_;
CallbackManager<void(esp_zb_zcl_attribute_t attribute)> on_value_callback_{};
CallbackManager<void(esp_zb_zcl_attribute_t attribute, esp_zb_zcl_addr_t src_address, uint8_t src_endpoint)>
on_report_callback_{};
void *value_p{nullptr};
bool set_attr_requested_{false};
bool report_requested_{false};
bool force_report_{false};
};
template<typename T> void ZigBeeAttribute::add_attr(uint8_t attr_access, uint8_t max_size, T value) {
this->max_size_ = max_size;
this->zb_->add_attr(this, this->endpoint_id_, this->cluster_id_, this->role_, this->attr_id_, this->attr_type_,
attr_access, max_size, std::move(value));
}
template<typename T> void ZigBeeAttribute::set_attr(const T &value) {
if constexpr (std::is_convertible<T, const char *>::value) {
auto zcl_str = get_zcl_string(value, this->max_size_);
if (this->value_p != nullptr) {
delete[](char *) this->value_p;
}
this->value_p = (void *) zcl_str;
} else if constexpr (std::is_same<T, std::string>::value) {
auto zcl_str = get_zcl_string(value.c_str(), this->max_size_);
if (this->value_p != nullptr) {
delete[](char *) this->value_p;
}
this->value_p = (void *) zcl_str;
} else {
if (this->value_p != nullptr) {
delete (T *) this->value_p;
}
T *value_p = new T;
*value_p = value;
this->value_p = (void *) value_p;
}
this->set_attr_requested_ = true;
this->enable_loop();
}
#ifdef USE_SENSOR
template<typename T> void ZigBeeAttribute::connect(sensor::Sensor *sensor) {
sensor->add_on_state_callback([=, this](float value) { this->set_attr((T) (this->scale_ * value)); });
}
template<typename T> void ZigBeeAttribute::connect(sensor::Sensor *sensor, std::function<T(float)> &&f) {
sensor->add_on_state_callback([=, this](float value) { this->set_attr(f(value)); });
}
#endif
#ifdef USE_BINARY_SENSOR
template<typename T> void ZigBeeAttribute::connect(binary_sensor::BinarySensor *sensor) {
sensor->add_on_state_callback([=, this](bool value) { this->set_attr((T) (this->scale_ * value)); });
}
template<typename T> void ZigBeeAttribute::connect(binary_sensor::BinarySensor *sensor, std::function<T(bool)> &&f) {
sensor->add_on_state_callback([=, this](bool value) { this->set_attr(f(value)); });
}
#endif
#ifdef USE_TEXT_SENSOR
template<typename T> void ZigBeeAttribute::connect(text_sensor::TextSensor *sensor) {
sensor->add_on_state_callback([=, this](std::string value) { this->set_attr((T) (value)); });
}
template<typename T> void ZigBeeAttribute::connect(text_sensor::TextSensor *sensor, std::function<T(std::string)> &&f) {
sensor->add_on_state_callback([=, this](std::string value) { this->set_attr(f(value)); });
}
#endif
#ifdef USE_SWITCH
template<typename T> void ZigBeeAttribute::connect(switch_::Switch *device) {
this->add_on_value_callback([=, this](esp_zb_zcl_attribute_t attribute) {
if (attribute.data.type == this->attr_type() && attribute.data.value) {
if (get_value_by_type<T>(this->attr_type(), attribute.data.value)) {
device->turn_on();
} else {
device->turn_off();
}
}
});
device->add_on_state_callback([=, this](bool value) { this->set_attr((T) (this->scale_ * value)); });
}
template<typename T> void ZigBeeAttribute::connect(switch_::Switch *device, std::function<bool(T)> &&f) {
this->add_on_value_callback([=, this](esp_zb_zcl_attribute_t attribute) {
if (attribute.data.type == this->attr_type() && attribute.data.value) {
if (f(get_value_by_type<T>(this->attr_type(), attribute.data.value))) {
device->turn_on();
} else {
device->turn_off();
}
}
});
}
#endif
#ifdef USE_LIGHT
template<typename T> void ZigBeeAttribute::connect(light::LightState *device) {
this->add_on_value_callback([=, this](esp_zb_zcl_attribute_t attribute) {
if (attribute.data.type == this->attr_type() && attribute.data.value) {
light::LightCall call = device->make_call();
ESP_LOGD(TAG, "Make light call");
if (std::is_same<T, bool>::value) {
call.set_state(get_value_by_type<T>(this->attr_type(), attribute.data.value));
ESP_LOGD(TAG, "Set state");
} else if (this->cluster_id_ == 0x0300 && this->attr_id_ == 0x3) {
// set X
set_light_color(this->endpoint_id_, &call, get_value_by_type<uint16_t>(this->attr_type(), attribute.data.value),
true);
ESP_LOGD(TAG, "Set X");
} else if (this->cluster_id_ == 0x0300 && this->attr_id_ == 0x4) {
// set Y
set_light_color(this->endpoint_id_, &call, get_value_by_type<uint16_t>(this->attr_type(), attribute.data.value),
false);
ESP_LOGD(TAG, "Set Y");
} else if (this->cluster_id_ != 0x0300 and std::numeric_limits<T>::is_integer) {
call.set_brightness((float) get_value_by_type<T>(this->attr_type(), attribute.data.value) /
255); // integer level between 0 and 255
ESP_LOGD(TAG, "Set level: %f", (float) get_value_by_type<T>(this->attr_type(), attribute.data.value) / 255);
//} else if (this->cluster_id_ != 0x0300 and std::is_floating_point<T>) {
// call.set_brightness(get_value_by_type<T>(this->attr_type(), attribute.data.value)); //float level between 0
// and 1
}
call.perform();
}
});
}
#endif
} // namespace zigbee
} // namespace esphome
================================================
FILE: components/zigbee/zigbee_const.py
================================================
import esphome.codegen as cg
ha_standard_devices = cg.esphome_ns.enum("esp_zb_ha_standard_devices_t")
DEVICE_ID = {
"ON_OFF_SWITCH": ha_standard_devices.ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID,
0x0000: ha_standard_devices.ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID,
"LEVEL_CONTROL_SWITCH": ha_standard_devices.ESP_ZB_HA_LEVEL_CONTROL_SWITCH_DEVICE_ID,
0x0001: ha_standard_devices.ESP_ZB_HA_LEVEL_CONTROL_SWITCH_DEVICE_ID,
"ON_OFF_OUTPUT": ha_standard_devices.ESP_ZB_HA_ON_OFF_OUTPUT_DEVICE_ID,
0x0002: ha_standard_devices.ESP_ZB_HA_ON_OFF_OUTPUT_DEVICE_ID,
"LEVEL_CONTROLLABLE_OUTPUT": ha_standard_devices.ESP_ZB_HA_LEVEL_CONTROLLABLE_OUTPUT_DEVICE_ID,
0x0003: ha_standard_devices.ESP_ZB_HA_LEVEL_CONTROLLABLE_OUTPUT_DEVICE_ID,
"SCENE_SELECTOR": ha_standard_devices.ESP_ZB_HA_SCENE_SELECTOR_DEVICE_ID,
0x0004: ha_standard_devices.ESP_ZB_HA_SCENE_SELECTOR_DEVICE_ID,
"CONFIGURATION_TOOL": ha_standard_devices.ESP_ZB_HA_CONFIGURATION_TOOL_DEVICE_ID,
0x0005: ha_standard_devices.ESP_ZB_HA_CONFIGURATION_TOOL_DEVICE_ID,
"REMOTE_CONTROL": ha_standard_devices.ESP_ZB_HA_REMOTE_CONTROL_DEVICE_ID,
0x0006: ha_standard_devices.ESP_ZB_HA_REMOTE_CONTROL_DEVICE_ID,
"COMBINED_INTERFACE": ha_standard_devices.ESP_ZB_HA_COMBINED_INTERFACE_DEVICE_ID,
0x0007: ha_standard_devices.ESP_ZB_HA_COMBINED_INTERFACE_DEVICE_ID,
"RANGE_EXTENDER": ha_standard_devices.ESP_ZB_HA_RANGE_EXTENDER_DEVICE_ID,
0x0008: ha_standard_devices.ESP_ZB_HA_RANGE_EXTENDER_DEVICE_ID,
"MAINS_POWER_OUTLET": ha_standard_devices.ESP_ZB_HA_MAINS_POWER_OUTLET_DEVICE_ID,
0x0009: ha_standard_devices.ESP_ZB_HA_MAINS_POWER_OUTLET_DEVICE_ID,
"DOOR_LOCK": ha_standard_devices.ESP_ZB_HA_DOOR_LOCK_DEVICE_ID,
0x000A: ha_standard_devices.ESP_ZB_HA_DOOR_LOCK_DEVICE_ID,
"DOOR_LOCK_CONTROLLER": ha_standard_devices.ESP_ZB_HA_DOOR_LOCK_CONTROLLER_DEVICE_ID,
0x000B: ha_standard_devices.ESP_ZB_HA_DOOR_LOCK_CONTROLLER_DEVICE_ID,
"SIMPLE_SENSOR": ha_standard_devices.ESP_ZB_HA_SIMPLE_SENSOR_DEVICE_ID,
0x000C: ha_standard_devices.ESP_ZB_HA_SIMPLE_SENSOR_DEVICE_ID,
"CONSUMPTION_AWARENESS": ha_standard_devices.ESP_ZB_HA_CONSUMPTION_AWARENESS_DEVICE_ID,
0x000D: ha_standard_devices.ESP_ZB_HA_CONSUMPTION_AWARENESS_DEVICE_ID,
"HOME_GATEWAY": ha_standard_devices.ESP_ZB_HA_HOME_GATEWAY_DEVICE_ID,
0x0050: ha_standard_devices.ESP_ZB_HA_HOME_GATEWAY_DEVICE_ID,
"SMART_PLUG": ha_standard_devices.ESP_ZB_HA_SMART_PLUG_DEVICE_ID,
0x0051: ha_standard_devices.ESP_ZB_HA_SMART_PLUG_DEVICE_ID,
"WHITE_GOODS": ha_standard_devices.ESP_ZB_HA_WHITE_GOODS_DEVICE_ID,
0x0052: ha_standard_devices.ESP_ZB_HA_WHITE_GOODS_DEVICE_ID,
"METER_INTERFACE": ha_standard_devices.ESP_ZB_HA_METER_INTERFACE_DEVICE_ID,
0x0053: ha_standard_devices.ESP_ZB_HA_METER_INTERFACE_DEVICE_ID,
"ON_OFF_LIGHT": ha_standard_devices.ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID,
0x0100: ha_standard_devices.ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID,
"DIMMABLE_LIGHT": ha_standard_devices.ESP_ZB_HA_DIMMABLE_LIGHT_DEVICE_ID,
0x0101: ha_standard_devices.ESP_ZB_HA_DIMMABLE_LIGHT_DEVICE_ID,
"COLOR_DIMMABLE_LIGHT": ha_standard_devices.ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID,
0x0102: ha_standard_devices.ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID,
"DIMMER_SWITCH": ha_standard_devices.ESP_ZB_HA_DIMMER_SWITCH_DEVICE_ID,
0x0104: ha_standard_devices.ESP_ZB_HA_DIMMER_SWITCH_DEVICE_ID,
"COLOR_DIMMER_SWITCH": ha_standard_devices.ESP_ZB_HA_COLOR_DIMMER_SWITCH_DEVICE_ID,
0x0105: ha_standard_devices.ESP_ZB_HA_COLOR_DIMMER_SWITCH_DEVICE_ID,
"LIGHT_SENSOR": ha_standard_devices.ESP_ZB_HA_LIGHT_SENSOR_DEVICE_ID,
0x0106: ha_standard_devices.ESP_ZB_HA_LIGHT_SENSOR_DEVICE_ID,
"SHADE": ha_standard_devices.ESP_ZB_HA_SHADE_DEVICE_ID,
0x0200: ha_standard_devices.ESP_ZB_HA_SHADE_DEVICE_ID,
"SHADE_CONTROLLER": ha_standard_devices.ESP_ZB_HA_SHADE_CONTROLLER_DEVICE_ID,
0x0201: ha_standard_devices.ESP_ZB_HA_SHADE_CONTROLLER_DEVICE_ID,
"WINDOW_COVERING": ha_standard_devices.ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID,
0x0202: ha_standard_devices.ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID,
"WINDOW_COVERING_CONTROLLER": ha_standard_devices.ESP_ZB_HA_WINDOW_COVERING_CONTROLLER_DEVICE_ID,
0x0203: ha_standard_devices.ESP_ZB_HA_WINDOW_COVERING_CONTROLLER_DEVICE_ID,
"HEATING_COOLING_UNIT": ha_standard_devices.ESP_ZB_HA_HEATING_COOLING_UNIT_DEVICE_ID,
0x0300: ha_standard_devices.ESP_ZB_HA_HEATING_COOLING_UNIT_DEVICE_ID,
"THERMOSTAT": ha_standard_devices.ESP_ZB_HA_THERMOSTAT_DEVICE_ID,
0x0301: ha_standard_devices.ESP_ZB_HA_THERMOSTAT_DEVICE_ID,
"TEMPERATURE_SENSOR": ha_standard_devices.ESP_ZB_HA_TEMPERATURE_SENSOR_DEVICE_ID,
0x0302: ha_standard_devices.ESP_ZB_HA_TEMPERATURE_SENSOR_DEVICE_ID,
"IAS_CONTROL_INDICATING_EQUIPMENT_ID": ha_standard_devices.ESP_ZB_HA_IAS_CONTROL_INDICATING_EQUIPMENT_ID,
0x0400: ha_standard_devices.ESP_ZB_HA_IAS_CONTROL_INDICATING_EQUIPMENT_ID,
"IAS_ANCILLARY_CONTROL_EQUIPMENT_ID": ha_standard_devices.ESP_ZB_HA_IAS_ANCILLARY_CONTROL_EQUIPMENT_ID,
0x0401: ha_standard_devices.ESP_ZB_HA_IAS_ANCILLARY_CONTROL_EQUIPMENT_ID,
"IAS_ZONE_ID": ha_standard_devices.ESP_ZB_HA_IAS_ZONE_ID,
0x0402: ha_standard_devices.ESP_ZB_HA_IAS_ZONE_ID,
"IAS_WARNING": ha_standard_devices.ESP_ZB_HA_IAS_WARNING_DEVICE_ID,
0x0403: ha_standard_devices.ESP_ZB_HA_IAS_WARNING_DEVICE_ID,
"TEST": ha_standard_devices.ESP_ZB_HA_TEST_DEVICE_ID,
0xFFF0: ha_standard_devices.ESP_ZB_HA_TEST_DEVICE_ID,
"CUSTOM_TUNNEL": ha_standard_devices.ESP_ZB_HA_CUSTOM_TUNNEL_DEVICE_ID,
0xFFF1: ha_standard_devices.ESP_ZB_HA_CUSTOM_TUNNEL_DEVICE_ID,
"CUSTOM_ATTR": ha_standard_devices.ESP_ZB_HA_CUSTOM_ATTR_DEVICE_ID,
0xFFF2: ha_standard_devices.ESP_ZB_HA_CUSTOM_ATTR_DEVICE_ID,
}
cluster_id = cg.esphome_ns.enum("esp_zb_zcl_cluster_id_t")
CLUSTER_ID = {
"BASIC": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BASIC,
0x0000: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BASIC,
"POWER_CONFIG": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
0x0001: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
"DEVICE_TEMP_CONFIG": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DEVICE_TEMP_CONFIG,
0x0002: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DEVICE_TEMP_CONFIG,
"IDENTIFY": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY,
0x0003: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY,
"GROUPS": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_GROUPS,
0x0004: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_GROUPS,
"SCENES": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_SCENES,
0x0005: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_SCENES,
"ON_OFF": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ON_OFF,
0x0006: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ON_OFF,
"ON_OFF_SWITCH_CONFIG": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ON_OFF_SWITCH_CONFIG,
0x0007: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ON_OFF_SWITCH_CONFIG,
"LEVEL_CONTROL": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL,
0x0008: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL,
"ALARMS": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ALARMS,
0x0009: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ALARMS,
"TIME": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_TIME,
0x000A: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_TIME,
"RSSI_LOCATION": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_RSSI_LOCATION,
0x000B: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_RSSI_LOCATION,
"ANALOG_INPUT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ANALOG_INPUT,
0x000C: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ANALOG_INPUT,
"ANALOG_OUTPUT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT,
0x000D: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT,
"ANALOG_VALUE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ANALOG_VALUE,
0x000E: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ANALOG_VALUE,
"BINARY_INPUT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BINARY_INPUT,
0x000F: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BINARY_INPUT,
"BINARY_OUTPUT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT,
0x0010: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT,
"BINARY_VALUE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BINARY_VALUE,
0x0011: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BINARY_VALUE,
"MULTI_INPUT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_MULTI_INPUT,
0x0012: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_MULTI_INPUT,
"MULTI_OUTPUT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_MULTI_OUTPUT,
0x0013: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_MULTI_OUTPUT,
"MULTI_VALUE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_MULTI_VALUE,
0x0014: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_MULTI_VALUE,
"COMMISSIONING": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_COMMISSIONING,
0x0015: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_COMMISSIONING,
"OTA_UPGRADE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_OTA_UPGRADE,
0x0019: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_OTA_UPGRADE,
"POLL_CONTROL": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_POLL_CONTROL,
0x0020: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_POLL_CONTROL,
"GREEN_POWER": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_GREEN_POWER,
0x0021: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_GREEN_POWER,
"KEEP_ALIVE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_KEEP_ALIVE,
0x0025: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_KEEP_ALIVE,
"SHADE_CONFIG": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_SHADE_CONFIG,
0x0100: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_SHADE_CONFIG,
"DOOR_LOCK": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DOOR_LOCK,
0x0101: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DOOR_LOCK,
"WINDOW_COVERING": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING,
0x0102: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING,
"PUMP_CONFIG_CONTROL": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PUMP_CONFIG_CONTROL,
0x0200: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PUMP_CONFIG_CONTROL,
"THERMOSTAT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT,
0x0201: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT,
"FAN_CONTROL": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_FAN_CONTROL,
0x0202: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_FAN_CONTROL,
"DEHUMIDIFICATION_CONTROL": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DEHUMIDIFICATION_CONTROL,
0x0203: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DEHUMIDIFICATION_CONTROL,
"THERMOSTAT_UI_CONFIG": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT_UI_CONFIG,
0x0204: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT_UI_CONFIG,
"COLOR_CONTROL": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL,
0x0300: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL,
"BALLAST_CONFIG": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BALLAST_CONFIG,
0x0301: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BALLAST_CONFIG,
"ILLUMINANCE_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT,
0x0400: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT,
"TEMP_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT,
0x0402: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT,
"PRESSURE_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT,
0x0403: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT,
"FLOW_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_FLOW_MEASUREMENT,
0x0404: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_FLOW_MEASUREMENT,
"REL_HUMIDITY_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT,
0x0405: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT,
"OCCUPANCY_SENSING": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING,
0x0406: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING,
"PH_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PH_MEASUREMENT,
0x0409: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PH_MEASUREMENT,
"EC_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_EC_MEASUREMENT,
0x040A: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_EC_MEASUREMENT,
"WIND_SPEED_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_WIND_SPEED_MEASUREMENT,
0x040B: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_WIND_SPEED_MEASUREMENT,
"CARBON_DIOXIDE_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_CARBON_DIOXIDE_MEASUREMENT,
0x040D: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_CARBON_DIOXIDE_MEASUREMENT,
"PM2_5_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT,
0x042A: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT,
"IAS_ZONE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE,
0x0500: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE,
"IAS_ACE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IAS_ACE,
0x0501: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IAS_ACE,
"IAS_WD": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IAS_WD,
0x0502: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IAS_WD,
"PRICE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PRICE,
0x0700: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PRICE,
"DRLC": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DRLC,
0x0701: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DRLC,
"METERING": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_METERING,
0x0702: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_METERING,
"METER_IDENTIFICATION": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_METER_IDENTIFICATION,
0x0B01: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_METER_IDENTIFICATION,
"ELECTRICAL_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT,
0x0B04: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT,
"DIAGNOSTICS": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DIAGNOSTICS,
0x0B05: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DIAGNOSTICS,
}
cluster_role = cg.esphome_ns.enum("esp_zb_zcl_cluster_role_t")
CLUSTER_ROLE = {
"SERVER": cluster_role.ESP_ZB_ZCL_CLUSTER_SERVER_ROLE,
"CLIENT": cluster_role.ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE,
}
attr_type = cg.esphome_ns.enum("esp_zb_zcl_attr_type_t")
ATTR_TYPE = {
"NULL": attr_type.ESP_ZB_ZCL_ATTR_TYPE_NULL,
"8BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_8BIT,
"16BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_16BIT,
"24BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_24BIT,
"32BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_32BIT,
"40BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_40BIT,
"48BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_48BIT,
"56BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_56BIT,
"64BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_64BIT,
"BOOL": attr_type.ESP_ZB_ZCL_ATTR_TYPE_BOOL,
"8BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_8BITMAP,
"16BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_16BITMAP,
"24BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_24BITMAP,
"32BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_32BITMAP,
"40BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_40BITMAP,
"48BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_48BITMAP,
"56BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_56BITMAP,
"64BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_64BITMAP,
"U8": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U8,
"U16": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U16,
"U24": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U24,
"U32": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U32,
"U40": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U40,
"U48": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U48,
"U56": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U56,
"U64": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U64,
"S8": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S8,
"S16": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S16,
"S24": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S24,
"S32": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S32,
"S40": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S40,
"S48": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S48,
"S56": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S56,
"S64": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S64,
"8BIT_ENUM": attr_type.ESP_ZB_ZCL_ATTR_TYPE_8BIT_ENUM,
"16BIT_ENUM": attr_type.ESP_ZB_ZCL_ATTR_TYPE_16BIT_ENUM,
"SEMI": attr_type.ESP_ZB_ZCL_ATTR_TYPE_SEMI,
"SINGLE": attr_type.ESP_ZB_ZCL_ATTR_TYPE_SINGLE,
"DOUBLE": attr_type.ESP_ZB_ZCL_ATTR_TYPE_DOUBLE,
"OCTET_STRING": attr_type.ESP_ZB_ZCL_ATTR_TYPE_OCTET_STRING,
"CHAR_STRING": attr_type.ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING,
"LONG_OCTET_STRING": attr_type.ESP_ZB_ZCL_ATTR_TYPE_LONG_OCTET_STRING,
"LONG_CHAR_STRING": attr_type.ESP_ZB_ZCL_ATTR_TYPE_LONG_CHAR_STRING,
"ARRAY": attr_type.ESP_ZB_ZCL_ATTR_TYPE_ARRAY,
"16BIT_ARRAY": attr_type.ESP_ZB_ZCL_ATTR_TYPE_16BIT_ARRAY,
"32BIT_ARRAY": attr_type.ESP_ZB_ZCL_ATTR_TYPE_32BIT_ARRAY,
"STRUCTURE": attr_type.ESP_ZB_ZCL_ATTR_TYPE_STRUCTURE,
"SET": attr_type.ESP_ZB_ZCL_ATTR_TYPE_SET,
"BAG": attr_type.ESP_ZB_ZCL_ATTR_TYPE_BAG,
"TIME_OF_DAY": attr_type.ESP_ZB_ZCL_ATTR_TYPE_TIME_OF_DAY,
"DATE": attr_type.ESP_ZB_ZCL_ATTR_TYPE_DATE,
"UTC_TIME": attr_type.ESP_ZB_ZCL_ATTR_TYPE_UTC_TIME,
"CLUSTER_ID": attr_type.ESP_ZB_ZCL_ATTR_TYPE_CLUSTER_ID,
"ATTRIBUTE_ID": attr_type.ESP_ZB_ZCL_ATTR_TYPE_ATTRIBUTE_ID,
"BACNET_OID": attr_type.ESP_ZB_ZCL_ATTR_TYPE_BACNET_OID,
"IEEE_ADDR": attr_type.ESP_ZB_ZCL_ATTR_TYPE_IEEE_ADDR,
"128_BIT_KEY": attr_type.ESP_ZB_ZCL_ATTR_TYPE_128_BIT_KEY,
"INVALID": attr_type.ESP_ZB_ZCL_ATTR_TYPE_INVALID,
}
ATTR_ACCESS = {
"READ_ONLY": 1,
"WRITE_ONLY": 2,
"READ_WRITE": 3,
}
================================================
FILE: components/zigbee/zigbee_ep.py
================================================
import copy
from esphome.components import light, output
import esphome.config_validation as cv
from esphome.const import (
CONF_COMPONENTS,
CONF_DEVICE,
CONF_DEVICE_CLASS,
CONF_ID,
CONF_MAX_LENGTH,
CONF_TYPE,
CONF_UNIT_OF_MEASUREMENT,
CONF_VALUE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_DURATION,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_FREQUENCY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLUME_FLOW_RATE,
UNIT_AMPERE,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
UNIT_HERTZ,
UNIT_KILOWATT,
UNIT_KILOWATT_HOURS,
UNIT_LUX,
UNIT_MICROGRAMS_PER_CUBIC_METER,
UNIT_OHM,
UNIT_PARTS_PER_MILLION,
UNIT_PASCAL,
UNIT_PERCENT,
UNIT_SECOND,
UNIT_VOLT,
UNIT_WATT,
)
from esphome.core import CORE, ID
from .const import (
CONF_ACCESS,
CONF_AS_GENERIC,
CONF_ATTRIBUTE_ID,
CONF_ATTRIBUTES,
CONF_CLUSTERS,
CONF_DEVICE_TYPE,
CONF_ENDPOINTS,
CONF_NUM,
CONF_REPORT,
CONF_ROLE,
CONF_SCALE,
AnalogInputType,
BacnetUnit,
BinarySensor,
Sensor,
Switch,
)
from .types import ZigBeeAttribute
# endpoint configs:
ep_configs = {
"binary_input": {
CONF_DEVICE_TYPE: "CUSTOM_ATTR",
CONF_CLUSTERS: [
{
CONF_ID: "BINARY_INPUT",
CONF_ROLE: "SERVER",
CONF_ATTRIBUTES: [
{
CONF_ATTRIBUTE_ID: 0x55,
CONF_VALUE: False,
CONF_ACCESS: 0,
CONF_TYPE: "BOOL",
CONF_REPORT: True,
CONF_SCALE: 1,
CONF_DEVICE: None,
},
{
CONF_ATTRIBUTE_ID: 0x51,
CONF_VALUE: False,
CONF_ACCESS: 0,
CONF_TYPE: "BOOL",
CONF_REPORT: False,
CONF_SCALE: 1,
},
{
CONF_ATTRIBUTE_ID: 0x6F,
CONF_VALUE: 0,
CONF_ACCESS: 0,
CONF_TYPE: "8BITMAP",
CONF_REPORT: False,
CONF_SCALE: 1,
},
{
CONF_ATTRIBUTE_ID: 0x1C,
CONF_ACCESS: 0,
CONF_TYPE: "CHAR_STRING",
CONF_REPORT: False,
CONF_SCALE: 1,
},
],
},
],
},
"analog_input": {
CONF_DEVICE_TYPE: "CUSTOM_ATTR",
CONF_CLUSTERS: [
{
CONF_ID: "ANALOG_INPUT",
CONF_ROLE: "SERVER",
CONF_ATTRIBUTES: [
{
CONF_ATTRIBUTE_ID: 0x55,
CONF_VALUE: False,
CONF_ACCESS: 0,
CONF_TYPE: "SINGLE",
CONF_REPORT: True,
CONF_SCALE: 1,
CONF_DEVICE: None,
},
{
CONF_ATTRIBUTE_ID: 0x51,
CONF_VALUE: False,
CONF_ACCESS: 0,
CONF_TYPE: "BOOL",
CONF_REPORT: False,
CONF_SCALE: 1,
},
{
CONF_ATTRIBUTE_ID: 0x6F,
CONF_VALUE: 0,
CONF_ACCESS: 0,
CONF_TYPE: "8BITMAP",
CONF_REPORT: False,
CONF_SCALE: 1,
},
{
CONF_ATTRIBUTE_ID: 0x1C,
CONF_ACCESS: 0,
CONF_TYPE: "CHAR_STRING",
CONF_REPORT: False,
CONF_SCALE: 1,
},
],
},
],
},
"binary_output": {
CONF_DEVICE_TYPE: "CUSTOM_ATTR",
CONF_CLUSTERS: [
{
CONF_ID: "BINARY_OUTPUT",
CONF_ROLE: "SERVER",
CONF_ATTRIBUTES: [
{
CONF_ATTRIBUTE_ID: 0x55,
CONF_VALUE: False,
CONF_ACCESS: 0,
CONF_TYPE: "BOOL",
CONF_REPORT: True,
CONF_SCALE: 1,
CONF_DEVICE: None,
},
{
CONF_ATTRIBUTE_ID: 0x51,
CONF_VALUE: False,
CONF_ACCESS: 0,
CONF_TYPE: "BOOL",
CONF_REPORT: False,
CONF_SCALE: 1,
},
{
CONF_ATTRIBUTE_ID: 0x6F,
CONF_VALUE: 0,
CONF_ACCESS: 0,
CONF_TYPE: "8BITMAP",
CONF_REPORT: False,
CONF_SCALE: 1,
},
{
CONF_ATTRIBUTE_ID: 0x1C,
CONF_ACCESS: 0,
CONF_TYPE: "CHAR_STRING",
CONF_REPORT: False,
CONF_SCALE: 1,
},
],
},
],
},
"temperature": {
CONF_DEVICE_TYPE: "TEMPERATURE_SENSOR",
CONF_CLUSTERS: [
{
CONF_ID: "TEMP_MEASUREMENT",
CONF_ROLE: "SERVER",
CONF_ATTRIBUTES: [
{
CONF_ATTRIBUTE_ID: 0x0,
CONF_VALUE: 0,
CONF_ACCESS: 0,
CONF_TYPE: "S16",
CONF_REPORT: True,
CONF_SCALE: 100,
CONF_DEVICE: None,
},
],
},
],
},
"on_off": {
CONF_DEVICE_TYPE: "ON_OFF_OUTPUT",
CONF_CLUSTERS: [
{
CONF_ID: "ON_OFF",
CONF_ROLE: "SERVER",
CONF_ATTRIBUTES: [
{
CONF_ATTRIBUTE_ID: 0x0,
CONF_VALUE: False,
CONF_ACCESS: 0,
CONF_TYPE: "BOOL",
CONF_REPORT: True,
CONF_SCALE: 1,
CONF_DEVICE: None,
},
],
},
],
},
"on_off_light": {
CONF_DEVICE_TYPE: "ON_OFF_LIGHT",
CONF_CLUSTERS: [
{
CONF_ID: "ON_OFF",
CONF_ROLE: "SERVER",
CONF_ATTRIBUTES: [
{
CONF_ATTRIBUTE_ID: 0x0,
CONF_VALUE: False,
CONF_ACCESS: 0,
CONF_TYPE: "BOOL",
CONF_REPORT: True,
CONF_SCALE: 1,
CONF_DEVICE: None,
},
],
},
],
},
"color_light": {
CONF_DEVICE_TYPE: "COLOR_DIMMABLE_LIGHT",
CONF_CLUSTERS: [
{
CONF_ID: "ON_OFF",
CONF_ROLE: "SERVER",
CONF_ATTRIBUTES: [
{
CONF_ATTRIBUTE_ID: 0x0,
CONF_VALUE: False,
CONF_ACCESS: 0,
CONF_TYPE: "BOOL",
CONF_REPORT: True,
CONF_SCALE: 1,
CONF_DEVICE: None,
},
],
},
{
CONF_ID: "LEVEL_CONTROL",
CONF_ROLE: "SERVER",
CONF_ATTRIBUTES: [
{
CONF_ATTRIBUTE_ID: 0x0,
CONF_VALUE: 255,
CONF_ACCESS: 0,
CONF_TYPE: "U8",
CONF_REPORT: True,
CONF_SCALE: 1,
CONF_DEVICE: None,
},
],
},
{
CONF_ID: "COLOR_CONTROL",
CONF_ROLE: "SERVER",
CONF_ATTRIBUTES: [
{
CONF_ATTRIBUTE_ID: 0x3,
CONF_VALUE: 0x616B,
CONF_ACCESS: 0,
CONF_TYPE: "U16",
CONF_REPORT: True,
CONF_SCALE: 1,
CONF_DEVICE: None,
},
{
CONF_ATTRIBUTE_ID: 0x4,
CONF_VALUE: 0x607D,
CONF_ACCESS: 0,
CONF_TYPE: "U16",
CONF_REPORT: True,
CONF_SCALE: 1,
CONF_DEVICE: None,
},
{
CONF_ATTRIBUTE_ID: 0x400A,
CONF_VALUE: 8,
CONF_ACCESS: 0,
CONF_TYPE: "16BITMAP",
CONF_REPORT: False,
CONF_SCALE: 1,
},
],
},
],
},
"level_light": {
CONF_DEVICE_TYPE: "DIMMABLE_LIGHT",
CONF_CLUSTERS: [
{
CONF_ID: "ON_OFF",
CONF_ROLE: "SERVER",
CONF_ATTRIBUTES: [
{
CONF_ATTRIBUTE_ID: 0x0,
CONF_VALUE: False,
CONF_ACCESS: 0,
CONF_TYPE: "BOOL",
CONF_REPORT: True,
CONF_SCALE: 1,
CONF_DEVICE: None,
},
],
},
{
CONF_ID: "LEVEL_CONTROL",
CONF_ROLE: "SERVER",
CONF_ATTRIBUTES: [
{
CONF_ATTRIBUTE_ID: 0x0,
CONF_VALUE: 255,
CONF_ACCESS: 0,
CONF_TYPE: "U8",
CONF_REPORT: True,
CONF_SCALE: 1,
CONF_DEVICE: None,
},
],
},
],
},
}
def get_next_ep_num(eps):
try:
ep_num = [i for i in range(1, 240) if i not in eps][0]
eps.append(ep_num)
except IndexError as e:
raise cv.Invalid(
"Too many devices. Zigbee can define only 240 endpoints."
) from e
return ep_num
ANALOG_INPUT_APPTYPE = {
(DEVICE_CLASS_TEMPERATURE, UNIT_CELSIUS): AnalogInputType.Temp_Degrees_C,
(DEVICE_CLASS_HUMIDITY, UNIT_PERCENT): AnalogInputType.Relative_Humidity_Percent,
(DEVICE_CLASS_PRESSURE, UNIT_PASCAL): AnalogInputType.Pressure_Pascal,
(DEVICE_CLASS_VOLUME_FLOW_RATE, "L/s"): AnalogInputType.Flow_Liters_Per_Sec,
(DEVICE_CLASS_CURRENT, UNIT_AMPERE): AnalogInputType.Current_Amps,
(DEVICE_CLASS_FREQUENCY, UNIT_HERTZ): AnalogInputType.Frequency_Hz,
(DEVICE_CLASS_POWER, UNIT_WATT): AnalogInputType.Power_Watts,
(DEVICE_CLASS_POWER, UNIT_KILOWATT): AnalogInputType.Power_Kilo_Watts,
(DEVICE_CLASS_ENERGY, UNIT_KILOWATT_HOURS): AnalogInputType.Energy_Kilo_Watt_Hours,
(DEVICE_CLASS_DURATION, UNIT_SECOND): AnalogInputType.Time_Seconds,
}
"""
not implemented:
AnalogInputType.Percentage
AnalogInputType.Parts_Per_Million
AnalogInputType.Rotational_Speed_RPM
AnalogInputType.Count
AnalogInputType.Enthalpy_KJoules_Per_Kg
"""
BACNET_UNIT = {
UNIT_CELSIUS: BacnetUnit.DEGREES_CELSIUS,
"°F": BacnetUnit.DEGREES_FAHRENHEIT,
UNIT_PERCENT: BacnetUnit.PERCENT,
UNIT_LUX: BacnetUnit.LUXES,
UNIT_VOLT: BacnetUnit.VOLTS,
UNIT_MICROGRAMS_PER_CUBIC_METER: BacnetUnit.MICROGRAMS_PER_CUBIC_METER,
UNIT_PARTS_PER_MILLION: BacnetUnit.PARTS_PER_MILLION,
UNIT_OHM: BacnetUnit.OHMS,
UNIT_HECTOPASCAL: BacnetUnit.HECTOPASCALS,
}
def create_device_ep(eps, dev, generic=False):
ep = {}
ep[CONF_NUM] = get_next_ep_num(eps)
if dev["id"].type.inherits_from(Sensor):
if dev.get(CONF_DEVICE_CLASS, "") in ep_configs and not generic:
ep.update(copy.deepcopy(ep_configs[dev[CONF_DEVICE_CLASS]]))
else:
# get application type from device class and meas unit
# if none get BACNET unit from meas unit
ep.update(copy.deepcopy(ep_configs["analog_input"]))
dev_class = dev.get(CONF_DEVICE_CLASS)
unit = dev.get(CONF_UNIT_OF_MEASUREMENT)
apptype = ANALOG_INPUT_APPTYPE.get((dev_class, unit))
bacunit = BACNET_UNIT.get(unit)
if apptype is not None:
ep[CONF_CLUSTERS][0][CONF_ATTRIBUTES].append(
{
CONF_ATTRIBUTE_ID: 0x100,
CONF_VALUE: (apptype << 16) + 0xFFFF,
CONF_ACCESS: 0,
CONF_TYPE: "U32",
CONF_REPORT: False,
CONF_SCALE: 1,
},
)
if bacunit is not None:
ep[CONF_CLUSTERS][0][CONF_ATTRIBUTES].append(
{
CONF_ATTRIBUTE_ID: 0x75,
CONF_VALUE: bacunit,
CONF_ACCESS: 0,
CONF_TYPE: "16BIT_ENUM",
CONF_REPORT: False,
CONF_SCALE: 1,
},
)
elif dev["id"].type.inherits_from(Switch):
if generic:
ep.update(copy.deepcopy(ep_configs["binary_output"]))
else:
ep.update(copy.deepcopy(ep_configs["on_off"]))
elif dev["id"].type.inherits_from(BinarySensor):
ep.update(copy.deepcopy(ep_configs["binary_input"]))
elif dev["id"].type.inherits_from(light.LightState):
# NB: output.FloatOutput is a subclass of output.BinaryOutput thus must be checked first
if (
dev["platform"] in ["monochromatic"]
or "dimmer" in dev["platform"]
or (
"output" in dev and dev["output"].type.inherits_from(output.FloatOutput)
)
):
if generic:
ep.update(copy.deepcopy(ep_configs["analog_output"]))
ep[CONF_CLUSTERS].append(
copy.deepcopy(ep_configs["binary_output"][CONF_CLUSTERS])
)
else:
ep.update(copy.deepcopy(ep_configs["level_light"]))
elif dev["platform"] in ["binary", "status_led"] or (
"output" in dev and dev["output"].type.inherits_from(output.BinaryOutput)
):
if generic:
ep.update(copy.deepcopy(ep_configs["binary_output"]))
else:
ep.update(copy.deepcopy(ep_configs["on_off_light"]))
else:
ep.update(copy.deepcopy(ep_configs["color_light"]))
for cl in ep.get(CONF_CLUSTERS, []):
for attr in cl[CONF_ATTRIBUTES]:
if (
attr[CONF_ATTRIBUTE_ID] == 0x1C
and CONF_VALUE not in attr
and "name" in dev
): # set name
name = dev["name"].encode("ascii", "ignore").decode() # use unidecode
attr[CONF_VALUE] = str(name)
attr[CONF_MAX_LENGTH] = len(str(name))
if CONF_DEVICE in attr: # connect device
attr[CONF_DEVICE] = dev["id"]
# create attribute ID
id = ID(None, is_declaration=True, type=ZigBeeAttribute)
id.resolve(CORE.component_ids)
CORE.component_ids.add(id.id)
attr[CONF_ID] = id
else:
attr[CONF_ID] = None
return ep
def get_device_entries(conf: list, component_type):
devices = []
for d in conf:
if CONF_ID in d and d[CONF_ID].type.inherits_from(component_type):
devices.append(d)
else:
for dd in d.values():
if isinstance(dd, dict):
devices.extend(get_device_entries([dd], component_type))
return devices
def create_ep(config, full_conf):
eps = []
comp_ids = len(CORE.component_ids)
if CONF_ENDPOINTS in config:
eps = [ep.get(CONF_NUM) for ep in config[CONF_ENDPOINTS]]
for ep in config[CONF_ENDPOINTS]:
if CONF_NUM not in ep:
ep[CONF_NUM] = get_next_ep_num(eps)
ep_list = config.get(CONF_ENDPOINTS, [])
if CONF_COMPONENTS in config:
devs = [
i["id"]
for i in get_device_entries(full_conf.get("light", []), light.LightState)
+ get_device_entries(full_conf.get("switch", []), Switch)
+ get_device_entries(full_conf.get("sensor", []), Sensor)
+ get_device_entries(full_conf.get("binary_sensor", []), BinarySensor)
]
add_devices = []
if isinstance(config[CONF_COMPONENTS], list):
list_devs = []
for dev in config[CONF_COMPONENTS]:
if dev in devs:
list_devs.append(dev)
add_devices = [
i
for i in get_device_entries(
full_conf.get("light", []), light.LightState
)
+ get_device_entries(full_conf.get("switch", []), Switch)
+ get_device_entries(full_conf.get("sensor", []), Sensor)
+ get_device_entries(full_conf.get("binary_sensor", []), BinarySensor)
if i["id"] in list_devs
]
if config[CONF_COMPONENTS] == "all":
add_devices = [
i
for i in get_device_entries(
full_conf.get("light", []), light.LightState
)
+ get_device_entries(full_conf.get("switch", []), Switch)
+ get_device_entries(full_conf.get("sensor", []), Sensor)
+ get_device_entries(full_conf.get("binary_sensor", []), BinarySensor)
if ("name" in i) and not i.get("internal")
]
for dev in add_devices:
ep_list.append(create_device_ep(eps, dev, config[CONF_AS_GENERIC]))
if not ep_list:
ep_list = [
{
CONF_DEVICE_TYPE: "CUSTOM_ATTR",
CONF_NUM: 1,
}
]
return ep_list, len(CORE.component_ids) - comp_ids
================================================
FILE: components/zigbee/zigbee_helpers.c
================================================
#include "ha/esp_zigbee_ha_standard.h"
#include "zigbee_helpers.h"
esp_zb_cluster_list_t *esphome_zb_default_clusters_create(esp_zb_ha_standard_devices_t device_type) {
esp_zb_cluster_list_t *cluster_list;
switch (device_type) {
case ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID: {
esp_zb_on_off_switch_cfg_t config = ESP_ZB_DEFAULT_ON_OFF_SWITCH_CONFIG();
cluster_list = esp_zb_on_off_switch_clusters_create(&config);
break;
}
case ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID: {
esp_zb_on_off_light_cfg_t config = ESP_ZB_DEFAULT_ON_OFF_LIGHT_CONFIG();
cluster_list = esp_zb_on_off_light_clusters_create(&config);
break;
}
case ESP_ZB_HA_COLOR_DIMMER_SWITCH_DEVICE_ID: {
esp_zb_color_dimmable_switch_cfg_t config = ESP_ZB_DEFAULT_COLOR_DIMMABLE_SWITCH_CONFIG();
cluster_list = esp_zb_color_dimmable_switch_clusters_create(&config);
break;
}
case ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID: {
esp_zb_color_dimmable_light_cfg_t config = ESP_ZB_DEFAULT_COLOR_DIMMABLE_LIGHT_CONFIG();
cluster_list = esp_zb_color_dimmable_light_clusters_create(&config);
break;
}
case ESP_ZB_HA_MAINS_POWER_OUTLET_DEVICE_ID: {
esp_zb_mains_power_outlet_cfg_t config = ESP_ZB_DEFAULT_MAINS_POWER_OUTLET_CONFIG();
cluster_list = esp_zb_mains_power_outlet_clusters_create(&config);
break;
}
case ESP_ZB_HA_SHADE_DEVICE_ID: {
esp_zb_shade_cfg_t config = ESP_ZB_DEFAULT_SHADE_CONFIG();
cluster_list = esp_zb_shade_clusters_create(&config);
break;
}
case ESP_ZB_HA_SHADE_CONTROLLER_DEVICE_ID: {
esp_zb_shade_controller_cfg_t config = ESP_ZB_DEFAULT_SHADE_CONTROLLER_CONFIG();
cluster_list = esp_zb_shade_controller_clusters_create(&config);
break;
}
case ESP_ZB_HA_DOOR_LOCK_DEVICE_ID: {
esp_zb_door_lock_cfg_t config = ESP_ZB_DEFAULT_DOOR_LOCK_CONFIG();
cluster_list = esp_zb_door_lock_clusters_create(&config);
break;
}
case ESP_ZB_HA_DOOR_LOCK_CONTROLLER_DEVICE_ID: {
esp_zb_door_lock_controller_cfg_t config = ESP_ZB_DEFAULT_DOOR_LOCK_CONTROLLER_CONFIG();
cluster_list = esp_zb_door_lock_controller_clusters_create(&config);
break;
}
case ESP_ZB_HA_TEMPERATURE_SENSOR_DEVICE_ID: {
esp_zb_temperature_sensor_cfg_t config = ESP_ZB_DEFAULT_TEMPERATURE_SENSOR_CONFIG();
cluster_list = esp_zb_temperature_sensor_clusters_create(&config);
break;
}
case ESP_ZB_HA_CONFIGURATION_TOOL_DEVICE_ID: {
esp_zb_configuration_tool_cfg_t config = ESP_ZB_DEFAULT_CONFIGURATION_TOOL_CONFIG();
cluster_list = esp_zb_configuration_tool_clusters_create(&config);
break;
}
case ESP_ZB_HA_THERMOSTAT_DEVICE_ID: {
esp_zb_thermostat_cfg_t config = ESP_ZB_DEFAULT_THERMOSTAT_CONFIG();
cluster_list = esp_zb_thermostat_clusters_create(&config);
break;
}
case ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID: {
esp_zb_window_covering_cfg_t config = ESP_ZB_DEFAULT_WINDOW_COVERING_CONFIG();
cluster_list = esp_zb_window_covering_clusters_create(&config);
break;
}
case ESP_ZB_HA_WINDOW_COVERING_CONTROLLER_DEVICE_ID: {
esp_zb_window_covering_controller_cfg_t config = ESP_ZB_DEFAULT_WINDOW_COVERING_CONTROLLER_CONFIG();
cluster_list = esp_zb_window_covering_controller_clusters_create(&config);
break;
}
default:
// create empty cluster list;
cluster_list = esp_zb_zcl_cluster_list_create();
}
return cluster_list;
}
esp_err_t esphome_zb_cluster_add_or_update_attr(uint16_t cluster_id, esp_zb_attribute_list_t *attr_list,
uint16_t attr_id, uint8_t attr_type, uint8_t attr_access,
void *value_p) {
esp_err_t ret;
ret = esp_zb_cluster_update_attr(attr_list, attr_id, value_p);
if (ret != ESP_OK) {
ESP_LOGE("zigbee_helper", "Ignore previous attribute not found error");
if (attr_access > 0) {
ret = esp_zb_cluster_add_attr(attr_list, cluster_id, attr_id, attr_type, attr_access, value_p);
} else {
ret = esphome_zb_cluster_add_attr(cluster_id, attr_list, attr_id, value_p);
}
}
return ret;
}
esp_err_t esphome_zb_cluster_list_add_or_update_cluster(uint16_t cluster_id, esp_zb_cluster_list_t *cluster_list,
esp_zb_attribute_list_t *attr_list, uint8_t role_mask) {
esp_err_t ret;
ret = esp_zb_cluster_list_update_cluster(cluster_list, attr_list, cluster_id, role_mask);
if (ret != ESP_OK) {
ESP_LOGE("zigbee_helper", "Ignore previous cluster not found error");
switch (cluster_id) {
case ESP_ZB_ZCL_CLUSTER_ID_BASIC:
ret = esp_zb_cluster_list_add_basic_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_POWER_CONFIG:
ret = esp_zb_cluster_list_add_power_config_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_DEVICE_TEMP_CONFIG:
ret = esp_zb_cluster_list_add_device_temp_config_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY:
ret = esp_zb_cluster_list_add_identify_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_GROUPS:
ret = esp_zb_cluster_list_add_groups_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_SCENES:
ret = esp_zb_cluster_list_add_scenes_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_ON_OFF:
ret = esp_zb_cluster_list_add_on_off_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_ON_OFF_SWITCH_CONFIG:
ret = esp_zb_cluster_list_add_on_off_switch_config_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL:
ret = esp_zb_cluster_list_add_level_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_ALARMS:
ret = esp_zb_cluster_list_add_alarms_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_TIME:
ret = esp_zb_cluster_list_add_time_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_INPUT:
ret = esp_zb_cluster_list_add_analog_input_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT:
ret = esp_zb_cluster_list_add_analog_output_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_VALUE:
ret = esp_zb_cluster_list_add_analog_value_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_BINARY_INPUT:
ret = esp_zb_cluster_list_add_binary_input_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT:
ret = esp_zb_cluster_list_add_binary_output_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_BINARY_VALUE:
ret = esp_zb_cluster_list_add_binary_value_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_MULTI_INPUT:
ret = esp_zb_cluster_list_add_multistate_input_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_MULTI_OUTPUT:
ret = esp_zb_cluster_list_add_multistate_output_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_MULTI_VALUE:
ret = esp_zb_cluster_list_add_multistate_value_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_COMMISSIONING:
ret = esp_zb_cluster_list_add_commissioning_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_OTA_UPGRADE:
ret = esp_zb_cluster_list_add_ota_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_POLL_CONTROL:
ret = esp_zb_cluster_list_add_poll_control_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_SHADE_CONFIG:
ret = esp_zb_cluster_list_add_shade_config_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_DOOR_LOCK:
ret = esp_zb_cluster_list_add_door_lock_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING:
ret = esp_zb_cluster_list_add_window_covering_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT:
ret = esp_zb_cluster_list_add_thermostat_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_FAN_CONTROL:
ret = esp_zb_cluster_list_add_fan_control_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_DEHUMIDIFICATION_CONTROL:
ret = esp_zb_cluster_list_add_dehumidification_control_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT_UI_CONFIG:
ret = esp_zb_cluster_list_add_thermostat_ui_config_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL:
ret = esp_zb_cluster_list_add_color_control_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT:
ret = esp_zb_cluster_list_add_illuminance_meas_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT:
ret = esp_zb_cluster_list_add_temperature_meas_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT:
ret = esp_zb_cluster_list_add_pressure_meas_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_FLOW_MEASUREMENT:
ret = esp_zb_cluster_list_add_flow_meas_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT:
ret = esp_zb_cluster_list_add_humidity_meas_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING:
ret = esp_zb_cluster_list_add_occupancy_sensing_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_PH_MEASUREMENT:
ret = esp_zb_cluster_list_add_ph_measurement_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_EC_MEASUREMENT:
ret = esp_zb_cluster_list_add_ec_measurement_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_WIND_SPEED_MEASUREMENT:
ret = esp_zb_cluster_list_add_wind_speed_measurement_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_CARBON_DIOXIDE_MEASUREMENT:
ret = esp_zb_cluster_list_add_carbon_dioxide_measurement_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT:
ret = esp_zb_cluster_list_add_pm2_5_measurement_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE:
ret = esp_zb_cluster_list_add_ias_zone_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_IAS_ACE:
ret = esp_zb_cluster_list_add_ias_ace_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_IAS_WD:
ret = esp_zb_cluster_list_add_ias_wd_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_PRICE:
ret = esp_zb_cluster_list_add_price_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_DRLC:
ret = esp_zb_cluster_list_add_drlc_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_METERING:
ret = esp_zb_cluster_list_add_metering_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_METER_IDENTIFICATION:
ret = esp_zb_cluster_list_add_meter_identification_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT:
ret = esp_zb_cluster_list_add_electrical_meas_cluster(cluster_list, attr_list, role_mask);
break;
case ESP_ZB_ZCL_CLUSTER_ID_DIAGNOSTICS:
ret = esp_zb_cluster_list_add_diagnostics_cluster(cluster_list, attr_list, role_mask);
break;
default:
ret = esp_zb_cluster_list_add_custom_cluster(cluster_list, attr_list, role_mask);
}
}
return ret;
}
esp_zb_attribute_list_t *esphome_zb_default_attr_list_create(uint16_t cluster_id) {
switch (cluster_id) {
case ESP_ZB_ZCL_CLUSTER_ID_BASIC:
return esp_zb_basic_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_POWER_CONFIG:
return esp_zb_power_config_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_DEVICE_TEMP_CONFIG:
return esp_zb_device_temp_config_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY:
return esp_zb_identify_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_GROUPS:
return esp_zb_groups_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_SCENES:
return esp_zb_scenes_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_ON_OFF:
return esp_zb_on_off_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_ON_OFF_SWITCH_CONFIG:
return esp_zb_on_off_switch_config_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL:
return esp_zb_level_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_ALARMS:
return esp_zb_alarms_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_TIME:
return esp_zb_time_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_INPUT:
return esp_zb_analog_input_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT:
return esp_zb_analog_output_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_VALUE:
return esp_zb_analog_value_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_BINARY_INPUT:
return esp_zb_binary_input_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT:
return esp_zb_binary_output_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_BINARY_VALUE:
return esp_zb_binary_value_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_MULTI_INPUT:
return esp_zb_multistate_input_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_MULTI_OUTPUT:
return esp_zb_multistate_output_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_MULTI_VALUE:
return esp_zb_multistate_value_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_COMMISSIONING:
return esp_zb_commissioning_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_OTA_UPGRADE:
return esp_zb_ota_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_POLL_CONTROL:
return esp_zb_poll_control_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_SHADE_CONFIG:
return esp_zb_shade_config_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_DOOR_LOCK:
return esp_zb_door_lock_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING:
return esp_zb_window_covering_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT:
return esp_zb_thermostat_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_FAN_CONTROL:
return esp_zb_fan_control_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_DEHUMIDIFICATION_CONTROL:
return esp_zb_dehumidification_control_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT_UI_CONFIG:
return esp_zb_thermostat_ui_config_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL:
return esp_zb_color_control_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT:
return esp_zb_illuminance_meas_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT:
return esp_zb_temperature_meas_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT:
return esp_zb_pressure_meas_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_FLOW_MEASUREMENT:
return esp_zb_flow_meas_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT:
return esp_zb_humidity_meas_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING:
return esp_zb_occupancy_sensing_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_PH_MEASUREMENT:
return esp_zb_ph_measurement_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_EC_MEASUREMENT:
return esp_zb_ec_measurement_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_WIND_SPEED_MEASUREMENT:
return esp_zb_wind_speed_measurement_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_CARBON_DIOXIDE_MEASUREMENT:
return esp_zb_carbon_dioxide_measurement_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT:
return esp_zb_pm2_5_measurement_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE:
return esp_zb_ias_zone_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_IAS_ACE:
return esp_zb_ias_ace_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_IAS_WD:
return esp_zb_ias_wd_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_PRICE:
return esp_zb_price_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_DRLC:
return esp_zb_drlc_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_METERING:
return esp_zb_metering_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_METER_IDENTIFICATION:
return esp_zb_meter_identification_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT:
return esp_zb_electrical_meas_cluster_create(NULL);
case ESP_ZB_ZCL_CLUSTER_ID_DIAGNOSTICS:
return esp_zb_diagnostics_cluster_create(NULL);
default:
return esp_zb_zcl_attr_list_create(cluster_id);
}
}
esp_err_t esphome_zb_cluster_add_attr(uint16_t cluster_id, esp_zb_attribute_list_t *attr_list, uint16_t attr_id,
void *value_p) {
switch (cluster_i
gitextract_o48vfjhm/ ├── .clang-format ├── .clang-tidy ├── .editorconfig ├── .flake8 ├── .gitattributes ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .yamllint ├── README.md ├── components/ │ └── zigbee/ │ ├── __init__.py │ ├── automation.cpp │ ├── automation.h │ ├── const.py │ ├── esp_zb_event.h │ ├── files_to_parse/ │ │ └── parse_zigbee_headers.py │ ├── partitions_zb.csv │ ├── time/ │ │ ├── __init__.py │ │ ├── zigbee_time.cpp │ │ └── zigbee_time.h │ ├── types.py │ ├── zigbee.cpp │ ├── zigbee.h │ ├── zigbee_attribute.cpp │ ├── zigbee_attribute.h │ ├── zigbee_const.py │ ├── zigbee_ep.py │ ├── zigbee_helpers.c │ ├── zigbee_helpers.h │ └── zigbee_pre_build.py.script ├── example_aht10_esp32h2.yaml ├── example_esp32c6.yaml ├── example_esp32h2.yaml ├── example_esp32h2_basic.yaml ├── example_time.yml ├── partitions_zb.csv └── pyproject.toml
SYMBOL INDEX (95 symbols across 16 files)
FILE: components/zigbee/__init__.py
function require_vfs_select (line 83) | def require_vfs_select():
function _register_action (line 92) | def _register_action(name, action_type, schema, **kwargs):
function validate_binary_sensor (line 115) | def validate_binary_sensor(x):
function validate_sensor (line 119) | def validate_sensor(x):
function validate_switch (line 123) | def validate_switch(x):
function validate_number (line 127) | def validate_number(x):
function setup_binary_sensor (line 131) | async def setup_binary_sensor(sensor, config):
function setup_sensor (line 135) | async def setup_sensor(sensor, config):
function setup_switch (line 139) | async def setup_switch(switch, config):
function setup_number (line 143) | async def setup_number(number, config, min_value, max_value, step):
function get_c_size (line 147) | def get_c_size(bits, options):
function get_c_type (line 151) | def get_c_type(attr_type):
function get_cv_by_type (line 169) | def get_cv_by_type(attr_type):
function get_default_by_type (line 185) | def get_default_by_type(attr_type):
function validate_clusters (line 191) | def validate_clusters(config):
function validate_string_attributes (line 201) | def validate_string_attributes(config):
function validate_attributes (line 217) | def validate_attributes(config):
function final_validate (line 242) | def final_validate(config):
function _require_vfs_select (line 268) | def _require_vfs_select(config):
function find_attr (line 391) | def find_attr(conf, id):
function attributes_to_code (line 400) | async def attributes_to_code(var, ep_num, cl):
function to_code (line 504) | async def to_code(config):
function reset_zigbee_to_code (line 594) | async def reset_zigbee_to_code(config, action_id, template_arg, args):
function report_to_code (line 606) | async def report_to_code(config, action_id, template_arg, args):
function zigbee_set_attr_to_code (line 634) | async def zigbee_set_attr_to_code(config, action_id, template_arg, args):
function zigbee_report_attr_to_code (line 659) | async def zigbee_report_attr_to_code(config, action_id, template_arg, ar...
FILE: components/zigbee/automation.cpp
type esphome (line 5) | namespace esphome {
type zigbee (line 6) | namespace zigbee {
function gamma_correct (line 15) | inline float gamma_correct(float linear) {
function get_r_from_xy (line 27) | float get_r_from_xy(float x, float y) {
function get_g_from_xy (line 35) | float get_g_from_xy(float x, float y) {
function get_b_from_xy (line 43) | float get_b_from_xy(float x, float y) {
function set_light_color (line 53) | void set_light_color(uint8_t ep, light::LightCall *call, uint16_t va...
FILE: components/zigbee/automation.h
function namespace (line 16) | namespace esphome {
FILE: components/zigbee/const.py
class AnalogInputType (line 36) | class AnalogInputType:
class BacnetUnit (line 55) | class BacnetUnit:
FILE: components/zigbee/esp_zb_event.h
function namespace (line 11) | namespace esphome::zigbee {
function callback_id_ (line 32) | callback_id_(ESP_ZB_CORE_BASIC_RESET_TO_FACTORY_RESET_CB_ID) {}
function release (line 34) | void release() {
function load_set_attr_value_event (line 70) | void load_set_attr_value_event(esp_zb_device_cb_common_info_t info, esp_...
function load_report_attr_event (line 77) | void load_report_attr_event(const esp_zb_zcl_report_attr_message_t *mess...
function load_read_attr_resp_event (line 83) | void load_read_attr_resp_event(esp_zb_zcl_cmd_info_t info, esp_zb_zcl_re...
type set_attr_event (line 94) | struct set_attr_event {
type report_attr_event (line 101) | struct report_attr_event {
type read_attr_resp_event (line 109) | struct read_attr_resp_event {
function init_report_attr_data (line 143) | void init_report_attr_data(const esp_zb_zcl_report_attr_message_t *messa...
function init_read_attr_resp_data (line 164) | void init_read_attr_resp_data(esp_zb_zcl_cmd_info_t info, esp_zb_zcl_rea...
function get_attribute_value_size_ (line 207) | size_t get_attribute_value_size_(esp_zb_zcl_attribute_data_t data) {
FILE: components/zigbee/files_to_parse/parse_zigbee_headers.py
function print_enum (line 16) | def print_enum(enum):
function write_profileIDs (line 23) | def write_profileIDs(enums):
function write_clusterIDs (line 36) | def write_clusterIDs(enums):
function write_clusterRoles (line 49) | def write_clusterRoles(enums):
function write_ZBtypes (line 61) | def write_ZBtypes(enums):
function write_cluster_list_aou (line 110) | def write_cluster_list_aou(enums):
function write_attr_list_create (line 150) | def write_attr_list_create(enums):
function write_attr_add (line 182) | def write_attr_add(enums):
FILE: components/zigbee/time/__init__.py
function to_code (line 22) | async def to_code(config):
FILE: components/zigbee/time/zigbee_time.cpp
type esphome (line 4) | namespace esphome {
type zigbee (line 5) | namespace zigbee {
FILE: components/zigbee/time/zigbee_time.h
function namespace (line 7) | namespace esphome {
FILE: components/zigbee/zigbee.cpp
type esphome (line 18) | namespace esphome {
type zigbee (line 19) | namespace zigbee {
function bdb_start_top_level_commissioning_cb (line 47) | static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) {
function esp_zb_app_signal_handler (line 59) | void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) {
function load_zb_event (line 201) | void load_zb_event(ZBEvent *event, esp_zb_device_cb_common_info_t in...
function load_zb_event (line 206) | void load_zb_event(ZBEvent *event, const esp_zb_zcl_report_attr_mess...
function load_zb_event (line 210) | void load_zb_event(ZBEvent *event, esp_zb_zcl_cmd_info_t info, esp_z...
function enqueue_zb_event (line 214) | void enqueue_zb_event(Args... args) {
function esp_err_t (line 239) | static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_valu...
function esp_err_t (line 274) | static esp_err_t zb_cmd_attribute_handler(const esp_zb_zcl_cmd_read_...
function esp_err_t (line 282) | static esp_err_t zb_report_attribute_handler(const esp_zb_zcl_report...
function esp_err_t (line 290) | static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t ...
function esp_zb_attribute_list_t (line 424) | esp_zb_attribute_list_t *ZigBeeComponent::create_basic_cluster_() {
function esp_err_t (line 459) | esp_err_t ZigBeeComponent::create_endpoint(uint8_t endpoint_id, esp_...
function esp_zb_task_ (line 469) | static void esp_zb_task_(void *pvParameters) {
FILE: components/zigbee/zigbee.h
function namespace (line 24) | namespace esphome {
function class (line 67) | class ZigBeeComponent : public Component {
FILE: components/zigbee/zigbee_attribute.cpp
type esphome (line 3) | namespace esphome {
type zigbee (line 4) | namespace zigbee {
function esp_zb_zcl_reporting_info_t (line 53) | esp_zb_zcl_reporting_info_t ZigBeeAttribute::get_reporting_info() {
FILE: components/zigbee/zigbee_attribute.h
function class (line 33) | class ZigBeeAttribute : public Component {
function on_value (line 56) | void on_value(esp_zb_zcl_attribute_t attribute) { this->on_value_callbac...
function add_on_report_callback (line 58) | void add_on_report_callback(
function on_report (line 63) | void on_report(esp_zb_zcl_attribute_t attribute, esp_zb_zcl_addr_t src_a...
function set_attr_requested_ (line 104) | bool set_attr_requested_{false};
FILE: components/zigbee/zigbee_ep.py
function get_next_ep_num (line 357) | def get_next_ep_num(eps):
function create_device_ep (line 402) | def create_device_ep(eps, dev, generic=False):
function get_device_entries (line 493) | def get_device_entries(conf: list, component_type):
function create_ep (line 505) | def create_ep(config, full_conf):
FILE: components/zigbee/zigbee_helpers.c
function esp_zb_cluster_list_t (line 4) | esp_zb_cluster_list_t *esphome_zb_default_clusters_create(esp_zb_ha_stan...
function esp_err_t (line 85) | esp_err_t esphome_zb_cluster_add_or_update_attr(uint16_t cluster_id, esp...
function esp_err_t (line 101) | esp_err_t esphome_zb_cluster_list_add_or_update_cluster(uint16_t cluster...
function esp_zb_attribute_list_t (line 268) | esp_zb_attribute_list_t *esphome_zb_default_attr_list_create(uint16_t cl...
function esp_err_t (line 377) | esp_err_t esphome_zb_cluster_add_attr(uint16_t cluster_id, esp_zb_attrib...
FILE: components/zigbee/zigbee_helpers.h
function namespace (line 23) | namespace esphome {
Condensed preview — 37 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (230K chars).
[
{
"path": ".clang-format",
"chars": 3753,
"preview": "Language: Cpp\nAccessModifierOffset: -1\nAlignAfterOpenBracket: Align\nAlignConsecutiveAssignments: false\nAlignConse"
},
{
"path": ".clang-tidy",
"chars": 6938,
"preview": "---\nChecks: >-\n *,\n -abseil-*,\n -altera-*,\n -android-*,\n -boost-*,\n -bugprone-easily-swappable-parameters,\n -bugp"
},
{
"path": ".editorconfig",
"chars": 398,
"preview": "root = true\n\n# general\n[*]\nend_of_line = lf\ninsert_final_newline = true\ncharset = utf-8\n\n# python\n[*.py]\nindent_style = "
},
{
"path": ".flake8",
"chars": 988,
"preview": "[flake8]\nmax-line-length = 120\n# Following 4 for black compatibility\n# E501: line too long\n# W503: Line break occurred b"
},
{
"path": ".gitattributes",
"chars": 76,
"preview": "# Normalize line endings to LF in the repository\n* text eol=lf\n*.png binary\n"
},
{
"path": ".github/FUNDING.yml",
"chars": 900,
"preview": "# These are supported funding model platforms\n\n#github: luar123 # Replace with up to 4 GitHub Sponsors-enabled usernames"
},
{
"path": ".gitignore",
"chars": 319,
"preview": "# Gitignore settings for ESPHome\n# This is an example and may include too much for your use-case.\n# You can modify this "
},
{
"path": ".pre-commit-config.yaml",
"chars": 1487,
"preview": "---\n# See https://pre-commit.com for more information\n# See https://pre-commit.com/hooks.html for more hooks\nrepos:\n - "
},
{
"path": ".yamllint",
"chars": 310,
"preview": "---\nextends: default\n\nignore-from-file: .gitignore\n\nrules:\n document-start: disable\n empty-lines:\n level: error\n "
},
{
"path": "README.md",
"chars": 15317,
"preview": "> [!TIP]\n> **New simple Mode! No more endpoint definitions needed.**\n>\n> I started to implement the automated endpoint d"
},
{
"path": "components/zigbee/__init__.py",
"chars": 22941,
"preview": "import datetime\nimport inspect\nfrom pathlib import Path\nimport re\n\nfrom esphome import automation\nimport esphome.codegen"
},
{
"path": "components/zigbee/automation.cpp",
"chars": 1835,
"preview": "#include \"automation.h\"\n#include <algorithm>\n#include \"esphome/core/log.h\"\n\nnamespace esphome {\nnamespace zigbee {\n\n/**\n"
},
{
"path": "components/zigbee/automation.h",
"chars": 5120,
"preview": "#pragma once\n\n//#include <stdfloat> //deactive because not working with esp-idf 5.1.4\n\n#include \"esphome/core/automation"
},
{
"path": "components/zigbee/const.py",
"chars": 8228,
"preview": "from dataclasses import dataclass\n\nimport esphome.codegen as cg\n\nCONF_ENDPOINTS = \"endpoints\"\nCONF_DEVICE_TYPE = \"device"
},
{
"path": "components/zigbee/esp_zb_event.h",
"chars": 12648,
"preview": "#pragma once\n\n#ifdef USE_ESP32\n\n#include <cstddef> // for offsetof\n#include <cstring> // for memcpy\n#include \"esp_zigb"
},
{
"path": "components/zigbee/files_to_parse/parse_zigbee_headers.py",
"chars": 7380,
"preview": "#!/usr/bin/env python3\n\"\"\"\nCreate python enums\n\"\"\"\n\nfrom pycparser import c_ast, parse_file\n\nfilename = \"esp_zigbee_zcl_"
},
{
"path": "components/zigbee/partitions_zb.csv",
"chars": 278,
"preview": "otadata, data, ota, , 0x2000,\nphy_init, data, phy, , 0x1000,\napp0, app, ota_0, , 0x"
},
{
"path": "components/zigbee/time/__init__.py",
"chars": 894,
"preview": "import esphome.codegen as cg\nfrom esphome.components import time as time_\nimport esphome.config_validation as cv\nfrom es"
},
{
"path": "components/zigbee/time/zigbee_time.cpp",
"chars": 2794,
"preview": "#include \"zigbee_time.h\"\n#include \"esphome/core/log.h\"\n\nnamespace esphome {\nnamespace zigbee {\n\nvoid ZigbeeTime::send_ti"
},
{
"path": "components/zigbee/time/zigbee_time.h",
"chars": 777,
"preview": "#pragma once\n\n#include \"esphome/core/component.h\"\n#include \"esphome/components/time/real_time_clock.h\"\n#include \"../zigb"
},
{
"path": "components/zigbee/types.py",
"chars": 904,
"preview": "from esphome import automation\nimport esphome.codegen as cg\n\nzigbee_ns = cg.esphome_ns.namespace(\"zigbee\")\nZigBeeCompone"
},
{
"path": "components/zigbee/zigbee.cpp",
"chars": 28381,
"preview": "#include \"zigbee.h\"\n#include \"freertos/FreeRTOS.h\"\n#include \"freertos/task.h\"\n#include \"esp_check.h\"\n#include \"nvs_flash"
},
{
"path": "components/zigbee/zigbee.h",
"chars": 7745,
"preview": "#pragma once\n\n#include <map>\n#include <tuple>\n\n#include \"esphome/core/defines.h\"\n#include \"esphome/core/automation.h\"\n#i"
},
{
"path": "components/zigbee/zigbee_attribute.cpp",
"chars": 2996,
"preview": "#include \"zigbee_attribute.h\"\n\nnamespace esphome {\nnamespace zigbee {\n\nvoid ZigBeeAttribute::set_attr_() {\n if (!this->"
},
{
"path": "components/zigbee/zigbee_attribute.h",
"chars": 8688,
"preview": "#pragma once\n\n#include <type_traits>\n\n#include \"zigbee.h\"\n#include \"esp_zigbee_core.h\"\n#include \"esphome/core/automation"
},
{
"path": "components/zigbee/zigbee_const.py",
"chars": 16578,
"preview": "import esphome.codegen as cg\n\nha_standard_devices = cg.esphome_ns.enum(\"esp_zb_ha_standard_devices_t\")\nDEVICE_ID = {\n "
},
{
"path": "components/zigbee/zigbee_ep.py",
"chars": 19141,
"preview": "import copy\n\nfrom esphome.components import light, output\nimport esphome.config_validation as cv\nfrom esphome.const impo"
},
{
"path": "components/zigbee/zigbee_helpers.c",
"chars": 24604,
"preview": "#include \"ha/esp_zigbee_ha_standard.h\"\n#include \"zigbee_helpers.h\"\n\nesp_zb_cluster_list_t *esphome_zb_default_clusters_c"
},
{
"path": "components/zigbee/zigbee_helpers.h",
"chars": 1032,
"preview": "#pragma once\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include \"esp_zigbee_core.h\"\n\nesp_zb_cluster_list_t *esphome_zb_def"
},
{
"path": "components/zigbee/zigbee_pre_build.py.script",
"chars": 882,
"preview": "from os import path\nimport re\n\ndef_file = \"./src/esphome/core/defines.h\"\n\nif path.exists(def_file):\n # open file and "
},
{
"path": "example_aht10_esp32h2.yaml",
"chars": 3792,
"preview": "esphome:\n name: zb-sensor\n\nexternal_components:\n - source: components\n components: [zigbee]\n\nesp32:\n board: esp32-"
},
{
"path": "example_esp32c6.yaml",
"chars": 3152,
"preview": "esphome:\n name: zb-example-c6\n\nexternal_components:\n - source: components\n components: [zigbee]\n\nesp32:\n board: es"
},
{
"path": "example_esp32h2.yaml",
"chars": 3302,
"preview": "esphome:\n name: zb-example-h2\n\nexternal_components:\n - source: components\n components: [zigbee]\n\nesp32:\n board: es"
},
{
"path": "example_esp32h2_basic.yaml",
"chars": 938,
"preview": "esphome:\n name: zb-example-h2\n\nexternal_components:\n - source: components\n components: [zigbee]\n\nesp32:\n board: es"
},
{
"path": "example_time.yml",
"chars": 951,
"preview": "esphome:\n name: timesync-demo\n\nexternal_components:\n - source: components\n components: [zigbee]\n\nesp32:\n board: es"
},
{
"path": "partitions_zb.csv",
"chars": 278,
"preview": "otadata, data, ota, , 0x2000,\nphy_init, data, phy, , 0x1000,\napp0, app, ota_0, , 0x"
},
{
"path": "pyproject.toml",
"chars": 3615,
"preview": "[build-system]\nrequires = [\"setuptools==69.2.0\", \"wheel~=0.43.0\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nnam"
}
]
About this extraction
This page contains the full source code of the luar123/zigbee_esphome GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 37 files (215.2 KB), approximately 58.4k tokens, and a symbol index with 95 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.