├── .clang-format ├── .clang-tidy ├── .editorconfig ├── .flake8 ├── .gitattributes ├── .gitignore ├── .pre-commit-config.yaml ├── .yamllint ├── README.md ├── components └── zigbee │ ├── __init__.py │ ├── automation.cpp │ ├── automation.h │ ├── files_to_parse │ └── parse_zigbee_headers.py │ ├── partitions_zb.csv │ ├── time │ ├── __init__.py │ ├── zigbee_time.cpp │ └── zigbee_time.h │ ├── zigbee.cpp │ ├── zigbee.h │ ├── zigbee_attribute.cpp │ ├── zigbee_attribute.h │ ├── zigbee_const.py │ ├── zigbee_helpers.c │ └── zigbee_helpers.h ├── example_aht10_esp32h2.yaml ├── example_esp32c6.yaml ├── example_esp32h2.yaml ├── example_time.yml ├── partitions_zb.csv └── pyproject.toml /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | AccessModifierOffset: -1 3 | AlignAfterOpenBracket: Align 4 | AlignConsecutiveAssignments: false 5 | AlignConsecutiveDeclarations: false 6 | AlignEscapedNewlines: DontAlign 7 | AlignOperands: true 8 | AlignTrailingComments: true 9 | AllowAllParametersOfDeclarationOnNextLine: true 10 | AllowShortBlocksOnASingleLine: false 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: All 13 | AllowShortIfStatementsOnASingleLine: false 14 | AllowShortLoopsOnASingleLine: false 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: false 17 | AlwaysBreakTemplateDeclarations: MultiLine 18 | BinPackArguments: true 19 | BinPackParameters: true 20 | BraceWrapping: 21 | AfterClass: false 22 | AfterControlStatement: false 23 | AfterEnum: false 24 | AfterFunction: false 25 | AfterNamespace: false 26 | AfterObjCDeclaration: false 27 | AfterStruct: false 28 | AfterUnion: false 29 | AfterExternBlock: false 30 | BeforeCatch: false 31 | BeforeElse: false 32 | IndentBraces: false 33 | SplitEmptyFunction: true 34 | SplitEmptyRecord: true 35 | SplitEmptyNamespace: true 36 | BreakBeforeBinaryOperators: None 37 | BreakBeforeBraces: Attach 38 | BreakBeforeInheritanceComma: false 39 | BreakInheritanceList: BeforeColon 40 | BreakBeforeTernaryOperators: true 41 | BreakConstructorInitializersBeforeComma: false 42 | BreakConstructorInitializers: BeforeColon 43 | BreakAfterJavaFieldAnnotations: false 44 | BreakStringLiterals: true 45 | ColumnLimit: 120 46 | CommentPragmas: '^ IWYU pragma:' 47 | CompactNamespaces: false 48 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 49 | ConstructorInitializerIndentWidth: 4 50 | ContinuationIndentWidth: 4 51 | Cpp11BracedListStyle: true 52 | DerivePointerAlignment: false 53 | DisableFormat: false 54 | ExperimentalAutoDetectBinPacking: false 55 | FixNamespaceComments: true 56 | ForEachMacros: 57 | - foreach 58 | - Q_FOREACH 59 | - BOOST_FOREACH 60 | IncludeBlocks: Preserve 61 | IncludeCategories: 62 | - Regex: '^' 63 | Priority: 2 64 | - Regex: '^<.*\.h>' 65 | Priority: 1 66 | - Regex: '^<.*' 67 | Priority: 2 68 | - Regex: '.*' 69 | Priority: 3 70 | IncludeIsMainRegex: '([-_](test|unittest))?$' 71 | IndentCaseLabels: true 72 | IndentPPDirectives: None 73 | IndentWidth: 2 74 | IndentWrappedFunctionNames: false 75 | KeepEmptyLinesAtTheStartOfBlocks: false 76 | MacroBlockBegin: '' 77 | MacroBlockEnd: '' 78 | MaxEmptyLinesToKeep: 1 79 | NamespaceIndentation: None 80 | PenaltyBreakAssignment: 2 81 | PenaltyBreakBeforeFirstCallParameter: 1 82 | PenaltyBreakComment: 300 83 | PenaltyBreakFirstLessLess: 120 84 | PenaltyBreakString: 1000 85 | PenaltyBreakTemplateDeclaration: 10 86 | PenaltyExcessCharacter: 1000000 87 | PenaltyReturnTypeOnItsOwnLine: 2000 88 | PointerAlignment: Right 89 | RawStringFormats: 90 | - Language: Cpp 91 | Delimiters: 92 | - cc 93 | - CC 94 | - cpp 95 | - Cpp 96 | - CPP 97 | - 'c++' 98 | - 'C++' 99 | CanonicalDelimiter: '' 100 | BasedOnStyle: google 101 | - Language: TextProto 102 | Delimiters: 103 | - pb 104 | - PB 105 | - proto 106 | - PROTO 107 | EnclosingFunctions: 108 | - EqualsProto 109 | - EquivToProto 110 | - PARSE_PARTIAL_TEXT_PROTO 111 | - PARSE_TEST_PROTO 112 | - PARSE_TEXT_PROTO 113 | - ParseTextOrDie 114 | - ParseTextProtoOrDie 115 | CanonicalDelimiter: '' 116 | BasedOnStyle: google 117 | ReflowComments: true 118 | SortIncludes: false 119 | SortUsingDeclarations: false 120 | SpaceAfterCStyleCast: true 121 | SpaceAfterTemplateKeyword: false 122 | SpaceBeforeAssignmentOperators: true 123 | SpaceBeforeCpp11BracedList: false 124 | SpaceBeforeCtorInitializerColon: true 125 | SpaceBeforeInheritanceColon: true 126 | SpaceBeforeParens: ControlStatements 127 | SpaceBeforeRangeBasedForLoopColon: true 128 | SpaceInEmptyParentheses: false 129 | SpacesBeforeTrailingComments: 2 130 | SpacesInAngles: false 131 | SpacesInContainerLiterals: false 132 | SpacesInCStyleCastParentheses: false 133 | SpacesInParentheses: false 134 | SpacesInSquareBrackets: false 135 | Standard: Auto 136 | TabWidth: 2 137 | UseTab: Never 138 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: >- 3 | *, 4 | -abseil-*, 5 | -altera-*, 6 | -android-*, 7 | -boost-*, 8 | -bugprone-easily-swappable-parameters, 9 | -bugprone-implicit-widening-of-multiplication-result, 10 | -bugprone-narrowing-conversions, 11 | -bugprone-signed-char-misuse, 12 | -cert-dcl50-cpp, 13 | -cert-err33-c, 14 | -cert-err58-cpp, 15 | -cert-oop57-cpp, 16 | -cert-str34-c, 17 | -clang-analyzer-optin.cplusplus.UninitializedObject, 18 | -clang-analyzer-osx.*, 19 | -clang-diagnostic-delete-abstract-non-virtual-dtor, 20 | -clang-diagnostic-delete-non-abstract-non-virtual-dtor, 21 | -clang-diagnostic-ignored-optimization-argument, 22 | -clang-diagnostic-shadow-field, 23 | -clang-diagnostic-unused-const-variable, 24 | -clang-diagnostic-unused-parameter, 25 | -concurrency-*, 26 | -cppcoreguidelines-avoid-c-arrays, 27 | -cppcoreguidelines-avoid-magic-numbers, 28 | -cppcoreguidelines-init-variables, 29 | -cppcoreguidelines-macro-usage, 30 | -cppcoreguidelines-narrowing-conversions, 31 | -cppcoreguidelines-non-private-member-variables-in-classes, 32 | -cppcoreguidelines-prefer-member-initializer, 33 | -cppcoreguidelines-pro-bounds-array-to-pointer-decay, 34 | -cppcoreguidelines-pro-bounds-constant-array-index, 35 | -cppcoreguidelines-pro-bounds-pointer-arithmetic, 36 | -cppcoreguidelines-pro-type-const-cast, 37 | -cppcoreguidelines-pro-type-cstyle-cast, 38 | -cppcoreguidelines-pro-type-member-init, 39 | -cppcoreguidelines-pro-type-reinterpret-cast, 40 | -cppcoreguidelines-pro-type-static-cast-downcast, 41 | -cppcoreguidelines-pro-type-union-access, 42 | -cppcoreguidelines-pro-type-vararg, 43 | -cppcoreguidelines-special-member-functions, 44 | -cppcoreguidelines-virtual-class-destructor, 45 | -fuchsia-multiple-inheritance, 46 | -fuchsia-overloaded-operator, 47 | -fuchsia-statically-constructed-objects, 48 | -fuchsia-default-arguments-declarations, 49 | -fuchsia-default-arguments-calls, 50 | -google-build-using-namespace, 51 | -google-explicit-constructor, 52 | -google-readability-braces-around-statements, 53 | -google-readability-casting, 54 | -google-readability-namespace-comments, 55 | -google-readability-todo, 56 | -google-runtime-references, 57 | -hicpp-*, 58 | -llvm-else-after-return, 59 | -llvm-header-guard, 60 | -llvm-include-order, 61 | -llvm-qualified-auto, 62 | -llvmlibc-*, 63 | -misc-non-private-member-variables-in-classes, 64 | -misc-no-recursion, 65 | -misc-unused-parameters, 66 | -modernize-avoid-c-arrays, 67 | -modernize-avoid-bind, 68 | -modernize-concat-nested-namespaces, 69 | -modernize-return-braced-init-list, 70 | -modernize-use-auto, 71 | -modernize-use-default-member-init, 72 | -modernize-use-equals-default, 73 | -modernize-use-trailing-return-type, 74 | -modernize-use-nodiscard, 75 | -mpi-*, 76 | -objc-*, 77 | -readability-container-data-pointer, 78 | -readability-convert-member-functions-to-static, 79 | -readability-else-after-return, 80 | -readability-function-cognitive-complexity, 81 | -readability-implicit-bool-conversion, 82 | -readability-isolate-declaration, 83 | -readability-magic-numbers, 84 | -readability-make-member-function-const, 85 | -readability-redundant-string-init, 86 | -readability-uppercase-literal-suffix, 87 | -readability-use-anyofallof, 88 | WarningsAsErrors: '*' 89 | AnalyzeTemporaryDtors: false 90 | FormatStyle: google 91 | CheckOptions: 92 | - key: google-readability-function-size.StatementThreshold 93 | value: '800' 94 | - key: google-runtime-int.TypeSuffix 95 | value: '_t' 96 | - key: llvm-namespace-comment.ShortNamespaceLines 97 | value: '10' 98 | - key: llvm-namespace-comment.SpacesBeforeComments 99 | value: '2' 100 | - key: modernize-loop-convert.MaxCopySize 101 | value: '16' 102 | - key: modernize-loop-convert.MinConfidence 103 | value: reasonable 104 | - key: modernize-loop-convert.NamingStyle 105 | value: CamelCase 106 | - key: modernize-pass-by-value.IncludeStyle 107 | value: llvm 108 | - key: modernize-replace-auto-ptr.IncludeStyle 109 | value: llvm 110 | - key: modernize-use-nullptr.NullMacros 111 | value: 'NULL' 112 | - key: modernize-make-unique.MakeSmartPtrFunction 113 | value: 'make_unique' 114 | - key: modernize-make-unique.MakeSmartPtrFunctionHeader 115 | value: 'esphome/core/helpers.h' 116 | - key: readability-braces-around-statements.ShortStatementLines 117 | value: 2 118 | - key: readability-identifier-naming.LocalVariableCase 119 | value: 'lower_case' 120 | - key: readability-identifier-naming.ClassCase 121 | value: 'CamelCase' 122 | - key: readability-identifier-naming.StructCase 123 | value: 'CamelCase' 124 | - key: readability-identifier-naming.EnumCase 125 | value: 'CamelCase' 126 | - key: readability-identifier-naming.EnumConstantCase 127 | value: 'UPPER_CASE' 128 | - key: readability-identifier-naming.StaticConstantCase 129 | value: 'UPPER_CASE' 130 | - key: readability-identifier-naming.StaticVariableCase 131 | value: 'lower_case' 132 | - key: readability-identifier-naming.GlobalConstantCase 133 | value: 'UPPER_CASE' 134 | - key: readability-identifier-naming.ParameterCase 135 | value: 'lower_case' 136 | - key: readability-identifier-naming.PrivateMemberCase 137 | value: 'lower_case' 138 | - key: readability-identifier-naming.PrivateMemberSuffix 139 | value: '_' 140 | - key: readability-identifier-naming.PrivateMethodCase 141 | value: 'lower_case' 142 | - key: readability-identifier-naming.PrivateMethodSuffix 143 | value: '_' 144 | - key: readability-identifier-naming.ClassMemberCase 145 | value: 'lower_case' 146 | - key: readability-identifier-naming.ClassMemberCase 147 | value: 'lower_case' 148 | - key: readability-identifier-naming.ProtectedMemberCase 149 | value: 'lower_case' 150 | - key: readability-identifier-naming.ProtectedMemberSuffix 151 | value: '_' 152 | - key: readability-identifier-naming.FunctionCase 153 | value: 'lower_case' 154 | - key: readability-identifier-naming.ClassMethodCase 155 | value: 'lower_case' 156 | - key: readability-identifier-naming.ProtectedMethodCase 157 | value: 'lower_case' 158 | - key: readability-identifier-naming.ProtectedMethodSuffix 159 | value: '_' 160 | - key: readability-identifier-naming.VirtualMethodCase 161 | value: 'lower_case' 162 | - key: readability-identifier-naming.VirtualMethodSuffix 163 | value: '' 164 | - key: readability-qualified-auto.AddConstToQualified 165 | value: 0 166 | - key: readability-identifier-length.MinimumVariableNameLength 167 | value: 0 168 | - key: readability-identifier-length.MinimumParameterNameLength 169 | value: 0 170 | - key: readability-identifier-length.MinimumLoopCounterNameLength 171 | value: 0 172 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # general 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | charset = utf-8 8 | 9 | # python 10 | [*.py] 11 | indent_style = space 12 | indent_size = 4 13 | 14 | # C++ 15 | [*.{cpp,h,tcc}] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | # Web 20 | [*.{js,html,css}] 21 | indent_style = space 22 | indent_size = 2 23 | 24 | # YAML 25 | [*.{yaml,yml}] 26 | indent_style = space 27 | indent_size = 2 28 | quote_type = double 29 | 30 | # JSON 31 | [*.json] 32 | indent_style = space 33 | indent_size = 2 34 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | # Following 4 for black compatibility 4 | # E501: line too long 5 | # W503: Line break occurred before a binary operator 6 | # E203: Whitespace before ':' 7 | # D202 No blank lines allowed after function docstring 8 | 9 | # TODO fix flake8 10 | # D100 Missing docstring in public module 11 | # D101 Missing docstring in public class 12 | # D102 Missing docstring in public method 13 | # D103 Missing docstring in public function 14 | # D104 Missing docstring in public package 15 | # D105 Missing docstring in magic method 16 | # D107 Missing docstring in __init__ 17 | # D200 One-line docstring should fit on one line with quotes 18 | # D205 1 blank line required between summary line and description 19 | # D209 Multi-line docstring closing quotes should be on a separate line 20 | # D400 First line should end with a period 21 | # D401 First line should be in imperative mood 22 | 23 | ignore = 24 | E501, 25 | W503, 26 | E203, 27 | D202, 28 | 29 | D100, 30 | D101, 31 | D102, 32 | D103, 33 | D104, 34 | D105, 35 | D107, 36 | D200, 37 | D205, 38 | D209, 39 | D400, 40 | D401, 41 | 42 | exclude = api_pb2.py 43 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize line endings to LF in the repository 2 | * text eol=lf 3 | *.png binary 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gitignore settings for ESPHome 2 | # This is an example and may include too much for your use-case. 3 | # You can modify this file to suit your needs. 4 | /.esphome/ 5 | /secrets.yaml 6 | /.vscode/ 7 | __pycache__/ 8 | /components/zigbee/files_to_parse/*.c 9 | /components/zigbee/files_to_parse/*.h 10 | /components/zigbee/files_to_parse/zigbee_const.py 11 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # See https://pre-commit.com for more information 3 | # See https://pre-commit.com/hooks.html for more hooks 4 | repos: 5 | - repo: https://github.com/astral-sh/ruff-pre-commit 6 | # Ruff version. 7 | rev: v0.5.4 8 | hooks: 9 | # Run the linter. 10 | - id: ruff 11 | args: [--fix] 12 | # Run the formatter. 13 | - id: ruff-format 14 | - repo: https://github.com/psf/black-pre-commit-mirror 15 | rev: 24.4.2 16 | hooks: 17 | - id: black 18 | args: 19 | - --safe 20 | - --quiet 21 | files: ^((esphome|script|tests)/.+)?[^/]+\.py$ 22 | - repo: https://github.com/PyCQA/flake8 23 | rev: 6.1.0 24 | hooks: 25 | - id: flake8 26 | additional_dependencies: 27 | - flake8-docstrings==1.5.0 28 | - pydocstyle==5.1.1 29 | files: ^(esphome|tests)/.+\.py$ 30 | - repo: https://github.com/pre-commit/pre-commit-hooks 31 | rev: v3.4.0 32 | hooks: 33 | - id: no-commit-to-branch 34 | args: 35 | - --branch=dev 36 | - --branch=release 37 | - --branch=beta 38 | - repo: https://github.com/asottile/pyupgrade 39 | rev: v3.15.2 40 | hooks: 41 | - id: pyupgrade 42 | args: [--py39-plus] 43 | - repo: https://github.com/adrienverge/yamllint.git 44 | rev: v1.35.1 45 | hooks: 46 | - id: yamllint 47 | - repo: https://github.com/pre-commit/mirrors-clang-format 48 | rev: v13.0.1 49 | hooks: 50 | - id: clang-format 51 | types_or: [c, c++] 52 | - repo: local 53 | hooks: 54 | - id: pylint 55 | name: pylint 56 | entry: pylint 57 | language: system 58 | types: [python] 59 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | ignore-from-file: .gitignore 5 | 6 | rules: 7 | document-start: disable 8 | empty-lines: 9 | level: error 10 | max: 1 11 | max-start: 0 12 | max-end: 1 13 | indentation: 14 | level: error 15 | spaces: 2 16 | indent-sequences: true 17 | check-multi-line-strings: false 18 | line-length: disable 19 | truthy: disable 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!Important] 2 | > **Please help to collect working cluster definitions [here](https://github.com/luar123/zigbee_esphome/discussions/22).** 3 | > 4 | > **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** 5 | 6 | # ESPHome ZigBee external component 7 | 8 | External ZigBee component for ESPHome. 9 | 10 | ### Features 11 | * Definition of endpoints, clusters and attributes supported by esp-zigbee-sdk 1.6 12 | * Set attributes action 13 | * Manual report action 14 | * Reset zigbee action 15 | * Join trigger 16 | * Attribute received trigger 17 | * Time sync with coordinator 18 | * Custom clusters and attributes 19 | * (binary) sensors can be connected to attributs without need for lambdas/actions 20 | * Wifi co-existence on ESP32-C6 21 | * Deep-sleep should work 22 | * Not tested: groups 23 | * Time sync with coordinator 24 | 25 | ### Limitations 26 | * Only end devices 27 | * Attribute set action and OnValue trigger works only with numeric types 28 | * Reporting can be enabled, but not configured 29 | * No control devices like switches ([workaround](https://github.com/luar123/zigbee_esphome/discussions/18#discussioncomment-11875376)) 30 | * Needs esp-idf >=5.1.4 31 | * Needs esphome >=2024.12 32 | * scenes not implemented 33 | 34 | ### ToDo List (Short-Mid term) 35 | * Light effects (through identify cluster commands) 36 | * Router devices 37 | * Easier setup of devices/entpoints 38 | 39 | ### Not planed (feel free to submit a PR) 40 | * Coordinator devices 41 | * Binding config in yaml 42 | * Reporting config in yaml 43 | * Control device support like switches ([workaround](https://github.com/luar123/zigbee_esphome/discussions/18#discussioncomment-11875376)) 44 | 45 | ## Usage 46 | 47 | Include external component: 48 | ``` 49 | external_components: 50 | - source: github://luar123/zigbee_esphome 51 | components: [zigbee] 52 | 53 | zigbee: 54 | ... 55 | ``` 56 | ### Configuration variables: 57 | [Todo] 58 | 59 | Example: 60 | ``` 61 | zigbee: 62 | id: "zb" 63 | endpoints: 64 | - num: 1 65 | device_type: COLOR_DIMMABLE_LIGHT 66 | clusters: 67 | - id: ON_OFF 68 | attributes: 69 | - attribute_id: 0 70 | type: bool 71 | on_value: 72 | then: 73 | - light.control: 74 | id: light_1 75 | state: !lambda "return (bool)x;" 76 | - id: LEVEL_CONTROL 77 | attributes: 78 | - attribute_id: 0 79 | type: U8 80 | on_value: 81 | then: 82 | - light.control: 83 | id: light_1 84 | brightness: !lambda "return ((float)x)/255;" 85 | - device_type: TEMPERATURE_SENSOR 86 | num: 2 87 | clusters: 88 | - id: REL_HUMIDITY_MEASUREMENT 89 | attributes: 90 | - attribute_id: 0 91 | id: hum_attr 92 | type: U16 93 | report: true 94 | value: 200 95 | - id: TEMP_MEASUREMENT 96 | attributes: 97 | - attribute_id: 0x0 98 | type: S16 99 | report: true 100 | value: 100 101 | device: temp_sensor_id 102 | scale: 100 103 | on_join: 104 | then: 105 | - logger.log: "Joined network" 106 | ``` 107 | 108 | ### Actions 109 | * zigbee.setAttr 110 | * id: "id of attribute" 111 | * value: "value, can be a lambda" 112 | * only numeric types 113 | * zigbee.report: "id of zigbee component" 114 | * Manually send reports 115 | * zigbee.reset: "id of zigbee component" 116 | * Reset Zigbee Network and Device. Leave the current network and tries to join open networks. 117 | 118 | Examples: 119 | ``` 120 | on_value: 121 | then: 122 | - zigbee.setAttr: 123 | id: hum_attr 124 | value: !lambda "return x*100;" 125 | ``` 126 | ``` 127 | on_press: 128 | then: 129 | - zigbee.report: zb 130 | ``` 131 | 132 | ### Time sync 133 | Add a 'time' component with platform 'zigbee', e.g.: 134 | ``` 135 | zigbee: 136 | id: "zb" 137 | ... 138 | 139 | time: 140 | - platform: zigbee 141 | timezone: Europe/London 142 | on_time_sync: 143 | then: 144 | - logger.log: "Synchronized system clock" 145 | on_time: 146 | - seconds: /10 147 | then: 148 | - logger.log: "Tick-tock 10 seconds" 149 | ``` 150 | 151 | ## Troubleshooting 152 | * Build errors 153 | - Try to run `esphome clean ` 154 | - Try to delete the `.esphome/build//` folder 155 | * ESP crashes 156 | - Try to erase completely with `esptool.py erase_flash` and flash again. 157 | - 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 158 | - Common issues are that attributes do not support reporting (try set `report: false`), use a different type, or are not readable/writable (see ZCL). 159 | * Zigbee is not working as expected 160 | - Whenever the cluster definition changed you need to re-interview and remove/add the device to your network. 161 | - Sometimes it helps to power-cycle the coordinator and restarting z2m. 162 | - Remove other endpoints. Sometimes coordinators struggle with multiple endpoints. 163 | 164 | ## Notes 165 | * I don't have much free time to work on this right now, so feel free to fork/improve/create PRs/etc. 166 | * 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. 167 | * 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 168 | * [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. 169 | * Deprecated [custom zigbee component](https://github.com/luar123/esphome_zb_sensor) 170 | 171 | ## Example Zigbee device 172 | 173 | ESPHome Zigbee using only dev board or additionally [AHT10 Temperature+Humidity Sensor](https://next.esphome.io/components/sensor/aht10). 174 | 175 | ### Hardware Required 176 | 177 | * One development board with ESP32-H2 or ESP32-C6 SoC acting as Zigbee end-device (that you will load ESPHome with the example config to). 178 | * 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. 179 | * [AHT10 Temperature+Humidity Sensor](https://next.esphome.io/components/sensor/aht10) connected to I2C pins (SDA: 12, SCL: 22) for the aht10 example. 180 | * A USB cable for power supply and programming. 181 | * (Optional) A USB-C cable to get ESP32 logs from the UART USB port (UART0). 182 | 183 | ### Build ESPHome Zigbee sensor 184 | 185 | * We will build [example_esp32h2.yaml](example_esp32h2.yaml) file. 186 | * Check [Getting Started with the ESPHome Command Line](https://esphome.io/guides/getting_started_command_line.html) tutorial to set up your dev environment. 187 | * Build with `esphome run example_esp32h2.yaml`. 188 | 189 | ## How to contribute 190 | 191 | **Please submit all PRs here** and not to https://github.com/luar123/esphome/tree/zigbee 192 | 193 | Use pre-commit hook by enabling you esphome environment first and then running `pre-commit install` in the git root foulder. 194 | 195 | If looking to contribute to this project, then suggest follow steps in these guides + look at issues in Espressif's ESP Zigbee SDK repository: 196 | 197 | - https://github.com/espressif/esp-zigbee-sdk/issues 198 | - https://github.com/firstcontributions/first-contributions/blob/master/README.md 199 | - https://github.com/firstcontributions/first-contributions/blob/master/github-desktop-tutorial.md 200 | 201 | 202 | ## External documentation and reference 203 | 204 | Note! The official documentation and reference examples for the ESP Zigbee SDK can currently be obtained from Espressif: 205 | 206 | - [ESP32 Zigbee SDK Programming Guide](https://docs.espressif.com/projects/esp-zigbee-sdk/en/latest/esp32/) 207 | - [ESP32-H2 Application User Guide](https://docs.espressif.com/projects/esp-zigbee-sdk/en/latest/esp32h2/application.html) 208 | - [ESP32-C6 Application User Guide](https://docs.espressif.com/projects/esp-zigbee-sdk/en/latest/esp32c6/application.html) 209 | - [ESP-Zigbee-SDK Github repo](https://github.com/espressif/esp-zigbee-sdk) 210 | - [ESP-Zigbee-SDK examples](https://github.com/espressif/esp-zigbee-sdk/tree/main/examples/) 211 | - [Zigbee HA Example](https://github.com/espressif/esp-zigbee-sdk/tree/main/examples/esp_zigbee_HA_sample) 212 | - [Zigbee HA Light Bulb example](https://github.com/espressif/esp-zigbee-sdk/tree/main/examples/esp_zigbee_HA_sample/HA_on_off_light) 213 | - [Zigbee HA temperature sensor example](https://github.com/espressif/esp-zigbee-sdk/tree/main/examples/esp_zigbee_HA_sample/HA_temperature_sensor) 214 | - [Zigbee HA thermostat example](https://github.com/espressif/esp-zigbee-sdk/tree/main/examples/esp_zigbee_HA_sample/HA_thermostat) 215 | -------------------------------------------------------------------------------- /components/zigbee/__init__.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import re 3 | 4 | from esphome import automation 5 | import esphome.codegen as cg 6 | from esphome.components.esp32 import ( 7 | CONF_PARTITIONS, 8 | # add_extra_build_file, 9 | add_idf_component, 10 | add_idf_sdkconfig_option, 11 | only_on_variant, 12 | ) 13 | from esphome.components.esp32.const import VARIANT_ESP32C6, VARIANT_ESP32H2 14 | import esphome.config_validation as cv 15 | from esphome.const import ( 16 | CONF_AP, 17 | CONF_AREA, 18 | CONF_DATE, 19 | CONF_DEVICE, 20 | CONF_ID, 21 | CONF_LAMBDA, 22 | CONF_MAX_LENGTH, 23 | CONF_NAME, 24 | CONF_ON_VALUE, 25 | CONF_POWER_SUPPLY, 26 | CONF_TRIGGER_ID, 27 | CONF_TYPE, 28 | CONF_VALUE, 29 | CONF_VERSION, 30 | CONF_WIFI, 31 | ) 32 | from esphome.core import CORE, EsphomeError 33 | import esphome.final_validate as fv 34 | 35 | from .zigbee_const import ATTR_ACCESS, ATTR_TYPE, CLUSTER_ID, CLUSTER_ROLE, DEVICE_ID 36 | 37 | DEPENDENCIES = ["esp32"] 38 | 39 | CONF_ENDPOINTS = "endpoints" 40 | CONF_DEVICE_TYPE = "device_type" 41 | CONF_NUM = "num" 42 | CONF_CLUSTERS = "clusters" 43 | CONF_ON_JOIN = "on_join" 44 | CONF_IDENT_TIME = "ident_time" 45 | CONF_MANUFACTURER = "manufacturer" 46 | CONF_ATTRIBUTES = "attributes" 47 | CONF_ROLE = "role" 48 | CONF_ENDPOINT = "endpoint" 49 | CONF_CLUSTER = "cluster" 50 | CONF_REPORT = "report" 51 | CONF_ACCESS = "access" 52 | CONF_SCALE = "scale" 53 | CONF_ATTRIBUTE_ID = "attribute_id" 54 | CONF_ZIGBEE_ID = "zigbee_id" 55 | CONF_ROUTER = "router" 56 | 57 | zigbee_ns = cg.esphome_ns.namespace("zigbee") 58 | ZigBeeComponent = zigbee_ns.class_("ZigBeeComponent", cg.Component) 59 | ZigBeeAttribute = zigbee_ns.class_("ZigBeeAttribute", cg.Component) 60 | ZigBeeJoinTrigger = zigbee_ns.class_("ZigBeeJoinTrigger", automation.Trigger) 61 | ZigBeeOnValueTrigger = zigbee_ns.class_( 62 | "ZigBeeOnValueTrigger", automation.Trigger.template(int), cg.Component 63 | ) 64 | ResetZigbeeAction = zigbee_ns.class_( 65 | "ResetZigbeeAction", automation.Action, cg.Parented.template(ZigBeeComponent) 66 | ) 67 | SetAttrAction = zigbee_ns.class_("SetAttrAction", automation.Action) 68 | ReportAction = zigbee_ns.class_( 69 | "ReportAction", automation.Action, cg.Parented.template(ZigBeeComponent) 70 | ) 71 | 72 | 73 | def get_c_size(bits, options): 74 | return str([n for n in options if n >= int(bits)][0]) 75 | 76 | 77 | def get_c_type(attr_type): 78 | if attr_type == "BOOL": 79 | return cg.bool_ 80 | if attr_type == "SINGLE": 81 | return cg.float_ 82 | if attr_type == "DOUBLE": 83 | return cg.double 84 | if "STRING" in attr_type: 85 | return cg.std_string 86 | test = re.match(r"(^U?)(\d{1,2})(BITMAP$|BIT$|BIT_ENUM$|$)", attr_type) 87 | if test and test.group(2): 88 | return getattr(cg, "uint" + get_c_size(test.group(2), [8, 16, 32, 64])) 89 | test = re.match(r"^S(\d{1,2})$", attr_type) 90 | if test and test.group(1): 91 | return getattr(cg, "int" + get_c_size(test.group(1), [16, 32, 64])) 92 | raise EsphomeError(f"Zigbee: type {attr_type} not supported or implemented") 93 | 94 | 95 | def get_cv_by_type(attr_type): 96 | if attr_type == "BOOL": 97 | return cv.boolean 98 | if attr_type in ["SEMI", "SINGLE", "DOUBLE"]: 99 | return cv.float_ 100 | if "STRING" in attr_type: 101 | return cv.string 102 | test = re.match(r"(^U?)(\d{1,2})(BITMAP$|BIT$|BIT_ENUM$|$)", attr_type) 103 | if test and test.group(2): 104 | return cv.positive_int 105 | test = re.match(r"^S(\d{1,2})$", attr_type) 106 | if test and test.group(1): 107 | return cv.int_ 108 | raise cv.Invalid(f"Zigbee: type {attr_type} not supported or implemented") 109 | 110 | 111 | def get_default_by_type(attr_type): 112 | if "CHAR_STRING" == attr_type: 113 | return "" 114 | return 0 115 | 116 | 117 | def validate_clusters(config): 118 | for attr in config.get(CONF_ATTRIBUTES): 119 | if isinstance(config.get(CONF_ID), int) and config.get(CONF_ID) >= 0xFC00: 120 | if not {CONF_TYPE, CONF_ACCESS, CONF_VALUE} <= attr.keys(): 121 | raise cv.Invalid( 122 | f"Parameters {CONF_TYPE}, {CONF_VALUE} and {CONF_ACCESS} are need for custom cluster." 123 | ) 124 | return config 125 | 126 | 127 | def validate_string_attributes(config): 128 | if "CHAR_STRING" == config[CONF_TYPE]: 129 | if CONF_MAX_LENGTH not in config.keys(): 130 | raise cv.Invalid( 131 | f"The '{CONF_MAX_LENGTH}' parameter is mandatory for string attributes." 132 | ) 133 | 134 | # Check that size of default value matches CONF_MAX_LENGTH 135 | if len(config[CONF_VALUE]) > config[CONF_MAX_LENGTH]: 136 | raise cv.Invalid( 137 | "The default value is larger than the maximum length of the string attribute." 138 | ) 139 | return config 140 | 141 | 142 | def validate_attributes(config): 143 | if CONF_VALUE in config: 144 | config[CONF_VALUE] = get_cv_by_type(config[CONF_TYPE])(config[CONF_VALUE]) 145 | else: 146 | config[CONF_VALUE] = get_default_by_type(config[CONF_TYPE]) 147 | config[CONF_ACCESS] = ( 148 | ATTR_ACCESS[config[CONF_ACCESS]] + config[CONF_REPORT] * 4 149 | if CONF_ACCESS in config 150 | else 0 151 | ) 152 | validate_string_attributes(config) 153 | 154 | return config 155 | 156 | 157 | def final_validate(config): 158 | esp_conf = fv.full_config.get()["esp32"] 159 | if CONF_PARTITIONS in esp_conf: 160 | with open( 161 | CORE.relative_config_path(esp_conf[CONF_PARTITIONS]), encoding="utf8" 162 | ) as f: 163 | partitions = f.read() 164 | if ("zb_storage" not in partitions) and ("zb_fct" not in partitions): 165 | raise cv.Invalid( 166 | "Add \n'zb_storage, data, fat, , 16K,'\n'zb_fct, data, fat, , 1K,'\n to your custom partition table." 167 | ) 168 | else: 169 | raise cv.Invalid( 170 | f"Use '{CONF_PARTITIONS}' in esp32 to specify a custom partition table including zigbee partitions" 171 | ) 172 | if CONF_WIFI in fv.full_config.get(): 173 | if CONF_AP in fv.full_config.get()[CONF_WIFI]: 174 | raise cv.Invalid("Zigbee can't be used together with an Wifi Access Point.") 175 | 176 | return config 177 | 178 | 179 | FINAL_VALIDATE_SCHEMA = cv.Schema(final_validate) 180 | 181 | 182 | CONFIG_SCHEMA = cv.All( 183 | cv.Schema( 184 | { 185 | cv.GenerateID(): cv.declare_id(ZigBeeComponent), 186 | cv.Optional(CONF_NAME): cv.string, 187 | cv.Optional(CONF_MANUFACTURER, default="esphome"): cv.string, 188 | cv.Optional( 189 | CONF_DATE, default=datetime.datetime.now().strftime("%Y%m%d") 190 | ): cv.string, 191 | cv.Optional(CONF_IDENT_TIME): cv.string, 192 | cv.Optional(CONF_POWER_SUPPLY, default=0): cv.int_, # make enum 193 | cv.Optional(CONF_VERSION, default=0): cv.int_, 194 | cv.Optional(CONF_AREA, default=0): cv.int_, # make enum 195 | cv.Required(CONF_ENDPOINTS): cv.ensure_list( 196 | cv.Schema( 197 | { 198 | cv.Required(CONF_DEVICE_TYPE): cv.enum(DEVICE_ID, upper=True), 199 | cv.Optional(CONF_NUM): cv.int_range(1, 240), 200 | cv.Optional(CONF_CLUSTERS, default={}): cv.ensure_list( 201 | cv.Schema( 202 | { 203 | cv.Required(CONF_ID): cv.Any( 204 | cv.enum(CLUSTER_ID, upper=True), 205 | cv.int_range(0xFC00, 0xFFFF), 206 | ), 207 | cv.Optional(CONF_ROLE, default="Server"): cv.enum( 208 | CLUSTER_ROLE, upper=True 209 | ), 210 | cv.Optional(CONF_ATTRIBUTES): cv.ensure_list( 211 | cv.Schema( 212 | { 213 | cv.GenerateID(): cv.declare_id( 214 | ZigBeeAttribute 215 | ), 216 | cv.Required(CONF_ATTRIBUTE_ID): cv.int_, 217 | cv.Required(CONF_TYPE): cv.enum( 218 | ATTR_TYPE, upper=True 219 | ), 220 | cv.Optional(CONF_ACCESS): cv.enum( 221 | ATTR_ACCESS, upper=True 222 | ), 223 | cv.Optional(CONF_VALUE): cv.valid, 224 | cv.Optional( 225 | CONF_REPORT, default=False 226 | ): cv.boolean, 227 | cv.Optional( 228 | CONF_ON_VALUE 229 | ): automation.validate_automation( 230 | { 231 | cv.GenerateID( 232 | CONF_TRIGGER_ID 233 | ): cv.declare_id( 234 | ZigBeeOnValueTrigger 235 | ), 236 | } 237 | ), 238 | cv.Optional(CONF_DEVICE): cv.use_id( 239 | cg.EntityBase 240 | ), 241 | cv.Optional( 242 | CONF_SCALE, default=1.0 243 | ): cv.float_, 244 | cv.Optional( 245 | CONF_LAMBDA 246 | ): cv.returning_lambda, 247 | cv.Optional( 248 | CONF_MAX_LENGTH 249 | ): cv.int_range(0, 254), 250 | } 251 | ), 252 | validate_attributes, 253 | ), 254 | }, 255 | ), 256 | validate_clusters, 257 | ), 258 | } 259 | ), 260 | ), 261 | cv.Optional(CONF_ON_JOIN): automation.validate_automation( 262 | { 263 | cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ZigBeeJoinTrigger), 264 | } 265 | ), 266 | cv.Optional(CONF_ROUTER, default=False): cv.boolean, 267 | } 268 | ).extend(cv.COMPONENT_SCHEMA), 269 | cv.require_framework_version(esp_idf=cv.Version(5, 1, 2)), 270 | only_on_variant( 271 | supported=[ 272 | VARIANT_ESP32H2, 273 | VARIANT_ESP32C6, 274 | ] 275 | ), 276 | ) 277 | 278 | 279 | def find_attr(conf, id): 280 | for ep in conf[CONF_ENDPOINTS]: 281 | for cl in ep[CONF_CLUSTERS]: 282 | for attr in cl[CONF_ATTRIBUTES]: 283 | if attr[CONF_ID] == id: 284 | return attr 285 | raise EsphomeError(f"Zigbee: Cannot find attribute {id}.") 286 | 287 | 288 | async def to_code(config): 289 | # use component manager: 290 | # add_extra_build_file( 291 | # "src/idf_component.yml", 292 | # os.path.join(os.path.dirname(__file__), "idf_component.yml"), 293 | # ) 294 | # use add_idf_component: v1.5.1 295 | add_idf_component( 296 | name="espressif__esp-zboss-lib", 297 | repo="https://github.com/espressif/esp-zboss-lib.git", 298 | ref="354f742fab22cdb12b4ae61a103a7b438c7c975f", 299 | ) 300 | add_idf_component( 301 | name="esp-zigbee-lib", 302 | repo="https://github.com/espressif/esp-zigbee-sdk.git", 303 | path="components/esp-zigbee-lib", 304 | ref="df56883084e89ebabf8a4985f92a023bd816a1b5", 305 | ) 306 | add_idf_sdkconfig_option("CONFIG_ZB_ENABLED", True) 307 | if config.get(CONF_ROUTER): 308 | add_idf_sdkconfig_option("CONFIG_ZB_ZCZR", True) 309 | else: 310 | add_idf_sdkconfig_option("CONFIG_ZB_ZED", True) 311 | add_idf_sdkconfig_option("CONFIG_ZB_RADIO_NATIVE", True) 312 | if CONF_WIFI in CORE.config: 313 | add_idf_sdkconfig_option("CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE", 4096) 314 | cg.add_define("CONFIG_WIFI_COEX") 315 | var = cg.new_Pvariable(config[CONF_ID]) 316 | await cg.register_component(var, config) 317 | if CONF_NAME not in config: 318 | config[CONF_NAME] = CORE.name or "" 319 | cg.add( 320 | var.set_basic_cluster( 321 | config[CONF_NAME], 322 | config[CONF_MANUFACTURER], 323 | config[CONF_DATE], 324 | config[CONF_POWER_SUPPLY], 325 | config[CONF_VERSION], 326 | 0, 327 | 0, 328 | CORE.area or "", 329 | config[CONF_AREA], 330 | ) 331 | ) 332 | if CONF_IDENT_TIME in config: 333 | cg.add(var.set_ident_time(config[CONF_IDENT_TIME])) 334 | for ep in config[CONF_ENDPOINTS]: 335 | cg.add( 336 | var.create_default_cluster(ep[CONF_NUM], DEVICE_ID[ep[CONF_DEVICE_TYPE]]) 337 | ) 338 | cg.add( 339 | var.add_cluster(ep[CONF_NUM], CLUSTER_ID["BASIC"], CLUSTER_ROLE["SERVER"]) 340 | ) 341 | if CONF_IDENT_TIME in config: 342 | cg.add( 343 | var.add_cluster( 344 | ep[CONF_NUM], 345 | CLUSTER_ID["IDENTIFY"], 346 | CLUSTER_ROLE["SERVER"], 347 | ) 348 | ) 349 | for cl in ep[CONF_CLUSTERS]: 350 | cg.add( 351 | var.add_cluster( 352 | ep[CONF_NUM], 353 | cl[CONF_ID], 354 | cl[CONF_ROLE], 355 | ) 356 | ) 357 | for attr in cl[CONF_ATTRIBUTES]: 358 | attr_var = cg.new_Pvariable( 359 | attr[CONF_ID], 360 | var, 361 | ep[CONF_NUM], 362 | cl[CONF_ID], 363 | cl[CONF_ROLE], 364 | attr[CONF_ATTRIBUTE_ID], 365 | attr[CONF_TYPE], 366 | attr[CONF_SCALE], 367 | ) 368 | await cg.register_component(attr_var, attr) 369 | 370 | cg.add( 371 | attr_var.add_attr( 372 | attr[CONF_ACCESS], 373 | attr.get(CONF_MAX_LENGTH, 0), 374 | attr[CONF_VALUE], 375 | ) 376 | ) 377 | if attr[CONF_REPORT]: 378 | cg.add(attr_var.set_report()) 379 | 380 | if CONF_LAMBDA in attr: 381 | lambda_ = await cg.process_lambda( 382 | attr[CONF_LAMBDA], 383 | [(cg.float_, "x")], 384 | return_type=get_c_type(attr[CONF_TYPE]), 385 | ) 386 | 387 | if CONF_DEVICE in attr: 388 | device = await cg.get_variable(attr[CONF_DEVICE]) 389 | template_arg = cg.TemplateArguments(get_c_type(attr[CONF_TYPE])) 390 | if CONF_LAMBDA in attr: 391 | lambda_ = await cg.process_lambda( 392 | attr[CONF_LAMBDA], 393 | [(cg.float_, "x")], 394 | return_type=get_c_type(attr[CONF_TYPE]), 395 | ) 396 | cg.add(attr_var.connect(template_arg, device, lambda_)) 397 | else: 398 | cg.add(attr_var.connect(template_arg, device)) 399 | 400 | for conf in attr.get(CONF_ON_VALUE, []): 401 | trigger = cg.new_Pvariable( 402 | conf[CONF_TRIGGER_ID], 403 | cg.TemplateArguments(get_c_type(attr[CONF_TYPE])), 404 | attr_var, 405 | ) 406 | await cg.register_component(trigger, conf) 407 | await automation.build_automation( 408 | trigger, [(get_c_type(attr[CONF_TYPE]), "x")], conf 409 | ) 410 | for conf in config.get(CONF_ON_JOIN, []): 411 | trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) 412 | await automation.build_automation(trigger, [], conf) 413 | 414 | 415 | ZIGBEE_ACTION_SCHEMA = cv.Schema( 416 | { 417 | cv.GenerateID(): cv.use_id(ZigBeeComponent), 418 | } 419 | ) 420 | 421 | 422 | @automation.register_action( 423 | "zigbee.reset", ResetZigbeeAction, automation.maybe_simple_id(ZIGBEE_ACTION_SCHEMA) 424 | ) 425 | async def reset_zigbee_to_code(config, action_id, template_arg, args): 426 | var = cg.new_Pvariable(action_id, template_arg) 427 | await cg.register_parented(var, config[CONF_ID]) 428 | return var 429 | 430 | 431 | @automation.register_action( 432 | "zigbee.report", ReportAction, automation.maybe_simple_id(ZIGBEE_ACTION_SCHEMA) 433 | ) 434 | async def report_to_code(config, action_id, template_arg, args): 435 | var = cg.new_Pvariable(action_id, template_arg) 436 | await cg.register_parented(var, config[CONF_ID]) 437 | return var 438 | 439 | 440 | ZIGBEE_ATTRIBUTE_ACTION_SCHEMA = cv.Schema( 441 | { 442 | cv.GenerateID(): cv.use_id(ZigBeeAttribute), 443 | } 444 | ) 445 | 446 | ZIGBEE_SET_ATTR_SCHEMA = cv.All( 447 | automation.maybe_simple_id( 448 | ZIGBEE_ATTRIBUTE_ACTION_SCHEMA.extend( 449 | cv.Schema( 450 | { 451 | cv.Required(CONF_VALUE): cv.templatable(cv.valid), 452 | } 453 | ) 454 | ) 455 | ), 456 | ) 457 | 458 | 459 | @automation.register_action("zigbee.setAttr", SetAttrAction, ZIGBEE_SET_ATTR_SCHEMA) 460 | async def zigbee_set_attr_to_code(config, action_id, template_arg, args): 461 | attr = find_attr( 462 | CORE.config["zigbee"], 463 | config[CONF_ID], 464 | ) 465 | template_arg = cg.TemplateArguments( 466 | get_c_type(attr[CONF_TYPE]), 467 | template_arg.args if template_arg.args.args else None, 468 | ) 469 | parent = await cg.get_variable(config[CONF_ID]) 470 | var = cg.new_Pvariable(action_id, template_arg, parent) 471 | template_ = await cg.templatable( 472 | config[CONF_VALUE], args, get_c_type(attr[CONF_TYPE]) 473 | ) 474 | cg.add(var.set_value(template_)) 475 | 476 | return var 477 | -------------------------------------------------------------------------------- /components/zigbee/automation.cpp: -------------------------------------------------------------------------------- 1 | #include "automation.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome { 5 | namespace zigbee { 6 | 7 | float get_r_from_xy(float x, float y) { 8 | float z = 1.0f - x - y; 9 | float X = x / y; 10 | float Z = z / y; 11 | float r = X * 1.4628067f - 0.1840623f - Z * 0.2743606f; 12 | return r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * pow(r, (1.0f / 2.4f)) - 0.055f; 13 | } 14 | 15 | float get_g_from_xy(float x, float y) { 16 | float z = 1.0f - x - y; 17 | float X = x / y; 18 | float Z = z / y; 19 | float g = -X * 0.5217933f + 1.4472381f + Z * 0.0677227f; 20 | return g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * pow(g, (1.0f / 2.4f)) - 0.055f; 21 | } 22 | 23 | float get_b_from_xy(float x, float y) { 24 | float z = 1.0f - x - y; 25 | float X = x / y; 26 | float Z = z / y; 27 | 28 | float b = X * 0.0349342f - 0.0968930f + Z * 1.2884099f; 29 | 30 | return b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * pow(b, (1.0f / 2.4f)) - 0.055f; 31 | } 32 | 33 | } // namespace zigbee 34 | } // namespace esphome 35 | -------------------------------------------------------------------------------- /components/zigbee/automation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //#include //deactive because not working with esp-idf 5.1.4 4 | 5 | #include "esphome/core/component.h" 6 | #include "esphome/core/automation.h" 7 | #include "zigbee_attribute.h" 8 | #include "zigbee.h" 9 | 10 | namespace esphome { 11 | namespace zigbee { 12 | 13 | class ZigBeeJoinTrigger : public Trigger<> { 14 | public: 15 | explicit ZigBeeJoinTrigger(ZigBeeComponent *parent) { 16 | parent->add_on_join_callback([this]() { this->trigger(); }); 17 | } 18 | }; 19 | 20 | template class ResetZigbeeAction : public Action, public Parented { 21 | public: 22 | void play(Ts... x) override { this->parent_->reset(); } 23 | }; 24 | 25 | template class ReportAction : public Action, public Parented { 26 | public: 27 | void play(Ts... x) override { this->parent_->report(); } 28 | }; 29 | 30 | template class SetAttrAction : public Action { 31 | public: 32 | SetAttrAction(ZigBeeAttribute *parent) : parent_(parent) {} 33 | TEMPLATABLE_VALUE(T, value); 34 | 35 | void play(Ts... x) override { 36 | T *value = new T; 37 | *value = this->value_.value(x...); 38 | this->parent_->set_attr(value); 39 | } 40 | 41 | protected: 42 | ZigBeeAttribute *parent_; 43 | }; 44 | 45 | template class ZigBeeOnValueTrigger : public Trigger, public Component { 46 | public: 47 | explicit ZigBeeOnValueTrigger(ZigBeeAttribute *parent) : parent_(parent) {} 48 | void setup() override { 49 | this->parent_->add_on_value_callback([this](esp_zb_zcl_attribute_t attribute) { this->on_value_(attribute); }); 50 | } 51 | 52 | protected: 53 | void on_value_(esp_zb_zcl_attribute_t attribute) { 54 | if (attribute.data.type == parent_->attr_type() && attribute.data.value) { 55 | this->trigger(get_value_by_type(parent_->attr_type(), attribute.data.value)); 56 | } 57 | } 58 | ZigBeeAttribute *parent_; 59 | }; 60 | 61 | template T get_value_by_type(uint8_t attr_type, void *data) { 62 | switch (attr_type) { 63 | case ESP_ZB_ZCL_ATTR_TYPE_SEMI: 64 | return 0; //(T) * (std::float16_t *) data; 65 | case ESP_ZB_ZCL_ATTR_TYPE_OCTET_STRING: 66 | return 0; 67 | case ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING: 68 | return 0; 69 | case ESP_ZB_ZCL_ATTR_TYPE_LONG_OCTET_STRING: 70 | return 0; 71 | case ESP_ZB_ZCL_ATTR_TYPE_LONG_CHAR_STRING: 72 | return 0; 73 | case ESP_ZB_ZCL_ATTR_TYPE_ARRAY: 74 | return 0; 75 | case ESP_ZB_ZCL_ATTR_TYPE_16BIT_ARRAY: 76 | return 0; 77 | case ESP_ZB_ZCL_ATTR_TYPE_32BIT_ARRAY: 78 | return 0; 79 | case ESP_ZB_ZCL_ATTR_TYPE_STRUCTURE: 80 | return 0; 81 | case ESP_ZB_ZCL_ATTR_TYPE_SET: 82 | return 0; 83 | case ESP_ZB_ZCL_ATTR_TYPE_BAG: 84 | return 0; 85 | case ESP_ZB_ZCL_ATTR_TYPE_TIME_OF_DAY: 86 | return (T) * (uint32_t *) data; 87 | case ESP_ZB_ZCL_ATTR_TYPE_DATE: 88 | return (T) * (uint32_t *) data; 89 | case ESP_ZB_ZCL_ATTR_TYPE_UTC_TIME: 90 | return (T) * (uint32_t *) data; 91 | case ESP_ZB_ZCL_ATTR_TYPE_CLUSTER_ID: 92 | return (T) * (uint16_t *) data; 93 | case ESP_ZB_ZCL_ATTR_TYPE_ATTRIBUTE_ID: 94 | return (T) * (uint16_t *) data; 95 | case ESP_ZB_ZCL_ATTR_TYPE_BACNET_OID: 96 | return 0; 97 | case ESP_ZB_ZCL_ATTR_TYPE_IEEE_ADDR: 98 | return (T) * (uint64_t *) data; 99 | case ESP_ZB_ZCL_ATTR_TYPE_128_BIT_KEY: 100 | return 0; 101 | default: 102 | return *(T *) data; 103 | } 104 | } 105 | 106 | float get_r_from_xy(float x, float y); 107 | float get_g_from_xy(float x, float y); 108 | float get_b_from_xy(float x, float y); 109 | 110 | } // namespace zigbee 111 | } // namespace esphome 112 | -------------------------------------------------------------------------------- /components/zigbee/files_to_parse/parse_zigbee_headers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Create python enums 4 | """ 5 | 6 | from pycparser import c_ast, parse_file 7 | 8 | filename = "esp_zigbee_zcl_common.h" 9 | 10 | ast = parse_file(filename, use_cpp=True) 11 | 12 | typedefs = [t[1].type for t in ast.children() if isinstance(t[1], c_ast.Typedef)] 13 | my_enums = [t for t in typedefs if isinstance(t.type, c_ast.Enum)] 14 | 15 | 16 | def print_enum(enum): 17 | print(enum.declname + ":") 18 | print( 19 | "\n".join([e.name + ": " + e.value.value for e in enum.type.values.enumerators]) 20 | ) 21 | 22 | 23 | def write_profileIDs(enums): 24 | enum = list(filter(lambda e: e.declname == "esp_zb_ha_standard_devices_t", enums))[ 25 | 0 26 | ] 27 | enum_name = "ha_standard_devices" 28 | to_py = f'{enum_name} = cg.esphome_ns.enum("{enum.declname}")\nDEVICE_ID' + " = {\n" 29 | for e in enum.type.values.enumerators: 30 | to_py += f' "{e.name.removeprefix("ESP_ZB_HA_").removesuffix("_DEVICE_ID")}": {enum_name}.{e.name},\n' 31 | to_py += f' {(e.value.value.removesuffix("U").upper().replace("X","x"))}: {enum_name}.{e.name},\n' 32 | to_py += "}\n" 33 | return to_py 34 | 35 | 36 | def write_clusterIDs(enums): 37 | enum = list(filter(lambda e: e.declname == "esp_zb_zcl_cluster_id_t", enums))[0] 38 | enum_name = "cluster_id" 39 | to_py = ( 40 | f'{enum_name} = cg.esphome_ns.enum("{enum.declname}")\nCLUSTER_ID' + " = {\n" 41 | ) 42 | for e in enum.type.values.enumerators: 43 | to_py += f' "{e.name.removeprefix("ESP_ZB_ZCL_CLUSTER_ID_")}": {enum_name}.{e.name},\n' 44 | to_py += f' {(e.value.value.removesuffix("U").upper().replace("X","x"))}: {enum_name}.{e.name},\n' 45 | to_py += "}\n" 46 | return to_py 47 | 48 | 49 | def write_clusterRoles(enums): 50 | enum = list(filter(lambda e: e.declname == "esp_zb_zcl_cluster_role_t", enums))[0] 51 | enum_name = "cluster_role" 52 | to_py = ( 53 | f'{enum_name} = cg.esphome_ns.enum("{enum.declname}")\nCLUSTER_ROLE' + " = {\n" 54 | ) 55 | for e in enum.type.values.enumerators: 56 | to_py += f' "{e.name.removeprefix("ESP_ZB_ZCL_CLUSTER_").removesuffix("_ROLE")}": {enum_name}.{e.name},\n' 57 | to_py += "}\n" 58 | return to_py 59 | 60 | 61 | def write_ZBtypes(enums): 62 | enum = list(filter(lambda e: e.declname == "esp_zb_zcl_attr_type_t", enums))[0] 63 | enum_name = "attr_type" 64 | to_py = f'{enum_name} = cg.esphome_ns.enum("{enum.declname}")\nATTR_TYPE' + " = {\n" 65 | for e in enum.type.values.enumerators: 66 | to_py += f' "{e.name.removeprefix("ESP_ZB_ZCL_ATTR_TYPE_")}": {enum_name}.{e.name},\n' 67 | to_py += "}\n" 68 | return to_py 69 | 70 | 71 | with open("zigbee_const.py", "w", encoding="utf-8") as f: 72 | f.write("import esphome.codegen as cg\n\n") 73 | f.write(write_profileIDs(my_enums)) 74 | f.write(write_clusterIDs(my_enums)) 75 | f.write(write_clusterRoles(my_enums)) 76 | f.write(write_ZBtypes(my_enums)) 77 | f.write( 78 | """ATTR_ACCESS = { 79 | "READ_ONLY": 1, 80 | "WRITE_ONLY": 2, 81 | "READ_WRITE": 3, 82 | } 83 | """ 84 | ) 85 | # [print_enum(e) for e in enums] 86 | 87 | replacements_cl = { 88 | "level_control": "level", 89 | "multi_value": "multistate_value", 90 | "ota_upgrade": "ota", 91 | "illuminance_measurement": "illuminance_meas", 92 | "temp_measurement": "temperature_meas", 93 | "pressure_measurement": "pressure_meas", 94 | "rel_humidity_measurement": "humidity_meas", 95 | "electrical_measurement": "electrical_meas", 96 | "flow_measurement": "flow_meas", 97 | } 98 | 99 | remove_cl = [ 100 | "device_temp_config", 101 | "alarms", 102 | "rssi_location", 103 | "binary_output", 104 | "binary_value", 105 | "multi_input", 106 | "multi_output", 107 | "poll_control", 108 | "green_power", 109 | "keep_alive", 110 | "pump_config_control", 111 | "ballast_config", 112 | ] 113 | 114 | 115 | def write_cluster_list_aou(enums): 116 | enum = list(filter(lambda e: e.declname == "esp_zb_zcl_cluster_id_t", enums))[0] 117 | ids = [e.name for e in enum.type.values.enumerators] 118 | 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" 119 | to_py += " ret = esp_zb_cluster_list_update_cluster(cluster_list, attr_list, cluster_id, role_mask);\n" 120 | to_py += " if (ret != ESP_OK) {\n" 121 | to_py += ( 122 | ' ESP_LOGE("zigbee_helper", "Ignore previous cluster not found error");\n' 123 | ) 124 | to_py += " switch (cluster_id) {\n" 125 | for id in ids: 126 | cluster_name = id.removeprefix("ESP_ZB_ZCL_CLUSTER_ID_").lower() 127 | if cluster_name in remove_cl: 128 | continue 129 | for k, i in replacements_cl.items(): 130 | cluster_name = cluster_name.replace(k, i) 131 | to_py += f" case {id}:\n" 132 | to_py += f" ret = esp_zb_cluster_list_add_{cluster_name}_cluster(cluster_list, attr_list, role_mask);\n" 133 | to_py += " break;\n" 134 | 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" 135 | return to_py 136 | 137 | 138 | replacements_attrl = { 139 | "level_control": "level", 140 | "multi_value": "multistate_value", 141 | "ota_upgrade": "ota", 142 | "illuminance_measurement": "illuminance_meas", 143 | "temp_measurement": "temperature_meas", 144 | "pressure_measurement": "pressure_meas", 145 | "rel_humidity_measurement": "humidity_meas", 146 | "electrical_measurement": "electrical_meas", 147 | "flow_measurement": "flow_meas", 148 | } 149 | 150 | void_list = [] 151 | 152 | 153 | def write_attr_list_create(enums): 154 | enum = list(filter(lambda e: e.declname == "esp_zb_zcl_cluster_id_t", enums))[0] 155 | ids = [e.name for e in enum.type.values.enumerators] 156 | to_py = "esp_zb_attribute_list_t *esphome_zb_default_attr_list_create(uint16_t cluster_id) {\n" 157 | to_py += " switch (cluster_id) {\n" 158 | for id in ids: 159 | cluster_name = id.removeprefix("ESP_ZB_ZCL_CLUSTER_ID_").lower() 160 | if cluster_name in remove_cl: 161 | continue 162 | for k, i in replacements_attrl.items(): 163 | cluster_name = cluster_name.replace(k, i) 164 | to_py += f" case {id}:\n" 165 | if cluster_name in void_list: 166 | to_py += f" return esp_zb_{cluster_name}_cluster_create();\n" 167 | else: 168 | to_py += f" return esp_zb_{cluster_name}_cluster_create(NULL);\n" 169 | to_py += " default:\n return esp_zb_zcl_attr_list_create(cluster_id);\n }\n}\n\n" 170 | return to_py 171 | 172 | 173 | remove_attra = [ 174 | "device_temp_config", 175 | "alarms", 176 | "rssi_location", 177 | "binary_output", 178 | "binary_value", 179 | "multi_input", 180 | "multi_output", 181 | "poll_control", 182 | "green_power", 183 | "keep_alive", 184 | "pump_config_control", 185 | "ballast_config", 186 | "ias_ace", 187 | "price", 188 | "metering", 189 | ] 190 | 191 | 192 | def write_attr_add(enums): 193 | enum = list(filter(lambda e: e.declname == "esp_zb_zcl_cluster_id_t", enums))[0] 194 | ids = [e.name for e in enum.type.values.enumerators] 195 | 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" 196 | to_py += " switch (cluster_id) {\n" 197 | for id in ids: 198 | cluster_name = id.removeprefix("ESP_ZB_ZCL_CLUSTER_ID_").lower() 199 | if cluster_name in remove_attra: 200 | continue 201 | for k, i in replacements_cl.items(): 202 | cluster_name = cluster_name.replace(k, i) 203 | to_py += f" case {id}:\n" 204 | to_py += f" return esp_zb_{cluster_name}_cluster_add_attr(attr_list, attr_id, value_p);\n" 205 | to_py += " default:\n return ESP_FAIL;\n }\n}\n\n" 206 | return to_py 207 | 208 | 209 | with open("c_helpers.c", "w", encoding="utf-8") as f: 210 | f.write(write_cluster_list_aou(my_enums)) 211 | f.write(write_attr_list_create(my_enums)) 212 | f.write(write_attr_add(my_enums)) 213 | -------------------------------------------------------------------------------- /components/zigbee/partitions_zb.csv: -------------------------------------------------------------------------------- 1 | otadata, data, ota, , 0x2000, 2 | phy_init, data, phy, , 0x1000, 3 | app0, app, ota_0, , 0x1B0000, 4 | app1, app, ota_1, , 0x1B0000, 5 | nvs, data, nvs, , 0x6D000, 6 | zb_storage, data, fat, , 16K, 7 | zb_fct, data, fat, , 1K, 8 | -------------------------------------------------------------------------------- /components/zigbee/time/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | from esphome.components import time as time_ 3 | import esphome.config_validation as cv 4 | from esphome.const import CONF_ID 5 | from esphome.cpp_generator import get_variable 6 | 7 | from .. import CONF_ZIGBEE_ID 8 | 9 | DEPENDENCIES = ["zigbee"] 10 | 11 | zigbee_ns = cg.esphome_ns.namespace("zigbee") 12 | ZigbeeTime = zigbee_ns.class_("ZigbeeTime", time_.RealTimeClock) 13 | ZigBeeComponent = zigbee_ns.class_("ZigBeeComponent", cg.Component) 14 | CONFIG_SCHEMA = time_.TIME_SCHEMA.extend( 15 | { 16 | cv.GenerateID(): cv.declare_id(ZigbeeTime), 17 | cv.GenerateID(CONF_ZIGBEE_ID): cv.use_id(ZigBeeComponent), 18 | } 19 | ).extend(cv.COMPONENT_SCHEMA) 20 | 21 | 22 | async def to_code(config): 23 | cg.add_define("USE_ZIGBEE_TIME") 24 | zb = await get_variable(config[CONF_ZIGBEE_ID]) 25 | var = cg.new_Pvariable(config[CONF_ID], zb) 26 | await cg.register_component(var, config) 27 | await time_.register_time(var, config) 28 | -------------------------------------------------------------------------------- /components/zigbee/time/zigbee_time.cpp: -------------------------------------------------------------------------------- 1 | #include "zigbee_time.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome { 5 | namespace zigbee { 6 | 7 | void ZigbeeTime::send_timesync_request() { 8 | ESP_LOGD(TAG, "Requesting time from coordinator..."); 9 | uint16_t attributes[] = {ESP_ZB_ZCL_ATTR_TIME_TIME_ID, ESP_ZB_ZCL_ATTR_TIME_TIME_STATUS_ID}; 10 | esp_zb_zcl_read_attr_cmd_t read_req; 11 | read_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; 12 | read_req.attr_field = attributes; 13 | read_req.attr_number = sizeof(attributes) / sizeof(uint16_t); 14 | read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TIME; 15 | read_req.zcl_basic_cmd.dst_endpoint = 1; 16 | read_req.zcl_basic_cmd.src_endpoint = 1; 17 | read_req.zcl_basic_cmd.dst_addr_u.addr_short = 0x0000; // coordinator 18 | if (esp_zb_lock_acquire(30 / portTICK_PERIOD_MS)) { 19 | esp_zb_zcl_read_attr_cmd_req(&read_req); 20 | esp_zb_lock_release(); 21 | this->requested_ = true; 22 | } 23 | } 24 | 25 | void ZigbeeTime::recieve_timesync_response(esp_zb_zcl_read_attr_resp_variable_t *variable) { 26 | uint32_t utc = 0; 27 | uint8_t sync_status = 0; 28 | while (variable) { 29 | ESP_LOGD(TAG, "Read attribute response: status(%d), attribute(0x%x), type(0x%x), value(%d)", variable->status, 30 | variable->attribute.id, variable->attribute.data.type, 31 | variable->attribute.data.value ? *(uint32_t *) variable->attribute.data.value : 0); 32 | switch (variable->attribute.id) { 33 | case ESP_ZB_ZCL_ATTR_TIME_TIME_ID: 34 | utc = *(uint32_t *) variable->attribute.data.value; 35 | utc = utc + zigbee_time_offset; 36 | ESP_LOGD(TAG, "Recieved UTC time: %d", utc); 37 | break; 38 | case ESP_ZB_ZCL_ATTR_TIME_TIME_STATUS_ID: 39 | sync_status = *(uint8_t *) variable->attribute.data.value; 40 | ESP_LOGD(TAG, "Recieved sync status time: 0x%x", sync_status); 41 | break; 42 | default: 43 | ESP_LOGD(TAG, "Recieved other time property: not yet handled"); 44 | break; 45 | } 46 | variable = variable->next; 47 | } 48 | if ((utc != 0) && (sync_status & 0x3 != 0)) { /* 0x3 = either Master or Syncronized bits set */ 49 | this->set_utc_time(utc); 50 | } else { 51 | ESP_LOGD(TAG, "Did not recieve both time and status; clock NOT updated"); 52 | } 53 | } 54 | 55 | void ZigbeeTime::setup() { 56 | ESP_LOGD(TAG, "Using Zigbee network as time source"); 57 | this->synced_ = false; 58 | this->requested_ = false; 59 | this->zc_->zt_ = this; 60 | } 61 | 62 | void ZigbeeTime::update() { 63 | ESP_LOGD(TAG, "Updating time sync from Zigbee network..."); 64 | this->synced_ = false; 65 | } 66 | 67 | void ZigbeeTime::loop() { 68 | if (this->synced_) 69 | return; 70 | if (this->requested_) 71 | return; 72 | if (zc_->connected) { 73 | this->send_timesync_request(); 74 | } 75 | } 76 | 77 | void ZigbeeTime::set_utc_time(uint32_t utc) { 78 | this->synchronize_epoch_(utc); 79 | this->synced_ = true; 80 | this->requested_ = false; 81 | } 82 | 83 | } // namespace zigbee 84 | } // namespace esphome -------------------------------------------------------------------------------- /components/zigbee/time/zigbee_time.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/components/time/real_time_clock.h" 5 | #include "../zigbee.h" 6 | 7 | namespace esphome { 8 | namespace zigbee { 9 | 10 | static const uint32_t zigbee_time_offset = 11 | 946684800; /* Zigbee time is based on counting seconds from 1 Jan 2000 (=946684800) */ 12 | 13 | class ZigBeeComponent; 14 | 15 | class ZigbeeTime : public time::RealTimeClock { 16 | public: 17 | ZigbeeTime(ZigBeeComponent *zc) : zc_(zc) {} 18 | void setup() override; 19 | void loop() override; 20 | void update() override; 21 | void set_utc_time(uint32_t utc); 22 | void send_timesync_request(); 23 | void recieve_timesync_response(esp_zb_zcl_read_attr_resp_variable_t *variable); 24 | 25 | protected: 26 | ZigBeeComponent *zc_; 27 | bool synced_; 28 | bool requested_; 29 | }; 30 | 31 | } // namespace zigbee 32 | } // namespace esphome 33 | -------------------------------------------------------------------------------- /components/zigbee/zigbee.cpp: -------------------------------------------------------------------------------- 1 | #include "freertos/FreeRTOS.h" 2 | #include "freertos/task.h" 3 | #include "esp_check.h" 4 | #include "esp_log.h" 5 | #include "nvs_flash.h" 6 | #include "zigbee_attribute.h" 7 | #include "zigbee.h" 8 | #include "esphome/core/log.h" 9 | #include "zigbee_helpers.h" 10 | #ifdef CONFIG_WIFI_COEX 11 | #include "esp_coexist.h" 12 | #endif 13 | 14 | #if !(defined ZB_ED_ROLE || defined ZB_ROUTER_ROLE) 15 | #error Define ZB_ED_ROLE or ZB_ROUTER_ROLE in idf.py menuconfig. 16 | #endif 17 | 18 | namespace esphome { 19 | namespace zigbee { 20 | 21 | ZigBeeComponent *zigbeeC; 22 | 23 | device_params_t coord; 24 | 25 | /********************* Define functions **************************/ 26 | uint8_t *get_character_string(std::string str) { 27 | uint8_t *cstr = new uint8_t[(str.size() + 2)]; 28 | std::snprintf((char *) (cstr + 1), str.size() + 1, "%s", str.c_str()); 29 | cstr[0] = str.size(); 30 | 31 | return cstr; 32 | } 33 | 34 | /** 35 | * Creates a ZCL string from the given input string. 36 | * 37 | * @param str Pointer to the input null-terminated C-style string. 38 | * @param max_size Maximum allowable size for the resulting ZCL string. Maximum value: 254. 39 | * @param use_max_size Optional. If true, the `max_size` is used directly, 40 | * overriding the actual size of the input string. 41 | * @return Pointer to a dynamically allocated ZCL string. 42 | * NOTE: Caller is responsible for freeing the allocated memory with `delete[]`. 43 | * 44 | */ 45 | uint8_t *get_zcl_string(const char *str, uint8_t max_size, bool use_max_size) { 46 | uint8_t str_len = static_cast(strlen(str)); 47 | uint8_t zcl_str_size = use_max_size ? max_size : std::min(max_size, str_len); 48 | 49 | uint8_t *zcl_str = new uint8_t[zcl_str_size + 1]; // string + length octet 50 | zcl_str[0] = zcl_str_size; 51 | memcpy(zcl_str + 1, str, str_len); 52 | 53 | return zcl_str; 54 | } 55 | 56 | static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) { 57 | if (esp_zb_bdb_start_top_level_commissioning(mode_mask) != ESP_OK) { 58 | ESP_LOGE(TAG, "Start network steering failed!"); 59 | } 60 | } 61 | 62 | void ZigBeeComponent::set_report(uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, uint16_t attr_id) { 63 | /* Config the reporting info */ 64 | esp_zb_zcl_reporting_info_t reporting_info = { 65 | .direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_SRV, 66 | .ep = endpoint_id, 67 | .cluster_id = cluster_id, 68 | .cluster_role = role, 69 | .attr_id = attr_id, 70 | .manuf_code = ESP_ZB_ZCL_ATTR_NON_MANUFACTURER_SPECIFIC, 71 | }; 72 | 73 | // reporting_info.dst.short_addr = 0; 74 | // reporting_info.dst.endpoint = 1; 75 | reporting_info.dst.profile_id = ESP_ZB_AF_HA_PROFILE_ID; 76 | reporting_info.u.send_info.min_interval = 10; /*!< Actual minimum reporting interval */ 77 | reporting_info.u.send_info.max_interval = 0; /*!< Actual maximum reporting interval */ 78 | reporting_info.u.send_info.def_min_interval = 10; /*!< Default minimum reporting interval */ 79 | reporting_info.u.send_info.def_max_interval = 0; /*!< Default maximum reporting interval */ 80 | reporting_info.u.send_info.delta.s16 = 0; /*!< Actual reportable change */ 81 | 82 | this->reporting_list.push_back(reporting_info); 83 | } 84 | 85 | void ZigBeeComponent::report() { this->report_ = true; } 86 | 87 | void ZigBeeComponent::send_report_() { 88 | if (esp_zb_lock_acquire(20 / portTICK_PERIOD_MS)) { 89 | esp_zb_zcl_report_attr_cmd_t cmd = { 90 | .address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT, 91 | .direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_CLI, 92 | }; 93 | cmd.zcl_basic_cmd.dst_addr_u.addr_short = 0x0000; 94 | cmd.zcl_basic_cmd.dst_endpoint = 1; 95 | 96 | for (auto reporting_info : zigbeeC->reporting_list) { 97 | cmd.zcl_basic_cmd.src_endpoint = reporting_info.ep; 98 | cmd.clusterID = reporting_info.cluster_id; 99 | cmd.attributeID = reporting_info.attr_id; 100 | // cmd.cluster_role = reporting_info.cluster_role; 101 | esp_zb_zcl_report_attr_cmd_req(&cmd); 102 | } 103 | this->report_ = false; 104 | esp_zb_lock_release(); 105 | } 106 | } 107 | 108 | void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) { 109 | static uint8_t steering_retry_count = 0; 110 | uint32_t *p_sg_p = signal_struct->p_app_signal; 111 | esp_err_t err_status = signal_struct->esp_err_status; 112 | esp_zb_app_signal_type_t sig_type = (esp_zb_app_signal_type_t) *p_sg_p; 113 | esp_zb_zdo_signal_leave_params_t *leave_params = NULL; 114 | switch (sig_type) { 115 | case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP: 116 | // Notifies the application that ZBOSS framework (scheduler, buffer pool, etc.) has started, but no 117 | // join/rejoin/formation/BDB initialization has been done yet. 118 | ESP_LOGD(TAG, "Zigbee stack initialized"); 119 | esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_INITIALIZATION); 120 | break; 121 | case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START: 122 | // Device started for the first time after the NVRAM erase 123 | case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT: 124 | // Device started using the NVRAM contents. 125 | if (err_status == ESP_OK) { 126 | ESP_LOGD(TAG, "Device started up in %sfactory-reset mode", esp_zb_bdb_is_factory_new() ? "" : "non "); 127 | zigbeeC->started = true; 128 | if (esp_zb_bdb_is_factory_new()) { 129 | ESP_LOGD(TAG, "Start network steering"); 130 | esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); 131 | } else { 132 | ESP_LOGD(TAG, "Device rebooted"); 133 | zigbeeC->connected = true; 134 | zigbeeC->searchBindings(); 135 | } 136 | } else { 137 | ESP_LOGE(TAG, "FIRST_START. Device started up in %sfactory-reset mode with an error %d (%s)", 138 | esp_zb_bdb_is_factory_new() ? "" : "non ", err_status, esp_err_to_name(err_status)); 139 | ESP_LOGW(TAG, "Failed to initialize Zigbee stack (status: %s)", esp_err_to_name(err_status)); 140 | esp_zb_scheduler_alarm((esp_zb_callback_t) bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_INITIALIZATION, 141 | 1000); 142 | } 143 | break; 144 | case ESP_ZB_BDB_SIGNAL_STEERING: 145 | // BDB network steering completed (Network steering only) 146 | if (err_status == ESP_OK) { 147 | steering_retry_count = 0; 148 | esp_zb_ieee_addr_t extended_pan_id; 149 | esp_zb_get_extended_pan_id(extended_pan_id); 150 | ESP_LOGI(TAG, 151 | "Joined network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: " 152 | "0x%04hx, Channel:%d)", 153 | extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], extended_pan_id[3], 154 | extended_pan_id[2], extended_pan_id[1], extended_pan_id[0], esp_zb_get_pan_id(), 155 | esp_zb_get_current_channel()); 156 | zigbeeC->on_join_callback_.call(); 157 | zigbeeC->connected = true; 158 | } else { 159 | ESP_LOGI(TAG, "Network steering was not successful (status: %s)", esp_err_to_name(err_status)); 160 | if (steering_retry_count < 10) { 161 | steering_retry_count++; 162 | esp_zb_scheduler_alarm((esp_zb_callback_t) bdb_start_top_level_commissioning_cb, 163 | ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000); 164 | } else { 165 | esp_zb_scheduler_alarm((esp_zb_callback_t) bdb_start_top_level_commissioning_cb, 166 | ESP_ZB_BDB_MODE_NETWORK_STEERING, 600 * 1000); 167 | } 168 | } 169 | break; 170 | case ESP_ZB_ZDO_SIGNAL_LEAVE: 171 | leave_params = (esp_zb_zdo_signal_leave_params_t *) esp_zb_app_signal_get_params(p_sg_p); 172 | if (leave_params->leave_type == ESP_ZB_NWK_LEAVE_TYPE_RESET) { 173 | ESP_LOGD(TAG, "Reset device"); 174 | esp_zb_factory_reset(); 175 | } else { 176 | ESP_LOGD(TAG, "Leave_type: %u", leave_params->leave_type); 177 | } 178 | break; 179 | default: 180 | ESP_LOGD(TAG, "ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type, 181 | esp_err_to_name(err_status)); 182 | break; 183 | } 184 | } 185 | 186 | // Recall bounded devices from the binding table after reboot 187 | void ZigBeeComponent::bindingTableCb(const esp_zb_zdo_binding_table_info_t *table_info, void *user_ctx) { 188 | bool done = true; 189 | esp_zb_zdo_mgmt_bind_param_t *req = (esp_zb_zdo_mgmt_bind_param_t *) user_ctx; 190 | esp_zb_zdp_status_t zdo_status = (esp_zb_zdp_status_t) table_info->status; 191 | ESP_LOGD(TAG, "Binding table callback for address 0x%04x with status %d", req->dst_addr, zdo_status); 192 | if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { 193 | // Print binding table log simple 194 | ESP_LOGD(TAG, "Binding table info: total %d, index %d, count %d", table_info->total, table_info->index, 195 | table_info->count); 196 | 197 | if (table_info->total == 0) { 198 | ESP_LOGD(TAG, "No binding table entries found"); 199 | free(req); 200 | return; 201 | } 202 | 203 | esp_zb_zdo_binding_table_record_t *record = table_info->record; 204 | for (int i = 0; i < table_info->count; i++) { 205 | ESP_LOGD(TAG, "Binding table record: src_endp %d, dst_endp %d, cluster_id 0x%04x, dst_addr_mode %d", 206 | record->src_endp, record->dst_endp, record->cluster_id, record->dst_addr_mode); 207 | 208 | zb_device_params_t *device = (zb_device_params_t *) calloc(1, sizeof(zb_device_params_t)); 209 | device->endpoint = record->dst_endp; 210 | if (record->dst_addr_mode == ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT || 211 | record->dst_addr_mode == ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT) { 212 | device->short_addr = record->dst_address.addr_short; 213 | } else { // ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT 214 | memcpy(device->ieee_addr, record->dst_address.addr_long, sizeof(esp_zb_ieee_addr_t)); 215 | } 216 | ESP_LOGD(TAG, 217 | "Device bound to EP %d -> device endpoint: %d, short addr: 0x%04x, ieee addr: " 218 | "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", 219 | record->src_endp, device->endpoint, device->short_addr, device->ieee_addr[7], device->ieee_addr[6], 220 | device->ieee_addr[5], device->ieee_addr[4], device->ieee_addr[3], device->ieee_addr[2], 221 | device->ieee_addr[1], device->ieee_addr[0]); 222 | record = record->next; 223 | } 224 | 225 | // Continue reading the binding table 226 | if (table_info->index + table_info->count < table_info->total) { 227 | /* There are unreported binding table entries, request for them. */ 228 | req->start_index = table_info->index + table_info->count; 229 | esp_zb_zdo_binding_table_req(req, bindingTableCb, req); 230 | done = false; 231 | } 232 | } 233 | 234 | if (done) { 235 | // Print bound devices 236 | ESP_LOGD(TAG, "Filling bounded devices finished"); 237 | free(req); 238 | } 239 | } 240 | 241 | void ZigBeeComponent::searchBindings() { 242 | 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)); 243 | mb_req->dst_addr = esp_zb_get_short_address(); 244 | mb_req->start_index = 0; 245 | ESP_LOGD(TAG, "Requesting binding table for address 0x%04x", mb_req->dst_addr); 246 | esp_zb_zdo_binding_table_req(mb_req, bindingTableCb, (void *) mb_req); 247 | } 248 | 249 | static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message) { 250 | esp_err_t ret = ESP_OK; 251 | 252 | ESP_RETURN_ON_FALSE(message, ESP_FAIL, TAG, "Empty message"); 253 | ESP_RETURN_ON_FALSE(message->info.status == ESP_ZB_ZCL_STATUS_SUCCESS, ESP_ERR_INVALID_ARG, TAG, 254 | "Received message: error status(%d)", message->info.status); 255 | ESP_LOGD(TAG, "Received message: endpoint(%d), cluster(0x%x), attribute(0x%x), data size(%d)", 256 | message->info.dst_endpoint, message->info.cluster, message->attribute.id, message->attribute.data.size); 257 | zigbeeC->handle_attribute(message->info, message->attribute); 258 | return ret; 259 | } 260 | 261 | static esp_err_t zb_cmd_attribute_handler(const esp_zb_zcl_cmd_read_attr_resp_message_t *message) { 262 | esp_err_t ret = ESP_OK; 263 | 264 | ESP_RETURN_ON_FALSE(message, ESP_FAIL, TAG, "Empty message"); 265 | ESP_RETURN_ON_FALSE(message->info.status == ESP_ZB_ZCL_STATUS_SUCCESS, ESP_ERR_INVALID_ARG, TAG, 266 | "Received message: error status(%d)", message->info.status); 267 | esp_zb_zcl_read_attr_resp_variable_t *variable = message->variables; 268 | switch (message->info.cluster) { 269 | case ESP_ZB_ZCL_CLUSTER_ID_TIME: 270 | ESP_LOGD(TAG, "Recieved time information"); 271 | #ifdef USE_ZIGBEE_TIME 272 | if (zigbeeC->zt_ == nullptr) { 273 | ESP_LOGD(TAG, "No time component linked to update time!"); 274 | } else { 275 | zigbeeC->zt_->recieve_timesync_response(message->variables); 276 | } 277 | #else 278 | ESP_LOGD(TAG, "No zigbee time component included at build time!"); 279 | #endif 280 | break; 281 | default: 282 | ESP_LOGD(TAG, "Attribute data recieved (but not yet handled):"); 283 | while (variable) { 284 | ESP_LOGD(TAG, "Read attribute response: status(%d), cluster(0x%x), attribute(0x%x), type(0x%x), value(%d)", 285 | variable->status, message->info.cluster, variable->attribute.id, variable->attribute.data.type, 286 | variable->attribute.data.value ? *(uint8_t *) variable->attribute.data.value : 0); 287 | variable = variable->next; 288 | } 289 | } 290 | return ret; 291 | } 292 | 293 | static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message) { 294 | esp_err_t ret = ESP_OK; 295 | switch (callback_id) { 296 | case ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID: 297 | ret = zb_attribute_handler((esp_zb_zcl_set_attr_value_message_t *) message); 298 | break; 299 | case ESP_ZB_CORE_CMD_READ_ATTR_RESP_CB_ID: 300 | ret = zb_cmd_attribute_handler((esp_zb_zcl_cmd_read_attr_resp_message_t *) message); 301 | break; 302 | case ESP_ZB_CORE_CMD_DEFAULT_RESP_CB_ID: 303 | ESP_LOGD(TAG, "Receive Zigbee default response callback"); 304 | break; 305 | default: 306 | ESP_LOGW(TAG, "Receive Zigbee action(0x%x) callback", callback_id); 307 | break; 308 | } 309 | return ret; 310 | } 311 | 312 | void ZigBeeComponent::handle_attribute(esp_zb_device_cb_common_info_t info, esp_zb_zcl_attribute_t attribute) { 313 | if (this->attributes_.find({info.dst_endpoint, info.cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, attribute.id}) != 314 | this->attributes_.end()) { 315 | this->attributes_[{info.dst_endpoint, info.cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, attribute.id}]->on_value( 316 | attribute); 317 | } 318 | } 319 | 320 | void ZigBeeComponent::create_default_cluster(uint8_t endpoint_id, esp_zb_ha_standard_devices_t device_id) { 321 | this->cluster_list_[endpoint_id] = esphome_zb_default_clusters_create(device_id); 322 | this->endpoint_list_[endpoint_id] = device_id; 323 | } 324 | 325 | void ZigBeeComponent::add_cluster(uint8_t endpoint_id, uint16_t cluster_id, uint8_t role) { 326 | esp_zb_attribute_list_t *attr_list; 327 | switch (cluster_id) { 328 | case 0: 329 | attr_list = create_basic_cluster_(); 330 | break; 331 | case 3: 332 | attr_list = create_ident_cluster_(); 333 | break; 334 | default: 335 | attr_list = esphome_zb_default_attr_list_create(cluster_id); 336 | } 337 | this->attribute_list_[{endpoint_id, cluster_id, role}] = attr_list; 338 | } 339 | 340 | void ZigBeeComponent::set_basic_cluster(std::string model, std::string manufacturer, std::string date, uint8_t power, 341 | uint8_t app_version, uint8_t stack_version, uint8_t hw_version, 342 | std::string area, uint8_t physical_env) { 343 | this->basic_cluster_data_ = { 344 | .model = model, 345 | .manufacturer = manufacturer, 346 | .date = date, 347 | .power = power, 348 | .app_version = app_version, 349 | .stack_version = stack_version, 350 | .hw_version = hw_version, 351 | .area = area, 352 | .physical_env = physical_env, 353 | }; 354 | } 355 | 356 | esp_zb_attribute_list_t *ZigBeeComponent::create_basic_cluster_() { 357 | // ------------------------------ Cluster BASIC ------------------------------ 358 | esp_zb_basic_cluster_cfg_t basic_cluster_cfg = { 359 | .zcl_version = ESP_ZB_ZCL_BASIC_ZCL_VERSION_DEFAULT_VALUE, 360 | .power_source = this->basic_cluster_data_.power, 361 | }; 362 | ESP_LOGD(TAG, "Model: %s", this->basic_cluster_data_.model.c_str()); 363 | ESP_LOGD(TAG, "Manufacturer: %s", this->basic_cluster_data_.manufacturer.c_str()); 364 | ESP_LOGD(TAG, "Date: %s", this->basic_cluster_data_.date.c_str()); 365 | ESP_LOGD(TAG, "Area: %s", this->basic_cluster_data_.area.c_str()); 366 | uint8_t *ManufacturerName = 367 | get_character_string(this->basic_cluster_data_.manufacturer); // warning: this is in format {length, 'string'} : 368 | uint8_t *ModelIdentifier = get_character_string(this->basic_cluster_data_.model); 369 | uint8_t *DateCode = get_character_string(this->basic_cluster_data_.date); 370 | uint8_t *Location = get_character_string(this->basic_cluster_data_.area); 371 | esp_zb_attribute_list_t *attr_list = esp_zb_basic_cluster_create(&basic_cluster_cfg); 372 | esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_APPLICATION_VERSION_ID, 373 | &(this->basic_cluster_data_.app_version)); 374 | esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_STACK_VERSION_ID, 375 | &(this->basic_cluster_data_.stack_version)); 376 | esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_HW_VERSION_ID, 377 | &(this->basic_cluster_data_.hw_version)); 378 | esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, ManufacturerName); 379 | esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, ModelIdentifier); 380 | esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_DATE_CODE_ID, DateCode); 381 | esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_LOCATION_DESCRIPTION_ID, Location); 382 | esp_zb_basic_cluster_add_attr(attr_list, ESP_ZB_ZCL_ATTR_BASIC_PHYSICAL_ENVIRONMENT_ID, 383 | &(this->basic_cluster_data_.physical_env)); 384 | return attr_list; 385 | } 386 | 387 | void ZigBeeComponent::set_ident_time(uint8_t ident_time) { 388 | // ------------------------------ Cluster IDENTIFY ------------------------------ 389 | this->ident_time_ = ident_time; 390 | } 391 | 392 | esp_zb_attribute_list_t *ZigBeeComponent::create_ident_cluster_() { 393 | // ------------------------------ Cluster IDENTIFY ------------------------------ 394 | esp_zb_identify_cluster_cfg_t identify_cluster_cfg = { 395 | .identify_time = this->ident_time_, 396 | }; 397 | return esp_zb_identify_cluster_create(&identify_cluster_cfg); 398 | } 399 | 400 | esp_err_t ZigBeeComponent::create_endpoint(uint8_t endpoint_id, esp_zb_ha_standard_devices_t device_id) { 401 | esp_zb_cluster_list_t *esp_zb_cluster_list = this->cluster_list_[endpoint_id]; 402 | // ------------------------------ Create endpoint list ------------------------------ 403 | esp_zb_endpoint_config_t endpoint_config = {.endpoint = endpoint_id, 404 | .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, 405 | .app_device_id = device_id, 406 | .app_device_version = 0}; 407 | return esp_zb_ep_list_add_ep(this->esp_zb_ep_list_, esp_zb_cluster_list, endpoint_config); 408 | } 409 | 410 | static void esp_zb_task_(void *pvParameters) { 411 | if (esp_zb_start(false) != ESP_OK) { 412 | ESP_LOGE(TAG, "Could not setup Zigbee"); 413 | // this->mark_failed(); 414 | vTaskDelete(NULL); 415 | } 416 | if (*(bool *) pvParameters) { 417 | zb_set_ed_node_descriptor(0, 1, 1); // workaround, rx_on_when_idle should be 0 for battery powered devices. 418 | } 419 | esp_zb_stack_main_loop(); 420 | } 421 | 422 | void ZigBeeComponent::setup() { 423 | zigbeeC = this; 424 | esp_zb_platform_config_t config = { 425 | .radio_config = ESP_ZB_DEFAULT_RADIO_CONFIG(), 426 | .host_config = ESP_ZB_DEFAULT_HOST_CONFIG(), 427 | }; 428 | #ifdef CONFIG_WIFI_COEX 429 | if (esp_coex_wifi_i154_enable() != ESP_OK) { 430 | this->mark_failed(); 431 | return; 432 | } 433 | #endif 434 | // ESP_ERROR_CHECK(nvs_flash_init()); not needed, called by esp32 component 435 | if (esp_zb_platform_config(&config) != ESP_OK) { 436 | this->mark_failed(); 437 | return; 438 | } 439 | 440 | /* initialize Zigbee stack */ 441 | esp_zb_zed_cfg_t zb_zed_cfg = { 442 | .ed_timeout = ED_AGING_TIMEOUT, 443 | .keep_alive = ED_KEEP_ALIVE, 444 | }; 445 | esp_zb_zczr_cfg_t zb_zczr_cfg = { 446 | .max_children = MAX_CHILDREN, 447 | }; 448 | esp_zb_cfg_t zb_nwk_cfg = { 449 | .esp_zb_role = this->device_role_, 450 | .install_code_policy = INSTALLCODE_POLICY_ENABLE, 451 | }; 452 | #ifdef ZB_ROUTER_ROLE 453 | zb_nwk_cfg.nwk_cfg.zczr_cfg = zb_zczr_cfg; 454 | #else 455 | zb_nwk_cfg.nwk_cfg.zed_cfg = zb_zed_cfg; 456 | #endif 457 | esp_zb_init(&zb_nwk_cfg); 458 | 459 | esp_err_t ret; 460 | 461 | // clusters 462 | for (auto const &[key, val] : this->attribute_list_) { 463 | esp_zb_cluster_list_t *esp_zb_cluster_list = this->cluster_list_[std::get<0>(key)]; 464 | ret = esphome_zb_cluster_list_add_or_update_cluster(std::get<1>(key), esp_zb_cluster_list, val, std::get<2>(key)); 465 | if (ret != ESP_OK) { 466 | ESP_LOGE(TAG, "Could not create cluster 0x%04X with role %u: %s", std::get<1>(key), std::get<2>(key), 467 | esp_err_to_name(ret)); 468 | } 469 | } 470 | 471 | // endpoints 472 | for (auto const &[ep_id, dev_id] : this->endpoint_list_) { 473 | // create_default_cluster(key, val); 474 | if (create_endpoint(ep_id, dev_id) != ESP_OK) { 475 | ESP_LOGE(TAG, "Could not create endpoint %u", ep_id); 476 | } 477 | } 478 | 479 | // ------------------------------ Register Device ------------------------------ 480 | if (esp_zb_device_register(this->esp_zb_ep_list_) != ESP_OK) { 481 | ESP_LOGE(TAG, "Could not register the endpoint list"); 482 | this->mark_failed(); 483 | return; 484 | } 485 | 486 | esp_zb_core_action_handler_register(zb_action_handler); 487 | 488 | if (esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK) != ESP_OK) { 489 | ESP_LOGE(TAG, "Could not setup Zigbee"); 490 | this->mark_failed(); 491 | return; 492 | } 493 | 494 | // reporting 495 | for (auto reporting_info : this->reporting_list) { 496 | ESP_LOGD(TAG, "set reporting for cluster: %u", reporting_info.cluster_id); 497 | esp_zb_zcl_attr_location_info_t attr_info = { 498 | .endpoint_id = reporting_info.ep, 499 | .cluster_id = reporting_info.cluster_id, 500 | .cluster_role = reporting_info.cluster_role, 501 | .manuf_code = reporting_info.manuf_code, 502 | .attr_id = reporting_info.attr_id, 503 | }; 504 | if (esp_zb_zcl_update_reporting_info(&reporting_info) != ESP_OK) { 505 | ESP_LOGE(TAG, "Could not configure reporting for attribute 0x%04X in cluster 0x%04X in endpoint %u", 506 | reporting_info.attr_id, reporting_info.cluster_id, reporting_info.ep); 507 | } 508 | // ESP_ERROR_CHECK(esp_zb_zcl_start_attr_reporting(attr_info)); // is this needed? 509 | } 510 | bool batterypowered = new bool; 511 | if ((this->device_role_ == ESP_ZB_DEVICE_TYPE_ED) && this->basic_cluster_data_.power == 0x03) { 512 | batterypowered = true; 513 | } else { 514 | batterypowered = false; 515 | } 516 | xTaskCreate(esp_zb_task_, "Zigbee_main", 4096, &batterypowered, 24, NULL); 517 | } 518 | 519 | void ZigBeeComponent::loop() { 520 | if (this->report_) { 521 | this->send_report_(); 522 | } 523 | } 524 | 525 | void ZigBeeComponent::dump_config() { 526 | ESP_LOGCONFIG(TAG, "ZigBee:"); 527 | for (auto const &[key, val] : this->endpoint_list_) { 528 | ESP_LOGCONFIG(TAG, "Endpoint: %u, %d", key, val); 529 | } 530 | } 531 | 532 | } // namespace zigbee 533 | } // namespace esphome 534 | -------------------------------------------------------------------------------- /components/zigbee/zigbee.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "esp_zigbee_core.h" 8 | #include "zboss_api.h" 9 | #include "ha/esp_zigbee_ha_standard.h" 10 | #include "esphome/core/component.h" 11 | #include "esphome/core/automation.h" 12 | #include "zigbee_helpers.h" 13 | 14 | #ifdef USE_ZIGBEE_TIME 15 | #include "time/zigbee_time.h" 16 | #endif 17 | 18 | namespace esphome { 19 | namespace zigbee { 20 | 21 | static const char *const TAG = "zigbee"; 22 | 23 | using device_params_t = struct DeviceParamsS { 24 | esp_zb_ieee_addr_t ieee_addr; 25 | uint8_t endpoint; 26 | uint16_t short_addr; 27 | }; 28 | 29 | using zdo_info_user_ctx_t = struct ZdoInfoCtxS { 30 | uint8_t endpoint; 31 | uint16_t short_addr; 32 | }; 33 | 34 | using zb_device_params_t = struct zb_device_params_s { 35 | esp_zb_ieee_addr_t ieee_addr; 36 | uint8_t endpoint; 37 | uint16_t short_addr; 38 | }; 39 | 40 | /* Zigbee configuration */ 41 | #define INSTALLCODE_POLICY_ENABLE false /* enable the install code policy for security */ 42 | #define ED_AGING_TIMEOUT ESP_ZB_ED_AGING_TIMEOUT_64MIN 43 | #define ED_KEEP_ALIVE 3000 /* 3000 millisecond */ 44 | #define MAX_CHILDREN 10 45 | #define ESP_ZB_PRIMARY_CHANNEL_MASK \ 46 | ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK /* Zigbee primary channel mask use in the example */ 47 | 48 | #define ESP_ZB_DEFAULT_RADIO_CONFIG() \ 49 | { .radio_mode = ZB_RADIO_MODE_NATIVE, } 50 | 51 | #define ESP_ZB_DEFAULT_HOST_CONFIG() \ 52 | { .host_connection_mode = ZB_HOST_CONNECTION_MODE_NONE, } 53 | 54 | template T get_value_by_type(uint8_t attr_type, void *data); 55 | uint8_t *get_zcl_string(const char *str, uint8_t max_size, bool use_max_size = false); 56 | 57 | class ZigBeeAttribute; 58 | class ZigbeeTime; 59 | 60 | class ZigBeeComponent : public Component { 61 | public: 62 | void setup() override; 63 | void loop() override; 64 | void dump_config() override; 65 | esp_err_t create_endpoint(uint8_t endpoint_id, esp_zb_ha_standard_devices_t device_id); 66 | void set_ident_time(uint8_t ident_time); 67 | void set_basic_cluster(std::string model, std::string manufacturer, std::string date, uint8_t power, 68 | uint8_t app_version, uint8_t stack_version, uint8_t hw_version, std::string area, 69 | uint8_t physical_env); 70 | void add_cluster(uint8_t endpoint_id, uint16_t cluster_id, uint8_t role); 71 | void create_default_cluster(uint8_t endpoint_id, esp_zb_ha_standard_devices_t device_id); 72 | 73 | template 74 | void add_attr(ZigBeeAttribute *attr, uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, uint16_t attr_id, 75 | uint8_t attr_type, uint8_t attr_access, uint8_t max_size, T value_p); 76 | 77 | void set_report(uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, uint16_t attr_id); 78 | void handle_attribute(esp_zb_device_cb_common_info_t info, esp_zb_zcl_attribute_t attribute); 79 | void searchBindings(); 80 | static void bindingTableCb(const esp_zb_zdo_binding_table_info_t *table_info, void *user_ctx); 81 | 82 | void reset() { 83 | esp_zb_lock_acquire(portMAX_DELAY); 84 | esp_zb_factory_reset(); 85 | esp_zb_lock_release(); 86 | } 87 | void report(); 88 | 89 | #ifdef USE_ZIGBEE_TIME 90 | ZigbeeTime *zt_{nullptr}; 91 | #endif 92 | 93 | void add_on_join_callback(std::function &&callback) { this->on_join_callback_.add(std::move(callback)); } 94 | 95 | bool is_started() { return this->started; } 96 | bool connected = false; 97 | bool started = false; 98 | 99 | CallbackManager on_join_callback_{}; 100 | std::deque reporting_list; 101 | 102 | protected: 103 | void send_report_(); 104 | esp_zb_attribute_list_t *create_ident_cluster_(); 105 | esp_zb_attribute_list_t *create_basic_cluster_(); 106 | template 107 | void add_attr_(ZigBeeAttribute *attr, uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, uint16_t attr_id, 108 | uint8_t attr_type, uint8_t attr_access, T *value_p); 109 | bool report_ = false; 110 | std::map endpoint_list_; 111 | std::map cluster_list_; 112 | std::map, esp_zb_attribute_list_t *> attribute_list_; 113 | std::map, ZigBeeAttribute *> attributes_; 114 | #ifdef ZB_ED_ROLE 115 | esp_zb_nwk_device_type_t device_role_ = ESP_ZB_DEVICE_TYPE_ED; 116 | #else 117 | esp_zb_nwk_device_type_t device_role_ = ESP_ZB_DEVICE_TYPE_ROUTER; 118 | #endif 119 | esp_zb_ep_list_t *esp_zb_ep_list_ = esp_zb_ep_list_create(); 120 | struct { 121 | std::string model; 122 | std::string manufacturer; 123 | std::string date; 124 | uint8_t power; 125 | uint8_t app_version; 126 | uint8_t stack_version; 127 | uint8_t hw_version; 128 | std::string area; 129 | uint8_t physical_env; 130 | } basic_cluster_data_; 131 | uint8_t ident_time_; 132 | }; 133 | 134 | extern "C" void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct); 135 | extern "C" void zb_set_ed_node_descriptor(bool power_src, bool rx_on_when_idle, bool alloc_addr); 136 | 137 | template 138 | void ZigBeeComponent::add_attr(ZigBeeAttribute *attr, uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, 139 | uint16_t attr_id, uint8_t attr_type, uint8_t attr_access, uint8_t max_size, T value_p) { 140 | // The size byte of the zcl_str must be set to the maximum value, 141 | // even though the initial string may be shorter. 142 | if constexpr (std::is_same::value) { 143 | auto zcl_str = get_zcl_string(value_p.c_str(), max_size, true); 144 | add_attr_(attr, endpoint_id, cluster_id, role, attr_id, attr_type, attr_access, zcl_str); 145 | delete[] zcl_str; 146 | } else if constexpr (std::is_convertible::value) { 147 | auto zcl_str = get_zcl_string(value_p, max_size, true); 148 | add_attr_(attr, endpoint_id, cluster_id, role, attr_id, attr_type, attr_access, zcl_str); 149 | delete[] zcl_str; 150 | } else { 151 | add_attr_(attr, endpoint_id, cluster_id, role, attr_id, attr_type, attr_access, &value_p); 152 | } 153 | } 154 | 155 | template 156 | void ZigBeeComponent::add_attr_(ZigBeeAttribute *attr, uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, 157 | uint16_t attr_id, uint8_t attr_type, uint8_t attr_access, T *value_p) { 158 | esp_zb_attribute_list_t *attr_list = this->attribute_list_[{endpoint_id, cluster_id, role}]; 159 | esp_err_t ret = 160 | esphome_zb_cluster_add_or_update_attr(cluster_id, attr_list, attr_id, attr_type, attr_access, value_p); 161 | if (ret != ESP_OK) { 162 | ESP_LOGE(TAG, "Could not add attribute 0x%04X to cluster 0x%04X in endpoint %u: %s", attr_id, cluster_id, 163 | endpoint_id, esp_err_to_name(ret)); 164 | } 165 | this->attributes_[{endpoint_id, cluster_id, role, attr_id}] = attr; 166 | } 167 | 168 | } // namespace zigbee 169 | } // namespace esphome 170 | -------------------------------------------------------------------------------- /components/zigbee/zigbee_attribute.cpp: -------------------------------------------------------------------------------- 1 | #include "zigbee_attribute.h" 2 | 3 | namespace esphome { 4 | namespace zigbee { 5 | 6 | void ZigBeeAttribute::set_attr_() { 7 | if (!this->zb_->is_started()) { 8 | return; 9 | } 10 | if (esp_zb_lock_acquire(20 / portTICK_PERIOD_MS)) { 11 | esp_zb_zcl_status_t state = esp_zb_zcl_set_attribute_val(this->endpoint_id_, this->cluster_id_, this->role_, 12 | this->attr_id_, this->value_p, false); 13 | this->set_attr_requested_ = false; 14 | // Check for error 15 | if (state != ESP_ZB_ZCL_STATUS_SUCCESS) { 16 | ESP_LOGE(TAG, "Setting attribute failed: %s", esp_err_to_name(state)); 17 | } 18 | ESP_LOGD(TAG, "Attribute set!"); 19 | esp_zb_lock_release(); 20 | } 21 | } 22 | 23 | void ZigBeeAttribute::set_attr(const std::string &str) { 24 | if (this->value_p != nullptr) { 25 | delete[](char *) this->value_p; 26 | } 27 | 28 | auto zcl_str = get_zcl_string(str.c_str(), str.size()); 29 | this->value_p = (void *) zcl_str; 30 | this->set_attr_requested_ = true; 31 | } 32 | 33 | void ZigBeeAttribute::set_report() { 34 | this->zb_->set_report(this->endpoint_id_, this->cluster_id_, this->role_, this->attr_id_); 35 | } 36 | 37 | void ZigBeeAttribute::loop() { 38 | if (this->set_attr_requested_) { 39 | this->set_attr_(); 40 | } 41 | } 42 | 43 | } // namespace zigbee 44 | } // namespace esphome 45 | -------------------------------------------------------------------------------- /components/zigbee/zigbee_attribute.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "esp_zigbee_core.h" 6 | #include "esphome/core/component.h" 7 | #include "esphome/core/automation.h" 8 | #include "zigbee.h" 9 | #ifdef USE_SENSOR 10 | #include "esphome/components/sensor/sensor.h" 11 | #endif 12 | #ifdef USE_BINARY_SENSOR 13 | #include "esphome/components/binary_sensor/binary_sensor.h" 14 | #endif 15 | 16 | namespace esphome { 17 | namespace zigbee { 18 | 19 | class ZigBeeAttribute : public Component { 20 | public: 21 | ZigBeeAttribute(ZigBeeComponent *parent, uint8_t endpoint_id, uint16_t cluster_id, uint8_t role, uint16_t attr_id, 22 | uint8_t attr_type, float scale) 23 | : zb_(parent), 24 | endpoint_id_(endpoint_id), 25 | cluster_id_(cluster_id), 26 | role_(role), 27 | attr_id_(attr_id), 28 | attr_type_(attr_type), 29 | scale_(scale) {} 30 | // void dump_config() override; 31 | void loop() override; 32 | 33 | template void add_attr(uint8_t attr_access, uint8_t max_size, T value_p); 34 | void set_report(); 35 | template void set_attr(T *value_p); 36 | void set_attr(const std::string &str); 37 | 38 | uint8_t attr_type() { return attr_type_; } 39 | 40 | void add_on_value_callback(std::function callback) { 41 | on_value_callback_.add(std::move(callback)); 42 | } 43 | void on_value(esp_zb_zcl_attribute_t attribute) { this->on_value_callback_.call(attribute); } 44 | 45 | #ifdef USE_SENSOR 46 | template void connect(sensor::Sensor *sensor); 47 | template void connect(sensor::Sensor *sensor, std::function &&f); 48 | #endif 49 | #ifdef USE_BINARY_SENSOR 50 | template void connect(binary_sensor::BinarySensor *sensor); 51 | template void connect(binary_sensor::BinarySensor *sensor, std::function &&f); 52 | #endif 53 | 54 | protected: 55 | void set_attr_(); 56 | ZigBeeComponent *zb_; 57 | uint8_t endpoint_id_; 58 | uint16_t cluster_id_; 59 | uint8_t role_; 60 | uint16_t attr_id_; 61 | uint8_t attr_type_; 62 | uint8_t max_size_; 63 | float scale_; 64 | CallbackManager on_value_callback_{}; 65 | void *value_p{nullptr}; 66 | bool set_attr_requested_{false}; 67 | }; 68 | 69 | template void ZigBeeAttribute::add_attr(uint8_t attr_access, uint8_t max_size, T value_p) { 70 | this->max_size_ = max_size; 71 | this->zb_->add_attr(this, this->endpoint_id_, this->cluster_id_, this->role_, this->attr_id_, this->attr_type_, 72 | attr_access, max_size, value_p); 73 | } 74 | 75 | template void ZigBeeAttribute::set_attr(T *value_p) { 76 | if constexpr (std::is_same::value || std::is_same::value) { 77 | auto zcl_str = get_zcl_string(value_p, this->max_size_); 78 | 79 | if (this->value_p != nullptr) { 80 | delete[](char *) this->value_p; 81 | } 82 | this->value_p = (void *) zcl_str; 83 | } else { 84 | if (this->value_p != nullptr) { 85 | delete (T *) this->value_p; 86 | } 87 | this->value_p = (void *) value_p; 88 | } 89 | this->set_attr_requested_ = true; 90 | } 91 | 92 | #ifdef USE_SENSOR 93 | template void ZigBeeAttribute::connect(sensor::Sensor *sensor) { 94 | sensor->add_on_state_callback([=, this](float value) { 95 | T *value_p = new T; 96 | *value_p = (T) (this->scale_ * value); 97 | this->set_attr(value_p); 98 | }); 99 | } 100 | 101 | template void ZigBeeAttribute::connect(sensor::Sensor *sensor, std::function &&f) { 102 | sensor->add_on_state_callback([=, this](float value) { 103 | T *value_p = new T; 104 | *value_p = f(value); 105 | this->set_attr(value_p); 106 | }); 107 | } 108 | #endif 109 | 110 | #ifdef USE_BINARY_SENSOR 111 | template void ZigBeeAttribute::connect(binary_sensor::BinarySensor *sensor) { 112 | sensor->add_on_state_callback([=, this](bool value) { 113 | T *value_p = new T; 114 | *value_p = (T) (this->scale_ * value); 115 | this->set_attr(value_p); 116 | }); 117 | } 118 | 119 | template void ZigBeeAttribute::connect(binary_sensor::BinarySensor *sensor, std::function &&f) { 120 | sensor->add_on_state_callback([=, this](bool value) { 121 | T *value_p = new T; 122 | *value_p = f(value); 123 | this->set_attr(value_p); 124 | }); 125 | } 126 | #endif 127 | 128 | } // namespace zigbee 129 | } // namespace esphome 130 | -------------------------------------------------------------------------------- /components/zigbee/zigbee_const.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | 3 | ha_standard_devices = cg.esphome_ns.enum("esp_zb_ha_standard_devices_t") 4 | DEVICE_ID = { 5 | "ON_OFF_SWITCH": ha_standard_devices.ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID, 6 | 0x0000: ha_standard_devices.ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID, 7 | "LEVEL_CONTROL_SWITCH": ha_standard_devices.ESP_ZB_HA_LEVEL_CONTROL_SWITCH_DEVICE_ID, 8 | 0x0001: ha_standard_devices.ESP_ZB_HA_LEVEL_CONTROL_SWITCH_DEVICE_ID, 9 | "ON_OFF_OUTPUT": ha_standard_devices.ESP_ZB_HA_ON_OFF_OUTPUT_DEVICE_ID, 10 | 0x0002: ha_standard_devices.ESP_ZB_HA_ON_OFF_OUTPUT_DEVICE_ID, 11 | "LEVEL_CONTROLLABLE_OUTPUT": ha_standard_devices.ESP_ZB_HA_LEVEL_CONTROLLABLE_OUTPUT_DEVICE_ID, 12 | 0x0003: ha_standard_devices.ESP_ZB_HA_LEVEL_CONTROLLABLE_OUTPUT_DEVICE_ID, 13 | "SCENE_SELECTOR": ha_standard_devices.ESP_ZB_HA_SCENE_SELECTOR_DEVICE_ID, 14 | 0x0004: ha_standard_devices.ESP_ZB_HA_SCENE_SELECTOR_DEVICE_ID, 15 | "CONFIGURATION_TOOL": ha_standard_devices.ESP_ZB_HA_CONFIGURATION_TOOL_DEVICE_ID, 16 | 0x0005: ha_standard_devices.ESP_ZB_HA_CONFIGURATION_TOOL_DEVICE_ID, 17 | "REMOTE_CONTROL": ha_standard_devices.ESP_ZB_HA_REMOTE_CONTROL_DEVICE_ID, 18 | 0x0006: ha_standard_devices.ESP_ZB_HA_REMOTE_CONTROL_DEVICE_ID, 19 | "COMBINED_INTERFACE": ha_standard_devices.ESP_ZB_HA_COMBINED_INTERFACE_DEVICE_ID, 20 | 0x0007: ha_standard_devices.ESP_ZB_HA_COMBINED_INTERFACE_DEVICE_ID, 21 | "RANGE_EXTENDER": ha_standard_devices.ESP_ZB_HA_RANGE_EXTENDER_DEVICE_ID, 22 | 0x0008: ha_standard_devices.ESP_ZB_HA_RANGE_EXTENDER_DEVICE_ID, 23 | "MAINS_POWER_OUTLET": ha_standard_devices.ESP_ZB_HA_MAINS_POWER_OUTLET_DEVICE_ID, 24 | 0x0009: ha_standard_devices.ESP_ZB_HA_MAINS_POWER_OUTLET_DEVICE_ID, 25 | "DOOR_LOCK": ha_standard_devices.ESP_ZB_HA_DOOR_LOCK_DEVICE_ID, 26 | 0x000A: ha_standard_devices.ESP_ZB_HA_DOOR_LOCK_DEVICE_ID, 27 | "DOOR_LOCK_CONTROLLER": ha_standard_devices.ESP_ZB_HA_DOOR_LOCK_CONTROLLER_DEVICE_ID, 28 | 0x000B: ha_standard_devices.ESP_ZB_HA_DOOR_LOCK_CONTROLLER_DEVICE_ID, 29 | "SIMPLE_SENSOR": ha_standard_devices.ESP_ZB_HA_SIMPLE_SENSOR_DEVICE_ID, 30 | 0x000C: ha_standard_devices.ESP_ZB_HA_SIMPLE_SENSOR_DEVICE_ID, 31 | "CONSUMPTION_AWARENESS": ha_standard_devices.ESP_ZB_HA_CONSUMPTION_AWARENESS_DEVICE_ID, 32 | 0x000D: ha_standard_devices.ESP_ZB_HA_CONSUMPTION_AWARENESS_DEVICE_ID, 33 | "HOME_GATEWAY": ha_standard_devices.ESP_ZB_HA_HOME_GATEWAY_DEVICE_ID, 34 | 0x0050: ha_standard_devices.ESP_ZB_HA_HOME_GATEWAY_DEVICE_ID, 35 | "SMART_PLUG": ha_standard_devices.ESP_ZB_HA_SMART_PLUG_DEVICE_ID, 36 | 0x0051: ha_standard_devices.ESP_ZB_HA_SMART_PLUG_DEVICE_ID, 37 | "WHITE_GOODS": ha_standard_devices.ESP_ZB_HA_WHITE_GOODS_DEVICE_ID, 38 | 0x0052: ha_standard_devices.ESP_ZB_HA_WHITE_GOODS_DEVICE_ID, 39 | "METER_INTERFACE": ha_standard_devices.ESP_ZB_HA_METER_INTERFACE_DEVICE_ID, 40 | 0x0053: ha_standard_devices.ESP_ZB_HA_METER_INTERFACE_DEVICE_ID, 41 | "ON_OFF_LIGHT": ha_standard_devices.ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID, 42 | 0x0100: ha_standard_devices.ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID, 43 | "DIMMABLE_LIGHT": ha_standard_devices.ESP_ZB_HA_DIMMABLE_LIGHT_DEVICE_ID, 44 | 0x0101: ha_standard_devices.ESP_ZB_HA_DIMMABLE_LIGHT_DEVICE_ID, 45 | "COLOR_DIMMABLE_LIGHT": ha_standard_devices.ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID, 46 | 0x0102: ha_standard_devices.ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID, 47 | "DIMMER_SWITCH": ha_standard_devices.ESP_ZB_HA_DIMMER_SWITCH_DEVICE_ID, 48 | 0x0104: ha_standard_devices.ESP_ZB_HA_DIMMER_SWITCH_DEVICE_ID, 49 | "COLOR_DIMMER_SWITCH": ha_standard_devices.ESP_ZB_HA_COLOR_DIMMER_SWITCH_DEVICE_ID, 50 | 0x0105: ha_standard_devices.ESP_ZB_HA_COLOR_DIMMER_SWITCH_DEVICE_ID, 51 | "LIGHT_SENSOR": ha_standard_devices.ESP_ZB_HA_LIGHT_SENSOR_DEVICE_ID, 52 | 0x0106: ha_standard_devices.ESP_ZB_HA_LIGHT_SENSOR_DEVICE_ID, 53 | "SHADE": ha_standard_devices.ESP_ZB_HA_SHADE_DEVICE_ID, 54 | 0x0200: ha_standard_devices.ESP_ZB_HA_SHADE_DEVICE_ID, 55 | "SHADE_CONTROLLER": ha_standard_devices.ESP_ZB_HA_SHADE_CONTROLLER_DEVICE_ID, 56 | 0x0201: ha_standard_devices.ESP_ZB_HA_SHADE_CONTROLLER_DEVICE_ID, 57 | "WINDOW_COVERING": ha_standard_devices.ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID, 58 | 0x0202: ha_standard_devices.ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID, 59 | "WINDOW_COVERING_CONTROLLER": ha_standard_devices.ESP_ZB_HA_WINDOW_COVERING_CONTROLLER_DEVICE_ID, 60 | 0x0203: ha_standard_devices.ESP_ZB_HA_WINDOW_COVERING_CONTROLLER_DEVICE_ID, 61 | "HEATING_COOLING_UNIT": ha_standard_devices.ESP_ZB_HA_HEATING_COOLING_UNIT_DEVICE_ID, 62 | 0x0300: ha_standard_devices.ESP_ZB_HA_HEATING_COOLING_UNIT_DEVICE_ID, 63 | "THERMOSTAT": ha_standard_devices.ESP_ZB_HA_THERMOSTAT_DEVICE_ID, 64 | 0x0301: ha_standard_devices.ESP_ZB_HA_THERMOSTAT_DEVICE_ID, 65 | "TEMPERATURE_SENSOR": ha_standard_devices.ESP_ZB_HA_TEMPERATURE_SENSOR_DEVICE_ID, 66 | 0x0302: ha_standard_devices.ESP_ZB_HA_TEMPERATURE_SENSOR_DEVICE_ID, 67 | "IAS_CONTROL_INDICATING_EQUIPMENT_ID": ha_standard_devices.ESP_ZB_HA_IAS_CONTROL_INDICATING_EQUIPMENT_ID, 68 | 0x0400: ha_standard_devices.ESP_ZB_HA_IAS_CONTROL_INDICATING_EQUIPMENT_ID, 69 | "IAS_ANCILLARY_CONTROL_EQUIPMENT_ID": ha_standard_devices.ESP_ZB_HA_IAS_ANCILLARY_CONTROL_EQUIPMENT_ID, 70 | 0x0401: ha_standard_devices.ESP_ZB_HA_IAS_ANCILLARY_CONTROL_EQUIPMENT_ID, 71 | "IAS_ZONE_ID": ha_standard_devices.ESP_ZB_HA_IAS_ZONE_ID, 72 | 0x0402: ha_standard_devices.ESP_ZB_HA_IAS_ZONE_ID, 73 | "IAS_WARNING": ha_standard_devices.ESP_ZB_HA_IAS_WARNING_DEVICE_ID, 74 | 0x0403: ha_standard_devices.ESP_ZB_HA_IAS_WARNING_DEVICE_ID, 75 | "TEST": ha_standard_devices.ESP_ZB_HA_TEST_DEVICE_ID, 76 | 0xFFF0: ha_standard_devices.ESP_ZB_HA_TEST_DEVICE_ID, 77 | "CUSTOM_TUNNEL": ha_standard_devices.ESP_ZB_HA_CUSTOM_TUNNEL_DEVICE_ID, 78 | 0xFFF1: ha_standard_devices.ESP_ZB_HA_CUSTOM_TUNNEL_DEVICE_ID, 79 | "CUSTOM_ATTR": ha_standard_devices.ESP_ZB_HA_CUSTOM_ATTR_DEVICE_ID, 80 | 0xFFF2: ha_standard_devices.ESP_ZB_HA_CUSTOM_ATTR_DEVICE_ID, 81 | } 82 | cluster_id = cg.esphome_ns.enum("esp_zb_zcl_cluster_id_t") 83 | CLUSTER_ID = { 84 | "BASIC": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BASIC, 85 | 0x0000: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BASIC, 86 | "POWER_CONFIG": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_POWER_CONFIG, 87 | 0x0001: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_POWER_CONFIG, 88 | "DEVICE_TEMP_CONFIG": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DEVICE_TEMP_CONFIG, 89 | 0x0002: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DEVICE_TEMP_CONFIG, 90 | "IDENTIFY": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY, 91 | 0x0003: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY, 92 | "GROUPS": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_GROUPS, 93 | 0x0004: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_GROUPS, 94 | "SCENES": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_SCENES, 95 | 0x0005: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_SCENES, 96 | "ON_OFF": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ON_OFF, 97 | 0x0006: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ON_OFF, 98 | "ON_OFF_SWITCH_CONFIG": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ON_OFF_SWITCH_CONFIG, 99 | 0x0007: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ON_OFF_SWITCH_CONFIG, 100 | "LEVEL_CONTROL": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, 101 | 0x0008: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, 102 | "ALARMS": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ALARMS, 103 | 0x0009: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ALARMS, 104 | "TIME": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_TIME, 105 | 0x000A: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_TIME, 106 | "RSSI_LOCATION": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_RSSI_LOCATION, 107 | 0x000B: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_RSSI_LOCATION, 108 | "ANALOG_INPUT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, 109 | 0x000C: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, 110 | "ANALOG_OUTPUT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT, 111 | 0x000D: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT, 112 | "ANALOG_VALUE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ANALOG_VALUE, 113 | 0x000E: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ANALOG_VALUE, 114 | "BINARY_INPUT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BINARY_INPUT, 115 | 0x000F: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BINARY_INPUT, 116 | "BINARY_OUTPUT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, 117 | 0x0010: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, 118 | "BINARY_VALUE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BINARY_VALUE, 119 | 0x0011: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BINARY_VALUE, 120 | "MULTI_INPUT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_MULTI_INPUT, 121 | 0x0012: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_MULTI_INPUT, 122 | "MULTI_OUTPUT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_MULTI_OUTPUT, 123 | 0x0013: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_MULTI_OUTPUT, 124 | "MULTI_VALUE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_MULTI_VALUE, 125 | 0x0014: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_MULTI_VALUE, 126 | "COMMISSIONING": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_COMMISSIONING, 127 | 0x0015: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_COMMISSIONING, 128 | "OTA_UPGRADE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_OTA_UPGRADE, 129 | 0x0019: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_OTA_UPGRADE, 130 | "POLL_CONTROL": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_POLL_CONTROL, 131 | 0x0020: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_POLL_CONTROL, 132 | "GREEN_POWER": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_GREEN_POWER, 133 | 0x0021: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_GREEN_POWER, 134 | "KEEP_ALIVE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_KEEP_ALIVE, 135 | 0x0025: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_KEEP_ALIVE, 136 | "SHADE_CONFIG": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_SHADE_CONFIG, 137 | 0x0100: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_SHADE_CONFIG, 138 | "DOOR_LOCK": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DOOR_LOCK, 139 | 0x0101: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DOOR_LOCK, 140 | "WINDOW_COVERING": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, 141 | 0x0102: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING, 142 | "PUMP_CONFIG_CONTROL": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PUMP_CONFIG_CONTROL, 143 | 0x0200: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PUMP_CONFIG_CONTROL, 144 | "THERMOSTAT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT, 145 | 0x0201: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT, 146 | "FAN_CONTROL": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_FAN_CONTROL, 147 | 0x0202: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_FAN_CONTROL, 148 | "DEHUMIDIFICATION_CONTROL": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DEHUMIDIFICATION_CONTROL, 149 | 0x0203: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DEHUMIDIFICATION_CONTROL, 150 | "THERMOSTAT_UI_CONFIG": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT_UI_CONFIG, 151 | 0x0204: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT_UI_CONFIG, 152 | "COLOR_CONTROL": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, 153 | 0x0300: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, 154 | "BALLAST_CONFIG": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BALLAST_CONFIG, 155 | 0x0301: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_BALLAST_CONFIG, 156 | "ILLUMINANCE_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT, 157 | 0x0400: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT, 158 | "TEMP_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, 159 | 0x0402: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, 160 | "PRESSURE_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT, 161 | 0x0403: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT, 162 | "FLOW_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_FLOW_MEASUREMENT, 163 | 0x0404: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_FLOW_MEASUREMENT, 164 | "REL_HUMIDITY_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT, 165 | 0x0405: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT, 166 | "OCCUPANCY_SENSING": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING, 167 | 0x0406: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING, 168 | "PH_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PH_MEASUREMENT, 169 | 0x0409: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PH_MEASUREMENT, 170 | "EC_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_EC_MEASUREMENT, 171 | 0x040A: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_EC_MEASUREMENT, 172 | "WIND_SPEED_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_WIND_SPEED_MEASUREMENT, 173 | 0x040B: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_WIND_SPEED_MEASUREMENT, 174 | "CARBON_DIOXIDE_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_CARBON_DIOXIDE_MEASUREMENT, 175 | 0x040D: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_CARBON_DIOXIDE_MEASUREMENT, 176 | "PM2_5_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT, 177 | 0x042A: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT, 178 | "IAS_ZONE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE, 179 | 0x0500: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE, 180 | "IAS_ACE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IAS_ACE, 181 | 0x0501: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IAS_ACE, 182 | "IAS_WD": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IAS_WD, 183 | 0x0502: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_IAS_WD, 184 | "PRICE": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PRICE, 185 | 0x0700: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_PRICE, 186 | "DRLC": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DRLC, 187 | 0x0701: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DRLC, 188 | "METERING": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_METERING, 189 | 0x0702: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_METERING, 190 | "METER_IDENTIFICATION": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_METER_IDENTIFICATION, 191 | 0x0B01: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_METER_IDENTIFICATION, 192 | "ELECTRICAL_MEASUREMENT": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT, 193 | 0x0B04: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT, 194 | "DIAGNOSTICS": cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DIAGNOSTICS, 195 | 0x0B05: cluster_id.ESP_ZB_ZCL_CLUSTER_ID_DIAGNOSTICS, 196 | } 197 | cluster_role = cg.esphome_ns.enum("esp_zb_zcl_cluster_role_t") 198 | CLUSTER_ROLE = { 199 | "SERVER": cluster_role.ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, 200 | "CLIENT": cluster_role.ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE, 201 | } 202 | attr_type = cg.esphome_ns.enum("esp_zb_zcl_attr_type_t") 203 | ATTR_TYPE = { 204 | "NULL": attr_type.ESP_ZB_ZCL_ATTR_TYPE_NULL, 205 | "8BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_8BIT, 206 | "16BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_16BIT, 207 | "24BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_24BIT, 208 | "32BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_32BIT, 209 | "40BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_40BIT, 210 | "48BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_48BIT, 211 | "56BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_56BIT, 212 | "64BIT": attr_type.ESP_ZB_ZCL_ATTR_TYPE_64BIT, 213 | "BOOL": attr_type.ESP_ZB_ZCL_ATTR_TYPE_BOOL, 214 | "8BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_8BITMAP, 215 | "16BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_16BITMAP, 216 | "24BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_24BITMAP, 217 | "32BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_32BITMAP, 218 | "40BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_40BITMAP, 219 | "48BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_48BITMAP, 220 | "56BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_56BITMAP, 221 | "64BITMAP": attr_type.ESP_ZB_ZCL_ATTR_TYPE_64BITMAP, 222 | "U8": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U8, 223 | "U16": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U16, 224 | "U24": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U24, 225 | "U32": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U32, 226 | "U40": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U40, 227 | "U48": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U48, 228 | "U56": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U56, 229 | "U64": attr_type.ESP_ZB_ZCL_ATTR_TYPE_U64, 230 | "S8": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S8, 231 | "S16": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S16, 232 | "S24": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S24, 233 | "S32": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S32, 234 | "S40": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S40, 235 | "S48": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S48, 236 | "S56": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S56, 237 | "S64": attr_type.ESP_ZB_ZCL_ATTR_TYPE_S64, 238 | "8BIT_ENUM": attr_type.ESP_ZB_ZCL_ATTR_TYPE_8BIT_ENUM, 239 | "16BIT_ENUM": attr_type.ESP_ZB_ZCL_ATTR_TYPE_16BIT_ENUM, 240 | "SEMI": attr_type.ESP_ZB_ZCL_ATTR_TYPE_SEMI, 241 | "SINGLE": attr_type.ESP_ZB_ZCL_ATTR_TYPE_SINGLE, 242 | "DOUBLE": attr_type.ESP_ZB_ZCL_ATTR_TYPE_DOUBLE, 243 | "OCTET_STRING": attr_type.ESP_ZB_ZCL_ATTR_TYPE_OCTET_STRING, 244 | "CHAR_STRING": attr_type.ESP_ZB_ZCL_ATTR_TYPE_CHAR_STRING, 245 | "LONG_OCTET_STRING": attr_type.ESP_ZB_ZCL_ATTR_TYPE_LONG_OCTET_STRING, 246 | "LONG_CHAR_STRING": attr_type.ESP_ZB_ZCL_ATTR_TYPE_LONG_CHAR_STRING, 247 | "ARRAY": attr_type.ESP_ZB_ZCL_ATTR_TYPE_ARRAY, 248 | "16BIT_ARRAY": attr_type.ESP_ZB_ZCL_ATTR_TYPE_16BIT_ARRAY, 249 | "32BIT_ARRAY": attr_type.ESP_ZB_ZCL_ATTR_TYPE_32BIT_ARRAY, 250 | "STRUCTURE": attr_type.ESP_ZB_ZCL_ATTR_TYPE_STRUCTURE, 251 | "SET": attr_type.ESP_ZB_ZCL_ATTR_TYPE_SET, 252 | "BAG": attr_type.ESP_ZB_ZCL_ATTR_TYPE_BAG, 253 | "TIME_OF_DAY": attr_type.ESP_ZB_ZCL_ATTR_TYPE_TIME_OF_DAY, 254 | "DATE": attr_type.ESP_ZB_ZCL_ATTR_TYPE_DATE, 255 | "UTC_TIME": attr_type.ESP_ZB_ZCL_ATTR_TYPE_UTC_TIME, 256 | "CLUSTER_ID": attr_type.ESP_ZB_ZCL_ATTR_TYPE_CLUSTER_ID, 257 | "ATTRIBUTE_ID": attr_type.ESP_ZB_ZCL_ATTR_TYPE_ATTRIBUTE_ID, 258 | "BACNET_OID": attr_type.ESP_ZB_ZCL_ATTR_TYPE_BACNET_OID, 259 | "IEEE_ADDR": attr_type.ESP_ZB_ZCL_ATTR_TYPE_IEEE_ADDR, 260 | "128_BIT_KEY": attr_type.ESP_ZB_ZCL_ATTR_TYPE_128_BIT_KEY, 261 | "INVALID": attr_type.ESP_ZB_ZCL_ATTR_TYPE_INVALID, 262 | } 263 | ATTR_ACCESS = { 264 | "READ_ONLY": 1, 265 | "WRITE_ONLY": 2, 266 | "READ_WRITE": 3, 267 | } 268 | -------------------------------------------------------------------------------- /components/zigbee/zigbee_helpers.c: -------------------------------------------------------------------------------- 1 | #include "ha/esp_zigbee_ha_standard.h" 2 | #include "zigbee_helpers.h" 3 | 4 | esp_zb_cluster_list_t *esphome_zb_default_clusters_create(esp_zb_ha_standard_devices_t device_type) { 5 | esp_zb_cluster_list_t *cluster_list; 6 | switch (device_type) { 7 | case ESP_ZB_HA_ON_OFF_SWITCH_DEVICE_ID: { 8 | esp_zb_on_off_switch_cfg_t config = ESP_ZB_DEFAULT_ON_OFF_SWITCH_CONFIG(); 9 | cluster_list = esp_zb_on_off_switch_clusters_create(&config); 10 | break; 11 | } 12 | case ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID: { 13 | esp_zb_on_off_light_cfg_t config = ESP_ZB_DEFAULT_ON_OFF_LIGHT_CONFIG(); 14 | cluster_list = esp_zb_on_off_light_clusters_create(&config); 15 | break; 16 | } 17 | case ESP_ZB_HA_COLOR_DIMMER_SWITCH_DEVICE_ID: { 18 | esp_zb_color_dimmable_switch_cfg_t config = ESP_ZB_DEFAULT_COLOR_DIMMABLE_SWITCH_CONFIG(); 19 | cluster_list = esp_zb_color_dimmable_switch_clusters_create(&config); 20 | break; 21 | } 22 | case ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID: { 23 | esp_zb_color_dimmable_light_cfg_t config = ESP_ZB_DEFAULT_COLOR_DIMMABLE_LIGHT_CONFIG(); 24 | cluster_list = esp_zb_color_dimmable_light_clusters_create(&config); 25 | break; 26 | } 27 | case ESP_ZB_HA_MAINS_POWER_OUTLET_DEVICE_ID: { 28 | esp_zb_mains_power_outlet_cfg_t config = ESP_ZB_DEFAULT_MAINS_POWER_OUTLET_CONFIG(); 29 | cluster_list = esp_zb_mains_power_outlet_clusters_create(&config); 30 | break; 31 | } 32 | case ESP_ZB_HA_SHADE_DEVICE_ID: { 33 | esp_zb_shade_cfg_t config = ESP_ZB_DEFAULT_SHADE_CONFIG(); 34 | cluster_list = esp_zb_shade_clusters_create(&config); 35 | break; 36 | } 37 | case ESP_ZB_HA_SHADE_CONTROLLER_DEVICE_ID: { 38 | esp_zb_shade_controller_cfg_t config = ESP_ZB_DEFAULT_SHADE_CONTROLLER_CONFIG(); 39 | cluster_list = esp_zb_shade_controller_clusters_create(&config); 40 | break; 41 | } 42 | case ESP_ZB_HA_DOOR_LOCK_DEVICE_ID: { 43 | esp_zb_door_lock_cfg_t config = ESP_ZB_DEFAULT_DOOR_LOCK_CONFIG(); 44 | cluster_list = esp_zb_door_lock_clusters_create(&config); 45 | break; 46 | } 47 | case ESP_ZB_HA_DOOR_LOCK_CONTROLLER_DEVICE_ID: { 48 | esp_zb_door_lock_controller_cfg_t config = ESP_ZB_DEFAULT_DOOR_LOCK_CONTROLLER_CONFIG(); 49 | cluster_list = esp_zb_door_lock_controller_clusters_create(&config); 50 | break; 51 | } 52 | case ESP_ZB_HA_TEMPERATURE_SENSOR_DEVICE_ID: { 53 | esp_zb_temperature_sensor_cfg_t config = ESP_ZB_DEFAULT_TEMPERATURE_SENSOR_CONFIG(); 54 | cluster_list = esp_zb_temperature_sensor_clusters_create(&config); 55 | break; 56 | } 57 | case ESP_ZB_HA_CONFIGURATION_TOOL_DEVICE_ID: { 58 | esp_zb_configuration_tool_cfg_t config = ESP_ZB_DEFAULT_CONFIGURATION_TOOL_CONFIG(); 59 | cluster_list = esp_zb_configuration_tool_clusters_create(&config); 60 | break; 61 | } 62 | case ESP_ZB_HA_THERMOSTAT_DEVICE_ID: { 63 | esp_zb_thermostat_cfg_t config = ESP_ZB_DEFAULT_THERMOSTAT_CONFIG(); 64 | cluster_list = esp_zb_thermostat_clusters_create(&config); 65 | break; 66 | } 67 | case ESP_ZB_HA_WINDOW_COVERING_DEVICE_ID: { 68 | esp_zb_window_covering_cfg_t config = ESP_ZB_DEFAULT_WINDOW_COVERING_CONFIG(); 69 | cluster_list = esp_zb_window_covering_clusters_create(&config); 70 | break; 71 | } 72 | case ESP_ZB_HA_WINDOW_COVERING_CONTROLLER_DEVICE_ID: { 73 | esp_zb_window_covering_controller_cfg_t config = ESP_ZB_DEFAULT_WINDOW_COVERING_CONTROLLER_CONFIG(); 74 | cluster_list = esp_zb_window_covering_controller_clusters_create(&config); 75 | break; 76 | } 77 | default: 78 | // create empty cluster list; 79 | cluster_list = esp_zb_zcl_cluster_list_create(); 80 | } 81 | 82 | return cluster_list; 83 | } 84 | 85 | esp_err_t esphome_zb_cluster_add_or_update_attr(uint16_t cluster_id, esp_zb_attribute_list_t *attr_list, 86 | uint16_t attr_id, uint8_t attr_type, uint8_t attr_access, 87 | void *value_p) { 88 | esp_err_t ret; 89 | ret = esp_zb_cluster_update_attr(attr_list, attr_id, value_p); 90 | if (ret != ESP_OK) { 91 | ESP_LOGE("zigbee_helper", "Ignore previous attribute not found error"); 92 | if (attr_access > 0) { 93 | ret = esp_zb_cluster_add_attr(attr_list, cluster_id, attr_id, attr_type, attr_access, value_p); 94 | } else { 95 | ret = esphome_zb_cluster_add_attr(cluster_id, attr_list, attr_id, value_p); 96 | } 97 | } 98 | return ret; 99 | } 100 | 101 | esp_err_t esphome_zb_cluster_list_add_or_update_cluster(uint16_t cluster_id, esp_zb_cluster_list_t *cluster_list, 102 | esp_zb_attribute_list_t *attr_list, uint8_t role_mask) { 103 | esp_err_t ret; 104 | ret = esp_zb_cluster_list_update_cluster(cluster_list, attr_list, cluster_id, role_mask); 105 | if (ret != ESP_OK) { 106 | ESP_LOGE("zigbee_helper", "Ignore previous cluster not found error"); 107 | switch (cluster_id) { 108 | case ESP_ZB_ZCL_CLUSTER_ID_BASIC: 109 | ret = esp_zb_cluster_list_add_basic_cluster(cluster_list, attr_list, role_mask); 110 | break; 111 | case ESP_ZB_ZCL_CLUSTER_ID_POWER_CONFIG: 112 | ret = esp_zb_cluster_list_add_power_config_cluster(cluster_list, attr_list, role_mask); 113 | break; 114 | case ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY: 115 | ret = esp_zb_cluster_list_add_identify_cluster(cluster_list, attr_list, role_mask); 116 | break; 117 | case ESP_ZB_ZCL_CLUSTER_ID_GROUPS: 118 | ret = esp_zb_cluster_list_add_groups_cluster(cluster_list, attr_list, role_mask); 119 | break; 120 | case ESP_ZB_ZCL_CLUSTER_ID_SCENES: 121 | ret = esp_zb_cluster_list_add_scenes_cluster(cluster_list, attr_list, role_mask); 122 | break; 123 | case ESP_ZB_ZCL_CLUSTER_ID_ON_OFF: 124 | ret = esp_zb_cluster_list_add_on_off_cluster(cluster_list, attr_list, role_mask); 125 | break; 126 | case ESP_ZB_ZCL_CLUSTER_ID_ON_OFF_SWITCH_CONFIG: 127 | ret = esp_zb_cluster_list_add_on_off_switch_config_cluster(cluster_list, attr_list, role_mask); 128 | break; 129 | case ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL: 130 | ret = esp_zb_cluster_list_add_level_cluster(cluster_list, attr_list, role_mask); 131 | break; 132 | case ESP_ZB_ZCL_CLUSTER_ID_TIME: 133 | ret = esp_zb_cluster_list_add_time_cluster(cluster_list, attr_list, role_mask); 134 | break; 135 | case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_INPUT: 136 | ret = esp_zb_cluster_list_add_analog_input_cluster(cluster_list, attr_list, role_mask); 137 | break; 138 | case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT: 139 | ret = esp_zb_cluster_list_add_analog_output_cluster(cluster_list, attr_list, role_mask); 140 | break; 141 | case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_VALUE: 142 | ret = esp_zb_cluster_list_add_analog_value_cluster(cluster_list, attr_list, role_mask); 143 | break; 144 | case ESP_ZB_ZCL_CLUSTER_ID_BINARY_INPUT: 145 | ret = esp_zb_cluster_list_add_binary_input_cluster(cluster_list, attr_list, role_mask); 146 | break; 147 | case ESP_ZB_ZCL_CLUSTER_ID_MULTI_VALUE: 148 | ret = esp_zb_cluster_list_add_multistate_value_cluster(cluster_list, attr_list, role_mask); 149 | break; 150 | case ESP_ZB_ZCL_CLUSTER_ID_COMMISSIONING: 151 | ret = esp_zb_cluster_list_add_commissioning_cluster(cluster_list, attr_list, role_mask); 152 | break; 153 | case ESP_ZB_ZCL_CLUSTER_ID_OTA_UPGRADE: 154 | ret = esp_zb_cluster_list_add_ota_cluster(cluster_list, attr_list, role_mask); 155 | break; 156 | case ESP_ZB_ZCL_CLUSTER_ID_SHADE_CONFIG: 157 | ret = esp_zb_cluster_list_add_shade_config_cluster(cluster_list, attr_list, role_mask); 158 | break; 159 | case ESP_ZB_ZCL_CLUSTER_ID_DOOR_LOCK: 160 | ret = esp_zb_cluster_list_add_door_lock_cluster(cluster_list, attr_list, role_mask); 161 | break; 162 | case ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING: 163 | ret = esp_zb_cluster_list_add_window_covering_cluster(cluster_list, attr_list, role_mask); 164 | break; 165 | case ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT: 166 | ret = esp_zb_cluster_list_add_thermostat_cluster(cluster_list, attr_list, role_mask); 167 | break; 168 | case ESP_ZB_ZCL_CLUSTER_ID_FAN_CONTROL: 169 | ret = esp_zb_cluster_list_add_fan_control_cluster(cluster_list, attr_list, role_mask); 170 | break; 171 | case ESP_ZB_ZCL_CLUSTER_ID_DEHUMIDIFICATION_CONTROL: 172 | ret = esp_zb_cluster_list_add_dehumidification_control_cluster(cluster_list, attr_list, role_mask); 173 | break; 174 | case ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT_UI_CONFIG: 175 | ret = esp_zb_cluster_list_add_thermostat_ui_config_cluster(cluster_list, attr_list, role_mask); 176 | break; 177 | case ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL: 178 | ret = esp_zb_cluster_list_add_color_control_cluster(cluster_list, attr_list, role_mask); 179 | break; 180 | case ESP_ZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT: 181 | ret = esp_zb_cluster_list_add_illuminance_meas_cluster(cluster_list, attr_list, role_mask); 182 | break; 183 | case ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT: 184 | ret = esp_zb_cluster_list_add_temperature_meas_cluster(cluster_list, attr_list, role_mask); 185 | break; 186 | case ESP_ZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT: 187 | ret = esp_zb_cluster_list_add_pressure_meas_cluster(cluster_list, attr_list, role_mask); 188 | break; 189 | case ESP_ZB_ZCL_CLUSTER_ID_FLOW_MEASUREMENT: 190 | ret = esp_zb_cluster_list_add_flow_meas_cluster(cluster_list, attr_list, role_mask); 191 | break; 192 | case ESP_ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT: 193 | ret = esp_zb_cluster_list_add_humidity_meas_cluster(cluster_list, attr_list, role_mask); 194 | break; 195 | case ESP_ZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING: 196 | ret = esp_zb_cluster_list_add_occupancy_sensing_cluster(cluster_list, attr_list, role_mask); 197 | break; 198 | case ESP_ZB_ZCL_CLUSTER_ID_PH_MEASUREMENT: 199 | ret = esp_zb_cluster_list_add_ph_measurement_cluster(cluster_list, attr_list, role_mask); 200 | break; 201 | case ESP_ZB_ZCL_CLUSTER_ID_EC_MEASUREMENT: 202 | ret = esp_zb_cluster_list_add_ec_measurement_cluster(cluster_list, attr_list, role_mask); 203 | break; 204 | case ESP_ZB_ZCL_CLUSTER_ID_WIND_SPEED_MEASUREMENT: 205 | ret = esp_zb_cluster_list_add_wind_speed_measurement_cluster(cluster_list, attr_list, role_mask); 206 | break; 207 | case ESP_ZB_ZCL_CLUSTER_ID_CARBON_DIOXIDE_MEASUREMENT: 208 | ret = esp_zb_cluster_list_add_carbon_dioxide_measurement_cluster(cluster_list, attr_list, role_mask); 209 | break; 210 | case ESP_ZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT: 211 | ret = esp_zb_cluster_list_add_pm2_5_measurement_cluster(cluster_list, attr_list, role_mask); 212 | break; 213 | case ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE: 214 | ret = esp_zb_cluster_list_add_ias_zone_cluster(cluster_list, attr_list, role_mask); 215 | break; 216 | case ESP_ZB_ZCL_CLUSTER_ID_IAS_ACE: 217 | ret = esp_zb_cluster_list_add_ias_ace_cluster(cluster_list, attr_list, role_mask); 218 | break; 219 | case ESP_ZB_ZCL_CLUSTER_ID_IAS_WD: 220 | ret = esp_zb_cluster_list_add_ias_wd_cluster(cluster_list, attr_list, role_mask); 221 | break; 222 | case ESP_ZB_ZCL_CLUSTER_ID_PRICE: 223 | ret = esp_zb_cluster_list_add_price_cluster(cluster_list, attr_list, role_mask); 224 | break; 225 | case ESP_ZB_ZCL_CLUSTER_ID_DRLC: 226 | ret = esp_zb_cluster_list_add_drlc_cluster(cluster_list, attr_list, role_mask); 227 | break; 228 | case ESP_ZB_ZCL_CLUSTER_ID_METERING: 229 | ret = esp_zb_cluster_list_add_metering_cluster(cluster_list, attr_list, role_mask); 230 | break; 231 | case ESP_ZB_ZCL_CLUSTER_ID_METER_IDENTIFICATION: 232 | ret = esp_zb_cluster_list_add_meter_identification_cluster(cluster_list, attr_list, role_mask); 233 | break; 234 | case ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT: 235 | ret = esp_zb_cluster_list_add_electrical_meas_cluster(cluster_list, attr_list, role_mask); 236 | break; 237 | case ESP_ZB_ZCL_CLUSTER_ID_DIAGNOSTICS: 238 | ret = esp_zb_cluster_list_add_diagnostics_cluster(cluster_list, attr_list, role_mask); 239 | break; 240 | default: 241 | ret = esp_zb_cluster_list_add_custom_cluster(cluster_list, attr_list, role_mask); 242 | } 243 | } 244 | return ret; 245 | } 246 | 247 | esp_zb_attribute_list_t *esphome_zb_default_attr_list_create(uint16_t cluster_id) { 248 | switch (cluster_id) { 249 | case ESP_ZB_ZCL_CLUSTER_ID_BASIC: 250 | return esp_zb_basic_cluster_create(NULL); 251 | case ESP_ZB_ZCL_CLUSTER_ID_POWER_CONFIG: 252 | return esp_zb_power_config_cluster_create(NULL); 253 | case ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY: 254 | return esp_zb_identify_cluster_create(NULL); 255 | case ESP_ZB_ZCL_CLUSTER_ID_GROUPS: 256 | return esp_zb_groups_cluster_create(NULL); 257 | case ESP_ZB_ZCL_CLUSTER_ID_SCENES: 258 | return esp_zb_scenes_cluster_create(NULL); 259 | case ESP_ZB_ZCL_CLUSTER_ID_ON_OFF: 260 | return esp_zb_on_off_cluster_create(NULL); 261 | case ESP_ZB_ZCL_CLUSTER_ID_ON_OFF_SWITCH_CONFIG: 262 | return esp_zb_on_off_switch_config_cluster_create(NULL); 263 | case ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL: 264 | return esp_zb_level_cluster_create(NULL); 265 | case ESP_ZB_ZCL_CLUSTER_ID_TIME: 266 | return esp_zb_time_cluster_create(NULL); 267 | case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_INPUT: 268 | return esp_zb_analog_input_cluster_create(NULL); 269 | case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT: 270 | return esp_zb_analog_output_cluster_create(NULL); 271 | case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_VALUE: 272 | return esp_zb_analog_value_cluster_create(NULL); 273 | case ESP_ZB_ZCL_CLUSTER_ID_BINARY_INPUT: 274 | return esp_zb_binary_input_cluster_create(NULL); 275 | case ESP_ZB_ZCL_CLUSTER_ID_MULTI_VALUE: 276 | return esp_zb_multistate_value_cluster_create(NULL); 277 | case ESP_ZB_ZCL_CLUSTER_ID_COMMISSIONING: 278 | return esp_zb_commissioning_cluster_create(NULL); 279 | case ESP_ZB_ZCL_CLUSTER_ID_OTA_UPGRADE: 280 | return esp_zb_ota_cluster_create(NULL); 281 | case ESP_ZB_ZCL_CLUSTER_ID_SHADE_CONFIG: 282 | return esp_zb_shade_config_cluster_create(NULL); 283 | case ESP_ZB_ZCL_CLUSTER_ID_DOOR_LOCK: 284 | return esp_zb_door_lock_cluster_create(NULL); 285 | case ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING: 286 | return esp_zb_window_covering_cluster_create(NULL); 287 | case ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT: 288 | return esp_zb_thermostat_cluster_create(NULL); 289 | case ESP_ZB_ZCL_CLUSTER_ID_FAN_CONTROL: 290 | return esp_zb_fan_control_cluster_create(NULL); 291 | case ESP_ZB_ZCL_CLUSTER_ID_DEHUMIDIFICATION_CONTROL: 292 | return esp_zb_dehumidification_control_cluster_create(NULL); 293 | case ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT_UI_CONFIG: 294 | return esp_zb_thermostat_ui_config_cluster_create(NULL); 295 | case ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL: 296 | return esp_zb_color_control_cluster_create(NULL); 297 | case ESP_ZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT: 298 | return esp_zb_illuminance_meas_cluster_create(NULL); 299 | case ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT: 300 | return esp_zb_temperature_meas_cluster_create(NULL); 301 | case ESP_ZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT: 302 | return esp_zb_pressure_meas_cluster_create(NULL); 303 | case ESP_ZB_ZCL_CLUSTER_ID_FLOW_MEASUREMENT: 304 | return esp_zb_flow_meas_cluster_create(NULL); 305 | case ESP_ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT: 306 | return esp_zb_humidity_meas_cluster_create(NULL); 307 | case ESP_ZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING: 308 | return esp_zb_occupancy_sensing_cluster_create(NULL); 309 | case ESP_ZB_ZCL_CLUSTER_ID_PH_MEASUREMENT: 310 | return esp_zb_ph_measurement_cluster_create(NULL); 311 | case ESP_ZB_ZCL_CLUSTER_ID_EC_MEASUREMENT: 312 | return esp_zb_ec_measurement_cluster_create(NULL); 313 | case ESP_ZB_ZCL_CLUSTER_ID_WIND_SPEED_MEASUREMENT: 314 | return esp_zb_wind_speed_measurement_cluster_create(NULL); 315 | case ESP_ZB_ZCL_CLUSTER_ID_CARBON_DIOXIDE_MEASUREMENT: 316 | return esp_zb_carbon_dioxide_measurement_cluster_create(NULL); 317 | case ESP_ZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT: 318 | return esp_zb_pm2_5_measurement_cluster_create(NULL); 319 | case ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE: 320 | return esp_zb_ias_zone_cluster_create(NULL); 321 | case ESP_ZB_ZCL_CLUSTER_ID_IAS_ACE: 322 | return esp_zb_ias_ace_cluster_create(NULL); 323 | case ESP_ZB_ZCL_CLUSTER_ID_IAS_WD: 324 | return esp_zb_ias_wd_cluster_create(NULL); 325 | case ESP_ZB_ZCL_CLUSTER_ID_PRICE: 326 | return esp_zb_price_cluster_create(NULL); 327 | case ESP_ZB_ZCL_CLUSTER_ID_DRLC: 328 | return esp_zb_drlc_cluster_create(NULL); 329 | case ESP_ZB_ZCL_CLUSTER_ID_METERING: 330 | return esp_zb_metering_cluster_create(NULL); 331 | case ESP_ZB_ZCL_CLUSTER_ID_METER_IDENTIFICATION: 332 | return esp_zb_meter_identification_cluster_create(NULL); 333 | case ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT: 334 | return esp_zb_electrical_meas_cluster_create(NULL); 335 | case ESP_ZB_ZCL_CLUSTER_ID_DIAGNOSTICS: 336 | return esp_zb_diagnostics_cluster_create(NULL); 337 | default: 338 | return esp_zb_zcl_attr_list_create(cluster_id); 339 | } 340 | } 341 | 342 | esp_err_t esphome_zb_cluster_add_attr(uint16_t cluster_id, esp_zb_attribute_list_t *attr_list, uint16_t attr_id, 343 | void *value_p) { 344 | switch (cluster_id) { 345 | case ESP_ZB_ZCL_CLUSTER_ID_BASIC: 346 | return esp_zb_basic_cluster_add_attr(attr_list, attr_id, value_p); 347 | case ESP_ZB_ZCL_CLUSTER_ID_POWER_CONFIG: 348 | return esp_zb_power_config_cluster_add_attr(attr_list, attr_id, value_p); 349 | case ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY: 350 | return esp_zb_identify_cluster_add_attr(attr_list, attr_id, value_p); 351 | case ESP_ZB_ZCL_CLUSTER_ID_GROUPS: 352 | return esp_zb_groups_cluster_add_attr(attr_list, attr_id, value_p); 353 | case ESP_ZB_ZCL_CLUSTER_ID_SCENES: 354 | return esp_zb_scenes_cluster_add_attr(attr_list, attr_id, value_p); 355 | case ESP_ZB_ZCL_CLUSTER_ID_ON_OFF: 356 | return esp_zb_on_off_cluster_add_attr(attr_list, attr_id, value_p); 357 | case ESP_ZB_ZCL_CLUSTER_ID_ON_OFF_SWITCH_CONFIG: 358 | return esp_zb_on_off_switch_config_cluster_add_attr(attr_list, attr_id, value_p); 359 | case ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL: 360 | return esp_zb_level_cluster_add_attr(attr_list, attr_id, value_p); 361 | case ESP_ZB_ZCL_CLUSTER_ID_TIME: 362 | return esp_zb_time_cluster_add_attr(attr_list, attr_id, value_p); 363 | case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_INPUT: 364 | return esp_zb_analog_input_cluster_add_attr(attr_list, attr_id, value_p); 365 | case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT: 366 | return esp_zb_analog_output_cluster_add_attr(attr_list, attr_id, value_p); 367 | case ESP_ZB_ZCL_CLUSTER_ID_ANALOG_VALUE: 368 | return esp_zb_analog_value_cluster_add_attr(attr_list, attr_id, value_p); 369 | case ESP_ZB_ZCL_CLUSTER_ID_BINARY_INPUT: 370 | return esp_zb_binary_input_cluster_add_attr(attr_list, attr_id, value_p); 371 | case ESP_ZB_ZCL_CLUSTER_ID_MULTI_VALUE: 372 | return esp_zb_multistate_value_cluster_add_attr(attr_list, attr_id, value_p); 373 | case ESP_ZB_ZCL_CLUSTER_ID_COMMISSIONING: 374 | return esp_zb_commissioning_cluster_add_attr(attr_list, attr_id, value_p); 375 | case ESP_ZB_ZCL_CLUSTER_ID_OTA_UPGRADE: 376 | return esp_zb_ota_cluster_add_attr(attr_list, attr_id, value_p); 377 | case ESP_ZB_ZCL_CLUSTER_ID_SHADE_CONFIG: 378 | return esp_zb_shade_config_cluster_add_attr(attr_list, attr_id, value_p); 379 | case ESP_ZB_ZCL_CLUSTER_ID_DOOR_LOCK: 380 | return esp_zb_door_lock_cluster_add_attr(attr_list, attr_id, value_p); 381 | case ESP_ZB_ZCL_CLUSTER_ID_WINDOW_COVERING: 382 | return esp_zb_window_covering_cluster_add_attr(attr_list, attr_id, value_p); 383 | case ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT: 384 | return esp_zb_thermostat_cluster_add_attr(attr_list, attr_id, value_p); 385 | case ESP_ZB_ZCL_CLUSTER_ID_FAN_CONTROL: 386 | return esp_zb_fan_control_cluster_add_attr(attr_list, attr_id, value_p); 387 | case ESP_ZB_ZCL_CLUSTER_ID_DEHUMIDIFICATION_CONTROL: 388 | return esp_zb_dehumidification_control_cluster_add_attr(attr_list, attr_id, value_p); 389 | case ESP_ZB_ZCL_CLUSTER_ID_THERMOSTAT_UI_CONFIG: 390 | return esp_zb_thermostat_ui_config_cluster_add_attr(attr_list, attr_id, value_p); 391 | case ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL: 392 | return esp_zb_color_control_cluster_add_attr(attr_list, attr_id, value_p); 393 | case ESP_ZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT: 394 | return esp_zb_illuminance_meas_cluster_add_attr(attr_list, attr_id, value_p); 395 | case ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT: 396 | return esp_zb_temperature_meas_cluster_add_attr(attr_list, attr_id, value_p); 397 | case ESP_ZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT: 398 | return esp_zb_pressure_meas_cluster_add_attr(attr_list, attr_id, value_p); 399 | case ESP_ZB_ZCL_CLUSTER_ID_FLOW_MEASUREMENT: 400 | return esp_zb_flow_meas_cluster_add_attr(attr_list, attr_id, value_p); 401 | case ESP_ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT: 402 | return esp_zb_humidity_meas_cluster_add_attr(attr_list, attr_id, value_p); 403 | case ESP_ZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING: 404 | return esp_zb_occupancy_sensing_cluster_add_attr(attr_list, attr_id, value_p); 405 | case ESP_ZB_ZCL_CLUSTER_ID_PH_MEASUREMENT: 406 | return esp_zb_ph_measurement_cluster_add_attr(attr_list, attr_id, value_p); 407 | case ESP_ZB_ZCL_CLUSTER_ID_EC_MEASUREMENT: 408 | return esp_zb_ec_measurement_cluster_add_attr(attr_list, attr_id, value_p); 409 | case ESP_ZB_ZCL_CLUSTER_ID_WIND_SPEED_MEASUREMENT: 410 | return esp_zb_wind_speed_measurement_cluster_add_attr(attr_list, attr_id, value_p); 411 | case ESP_ZB_ZCL_CLUSTER_ID_CARBON_DIOXIDE_MEASUREMENT: 412 | return esp_zb_carbon_dioxide_measurement_cluster_add_attr(attr_list, attr_id, value_p); 413 | case ESP_ZB_ZCL_CLUSTER_ID_PM2_5_MEASUREMENT: 414 | return esp_zb_pm2_5_measurement_cluster_add_attr(attr_list, attr_id, value_p); 415 | case ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE: 416 | return esp_zb_ias_zone_cluster_add_attr(attr_list, attr_id, value_p); 417 | case ESP_ZB_ZCL_CLUSTER_ID_IAS_WD: 418 | return esp_zb_ias_wd_cluster_add_attr(attr_list, attr_id, value_p); 419 | case ESP_ZB_ZCL_CLUSTER_ID_DRLC: 420 | return esp_zb_drlc_cluster_add_attr(attr_list, attr_id, value_p); 421 | case ESP_ZB_ZCL_CLUSTER_ID_METER_IDENTIFICATION: 422 | return esp_zb_meter_identification_cluster_add_attr(attr_list, attr_id, value_p); 423 | case ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT: 424 | return esp_zb_electrical_meas_cluster_add_attr(attr_list, attr_id, value_p); 425 | case ESP_ZB_ZCL_CLUSTER_ID_DIAGNOSTICS: 426 | return esp_zb_diagnostics_cluster_add_attr(attr_list, attr_id, value_p); 427 | default: 428 | return ESP_FAIL; 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /components/zigbee/zigbee_helpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifdef __cplusplus 3 | extern "C" { 4 | #endif 5 | 6 | #include "esp_zigbee_core.h" 7 | 8 | esp_zb_cluster_list_t *esphome_zb_default_clusters_create(esp_zb_ha_standard_devices_t device_type); 9 | 10 | esp_err_t esphome_zb_cluster_list_add_or_update_cluster(uint16_t cluster_id, esp_zb_cluster_list_t *cluster_list, 11 | esp_zb_attribute_list_t *attr_list, uint8_t role_mask); 12 | 13 | esp_zb_attribute_list_t *esphome_zb_default_attr_list_create(uint16_t cluster_id); 14 | esp_err_t esphome_zb_cluster_add_attr(uint16_t cluster_id, esp_zb_attribute_list_t *attr_list, uint16_t attr_id, 15 | void *value_p); 16 | esp_err_t esphome_zb_cluster_add_or_update_attr(uint16_t cluster_id, esp_zb_attribute_list_t *attr_list, 17 | uint16_t attr_id, uint8_t attr_type, uint8_t attr_access, 18 | void *value_p); 19 | 20 | #ifdef __cplusplus 21 | } 22 | 23 | namespace esphome { 24 | namespace zigbee {} 25 | } // namespace esphome 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /example_aht10_esp32h2.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: zb-sensor 3 | 4 | external_components: 5 | - source: components 6 | components: [zigbee] 7 | 8 | esp32: 9 | board: esp32-h2-devkitm-1 10 | #flash_size: 4MB 11 | partitions: partitions_zb.csv 12 | framework: 13 | type: esp-idf 14 | #sdkconfig_options: 15 | #CONFIG_ESPTOOLPY_FLASHSIZE_4MB: y 16 | 17 | # Enable logging 18 | logger: 19 | hardware_uart: UART0 20 | 21 | globals: 22 | - id: color_x 23 | type: float 24 | restore_value: no 25 | initial_value: '0' 26 | - id: color_y 27 | type: float 28 | restore_value: no 29 | initial_value: '0' 30 | 31 | i2c: 32 | sda: 12 33 | scl: 22 34 | 35 | sensor: 36 | - platform: aht10 37 | variant: AHT10 38 | temperature: 39 | name: "Living Room Temperature" 40 | id: "temp" 41 | filters: 42 | - delta: 0.1 43 | humidity: 44 | name: "Living Room Humidity" 45 | id: "hum" 46 | filters: 47 | - delta: 1 48 | on_value: 49 | then: 50 | - zigbee.setAttr: 51 | id: hum_attr 52 | value: !lambda "return x*100;" 53 | update_interval: 60s 54 | 55 | zigbee: 56 | id: "zb" 57 | endpoints: 58 | - num: 1 59 | device_type: COLOR_DIMMABLE_LIGHT 60 | clusters: 61 | - id: ON_OFF 62 | attributes: 63 | - attribute_id: 0 64 | type: bool 65 | on_value: 66 | then: 67 | - light.control: 68 | id: light_1 69 | state: !lambda "return x;" 70 | - id: LEVEL_CONTROL 71 | attributes: 72 | - attribute_id: 0 73 | type: U8 74 | value: 255 75 | on_value: 76 | then: 77 | - light.control: 78 | id: light_1 79 | brightness: !lambda "return ((float)x)/255;" 80 | - id: COLOR_CONTROL 81 | attributes: 82 | - attribute_id: 3 83 | type: U16 84 | on_value: 85 | then: 86 | - lambda: id(color_x) = (float)x/65536; 87 | - light.control: 88 | id: light_1 89 | red: !lambda "return zigbee::get_r_from_xy(id(color_x), id(color_y));" 90 | green: !lambda "return zigbee::get_g_from_xy(id(color_x), id(color_y));" 91 | blue: !lambda "return zigbee::get_b_from_xy(id(color_x), id(color_y));" 92 | - attribute_id: 4 93 | type: U16 94 | on_value: 95 | then: 96 | - lambda: id(color_y) = (float)x/65536; 97 | - light.control: 98 | id: light_1 99 | red: !lambda "return zigbee::get_r_from_xy(id(color_x), id(color_y));" 100 | green: !lambda "return zigbee::get_g_from_xy(id(color_x), id(color_y));" 101 | blue: !lambda "return zigbee::get_b_from_xy(id(color_x), id(color_y));" 102 | - device_type: TEMPERATURE_SENSOR 103 | num: 2 104 | clusters: 105 | - id: REL_HUMIDITY_MEASUREMENT 106 | attributes: 107 | - attribute_id: 0 108 | id: hum_attr 109 | type: U16 110 | report: true 111 | value: 200 112 | - id: TEMP_MEASUREMENT 113 | attributes: 114 | - attribute_id: 0x0 115 | type: S16 116 | report: true 117 | value: 100 118 | device: temp 119 | scale: 100 120 | on_join: 121 | then: 122 | - logger.log: "Joined network" 123 | 124 | light: 125 | - platform: esp32_rmt_led_strip 126 | rgb_order: GRB 127 | pin: 8 128 | num_leds: 1 129 | #rmt_channel: 0 130 | id: light_1 131 | chipset: ws2812 132 | #bit0_high: 100ns # rmt clk freq seems to be different on H2 133 | #bit0_low: 300ns 134 | #bit1_high: 300ns 135 | #bit1_low: 100ns 136 | 137 | binary_sensor: 138 | - platform: gpio 139 | pin: 140 | number: 9 141 | mode: 142 | input: true 143 | pullup: true 144 | inverted: true 145 | id: button_1 146 | on_press: 147 | then: 148 | - zigbee.report: zb 149 | on_click: 150 | min_length: 5s 151 | max_length: 20s 152 | then: 153 | - zigbee.reset: zb 154 | -------------------------------------------------------------------------------- /example_esp32c6.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: zb-example-c6 3 | 4 | external_components: 5 | - source: components 6 | components: [zigbee] 7 | 8 | esp32: 9 | board: esp32-c6-devkitc-1 10 | #flash_size: 4MB 11 | partitions: partitions_zb.csv 12 | framework: 13 | type: esp-idf 14 | #sdkconfig_options: 15 | #CONFIG_ESPTOOLPY_FLASHSIZE_4MB: y 16 | 17 | # Enable logging 18 | logger: 19 | hardware_uart: UART0 20 | 21 | globals: 22 | - id: color_x 23 | type: float 24 | restore_value: no 25 | initial_value: '0' 26 | - id: color_y 27 | type: float 28 | restore_value: no 29 | initial_value: '0' 30 | 31 | sensor: 32 | - platform: internal_temperature 33 | name: "Internal Temperature" 34 | id: "temp" 35 | filters: 36 | - delta: 0.1 37 | 38 | zigbee: 39 | id: "zb" 40 | endpoints: 41 | - num: 1 42 | device_type: COLOR_DIMMABLE_LIGHT 43 | clusters: 44 | - id: ON_OFF 45 | attributes: 46 | - attribute_id: 0 47 | type: bool 48 | on_value: 49 | then: 50 | - light.control: 51 | id: light_1 52 | state: !lambda "return (bool)x;" 53 | - id: LEVEL_CONTROL 54 | attributes: 55 | - attribute_id: 0 56 | type: U8 57 | value: 255 58 | on_value: 59 | then: 60 | - light.control: 61 | id: light_1 62 | brightness: !lambda "return ((float)x)/255;" 63 | - id: COLOR_CONTROL 64 | attributes: 65 | - attribute_id: 3 66 | type: U16 67 | on_value: 68 | then: 69 | - lambda: id(color_x) = (float)x/65536; 70 | - light.control: 71 | id: light_1 72 | red: !lambda "return zigbee::get_r_from_xy(id(color_x), id(color_y));" 73 | green: !lambda "return zigbee::get_g_from_xy(id(color_x), id(color_y));" 74 | blue: !lambda "return zigbee::get_b_from_xy(id(color_x), id(color_y));" 75 | - attribute_id: 4 76 | type: U16 77 | on_value: 78 | then: 79 | - lambda: id(color_y) = (float)x/65536; 80 | - light.control: 81 | id: light_1 82 | red: !lambda "return zigbee::get_r_from_xy(id(color_x), id(color_y));" 83 | green: !lambda "return zigbee::get_g_from_xy(id(color_x), id(color_y));" 84 | blue: !lambda "return zigbee::get_b_from_xy(id(color_x), id(color_y));" 85 | - device_type: TEMPERATURE_SENSOR 86 | num: 2 87 | clusters: 88 | - id: TEMP_MEASUREMENT 89 | attributes: 90 | - attribute_id: 0x0 91 | type: S16 92 | report: true 93 | value: 100 94 | device: temp 95 | scale: 100 96 | on_join: 97 | then: 98 | - logger.log: "Joined network" 99 | 100 | light: 101 | - platform: esp32_rmt_led_strip 102 | rgb_order: GRB 103 | pin: 8 104 | num_leds: 1 105 | #rmt_channel: 0 106 | chipset: ws2812 107 | id: light_1 108 | 109 | binary_sensor: 110 | - platform: gpio 111 | pin: 112 | number: 9 113 | mode: 114 | input: true 115 | pullup: true 116 | inverted: true 117 | id: button_1 118 | on_press: 119 | then: 120 | - zigbee.report: zb 121 | on_click: 122 | min_length: 5s 123 | max_length: 20s 124 | then: 125 | - zigbee.reset: zb 126 | -------------------------------------------------------------------------------- /example_esp32h2.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: zb-example-h2 3 | 4 | external_components: 5 | - source: components 6 | components: [zigbee] 7 | 8 | esp32: 9 | board: esp32-h2-devkitm-1 10 | #flash_size: 4MB 11 | partitions: partitions_zb.csv 12 | framework: 13 | type: esp-idf 14 | #sdkconfig_options: 15 | #CONFIG_ESPTOOLPY_FLASHSIZE_4MB: y 16 | 17 | # Enable logging 18 | logger: 19 | hardware_uart: UART0 20 | 21 | globals: 22 | - id: color_x 23 | type: float 24 | restore_value: no 25 | initial_value: '0' 26 | - id: color_y 27 | type: float 28 | restore_value: no 29 | initial_value: '0' 30 | 31 | sensor: 32 | - platform: internal_temperature 33 | name: "Internal Temperature" 34 | id: "temp" 35 | filters: 36 | - delta: 0.1 37 | 38 | zigbee: 39 | id: "zb" 40 | endpoints: 41 | - num: 1 42 | device_type: COLOR_DIMMABLE_LIGHT 43 | clusters: 44 | - id: ON_OFF 45 | attributes: 46 | - attribute_id: 0 47 | type: bool 48 | on_value: 49 | then: 50 | - light.control: 51 | id: light_1 52 | state: !lambda "return x;" 53 | - id: LEVEL_CONTROL 54 | attributes: 55 | - attribute_id: 0 56 | type: U8 57 | value: 255 58 | on_value: 59 | then: 60 | - light.control: 61 | id: light_1 62 | brightness: !lambda "return ((float)x)/255;" 63 | - id: COLOR_CONTROL 64 | attributes: 65 | - attribute_id: 3 66 | type: U16 67 | on_value: 68 | then: 69 | - lambda: id(color_x) = (float)x/65536; 70 | - light.control: 71 | id: light_1 72 | red: !lambda "return zigbee::get_r_from_xy(id(color_x), id(color_y));" 73 | green: !lambda "return zigbee::get_g_from_xy(id(color_x), id(color_y));" 74 | blue: !lambda "return zigbee::get_b_from_xy(id(color_x), id(color_y));" 75 | - attribute_id: 4 76 | type: U16 77 | on_value: 78 | then: 79 | - lambda: id(color_y) = (float)x/65536; 80 | - light.control: 81 | id: light_1 82 | red: !lambda "return zigbee::get_r_from_xy(id(color_x), id(color_y));" 83 | green: !lambda "return zigbee::get_g_from_xy(id(color_x), id(color_y));" 84 | blue: !lambda "return zigbee::get_b_from_xy(id(color_x), id(color_y));" 85 | - device_type: TEMPERATURE_SENSOR 86 | num: 2 87 | clusters: 88 | - id: TEMP_MEASUREMENT 89 | attributes: 90 | - attribute_id: 0x0 91 | type: S16 92 | report: true 93 | value: 100 94 | device: temp 95 | scale: 100 96 | on_join: 97 | then: 98 | - logger.log: "Joined network" 99 | 100 | light: 101 | - platform: esp32_rmt_led_strip 102 | rgb_order: GRB 103 | pin: 8 104 | num_leds: 1 105 | #rmt_channel: 0 106 | id: light_1 107 | chipset: ws2812 108 | #bit0_high: 100ns # rmt clk freq seems to be different on H2. Fixed in esphome 2025.02 109 | #bit0_low: 300ns 110 | #bit1_high: 300ns 111 | #bit1_low: 100ns 112 | 113 | binary_sensor: 114 | - platform: gpio 115 | pin: 116 | number: 9 117 | mode: 118 | input: true 119 | pullup: true 120 | inverted: true 121 | id: button_1 122 | on_press: 123 | then: 124 | - zigbee.report: zb 125 | on_click: 126 | min_length: 5s 127 | max_length: 20s 128 | then: 129 | - zigbee.reset: zb 130 | -------------------------------------------------------------------------------- /example_time.yml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: timesync-demo 3 | 4 | external_components: 5 | - source: components 6 | components: [zigbee] 7 | 8 | esp32: 9 | board: esp32-c6-devkitc-1 10 | variant: esp32c6 11 | flash_size: 8MB 12 | partitions: partitions_zb.csv 13 | framework: 14 | type: esp-idf 15 | sdkconfig_options: 16 | CONFIG_ESPTOOLPY_FLASHSIZE_8MB: y 17 | 18 | logger: 19 | 20 | zigbee: 21 | id: "zb" 22 | endpoints: 23 | # We need at least one endpoint defined 24 | - device_type: TEMPERATURE_SENSOR 25 | num: 1 26 | clusters: 27 | - id: TEMP_MEASUREMENT 28 | attributes: 29 | - attribute_id: 0x0 30 | type: S16 31 | report: true 32 | id: zb_temp 33 | value: 0 34 | on_join: 35 | then: 36 | - logger.log: "Joined network" 37 | 38 | time: 39 | - platform: zigbee 40 | id: the_time 41 | timezone: Europe/London 42 | on_time_sync: 43 | then: 44 | - logger.log: "Synchronized system clock" 45 | on_time: 46 | - seconds: /10 47 | then: 48 | - logger.log: "Tick-tock, every 10 seconds" 49 | -------------------------------------------------------------------------------- /partitions_zb.csv: -------------------------------------------------------------------------------- 1 | otadata, data, ota, , 0x2000, 2 | phy_init, data, phy, , 0x1000, 3 | app0, app, ota_0, , 0x1B0000, 4 | app1, app, ota_1, , 0x1B0000, 5 | nvs, data, nvs, , 0x6D000, 6 | zb_storage, data, fat, , 16K, 7 | zb_fct, data, fat, , 1K, 8 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools==69.2.0", "wheel~=0.43.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "esphome" 7 | license = {text = "MIT"} 8 | description = "Make creating custom firmwares for ESP32/ESP8266 super easy." 9 | readme = "README.md" 10 | authors = [ 11 | {name = "The ESPHome Authors", email = "esphome@nabucasa.com"} 12 | ] 13 | keywords = ["home", "automation"] 14 | classifiers = [ 15 | "Environment :: Console", 16 | "Intended Audience :: Developers", 17 | "Intended Audience :: End Users/Desktop", 18 | "License :: OSI Approved :: MIT License", 19 | "Programming Language :: C++", 20 | "Programming Language :: Python :: 3", 21 | "Topic :: Home Automation", 22 | ] 23 | requires-python = ">=3.9.0" 24 | 25 | dynamic = ["dependencies", "optional-dependencies", "version"] 26 | 27 | [project.urls] 28 | "Documentation" = "https://esphome.io" 29 | "Source Code" = "https://github.com/esphome/esphome" 30 | "Bug Tracker" = "https://github.com/esphome/issues/issues" 31 | "Feature Request Tracker" = "https://github.com/esphome/feature-requests/issues" 32 | "Discord" = "https://discord.gg/KhAMKrd" 33 | "Forum" = "https://community.home-assistant.io/c/esphome" 34 | "Twitter" = "https://twitter.com/esphome_" 35 | 36 | [project.scripts] 37 | esphome = "esphome.__main__:main" 38 | 39 | [tool.setuptools] 40 | platforms = ["any"] 41 | zip-safe = false 42 | include-package-data = true 43 | 44 | [tool.setuptools.dynamic] 45 | dependencies = {file = ["requirements.txt"]} 46 | optional-dependencies.dev = { file = ["requirements_dev.txt"] } 47 | optional-dependencies.test = { file = ["requirements_test.txt"] } 48 | optional-dependencies.displays = { file = ["requirements_optional.txt"] } 49 | version = {attr = "esphome.const.__version__"} 50 | 51 | [tool.setuptools.packages.find] 52 | include = ["esphome*"] 53 | 54 | [tool.black] 55 | target-version = ["py39", "py310"] 56 | exclude = 'generated' 57 | 58 | [tool.pytest.ini_options] 59 | testpaths = [ 60 | "tests", 61 | ] 62 | addopts = [ 63 | "--cov=esphome", 64 | "--cov-branch", 65 | ] 66 | 67 | [tool.pylint.MAIN] 68 | py-version = "3.9" 69 | ignore = [ 70 | "api_pb2.py", 71 | ] 72 | persistent = false 73 | 74 | [tool.pylint.REPORTS] 75 | score = false 76 | 77 | [tool.pylint."MESSAGES CONTROL"] 78 | disable = [ 79 | "format", 80 | "missing-docstring", 81 | "fixme", 82 | "unused-argument", 83 | "global-statement", 84 | "too-few-public-methods", 85 | "too-many-lines", 86 | "too-many-locals", 87 | "too-many-ancestors", 88 | "too-many-branches", 89 | "too-many-statements", 90 | "too-many-arguments", 91 | "too-many-return-statements", 92 | "too-many-instance-attributes", 93 | "duplicate-code", 94 | "invalid-name", 95 | "cyclic-import", 96 | "redefined-builtin", 97 | "undefined-loop-variable", 98 | "useless-object-inheritance", 99 | "stop-iteration-return", 100 | "import-outside-toplevel", 101 | # Broken 102 | "unsupported-membership-test", 103 | "unsubscriptable-object", 104 | ] 105 | 106 | [tool.pylint.FORMAT] 107 | expected-line-ending-format = "LF" 108 | 109 | [tool.ruff] 110 | required-version = ">=0.5.0" 111 | 112 | [tool.ruff.lint] 113 | select = [ 114 | "E", # pycodestyle 115 | "F", # pyflakes/autoflake 116 | "I", # isort 117 | "PL", # pylint 118 | "UP", # pyupgrade 119 | ] 120 | 121 | ignore = [ 122 | "E501", # line too long 123 | "PLR0911", # Too many return statements ({returns} > {max_returns}) 124 | "PLR0912", # Too many branches ({branches} > {max_branches}) 125 | "PLR0913", # Too many arguments to function call ({c_args} > {max_args}) 126 | "PLR0915", # Too many statements ({statements} > {max_statements}) 127 | "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable 128 | "PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target 129 | ] 130 | 131 | [tool.ruff.lint.isort] 132 | force-sort-within-sections = true 133 | known-first-party = [ 134 | "esphome", 135 | ] 136 | combine-as-imports = true 137 | split-on-trailing-comma = false 138 | --------------------------------------------------------------------------------