Full Code of luar123/zigbee_esphome for AI

master 41761b894b8e cached
37 files
215.2 KB
58.4k tokens
95 symbols
1 requests
Download .txt
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
Download .txt
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
Download .txt
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.

Copied to clipboard!