├── .gitattributes ├── images ├── ha.png ├── dash.png ├── top.png ├── bottom.png ├── exposes.png ├── ZigUSB_C6.png ├── top_case.jpeg └── back_case.jpeg ├── Gemfile ├── hardware ├── BOM.csv ├── Gerber.zip └── Schematic.png ├── .gitmodules ├── components ├── ina219 │ ├── component.mk │ ├── CMakeLists.txt │ ├── .eil.yml │ ├── LICENSE │ ├── ina219.c │ └── ina219.h ├── esp_idf_lib_helpers │ ├── CMakeLists.txt │ ├── component.mk │ ├── .eil.yml │ ├── ets_sys.h │ ├── LICENSE │ └── esp_idf_lib_helpers.h └── i2cdev │ ├── component.mk │ ├── CMakeLists.txt │ ├── .eil.yml │ ├── Kconfig │ ├── LICENSE │ ├── i2cdev.h │ └── i2cdev.c ├── main ├── CMakeLists.txt ├── ota.h ├── idf_component.yml ├── tools.h ├── perf.h ├── main.h ├── main.c ├── zigbee.h ├── const.h ├── ota.c ├── tools.c ├── perf.c └── zigbee.c ├── _data └── menu.yml ├── partitions.csv ├── .gitignore ├── _config.yml ├── .github ├── FUNDING.yml ├── workflows │ ├── update_readme.yml │ └── build.yml └── scripts │ └── pages.py ├── .vscode ├── c_cpp_properties.json └── settings.json ├── CMakeLists.txt ├── tools ├── update_version.sh ├── make_ota.sh ├── create-ota.py └── commit.sh ├── CONTRIBUTE.md ├── README.md ├── index.md └── LICENSE.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-generated=true 2 | -------------------------------------------------------------------------------- /images/ha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/ZigUSB_C6/main/images/ha.png -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'github-pages', group: :jekyll_plugins -------------------------------------------------------------------------------- /images/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/ZigUSB_C6/main/images/dash.png -------------------------------------------------------------------------------- /images/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/ZigUSB_C6/main/images/top.png -------------------------------------------------------------------------------- /hardware/BOM.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/ZigUSB_C6/main/hardware/BOM.csv -------------------------------------------------------------------------------- /images/bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/ZigUSB_C6/main/images/bottom.png -------------------------------------------------------------------------------- /images/exposes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/ZigUSB_C6/main/images/exposes.png -------------------------------------------------------------------------------- /hardware/Gerber.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/ZigUSB_C6/main/hardware/Gerber.zip -------------------------------------------------------------------------------- /images/ZigUSB_C6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/ZigUSB_C6/main/images/ZigUSB_C6.png -------------------------------------------------------------------------------- /images/top_case.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/ZigUSB_C6/main/images/top_case.jpeg -------------------------------------------------------------------------------- /hardware/Schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/ZigUSB_C6/main/hardware/Schematic.png -------------------------------------------------------------------------------- /images/back_case.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyzroe/ZigUSB_C6/main/images/back_case.jpeg -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "esp-idf-lib"] 2 | path = esp-idf-lib 3 | url = https://github.com/UncleRus/esp-idf-lib.git 4 | -------------------------------------------------------------------------------- /components/ina219/component.mk: -------------------------------------------------------------------------------- 1 | COMPONENT_ADD_INCLUDEDIRS = . 2 | COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers 3 | -------------------------------------------------------------------------------- /components/esp_idf_lib_helpers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | INCLUDE_DIRS . 3 | REQUIRES freertos 4 | ) 5 | -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/main/*.*) 2 | 3 | idf_component_register(SRCS ${app_sources}) 4 | -------------------------------------------------------------------------------- /components/ina219/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS ina219.c 3 | INCLUDE_DIRS . 4 | REQUIRES i2cdev log esp_idf_lib_helpers 5 | ) 6 | -------------------------------------------------------------------------------- /_data/menu.yml: -------------------------------------------------------------------------------- 1 | - xyzroe-blog: 2 | title: "xyzroe's blog" 3 | url: https://xyzroe.cc 4 | position: 1 5 | - git-hub-repo: 6 | title: "GitHub Repo" 7 | url: https://github.com/xyzroe/ZigUSB_C6 8 | position: 2 -------------------------------------------------------------------------------- /components/esp_idf_lib_helpers/component.mk: -------------------------------------------------------------------------------- 1 | COMPONENT_ADD_INCLUDEDIRS = . 2 | 3 | ifdef CONFIG_IDF_TARGET_ESP8266 4 | COMPONENT_DEPENDS = esp8266 freertos 5 | else 6 | COMPONENT_DEPENDS = freertos 7 | endif 8 | 9 | -------------------------------------------------------------------------------- /components/i2cdev/component.mk: -------------------------------------------------------------------------------- 1 | COMPONENT_ADD_INCLUDEDIRS = . 2 | 3 | ifdef CONFIG_IDF_TARGET_ESP8266 4 | COMPONENT_DEPENDS = esp8266 freertos esp_idf_lib_helpers 5 | else 6 | COMPONENT_DEPENDS = driver freertos esp_idf_lib_helpers 7 | endif 8 | -------------------------------------------------------------------------------- /main/ota.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | esp_err_t zb_ota_upgrade_status_handler(esp_zb_zcl_ota_upgrade_value_message_t message); 10 | 11 | #ifdef __cplusplus 12 | } // extern "C" 13 | #endif -------------------------------------------------------------------------------- /main/idf_component.yml: -------------------------------------------------------------------------------- 1 | ## IDF Component Manager Manifest File 2 | dependencies: 3 | espressif/button: "^3.2.0" 4 | espressif/esp-zboss-lib: "1.5.0" 5 | espressif/esp-zigbee-lib: "1.5.0" 6 | espressif/zlib: "1.3.0" 7 | ## Required IDF version 8 | idf: 9 | version: "5.3.1" 10 | -------------------------------------------------------------------------------- /components/i2cdev/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(${IDF_TARGET} STREQUAL esp8266) 2 | set(req esp8266 freertos esp_idf_lib_helpers) 3 | else() 4 | set(req driver freertos esp_idf_lib_helpers) 5 | endif() 6 | 7 | idf_component_register( 8 | SRCS i2cdev.c 9 | INCLUDE_DIRS . 10 | REQUIRES ${req} 11 | ) 12 | -------------------------------------------------------------------------------- /components/esp_idf_lib_helpers/.eil.yml: -------------------------------------------------------------------------------- 1 | name: esp_idf_lib_helpers 2 | description: Common support library for esp-idf-lib 3 | version: 1.2.0 4 | groups: 5 | - common 6 | code_owners: 7 | - trombik 8 | - UncleRus 9 | depends: 10 | - freertos 11 | thread_safe: n/a 12 | targets: 13 | - esp32 14 | - esp8266 15 | - esp32s2 16 | - esp32c3 17 | license: ISC 18 | copyrights: 19 | - name: trombik 20 | year: 2019 21 | -------------------------------------------------------------------------------- /components/i2cdev/.eil.yml: -------------------------------------------------------------------------------- 1 | name: i2cdev 2 | description: ESP-IDF I2C master thread-safe utilities 3 | version: 1.5.0 4 | groups: 5 | - common 6 | code_owners: 7 | - UncleRus 8 | depends: 9 | - driver 10 | - freertos 11 | - esp_idf_lib_helpers 12 | thread_safe: yes 13 | targets: 14 | - esp32 15 | - esp8266 16 | - esp32s2 17 | - esp32c3 18 | license: MIT 19 | copyrights: 20 | - name: UncleRus 21 | year: 2018 22 | -------------------------------------------------------------------------------- /components/ina219/.eil.yml: -------------------------------------------------------------------------------- 1 | name: ina219 2 | description: Driver for INA219/INA220 bidirectional current/power monitor 3 | version: 1.0.0 4 | groups: 5 | - current 6 | code_owners: UncleRus 7 | depends: 8 | - i2cdev 9 | - log 10 | - esp_idf_lib_helpers 11 | thread_safe: yes 12 | targets: 13 | - esp32 14 | - esp8266 15 | - esp32s2 16 | - esp32c3 17 | license: BSD-3 18 | copyrights: 19 | - name: UncleRus 20 | year: 2019 21 | -------------------------------------------------------------------------------- /components/i2cdev/Kconfig: -------------------------------------------------------------------------------- 1 | menu "I2C" 2 | 3 | config I2CDEV_TIMEOUT 4 | int "I2C transaction timeout, milliseconds" 5 | default 1000 6 | range 10 5000 7 | 8 | config I2CDEV_NOLOCK 9 | bool "Disable the use of mutexes" 10 | default n 11 | help 12 | Attention! After enabling this option, all I2C device 13 | drivers will become non-thread safe. 14 | Use this option if you need to access your I2C devices 15 | from interrupt handlers. 16 | 17 | endmenu -------------------------------------------------------------------------------- /partitions.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap 3 | nvs, data, nvs, , 0x6000, 4 | otadata, data, ota, , 0x2000, 5 | phy_init, data, phy, , 0x1000, 6 | zb_storage, data, fat, , 16K, 7 | zb_fct, data, fat, , 1K, 8 | ota_0, app, ota_0, , 1700K, 9 | ota_1, app, ota_1, , 1700K, 10 | storage, data, spiffs, , 500K, 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | compile_commands.json 3 | *.swp 4 | 5 | # ESP-IDF default build directory name 6 | build 7 | build/ 8 | build_*/ 9 | sdkconfig.* 10 | sdkconfig.defaults 11 | dependencies.lock 12 | managed_components 13 | __pycache__/ 14 | out/ 15 | _build/ 16 | 17 | pytest_embedded_log/ 18 | # VS Code Settings 19 | #.vscode/ 20 | 21 | # clangd set 22 | .clangd 23 | compile_commands.json 24 | .clang-format 25 | 26 | # pre-commit 27 | .pre-commit-config.yaml 28 | 29 | output/ 30 | 31 | # Commit message 32 | commit.md 33 | 34 | _site/ 35 | ./vscode/settings.json 36 | Gemfile.lock -------------------------------------------------------------------------------- /main/tools.h: -------------------------------------------------------------------------------- 1 | #ifndef TOOLS_H 2 | #define TOOLS_H 3 | 4 | #include 5 | #include 6 | 7 | void get_rtc_time(); 8 | bool int_to_bool(int32_t value); 9 | 10 | void setup_NVS(); 11 | int read_NVS(const char *nvs_key); 12 | bool write_NVS(const char *nvs_key, int value); 13 | 14 | const char *get_endpoint_name(int endpoint); 15 | float random_float(float min, float max); 16 | float round_to_4_decimals(float value); 17 | 18 | void set_zcl_string(char *buffer, char *value); 19 | 20 | void print_chip_info(); 21 | void heap_stats(); 22 | 23 | void debug_task(void *pvParameters); 24 | 25 | #endif // TOOLS_H -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | title: ZigUSB_C6 2 | description: Zigbee USB power monitor and switch based on ESP32 C6 3 | 4 | email: xyzroe@mind.in.ua 5 | avatar: https://avatars.githubusercontent.com/u/6440415?v=4 6 | favicon: https://xyzroe.cc/favicon.ico 7 | url: "https://xyzroe.cc" 8 | remote_theme: sylhare/Type-on-Strap 9 | cookie_consent: true 10 | google_analytics: G-5JP6L4L4DL 11 | color_theme: auto 12 | footer_text: 'xyzroe © 2022-2024
Creative Commons License' 13 | plugins: 14 | - jekyll-remote-theme -------------------------------------------------------------------------------- /components/esp_idf_lib_helpers/ets_sys.h: -------------------------------------------------------------------------------- 1 | #if CONFIG_IDF_TARGET_ESP32 2 | #include 3 | #elif CONFIG_IDF_TARGET_ESP32C2 4 | #include 5 | #elif CONFIG_IDF_TARGET_ESP32C3 6 | #include 7 | #elif CONFIG_IDF_TARGET_ESP32C6 8 | #include 9 | #elif CONFIG_IDF_TARGET_ESP32H2 10 | #include 11 | #elif CONFIG_IDF_TARGET_ESP32H4 12 | #include 13 | #elif CONFIG_IDF_TARGET_ESP32S2 14 | #include 15 | #elif CONFIG_IDF_TARGET_ESP32S3 16 | #include 17 | #elif CONFIG_IDF_TARGET_ESP8266 18 | #include 19 | #else 20 | #error "ets_sys: Unknown target" 21 | #endif 22 | -------------------------------------------------------------------------------- /main/perf.h: -------------------------------------------------------------------------------- 1 | #ifndef PERF_H 2 | #define PERF_H 3 | 4 | void init_outputs(); 5 | // void init_pullup_i2c_pins(); 6 | 7 | void usb_driver_set_power(bool state); 8 | 9 | void led_task(void *pvParameters); 10 | void int_led_blink(); 11 | void ext_led_action(int mode); 12 | 13 | void register_alarm_input(); 14 | static void alarm_input_active_cb(void *arg, void *usr_data); 15 | static void alarm_input_deactive_cb(void *arg, void *usr_data); 16 | 17 | void register_button(); 18 | static void button_single_click_cb(void *arg, void *usr_data); 19 | static void button_long_press_cb(void *arg, void *usr_data); 20 | 21 | void int_temp_task(void *pvParameters); 22 | 23 | void test_task(void *pvParameters); 24 | 25 | void ina219_task(void *pvParameters); 26 | 27 | #endif // PERF_H -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: xyzroe 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: xyzroe 14 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] -------------------------------------------------------------------------------- /components/esp_idf_lib_helpers/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Tomoyuki Sakurai 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Windows", 5 | "cStandard": "c11", 6 | "cppStandard": "c++17", 7 | "includePath": [ 8 | "${config:idf.espIdfPath}/components/**", 9 | "${workspaceFolder}/**" 10 | ], 11 | "browse": { 12 | "path": [ 13 | "${config:idf.espIdfPath}/components" 14 | ], 15 | "limitSymbolsToIncludedHeaders": false 16 | }, 17 | "compileCommands": "${workspaceFolder}/build/compile_commands.json", 18 | "compilerPath": "${config:idf.toolsPath}/tools/riscv32-esp-elf/esp-13.2.0_20240530/riscv32-esp-elf/bin/riscv32-esp-elf-gcc" 19 | } 20 | ], 21 | "version": 4 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/update_readme.yml: -------------------------------------------------------------------------------- 1 | name: Update README 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | paths: 9 | - 'index.md' 10 | workflow_dispatch: 11 | 12 | jobs: 13 | update-readme: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v2 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v2 22 | with: 23 | python-version: '3.x' 24 | 25 | - name: Install dependencies 26 | run: | 27 | python -m pip install --upgrade pip 28 | 29 | - name: Run pages.py script 30 | run: | 31 | cd .github/scripts/ 32 | python pages.py 33 | 34 | - name: Commit and push changes 35 | run: | 36 | git config --global user.name 'github-actions[bot]' 37 | git config --global user.email 'github-actions[bot]@users.noreply.github.com' 38 | git add README.md 39 | git commit -m 'Update README.md from index.md' 40 | git push 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /components/i2cdev/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate must be in your project's CMakeLists 2 | # in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.16) 4 | 5 | 6 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 7 | project(ZigUSB) 8 | 9 | # Add a custom command that will be executed before building the main target 10 | add_custom_target(PreBuildCommand 11 | COMMAND ${CMAKE_COMMAND} -E echo "Executing update version script..." 12 | COMMAND bash ${CMAKE_SOURCE_DIR}/tools/update_version.sh 13 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 14 | COMMENT "Update version script finish!" 15 | ) 16 | 17 | # Add a dependency of the main project on PreBuildCommand build target 18 | add_dependencies(${CMAKE_PROJECT_NAME}.elf PreBuildCommand) 19 | 20 | # Define a custom target for the OTA script 21 | add_custom_target(make_ota ALL 22 | COMMAND ${CMAKE_COMMAND} -E echo "Executing make OTA script..." 23 | COMMAND bash ${CMAKE_SOURCE_DIR}/tools/make_ota.sh 24 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 25 | COMMENT "Make OTA script finish!" 26 | ) 27 | 28 | # Add a dependency of make_ota target on the main project build target 29 | add_dependencies(make_ota ${CMAKE_PROJECT_NAME}.elf) -------------------------------------------------------------------------------- /main/main.h: -------------------------------------------------------------------------------- 1 | #ifndef ZIGUSB_H 2 | #define ZIGUSB_H 3 | 4 | #include 5 | #include 6 | #include "driver/gpio.h" 7 | #include "esp_zigbee_core.h" 8 | 9 | void int_led_blink(); 10 | void ext_led_action(int mode); 11 | 12 | typedef struct 13 | { 14 | bool USB_state; 15 | bool ext_led_mode; 16 | bool int_led_mode; 17 | bool alarm_state; 18 | int start_up_on_off; 19 | } DataStructure; 20 | 21 | // #define TEST_MODE 22 | 23 | extern float led_hz; 24 | extern char strftime_buf[64]; 25 | extern DataStructure data; 26 | 27 | extern uint16_t manuf_id; 28 | extern char manufacturer[16]; 29 | extern char model[16]; 30 | extern char firmware_version[16]; 31 | extern char firmware_date[16]; 32 | extern bool time_updated; 33 | extern bool connected; 34 | 35 | extern uint16_t power; 36 | extern uint16_t voltage; 37 | extern uint16_t current; 38 | extern uint16_t CPU_temp; 39 | extern uint16_t uptime; 40 | extern float ina_bus_voltage; 41 | extern float ina_shunt_voltage; 42 | extern float ina_current; 43 | extern float ina_power; 44 | 45 | extern TaskHandle_t ledTaskHandle; 46 | 47 | typedef enum 48 | { 49 | ATTRIBUTE_ALL, 50 | ATTRIBUTE_TEMP, 51 | ATTRIBUTE_ELECTRO 52 | } attribute_t; 53 | 54 | void app_main(void); 55 | 56 | #endif // ZIGUSB_H 57 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: ESP-IDF Build 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | workflow_dispatch: 8 | push: 9 | tags: 10 | - "*" 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | 19 | - name: Setup Python 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: '3.7' 23 | 24 | - name: Install ESP-IDF 25 | run: | 26 | git clone --recursive -b v5.3.1 https://github.com/espressif/esp-idf.git 27 | cd esp-idf 28 | ./install.sh 29 | . ./export.sh 30 | 31 | - name: Install IDF Component Manager and dependencies 32 | run: | 33 | . ./esp-idf/export.sh 34 | python -m pip install idf-component-manager 35 | idf.py reconfigure 36 | 37 | - name: Install esptool 38 | run: | 39 | python -m pip install esptool 40 | 41 | - name: Build 42 | id: build 43 | run: | 44 | . ./esp-idf/export.sh 45 | idf.py build 46 | 47 | - name: Make OTA and move BIN files 48 | run: | 49 | . ./tools/make_ota.sh 50 | 51 | - name: Release 52 | uses: softprops/action-gh-release@v1 53 | with: 54 | generate_release_notes: true 55 | name: "${{ env.version }} (${{ env.build_date }})" 56 | files: | 57 | output/ZigUSB_C6.ota 58 | output/ZigUSB_C6.bin 59 | -------------------------------------------------------------------------------- /tools/update_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | current_date=$(date +%Y%m%d) 4 | 5 | # Check if the script is being run in a GitHub workflow 6 | if [ "$GITHUB_ACTIONS" = true ]; then 7 | # Get the latest tag 8 | latest_tag=$(git describe --tags --abbrev=0) 9 | # Extract the version number from the latest tag 10 | version_number=${latest_tag} 11 | 12 | else 13 | # Set the first release date 14 | first_release_date="20240701" 15 | 16 | # Get the current date 17 | current_date=$(date +%Y%m%d) 18 | 19 | # Calculate the version number 20 | version_number=$((current_date - first_release_date)) 21 | if [ $version_number -lt 1 ]; then 22 | version_number=1 23 | fi 24 | fi 25 | 26 | # Convert the version number to hexadecimal 27 | hex_version=$(printf "%08X" $version_number) 28 | 29 | # Print the new version number, HEX version, date 30 | echo "New version number: $version_number" 31 | echo "HEX: 0x$hex_version" 32 | echo "build date: $current_date" 33 | 34 | # Set the output date variable 35 | echo "version=$version_number" >> $GITHUB_ENV 36 | # Set the output date variable 37 | echo "build_date=$current_date" >> $GITHUB_ENV 38 | 39 | # Update the device version in const.h using awk 40 | awk -v hex_version="$hex_version" '/OTA_FW_VERSION/ {sub(/0x[0-9A-F]*/, "0x" hex_version)} 1' main/const.h > temp && mv temp main/const.h 41 | awk -v fw_date="$current_date" '/FW_BUILD_DATE/ {sub(/"[^"]*"/, "\"" fw_date "\"")} 1' main/const.h > temp && mv temp main/const.h -------------------------------------------------------------------------------- /tools/make_ota.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Set the script to exit on any errors 4 | set -e 5 | 6 | # Description: This script creates an OTA file from the ZigUSB.bin file. 7 | cd ./tools 8 | 9 | # Install the required Python packages 10 | python3 -m pip install -q zigpy 11 | 12 | # Read the values from const.h 13 | MANUFACTURER=$(grep -o '#define\s\+OTA_UPGRADE_MANUFACTURER\s\+0x[0-9a-fA-F]\+' ../main/const.h | awk '{print $3}') 14 | IMAGE_TYPE=$(grep -o '#define\s\+OTA_UPGRADE_IMAGE_TYPE\s\+0x[0-9a-fA-F]\+' ../main/const.h | awk '{print $3}') 15 | FILE_VERSION=$(grep -o '#define\s\+OTA_FW_VERSION\s\+0x[0-9a-fA-F]\+' ../main/const.h | awk '{print $3}') 16 | 17 | # Check if the ZigUSB.bin file exists 18 | if [ ! -f "../build/ZigUSB.bin" ]; then 19 | echo "ZigUSB.bin file not found!" 20 | exit 1 21 | fi 22 | 23 | # Print the values 24 | echo "M: $MANUFACTURER | IT: $IMAGE_TYPE | FV: $FILE_VERSION"; 25 | 26 | # Create the output folder if it doesn't exist yet 27 | mkdir -p ../output 28 | 29 | # Create the OTA file 30 | python3 create-ota.py -m "$MANUFACTURER" -i "$IMAGE_TYPE" -v "$FILE_VERSION" ../build/ZigUSB.bin ../output/ZigUSB_C6.ota 31 | 32 | # Create a combined binary file 33 | esptool.py --chip esp32 merge_bin -o ../output/ZigUSB_C6.bin \ 34 | --flash_mode dio --flash_freq 40m --flash_size 4MB \ 35 | 0x0 ../build/bootloader/bootloader.bin \ 36 | 0x8000 ../build/partition_table/partition-table.bin \ 37 | 0xf000 ../build/ota_data_initial.bin \ 38 | 0x20000 ../build/ZigUSB.bin 39 | 40 | echo "OTA file and combined binary created successfully! Version: $FILE_VERSION" -------------------------------------------------------------------------------- /components/ina219/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Ruslan V. Uss 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of itscontributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /.github/scripts/pages.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import subprocess 4 | 5 | # Step 1: Read the content of index.md 6 | with open('../../index.md', 'r', encoding='utf-8') as file: 7 | index_content = file.read() 8 | 9 | # Step 2: Remove the block --- 10 | index_content = re.sub(r'---\nlayout: page\nhide_title: true\nhide: true\n---\n', '', index_content) 11 | 12 | # Step 3: Get the repository name using Git 13 | try: 14 | repo_name = subprocess.check_output(['git', 'config', '--get', 'remote.origin.url']).decode('utf-8').strip() 15 | repo_name = repo_name.split('/')[-1].replace('.git', '') 16 | except subprocess.CalledProcessError: 17 | repo_name = 'not found repository name' 18 | 19 | # Step 4: Replace the content of the ### Web Flasher block 20 | index_content = re.sub(r'(### Web Flasher\n)(.*?)(?=\n# |\n## |\n### |\n#### |\n##### )', 21 | rf'\1\nGo to [xyzroe.cc/{repo_name}](https://xyzroe.cc/{repo_name}).\n', 22 | index_content, flags=re.DOTALL) 23 | 24 | # Step 5: Check if the file README.md exists 25 | if os.path.exists('../../README.md'): 26 | # Step 6: Delete the file if it exists 27 | os.remove('../../README.md') 28 | 29 | # Step 6: Create a list to store the content of README.md and add the title 30 | readme_content = [] 31 | readme_content.append(f"# {repo_name}\n") 32 | 33 | # Step 7: Add the modified content from index.md to README.md 34 | readme_content.append(index_content) 35 | 36 | # Step 8: Save the changes to README.md 37 | with open('../../README.md', 'w', encoding='utf-8') as file: 38 | file.writelines(readme_content) -------------------------------------------------------------------------------- /CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | ### How to Contribute 2 | 3 | We welcome contributions to the ZigUSB C6 project! Here’s how you can get involved: 4 | 5 | 1. **Development Environment**: 6 | - All development is done using Visual Studio Code (VSCode) with the ESP-IDF extension. 7 | - Ensure you have the ESP-IDF environment set up correctly. You can follow the [ESP-IDF setup guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html) for detailed instructions. 8 | 9 | 2. **Cloning the Repository**: 10 | - Clone the repository to your local machine using: 11 | ```sh 12 | git clone https://github.com/xyzroe/ZigUSB_C6.git 13 | cd ZigUSB_C6 14 | ``` 15 | 16 | 3. **Building the Firmware**: 17 | - The firmware is automatically built on GitHub after each commit using the `commit.sh` script. 18 | - To manually build the firmware locally, use the following commands: 19 | ```sh 20 | ./commit.sh 21 | ``` 22 | 23 | 4. **Submitting Changes**: 24 | - Create a new branch for your feature or bug fix: 25 | ```sh 26 | git checkout -b feature-name 27 | ``` 28 | - Make your changes and commit them with a descriptive message: 29 | ```sh 30 | git commit -m "Description of your changes" 31 | ``` 32 | - Push your changes to your forked repository: 33 | ```sh 34 | git push origin feature-name 35 | ``` 36 | - Open a pull request on GitHub, describing the changes you have made and why they should be merged. 37 | 38 | 5. **Code Reviews**: 39 | - All contributions will be reviewed by the maintainers. Please be responsive to feedback and make any necessary changes. 40 | 41 | By following these steps, you can help improve the ZigUSB C6 project and contribute to its success. Thank you for your support! -------------------------------------------------------------------------------- /main/main.c: -------------------------------------------------------------------------------- 1 | 2 | #include "esp_check.h" 3 | #include "esp_err.h" 4 | #include "esp_log.h" 5 | #include "nvs_flash.h" 6 | #include "string.h" 7 | #include "freertos/FreeRTOS.h" 8 | #include "freertos/task.h" 9 | #include "zcl/esp_zigbee_zcl_common.h" 10 | #include "i2cdev.h" 11 | #include "ina219.h" 12 | #include 13 | #include 14 | 15 | #include "ha/esp_zigbee_ha_standard.h" 16 | #include "esp_timer.h" 17 | #include "esp_ota_ops.h" 18 | #include "zboss_api.h" 19 | #include "zcl/esp_zigbee_zcl_command.h" 20 | #include "zcl/zb_zcl_common.h" 21 | 22 | #include "main.h" 23 | #include "const.h" 24 | #include "tools.h" 25 | #include "perf.h" 26 | #include "zigbee.h" 27 | 28 | /*------ Global definitions -----------*/ 29 | float led_hz = 0; 30 | char strftime_buf[64]; 31 | DataStructure data; 32 | 33 | uint16_t manuf_id = OTA_UPGRADE_MANUFACTURER; 34 | char manufacturer[16]; 35 | char model[16]; 36 | char firmware_version[16]; 37 | char firmware_date[16]; 38 | bool time_updated = false; 39 | bool connected = false; 40 | 41 | uint16_t power = 0; 42 | uint16_t voltage = 0; 43 | uint16_t current = 0; 44 | uint16_t CPU_temp = 0; 45 | uint16_t uptime = 0; 46 | float ina_bus_voltage = 0; 47 | float ina_shunt_voltage = 0; 48 | float ina_current = 0; 49 | float ina_power = 0; 50 | 51 | TaskHandle_t ledTaskHandle = NULL; 52 | 53 | void app_main(void) 54 | { 55 | 56 | ESP_LOGW(__func__, "FW verison 0x%x (%d) date: %s", OTA_FW_VERSION, OTA_FW_VERSION, FW_BUILD_DATE); 57 | 58 | setup_NVS(); 59 | 60 | print_chip_info(); 61 | 62 | register_button(BTN_GPIO_1); /* Button from v0.3 */ 63 | register_button(BTN_GPIO_2); /* Button till v0.2 */ 64 | register_alarm_input(); 65 | 66 | data.int_led_mode = read_NVS("int_led_mode"); 67 | data.ext_led_mode = read_NVS("ext_led_mode"); 68 | data.USB_state = read_NVS("USB_state"); 69 | data.start_up_on_off = read_NVS("start_up_on_off"); 70 | 71 | init_outputs(); 72 | 73 | ESP_ERROR_CHECK(i2cdev_init()); 74 | 75 | zigbee_setup(); 76 | 77 | xTaskCreate(led_task, "led_task", 4096, NULL, 5, &ledTaskHandle); 78 | xTaskCreate(force_update, "force_update_task", 4096, NULL, 4, NULL); 79 | xTaskCreate(int_temp_task, "int_temp_task", 4096, NULL, 3, NULL); 80 | xTaskCreate(ina219_task, "ina219_task", 4096, NULL, 2, NULL); 81 | 82 | // xTaskCreate(test_task, "test_task", 4096, NULL, 2, NULL); 83 | xTaskCreate(debug_task, "debug_task", 4096, NULL, 3, NULL); 84 | } 85 | -------------------------------------------------------------------------------- /main/zigbee.h: -------------------------------------------------------------------------------- 1 | #ifndef ZIGBEE_H 2 | #define ZIGBEE_H 3 | 4 | #include "esp_zigbee_core.h" 5 | #include "const.h" 6 | 7 | #ifdef __cplusplus 8 | extern "C" 9 | { 10 | #endif 11 | 12 | #define ESP_ZB_ZR_CONFIG() \ 13 | { \ 14 | .esp_zb_role = ESP_ZB_DEVICE_TYPE_ROUTER, \ 15 | .install_code_policy = INSTALLCODE_POLICY_ENABLE, \ 16 | .nwk_cfg.zczr_cfg = { \ 17 | .max_children = MAX_CHILDREN, \ 18 | }, \ 19 | } 20 | 21 | #define ESP_ZB_DEFAULT_RADIO_CONFIG() \ 22 | { \ 23 | .radio_mode = ZB_RADIO_MODE_NATIVE, \ 24 | } 25 | 26 | #define ESP_ZB_DEFAULT_HOST_CONFIG() \ 27 | { \ 28 | .host_connection_mode = ZB_HOST_CONNECTION_MODE_NONE, \ 29 | } 30 | 31 | #if !defined CONFIG_ZB_ZCZR 32 | #error Define ZB_ZCZR in idf.py menuconfig to compile light (Router) source code. 33 | #endif 34 | 35 | extern char strftime_buf[64]; 36 | extern uint16_t manuf_id; 37 | extern char manufacturer[16]; 38 | extern char model[16]; 39 | extern char firmware_version[16]; 40 | extern char firmware_date[16]; 41 | extern bool time_updated; 42 | extern bool connected; 43 | 44 | void zigbee_setup(); 45 | 46 | void update_attributes(attribute_t attribute); 47 | void send_bin_cfg_option(int endpoint, bool value); 48 | void send_alarm_state(bool alarm); 49 | void force_update(); 50 | void read_server_time(); 51 | 52 | static void esp_zb_task(void *pvParameters); 53 | static void update_attribute_value(uint8_t endpoint, uint16_t cluster_id, uint8_t role, uint16_t attr_id, void *value, const char *attr_name); 54 | 55 | void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct); 56 | static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask); 57 | 58 | static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message); 59 | static esp_err_t zb_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_resp_message_t *message); 60 | //static esp_err_t zb_ota_upgrade_status_handler(esp_zb_zcl_ota_upgrade_value_message_t message); 61 | static esp_err_t zb_ota_upgrade_query_image_resp_handler(esp_zb_zcl_ota_upgrade_query_image_resp_message_t message); 62 | static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message); 63 | 64 | #ifdef __cplusplus 65 | } 66 | #endif 67 | 68 | #endif // ZIGBEE_H -------------------------------------------------------------------------------- /main/const.h: -------------------------------------------------------------------------------- 1 | #ifndef CONST_H 2 | #define CONST_H 3 | 4 | /* Text constants */ 5 | #define T_STATUS_FAILED "Failed!" 6 | #define T_STATUS_DONE "Done" 7 | 8 | /* Number constants */ 9 | #define LED_ON_STATE 0 10 | #define LED_OFF_STATE 1 11 | 12 | /* Zigbee configuration */ 13 | #define OTA_UPGRADE_MANUFACTURER 0x3443 /* The attribute indicates the value for the manufacturer of the device */ 14 | #define OTA_UPGRADE_IMAGE_TYPE 0x1011 /* The attribute indicates the type of the image */ 15 | #define OTA_UPGRADE_HW_VERSION 0x0101 /* The parameter indicates the version of hardware */ 16 | #define OTA_UPGRADE_MAX_DATA_SIZE 64 /* The parameter indicates the maximum data size of query block image */ 17 | 18 | #define MAX_CHILDREN 10 /* the max amount of connected devices */ 19 | #define INSTALLCODE_POLICY_ENABLE false /* enable the install code policy for security */ 20 | #define ESP_ZB_PRIMARY_CHANNEL_MASK ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK /* Zigbee primary channel mask use in the example */ 21 | 22 | #define HW_MANUFACTURER "xyzroe" /* The parameter indicates the manufacturer of the device */ 23 | #define HW_MODEL "ZigUSB_C6" /* The parameter indicates the model of the device */ 24 | 25 | #define SENSOR_ENDPOINT 1 /* the endpoint number for the sensor */ 26 | #define INT_LED_ENDPOINT 2 /* the endpoint number for the internal LED */ 27 | #define EXT_LED_ENDPOINT 3 /* the endpoint number for the external LED */ 28 | #define INV_USB_ENDPOINT 4 /* the endpoint number for the USB switch (inverted logic) */ 29 | 30 | #define OTA_FW_VERSION 0x00000133 /* The attribute indicates the version of the firmware */ 31 | #define FW_BUILD_DATE "20241108" /* The parameter indicates the build date of the firmware */ 32 | 33 | /* GPIO configuration */ 34 | #define BTN_GPIO_1 5 /* Button from v0.3 */ 35 | #define BTN_GPIO_2 9 /* Button till v0.2 */ 36 | #define ALARM_GPIO 10 /* INA219 alert pin */ 37 | #define EXT_LED_GPIO 7 /* External LED - yellow LED */ 38 | #define INT_LED_GPIO 8 /* Internal LED - blue LED */ 39 | #define USB_GPIO 3 /* USB switch control */ 40 | #define I2C_SDA_GPIO 0 /* I2C SDA */ 41 | #define I2C_SCL_GPIO 1 /* I2C SCL */ 42 | #define I2C_PORT 0 /* I2C port number */ 43 | #define I2C_ADDR INA219_ADDR_GND_GND /* I2C address */ 44 | #define SHUNT_RESISTOR_MILLI_OHM 100 /* Shunt resistor value */ 45 | 46 | /* Time constants */ 47 | #define INA219_INTERVAL 1000 /* Reading interval */ 48 | #define CPU_TEMP_INTERVAL 10000 /* Reading interval */ 49 | #define LONG_PRESS_TIME 5000 /* to make factory reset */ 50 | #define SHORT_PRESS_TIME 150 /* to toggle USB power */ 51 | #define UPDATE_ATTRIBUTE_INTERVAL 600000 /* 10 minutes to FORCE update all states */ 52 | #define WAIT_BEFORE_FIRST_UPDATE 15000 /* 15 seconds to wait before first update */ 53 | #define DEBUG_TASK_INTERVAL 60000 /* Debug task interval */ 54 | 55 | #endif // CONST_H 56 | -------------------------------------------------------------------------------- /tools/create-ota.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # create-ota - Create zlib-compressed Zigbee OTA file 3 | # Copyright 2023 Simon Arlott 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import argparse 19 | import functools 20 | import zlib 21 | 22 | import zigpy.ota 23 | 24 | 25 | def create(filename, manufacturer_id, image_type, file_version, header_string): 26 | with open(filename, "rb") as f: 27 | data = f.read() 28 | 29 | zobj = zlib.compressobj(level=zlib.Z_BEST_COMPRESSION) 30 | zdata = zobj.compress(data) 31 | zdata += zobj.flush() 32 | #zdata = data 33 | 34 | image = zigpy.ota.image.OTAImage( 35 | header=zigpy.ota.image.OTAImageHeader( 36 | upgrade_file_id=zigpy.ota.image.OTAImageHeader.MAGIC_VALUE, 37 | header_version=0x0100, 38 | header_length=0, 39 | field_control=zigpy.ota.image.FieldControl(0), 40 | 41 | manufacturer_id=manufacturer_id, 42 | image_type=image_type, 43 | file_version=file_version, 44 | 45 | stack_version=2, 46 | header_string=header_string[0:32], 47 | image_size=0, 48 | ), 49 | subelements=[ 50 | zigpy.ota.image.SubElement( 51 | tag_id=zigpy.ota.image.ElementTagId.UPGRADE_IMAGE, data=zdata, 52 | ) 53 | ], 54 | ) 55 | 56 | image.header.header_length = len(image.header.serialize()) 57 | image.header.image_size = image.header.header_length + len(image.subelements.serialize()) 58 | return image.serialize() 59 | 60 | if __name__ == "__main__": 61 | any_int = functools.wraps(int)(functools.partial(int, base=0)) 62 | parser = argparse.ArgumentParser(description="Create zlib-compressed Zigbee OTA file", 63 | epilog="Reads a firmware image file and outputs an OTA file on standard output") 64 | parser.add_argument("filename", metavar="INPUT", type=str, help="Firmware image filename") 65 | parser.add_argument("output", metavar="OUTPUT", type=str, help="OTA filename") 66 | parser.add_argument("-m", "--manufacturer_id", metavar="MANUFACTURER_ID", type=any_int, required=True, help="Manufacturer ID") 67 | parser.add_argument("-i", "--image_type", metavar="IMAGE_ID", type=any_int, required=True, help="Image ID") 68 | parser.add_argument("-v", "--file_version", metavar="VERSION", type=any_int, required=True, help="File version") 69 | parser.add_argument("-s", "--header_string", metavar="HEADER_STRING", type=str, default="", help="Header String") 70 | 71 | args = parser.parse_args() 72 | output = args.output 73 | del args.output 74 | 75 | data = create(**vars(args)) 76 | with open(output, "wb") as f: 77 | f.write(data) 78 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "idf.adapterTargetName": "esp32c6", 3 | "idf.portWin": "COM5", 4 | "idf.flashType": "UART", 5 | "files.associations": { 6 | "esp_zigbee_zcl_common.h": "c", 7 | "scd4x.h": "c", 8 | "zigbee_disconnected.h": "c", 9 | "zigbee_connected.h": "c", 10 | "zigbee_logo.h": "c", 11 | "array": "c", 12 | "string": "c", 13 | "string_view": "c", 14 | "vector": "c", 15 | "si7021.h": "c", 16 | "esp_idf_lib_helpers.h": "c", 17 | "aht.h": "c", 18 | "ssd1306.h": "c", 19 | "esp_zigbee_core.h": "c", 20 | "stdint.h": "c", 21 | "ssd1306_fonts.h": "c", 22 | "i2cdev.h": "c", 23 | "iot_button.h": "c", 24 | "*.xbm": "c", 25 | "esp_zigbee_zcl_carbon_dioxide_measurement.h": "c", 26 | "esp_check.h": "c", 27 | "ina219.h": "c", 28 | "temperature_sensor.h": "c", 29 | "esp_zigbee_ha_standard.h": "c", 30 | "*.tcc": "c", 31 | "optional": "c", 32 | "ratio": "c", 33 | "system_error": "c", 34 | "functional": "c", 35 | "regex": "c", 36 | "tuple": "c", 37 | "type_traits": "c", 38 | "utility": "c", 39 | "numbers": "c", 40 | "bit": "c", 41 | "charconv": "c", 42 | "numeric": "c", 43 | "limits": "c", 44 | "perf.h": "c", 45 | "zigusb.h": "c", 46 | "nvs_flash.h": "c", 47 | "task.h": "c", 48 | "esp_log.h": "c", 49 | "string.h": "c", 50 | "esp_zigbee_zcl_on_off_switch_config.h": "c", 51 | "esp_zigbee_zcl_on_off.h": "c", 52 | "esp_zigbee_type.h": "c", 53 | "esp_ota_ops.h": "c", 54 | "esp_zigbee_attribute.h": "c", 55 | "time.h": "c", 56 | "esp_timer.h": "c", 57 | "zboss_api.h": "c", 58 | "zboss_api_aps.h": "c", 59 | "esp_zigbee_zcl_command.h": "c", 60 | "esp_zigbee_aps.h": "c", 61 | "zb_zcl_common.h": "c", 62 | "tools.h": "c", 63 | "freertos.h": "c", 64 | "zigbee.h": "c" 65 | }, 66 | "editor.formatOnSave": false, 67 | "idf.projectDirPath": "${workspaceFolder}/firmware", 68 | "idf.port": "/dev/tty.usbmodemflip_Elevlox1", 69 | "idf.espIdfPath": "/Users/lost/esp/v5.3.1/esp-idf", 70 | "idf.pythonBinPath": "/Users/lost/.espressif/python_env/idf5.3_py3.9_env/bin/python", 71 | "idf.toolsPath": "/Users/lost/.espressif", 72 | "idf.customExtraPaths": "/Users/lost/.espressif/tools/xtensa-esp-elf-gdb/14.2_20240403/xtensa-esp-elf-gdb/bin:/Users/lost/.espressif/tools/riscv32-esp-elf-gdb/14.2_20240403/riscv32-esp-elf-gdb/bin:/Users/lost/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20240530/xtensa-esp-elf/bin:/Users/lost/.espressif/tools/riscv32-esp-elf/esp-13.2.0_20240530/riscv32-esp-elf/bin:/Users/lost/.espressif/tools/esp32ulp-elf/2.38_20240113/esp32ulp-elf/bin:/Users/lost/.espressif/tools/cmake/3.24.0/CMake.app/Contents/bin:/Users/lost/.espressif/tools/openocd-esp32/v0.12.0-esp32-20240318/openocd-esp32/bin:/Users/lost/.espressif/tools/ninja/1.11.1:/Users/lost/.espressif/tools/esp-rom-elfs/20240305", 73 | "idf.customExtraVars": { 74 | "OPENOCD_SCRIPTS": "/Users/lost/.espressif/tools/openocd-esp32/v0.12.0-esp32-20240318/openocd-esp32/share/openocd/scripts", 75 | "ESP_ROM_ELF_DIR": "/Users/lost/.espressif/tools/esp-rom-elfs/20240305/" 76 | }, 77 | "idf.gitPath": "git" 78 | } 79 | -------------------------------------------------------------------------------- /components/esp_idf_lib_helpers/esp_idf_lib_helpers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Tomoyuki Sakurai 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #if !defined(__ESP_IDF_LIB_HELPERS__H__) 18 | #define __ESP_IDF_LIB_HELPERS__H__ 19 | 20 | /* XXX this header file does not need to include freertos/FreeRTOS.h. 21 | * but without it, ESP8266 RTOS SDK does not include `sdkconfig.h` in correct 22 | * order. as this header depends on sdkconfig.h, sdkconfig.h must be included 23 | * first. however, the SDK includes this header first, then includes 24 | * `sdkconfig.h` when freertos/FreeRTOS.h is not explicitly included. an 25 | * evidence can be found in `build/${COMPONENT}/${COMPONENT}.d` in a failed 26 | * build. 27 | */ 28 | #include 29 | #include 30 | 31 | #if !defined(ESP_IDF_VERSION) || !defined(ESP_IDF_VERSION_VAL) 32 | #error Unknown ESP-IDF/ESP8266 RTOS SDK version 33 | #endif 34 | 35 | /* Minimal supported version for ESP32, ESP32S2 */ 36 | #define HELPER_ESP32_MIN_VER ESP_IDF_VERSION_VAL(3, 3, 5) 37 | /* Minimal supported version for ESP8266 */ 38 | #define HELPER_ESP8266_MIN_VER ESP_IDF_VERSION_VAL(3, 3, 0) 39 | 40 | /* HELPER_TARGET_IS_ESP32 41 | * 1 when the target is esp32 42 | */ 43 | #if defined(CONFIG_IDF_TARGET_ESP32) \ 44 | || defined(CONFIG_IDF_TARGET_ESP32S2) \ 45 | || defined(CONFIG_IDF_TARGET_ESP32S3) \ 46 | || defined(CONFIG_IDF_TARGET_ESP32C2) \ 47 | || defined(CONFIG_IDF_TARGET_ESP32C3) \ 48 | || defined(CONFIG_IDF_TARGET_ESP32C6) \ 49 | || defined(CONFIG_IDF_TARGET_ESP32H2) 50 | #define HELPER_TARGET_IS_ESP32 (1) 51 | #define HELPER_TARGET_IS_ESP8266 (0) 52 | 53 | /* HELPER_TARGET_IS_ESP8266 54 | * 1 when the target is esp8266 55 | */ 56 | #elif defined(CONFIG_IDF_TARGET_ESP8266) 57 | #define HELPER_TARGET_IS_ESP32 (0) 58 | #define HELPER_TARGET_IS_ESP8266 (1) 59 | #else 60 | #error BUG: cannot determine the target 61 | #endif 62 | 63 | #if HELPER_TARGET_IS_ESP32 && ESP_IDF_VERSION < HELPER_ESP32_MIN_VER 64 | #error Unsupported ESP-IDF version. Please update! 65 | #endif 66 | 67 | #if HELPER_TARGET_IS_ESP8266 && ESP_IDF_VERSION < HELPER_ESP8266_MIN_VER 68 | #error Unsupported ESP8266 RTOS SDK version. Please update! 69 | #endif 70 | 71 | /* show the actual values for debugging */ 72 | #if DEBUG 73 | #define VALUE_TO_STRING(x) #x 74 | #define VALUE(x) VALUE_TO_STRING(x) 75 | #define VAR_NAME_VALUE(var) #var "=" VALUE(var) 76 | #pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32C3)) 77 | #pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32H2)) 78 | #pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32S2)) 79 | #pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32)) 80 | #pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP8266)) 81 | #pragma message(VAR_NAME_VALUE(ESP_IDF_VERSION_MAJOR)) 82 | #endif 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /tools/commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define colors 4 | RED='\033[0;31m' 5 | GREEN='\033[0;32m' 6 | YELLOW='\033[1;33m' 7 | NC='\033[0m' # No Color 8 | 9 | # Ask for confirmation to pull changes 10 | echo -e "${YELLOW}Do you want to pull the latest changes from the repository? 🔄 (Y/n)${NC}" 11 | read -r response 12 | response=${response:-y} # default 'yes' if empty 13 | if [[ "$response" =~ ^[Yy]$ ]]; then 14 | git pull 15 | echo -e "${GREEN}Changes pulled successfully. ✔️${NC}" 16 | else 17 | echo -e "${YELLOW}Pull skipped. Continuing without pulling changes. ⚠️${NC}" 18 | fi 19 | 20 | # Adding all changes to staging 21 | git add -A 22 | echo -e "${GREEN}All changes added to staging. ✔️${NC}" 23 | 24 | # Path to the version and commit message files 25 | COMMIT_MESSAGE_FILE="commit.md" 26 | 27 | # Get the latest tag 28 | latest_tag=$(git describe --tags --abbrev=0) 29 | 30 | # Check if any tags exist 31 | if [ -z "$latest_tag" ]; then 32 | echo -e "${RED}No tags found. ❌${NC}" 33 | git_version_number=1 34 | else 35 | 36 | # Extract the version number from the latest tag 37 | git_version_number=${latest_tag} 38 | fi 39 | 40 | # Set the first release date 41 | first_release_date="20240701" 42 | 43 | # Get the current date 44 | current_date=$(date +%Y%m%d) 45 | 46 | # Calculate the version number 47 | local_version_number=$((current_date - first_release_date)) 48 | if [ $local_version_number -lt 1 ]; then 49 | local_version_number=1 50 | fi 51 | 52 | if [ "$git_version_number" -lt "$local_version_number" ]; then 53 | version_number=$((local_version_number)) 54 | else 55 | version_number=$((git_version_number + 1)) 56 | fi 57 | 58 | tag=$version_number 59 | 60 | echo "Using tag to $tag" 61 | 62 | # Checking for commit message file 63 | if [ -f "$COMMIT_MESSAGE_FILE" ]; then 64 | echo -e "${YELLOW}Commit message file found. Do you want to use the existing commit message? (y/N) 📝${NC}" 65 | read -r useExistingMessage 66 | useExistingMessage=${useExistingMessage:-n} # default 'no' if empty 67 | if [[ "$useExistingMessage" =~ ^[Yy]$ ]]; then 68 | commitMessage=$(cat "$COMMIT_MESSAGE_FILE") 69 | # Prepend version to the commit message with a newline for separation 70 | formattedCommitMessage="${tag} 71 | ${commitMessage}" 72 | # Cleaning up the commit message file, if used 73 | if [ -f "$COMMIT_MESSAGE_FILE" ]; then 74 | tools/clean_file.sh "$COMMIT_MESSAGE_FILE" 75 | fi 76 | else 77 | echo -e "${YELLOW}Please enter your commit message: 📝${NC}" 78 | read -r commitMessage 79 | formattedCommitMessage="${commitMessage}" 80 | fi 81 | else 82 | echo -e "${YELLOW}Commit message file not found. Please enter your commit message: 📝${NC}" 83 | read -r commitMessage 84 | formattedCommitMessage="${commitMessage}" 85 | fi 86 | 87 | # Committing changes 88 | git commit -m "$formattedCommitMessage" 89 | echo -e "${GREEN}Changes committed with version prepended to message: ✔️${NC}" 90 | 91 | # Tagging process 92 | echo -e "${YELLOW}Do you want to create a new release by publishing a tag? 🏷️ (y/N)${NC}" 93 | read -r tagCommit 94 | tagCommit=${tagCommit:-n} # default 'no' if empty 95 | if [[ "$tagCommit" =~ ^[Yy]$ ]]; then 96 | git tag "$tag" 97 | echo -e "${GREEN}Tag assigned: $tag 🏷️${NC}" 98 | 99 | # Pushing changes and tag 100 | echo -e "${YELLOW}Do you want to push the changes and the new tag to the remote repository? 🚀 (Y/n)${NC}" 101 | read -r pushChanges 102 | pushChanges=${pushChanges:-y} # default 'yes' if empty 103 | if [[ "$pushChanges" =~ ^[Yy]$ ]]; then 104 | git push 105 | git push origin "$tag" 106 | echo -e "${GREEN}Changes and new tag pushed successfully. ✔️${NC}" 107 | else 108 | echo -e "${RED}Push of changes and tag skipped. ❌${NC}" 109 | fi 110 | else 111 | echo -e "${YELLOW}No new release will be created. Do you still want to push the changes? (Y/n) 🚀${NC}" 112 | read -r pushChanges 113 | pushChanges=${pushChanges:-y} # default 'yes' if empty 114 | if [[ "$pushChanges" =~ ^[Yy]$ ]]; then 115 | git push 116 | echo -e "${GREEN}Changes pushed successfully without creating a new release. ✔️${NC}" 117 | else 118 | echo -e "${RED}Push skipped. ❌${NC}" 119 | fi 120 | fi 121 | 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZigUSB_C6 2 | 3 |
4 | GitHub version 5 | GitHub Actions Workflow Status 6 | GitHub download 7 | GitHub Issues or Pull Requests 8 |
9 | 10 | The ZigUSB C6 project is an innovative solution designed to enhance the control and monitoring of USB-powered devices through Zigbee communication. This project aims to provide a seamless integration for smart home enthusiasts and professionals alike, enabling remote control, automation, and monitoring of USB devices in a Zigbee-enabled ecosystem. 11 | 12 | This project is based on the original ZigUSB but uses a modern chip and custom-developed firmware. 13 | 14 | Using this device, you can remotely control the power of the USB port to turn on or off the connected device. Additionally, you can monitor the current voltage and current. It also functions as a reliable Zigbee network router. 15 | 16 | Frequent use cases include converting a "dumb" USB lamp into a "smart" one, connecting modems/sticks/adapters that sometimes require a power reset, and monitoring the current consumption of any connected device. 17 | 18 | ### Key Features 19 | 20 | - **USB Power Control**: Remotely manage the power supply to USB devices, allowing for energy savings and enhanced device management. 21 | - **Zigbee Integration**: Fully compatible with Zigbee networks, facilitating easy integration into existing smart home setups. 22 | - **OTA Updates**: Support for Over-The-Air (OTA) firmware updates, ensuring the device remains up-to-date with the latest features and security enhancements. 23 | - **USB data transfer** is available. This may be needed when connecting a USB modem to a router that does not know how to manage USB power, and the modem may need to be rebooted. 24 | - Monitoring of voltage and current - INA219 chip. 25 | - For power management, USB switch - AP22804AW5-7 chip. 26 | - WT0132C6-S5 module ​​was used as the Zigbee chip. It's ESP32 C6 based module. 27 | - Designed for AK-N-12 case. 28 | 29 | #### To re-pairing or reset to factory defaults: 30 | 31 | **Hold touch button for more than 5 seconds** 32 | 33 | ### Project Goals 34 | 35 | ZigUSB C6 was created with the vision of making smart home automation more accessible and versatile. By providing a bridge between USB devices and Zigbee networks, it opens up new possibilities for device automation and control. Whether you're looking to remotely manage lighting, charge devices on a schedule, or integrate USB devices into complex automation routines, ZigUSB C6 offers the flexibility and reliability needed for modern smart homes. 36 | 37 | Stay tuned for updates as we continue to expand the capabilities of ZigUSB C6, and feel free to contribute to the project or suggest new features through our GitHub repository. 38 | 39 | ### Overview 40 | 41 |
42 | 43 | 44 |
45 | 46 | ### Photos 47 | 48 |
49 | 50 | 51 |
52 | 53 | ### Schematic 54 | 55 |
56 | 57 | #### zigbee2mqtt overview 58 | 59 |
60 | 61 | 62 |
63 | 64 | #### Home Assistant overview 65 | 66 |
67 | 68 |
69 | 70 | ### Hardware files 71 | 72 | - [iBOM page](https://raw.githubusercontent.com/xyzroe/ZigUSB_C6/master/hardware/iBOM.html) 🌍 73 | - [BOM file](https://raw.githubusercontent.com/xyzroe/ZigUSB_C6/master/hardware/BOM.csv) 📃 74 | - [Gerber zip](https://raw.githubusercontent.com/xyzroe/ZigUSB_C6/master/hardware/Gerber.zip) 🗂 75 | 76 | This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License 77 | 78 | ### Web Flasher 79 | 80 | Go to [xyzroe.cc/ZigUSB_C6](https://xyzroe.cc/ZigUSB_C6). 81 | 82 | #### Firmware Files 83 | 84 | All source files are available in this repository. Pre-built firmware files can be found in the [releases section](https://github.com/xyzroe/ZigUSB_C6/releases). 85 | 86 | ### OTA 87 | 88 | ##### zigbee2mqtt 89 | 90 | 1. Put firmware update (.\*ota) file next to config file 91 | 2. Create `index.json` file: 92 | 93 | ``` 94 | [ 95 | { 96 | "url": "ZigUSB_C6.ota", 97 | "force": true 98 | } 99 | ] 100 | ``` 101 | 102 | 3. Add config option to you zigbee2mqtt configuration.yaml file 103 | 104 | ``` 105 | ota: 106 | zigbee_ota_override_index_location: index.json 107 | ``` 108 | 109 | 4. Open OTA tab in z2m and click check update next to your device. 110 | 5. Check update process via web UI 111 | 112 | ##### homed 113 | 114 | 1. Put firmware update (.\*ota) file to "ota" folder next to config file 115 | 2. Open device page and click OTA button 116 | 3. Click refresh and then update 117 | 118 | ### Verified Supported Zigbee Systems 119 | 120 | - [zigbee2mqtt](https://www.zigbee2mqtt.io/) - Full support, no longer requires an [external converter](https://github.com/xyzroe/ZigUSB_C6/tree/main/external_converter/ZigUSB_C6.js) ⭐⭐⭐⭐⭐ 121 | - [HOMEd](https://wiki.homed.dev/page/HOMEd) - Partial support ⭐⭐⭐⭐ 122 | - [ZHA](https://www.home-assistant.io/integrations/zha/) - Partial support ⭐⭐⭐⭐ 123 | - Other systems must be tested. The device uses standard clusters and attributes, so most coordinators can support it out of the box. 124 | 125 | ### Where to buy? 126 |
127 | I sell on Lectronz 128 |
129 | I sell on Tindie 130 |
131 | 132 | ### Like ♥️? 133 | 134 | [![badges](https://badges.aleen42.com/src/buymeacoffee.svg)](https://www.buymeacoffee.com/xyzroe) 135 | [![badges](https://badges.aleen42.com/src/github.svg)](https://github.com/sponsors/xyzroe) 136 | [![badges](https://badges.aleen42.com/src/paypal.svg)](http://paypal.me/xyzroe) 137 | 138 | ### Contribute 🚀 139 | 140 | - [How-to](./CONTRIBUTE.md) 141 | 142 |
143 | ZigUSB_C6 is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License 144 | -------------------------------------------------------------------------------- /components/i2cdev/i2cdev.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2018 Ruslan V. Uss 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | 24 | /** 25 | * @file i2cdev.h 26 | * @defgroup i2cdev i2cdev 27 | * @{ 28 | * 29 | * ESP-IDF I2C master thread-safe functions for communication with I2C slave 30 | * 31 | * Copyright (c) 2018 Ruslan V. Uss 32 | * 33 | * MIT Licensed as described in the file LICENSE 34 | */ 35 | #ifndef __I2CDEV_H__ 36 | #define __I2CDEV_H__ 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #ifdef __cplusplus 45 | extern "C" { 46 | #endif 47 | 48 | #if HELPER_TARGET_IS_ESP8266 49 | 50 | #define I2CDEV_MAX_STRETCH_TIME 0xffffffff 51 | 52 | #else 53 | 54 | #include 55 | #if defined(I2C_TIME_OUT_VALUE_V) 56 | #define I2CDEV_MAX_STRETCH_TIME I2C_TIME_OUT_VALUE_V 57 | #elif defined(I2C_TIME_OUT_REG_V) 58 | #define I2CDEV_MAX_STRETCH_TIME I2C_TIME_OUT_REG_V 59 | #else 60 | #define I2CDEV_MAX_STRETCH_TIME 0x00ffffff 61 | #endif 62 | 63 | #endif /* HELPER_TARGET_IS_ESP8266 */ 64 | 65 | /** 66 | * I2C device descriptor 67 | */ 68 | typedef struct 69 | { 70 | i2c_port_t port; //!< I2C port number 71 | i2c_config_t cfg; //!< I2C driver configuration 72 | uint8_t addr; //!< Unshifted address 73 | SemaphoreHandle_t mutex; //!< Device mutex 74 | uint32_t timeout_ticks; /*!< HW I2C bus timeout (stretch time), in ticks. 80MHz APB clock 75 | ticks for ESP-IDF, CPU ticks for ESP8266. 76 | When this value is 0, I2CDEV_MAX_STRETCH_TIME will be used */ 77 | } i2c_dev_t; 78 | 79 | /** 80 | * I2C transaction type 81 | */ 82 | typedef enum { 83 | I2C_DEV_WRITE = 0, /**< Write operation */ 84 | I2C_DEV_READ /**< Read operation */ 85 | } i2c_dev_type_t; 86 | 87 | /** 88 | * @brief Init library 89 | * 90 | * The function must be called before any other 91 | * functions of this library. 92 | * 93 | * @return ESP_OK on success 94 | */ 95 | esp_err_t i2cdev_init(); 96 | 97 | /** 98 | * @brief Finish work with library 99 | * 100 | * Uninstall i2c drivers. 101 | * 102 | * @return ESP_OK on success 103 | */ 104 | esp_err_t i2cdev_done(); 105 | 106 | /** 107 | * @brief Create mutex for device descriptor 108 | * 109 | * This function does nothing if option CONFIG_I2CDEV_NOLOCK is enabled. 110 | * 111 | * @param dev Device descriptor 112 | * @return ESP_OK on success 113 | */ 114 | esp_err_t i2c_dev_create_mutex(i2c_dev_t *dev); 115 | 116 | /** 117 | * @brief Delete mutex for device descriptor 118 | * 119 | * This function does nothing if option CONFIG_I2CDEV_NOLOCK is enabled. 120 | * 121 | * @param dev Device descriptor 122 | * @return ESP_OK on success 123 | */ 124 | esp_err_t i2c_dev_delete_mutex(i2c_dev_t *dev); 125 | 126 | /** 127 | * @brief Take device mutex 128 | * 129 | * This function does nothing if option CONFIG_I2CDEV_NOLOCK is enabled. 130 | * 131 | * @param dev Device descriptor 132 | * @return ESP_OK on success 133 | */ 134 | esp_err_t i2c_dev_take_mutex(i2c_dev_t *dev); 135 | 136 | /** 137 | * @brief Give device mutex 138 | * 139 | * This function does nothing if option CONFIG_I2CDEV_NOLOCK is enabled. 140 | * 141 | * @param dev Device descriptor 142 | * @return ESP_OK on success 143 | */ 144 | esp_err_t i2c_dev_give_mutex(i2c_dev_t *dev); 145 | 146 | /** 147 | * @brief Check the availability of the device 148 | * 149 | * Issue an operation of \p operation_type to the I2C device then stops. 150 | * 151 | * @param dev Device descriptor 152 | * @param operation_type Operation type 153 | * @return ESP_OK if device is available 154 | */ 155 | esp_err_t i2c_dev_probe(const i2c_dev_t *dev, i2c_dev_type_t operation_type); 156 | 157 | /** 158 | * @brief Read from slave device 159 | * 160 | * Issue a send operation of \p out_data register address, followed by reading \p in_size bytes 161 | * from slave into \p in_data . 162 | * Function is thread-safe. 163 | * 164 | * @param dev Device descriptor 165 | * @param out_data Pointer to data to send if non-null 166 | * @param out_size Size of data to send 167 | * @param[out] in_data Pointer to input data buffer 168 | * @param in_size Number of byte to read 169 | * @return ESP_OK on success 170 | */ 171 | esp_err_t i2c_dev_read(const i2c_dev_t *dev, const void *out_data, 172 | size_t out_size, void *in_data, size_t in_size); 173 | 174 | /** 175 | * @brief Write to slave device 176 | * 177 | * Write \p out_size bytes from \p out_data to slave into \p out_reg register address. 178 | * Function is thread-safe. 179 | * 180 | * @param dev Device descriptor 181 | * @param out_reg Pointer to register address to send if non-null 182 | * @param out_reg_size Size of register address 183 | * @param out_data Pointer to data to send 184 | * @param out_size Size of data to send 185 | * @return ESP_OK on success 186 | */ 187 | esp_err_t i2c_dev_write(const i2c_dev_t *dev, const void *out_reg, 188 | size_t out_reg_size, const void *out_data, size_t out_size); 189 | 190 | /** 191 | * @brief Read from register with an 8-bit address 192 | * 193 | * Shortcut to ::i2c_dev_read(). 194 | * 195 | * @param dev Device descriptor 196 | * @param reg Register address 197 | * @param[out] in_data Pointer to input data buffer 198 | * @param in_size Number of byte to read 199 | * @return ESP_OK on success 200 | */ 201 | esp_err_t i2c_dev_read_reg(const i2c_dev_t *dev, uint8_t reg, 202 | void *in_data, size_t in_size); 203 | 204 | /** 205 | * @brief Write to register with an 8-bit address 206 | * 207 | * Shortcut to ::i2c_dev_write(). 208 | * 209 | * @param dev Device descriptor 210 | * @param reg Register address 211 | * @param out_data Pointer to data to send 212 | * @param out_size Size of data to send 213 | * @return ESP_OK on success 214 | */ 215 | esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, 216 | const void *out_data, size_t out_size); 217 | 218 | #define I2C_DEV_TAKE_MUTEX(dev) do { \ 219 | esp_err_t __ = i2c_dev_take_mutex(dev); \ 220 | if (__ != ESP_OK) return __;\ 221 | } while (0) 222 | 223 | #define I2C_DEV_GIVE_MUTEX(dev) do { \ 224 | esp_err_t __ = i2c_dev_give_mutex(dev); \ 225 | if (__ != ESP_OK) return __;\ 226 | } while (0) 227 | 228 | #define I2C_DEV_CHECK(dev, X) do { \ 229 | esp_err_t ___ = X; \ 230 | if (___ != ESP_OK) { \ 231 | I2C_DEV_GIVE_MUTEX(dev); \ 232 | return ___; \ 233 | } \ 234 | } while (0) 235 | 236 | #define I2C_DEV_CHECK_LOGE(dev, X, msg, ...) do { \ 237 | esp_err_t ___ = X; \ 238 | if (___ != ESP_OK) { \ 239 | I2C_DEV_GIVE_MUTEX(dev); \ 240 | ESP_LOGE(TAG, msg, ## __VA_ARGS__); \ 241 | return ___; \ 242 | } \ 243 | } while (0) 244 | 245 | #ifdef __cplusplus 246 | } 247 | #endif 248 | 249 | /**@}*/ 250 | 251 | #endif /* __I2CDEV_H__ */ 252 | -------------------------------------------------------------------------------- /main/ota.c: -------------------------------------------------------------------------------- 1 | #include "ota.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | bool zlib_init = false; 11 | static const esp_partition_t *s_ota_partition = NULL; 12 | static esp_ota_handle_t s_ota_handle = 0; 13 | z_stream zlib_stream; 14 | 15 | uint8_t ota_header[6]; 16 | size_t ota_header_len = 0; 17 | bool ota_upgrade_subelement = false; 18 | size_t ota_data_len = 0; 19 | uint64_t ota_last_receive_us = 0; 20 | size_t ota_receive_not_logged = 0; 21 | 22 | void ota_reset() 23 | { 24 | if (s_ota_partition) { 25 | esp_ota_abort(s_ota_handle); 26 | s_ota_partition = NULL; 27 | } 28 | 29 | if (zlib_init) { 30 | inflateEnd(&zlib_stream); 31 | zlib_init = false; 32 | } 33 | } 34 | 35 | bool ota_start() 36 | { 37 | zlib_init = false; 38 | s_ota_partition = NULL; 39 | s_ota_handle = 0; 40 | 41 | memset(&zlib_stream, 0, sizeof(zlib_stream)); 42 | int ret = inflateInit(&zlib_stream); 43 | if (ret == Z_OK) { 44 | zlib_init = true; 45 | } else { 46 | ESP_LOGE(__func__, "zlib init failed: %d", ret); 47 | return false; 48 | } 49 | 50 | s_ota_partition = esp_ota_get_next_update_partition(NULL); 51 | if (!s_ota_partition) { 52 | ESP_LOGE(__func__, "No next OTA partition"); 53 | return false; 54 | } 55 | 56 | esp_err_t err = esp_ota_begin(s_ota_partition, OTA_WITH_SEQUENTIAL_WRITES, &s_ota_handle); 57 | if (err != ESP_OK) { 58 | ESP_LOGE(__func__, "Error starting OTA: %d", err); 59 | s_ota_partition = NULL; 60 | return false; 61 | } 62 | 63 | return true; 64 | } 65 | 66 | bool ota_write(const uint8_t *data, size_t size, bool flush) 67 | { 68 | uint8_t buf[256]; 69 | 70 | if (!s_ota_partition) { 71 | return false; 72 | } 73 | 74 | zlib_stream.avail_in = size; 75 | zlib_stream.next_in = data; 76 | 77 | do { 78 | zlib_stream.avail_out = sizeof(buf); 79 | zlib_stream.next_out = buf; 80 | 81 | int ret = inflate(&zlib_stream, flush ? Z_FINISH : Z_NO_FLUSH); 82 | if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) { 83 | ESP_LOGE(__func__, "zlib error: %d", ret); 84 | esp_ota_abort(s_ota_handle); 85 | s_ota_partition = NULL; 86 | return false; 87 | } 88 | 89 | size_t available = sizeof(buf) - zlib_stream.avail_out; 90 | if (available > 0) { 91 | esp_err_t err = esp_ota_write(s_ota_handle, buf, available); 92 | if (err != ESP_OK) { 93 | ESP_LOGE(__func__, "Error writing OTA: %d", err); 94 | esp_ota_abort(s_ota_handle); 95 | s_ota_partition = NULL; 96 | return false; 97 | } 98 | } 99 | } while (zlib_stream.avail_in > 0 || zlib_stream.avail_out == 0); 100 | 101 | return true; 102 | } 103 | 104 | bool ota_finish() 105 | { 106 | if (!s_ota_partition) { 107 | ESP_LOGE(__func__, "OTA not running"); 108 | return false; 109 | } 110 | 111 | if (!ota_write(NULL, 0, true)) { 112 | return false; 113 | } 114 | 115 | esp_err_t err = esp_ota_end(s_ota_handle); 116 | if (err != ESP_OK) { 117 | ESP_LOGE(__func__, "Error ending OTA: %d", err); 118 | s_ota_partition = NULL; 119 | return false; 120 | } 121 | 122 | inflateEnd(&zlib_stream); 123 | zlib_init = false; 124 | 125 | err = esp_ota_set_boot_partition(s_ota_partition); 126 | if (err != ESP_OK) { 127 | ESP_LOGE(__func__, "Error setting boot partition: %d", err); 128 | s_ota_partition = NULL; 129 | return false; 130 | } 131 | 132 | s_ota_partition = NULL; 133 | return true; 134 | } 135 | 136 | esp_err_t zb_ota_upgrade_status_handler(esp_zb_zcl_ota_upgrade_value_message_t message) 137 | { 138 | esp_err_t ret = ESP_OK; 139 | 140 | if (message.info.status == ESP_ZB_ZCL_STATUS_SUCCESS) { 141 | if (message.upgrade_status != ESP_ZB_ZCL_OTA_UPGRADE_STATUS_RECEIVE) { 142 | if (ota_receive_not_logged) { 143 | ESP_LOGD(__func__, "OTA (%zu receive data messages suppressed)", 144 | ota_receive_not_logged); 145 | ota_receive_not_logged = 0; 146 | } 147 | ota_last_receive_us = 0; 148 | } 149 | 150 | switch (message.upgrade_status) { 151 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_START: 152 | ESP_LOGI(__func__, "OTA start"); 153 | ota_reset(); 154 | ota_header_len = 0; 155 | ota_upgrade_subelement = false; 156 | ota_data_len = 0; 157 | if (!ota_start()) { 158 | ota_reset(); 159 | ret = ESP_FAIL; 160 | } 161 | break; 162 | 163 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_RECEIVE: 164 | const uint8_t *payload = message.payload; 165 | size_t payload_size = message.payload_size; 166 | 167 | // Read and process the first sub-element, ignoring everything else 168 | while (ota_header_len < 6 && payload_size > 0) { 169 | ota_header[ota_header_len++] = payload[0]; 170 | payload++; 171 | payload_size--; 172 | } 173 | 174 | if (!ota_upgrade_subelement && ota_header_len == 6) { 175 | if (ota_header[0] == 0 && ota_header[1] == 0) { 176 | ota_upgrade_subelement = true; 177 | ota_data_len = 178 | (((int)ota_header[5] & 0xFF) << 24) 179 | | (((int)ota_header[4] & 0xFF) << 16) 180 | | (((int)ota_header[3] & 0xFF) << 8 ) 181 | | ((int)ota_header[2] & 0xFF); 182 | ESP_LOGD(__func__, "OTA sub-element size %zu", ota_data_len); 183 | } else { 184 | ESP_LOGE(__func__, "OTA sub-element type %02x%02x not supported", ota_header[0], ota_header[1]); 185 | ota_reset(); 186 | ret = ESP_FAIL; 187 | } 188 | } 189 | 190 | if (ota_data_len) { 191 | if (payload_size > ota_data_len) 192 | payload_size = ota_data_len; 193 | ota_data_len -= payload_size; 194 | 195 | if (ota_write(payload, payload_size, false)) { 196 | uint64_t now_us = esp_timer_get_time(); 197 | if (!ota_last_receive_us 198 | || now_us - ota_last_receive_us >= 30 * 1000 * 1000) { 199 | ESP_LOGD(__func__, "OTA receive data (%zu messages suppressed)", 200 | ota_receive_not_logged); 201 | ota_last_receive_us = now_us; 202 | ota_receive_not_logged = 0; 203 | } else { 204 | ota_receive_not_logged++; 205 | } 206 | } else { 207 | ota_reset(); 208 | ret = ESP_FAIL; 209 | } 210 | } 211 | break; 212 | 213 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_APPLY: 214 | ESP_LOGI(__func__, "OTA apply"); 215 | break; 216 | 217 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_CHECK: 218 | ESP_LOGI(__func__, "OTA data complete"); 219 | break; 220 | 221 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_FINISH: 222 | ESP_LOGI(__func__, "OTA finished"); 223 | bool ok = ota_finish(); 224 | ota_reset(); 225 | if (ok) 226 | esp_restart(); 227 | break; 228 | 229 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_ABORT: 230 | ESP_LOGI(__func__, "OTA aborted"); 231 | ota_reset(); 232 | break; 233 | 234 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_OK: 235 | ESP_LOGI(__func__, "OTA data ok"); 236 | break; 237 | 238 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_ERROR: 239 | ESP_LOGI(__func__, "OTA data error"); 240 | ota_reset(); 241 | break; 242 | 243 | case ESP_ZB_ZCL_OTA_UPGRADE_IMAGE_STATUS_NORMAL: 244 | ESP_LOGI(__func__, "OTA image accepted"); 245 | break; 246 | 247 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_BUSY: 248 | ESP_LOGI(__func__, "OTA busy"); 249 | ota_reset(); 250 | break; 251 | 252 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_SERVER_NOT_FOUND: 253 | ESP_LOGI(__func__, "OTA server not found"); 254 | ota_reset(); 255 | break; 256 | 257 | default: 258 | ESP_LOGI(__func__, "OTA status: %d", message.upgrade_status); 259 | break; 260 | } 261 | } 262 | 263 | return ret; 264 | } -------------------------------------------------------------------------------- /main/tools.c: -------------------------------------------------------------------------------- 1 | 2 | #include "nvs_flash.h" 3 | #include "esp_log.h" 4 | #include "esp_chip_info.h" 5 | #include "esp_flash.h" 6 | #include "esp_system.h" 7 | #include "esp_mac.h" 8 | #include 9 | #include "string.h" 10 | 11 | #include "main.h" 12 | #include "tools.h" 13 | #include "const.h" 14 | #include "zigbee.h" 15 | 16 | void get_rtc_time() 17 | { 18 | time_t now; 19 | struct tm timeinfo; 20 | time(&now); 21 | localtime_r(&now, &timeinfo); 22 | strftime(strftime_buf, sizeof(strftime_buf), "%a %H:%M:%S", &timeinfo); 23 | } 24 | 25 | bool int_to_bool(int32_t value) 26 | { 27 | return (value != 0); 28 | } 29 | 30 | void setup_NVS() 31 | { 32 | esp_err_t err = nvs_flash_init(); 33 | if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) 34 | { 35 | // NVS partition was truncated and needs to be erased 36 | // Retry nvs_flash_init 37 | ESP_ERROR_CHECK(nvs_flash_erase()); 38 | err = nvs_flash_init(); 39 | } 40 | ESP_ERROR_CHECK(err); 41 | nvs_handle_t my_handle; 42 | err = nvs_open("storage", NVS_READWRITE, &my_handle); 43 | ESP_LOGI(__func__, "Opening Non-Volatile Storage (NVS) handle... %s", (err != ESP_OK) ? T_STATUS_FAILED : T_STATUS_DONE); 44 | if (err == ESP_OK) 45 | { 46 | 47 | // Read 48 | int32_t restart_counter = 0; // value will default to 0, if not set yet in NVS 49 | err = nvs_get_i32(my_handle, "restart_counter", &restart_counter); 50 | ESP_LOGI(__func__, "Reading restart counter from NVS ... %s", (err != ESP_OK) ? T_STATUS_FAILED : T_STATUS_DONE); 51 | switch (err) 52 | { 53 | case ESP_OK: 54 | ESP_LOGI(__func__, "Restart counter = %" PRIu32, restart_counter); 55 | break; 56 | case ESP_ERR_NVS_NOT_FOUND: 57 | ESP_LOGI(__func__, "The value is not initialized yet!"); 58 | break; 59 | default: 60 | ESP_LOGI(__func__, "Error (%s) reading!", esp_err_to_name(err)); 61 | } 62 | 63 | // Write 64 | restart_counter++; 65 | err = nvs_set_i32(my_handle, "restart_counter", restart_counter); 66 | ESP_LOGI(__func__, "Updating restart counter in NVS ... %s", (err != ESP_OK) ? T_STATUS_FAILED : T_STATUS_DONE); 67 | 68 | // Commit written value. 69 | // After setting any values, nvs_commit() must be called to ensure changes are written 70 | // to flash storage. Implementations may write to storage at other times, 71 | // but this is not guaranteed. 72 | err = nvs_commit(my_handle); 73 | ESP_LOGI(__func__, "Committing updates in NVS ... %s", (err != ESP_OK) ? T_STATUS_FAILED : T_STATUS_DONE); 74 | 75 | // Close 76 | nvs_close(my_handle); 77 | } 78 | } 79 | 80 | int read_NVS(const char *nvs_key) 81 | { 82 | nvs_handle_t my_handle; 83 | esp_err_t err = nvs_open("storage", NVS_READWRITE, &my_handle); 84 | 85 | // Read 86 | // ESP_LOGI(__func__, "Reading restart counter from NVS ... "); 87 | // int32_t restart_counter = 0; // value will default to 0, if not set yet in NVS 88 | int32_t value = 0; 89 | err = nvs_get_i32(my_handle, nvs_key, &value); 90 | switch (err) 91 | { 92 | case ESP_OK: 93 | ESP_LOGI(__func__, "%s is %ld ", nvs_key, value); 94 | break; 95 | case ESP_ERR_NVS_NOT_FOUND: 96 | ESP_LOGE(__func__, "The value is not initialized yet!"); 97 | int value = 0; 98 | 99 | char *substring = "_led_mode"; 100 | if (strstr(nvs_key, substring) != NULL) 101 | { 102 | value = 1; 103 | } 104 | err = nvs_set_i32(my_handle, nvs_key, value); 105 | ESP_LOGW(__func__, "Updating %s in NVS ... %s", nvs_key, (err != ESP_OK) ? T_STATUS_FAILED : T_STATUS_DONE); 106 | break; 107 | default: 108 | ESP_LOGE(__func__, "Error (%s) reading!", esp_err_to_name(err)); 109 | } 110 | // Close 111 | nvs_close(my_handle); 112 | if (err != ESP_OK) 113 | { 114 | return false; 115 | } 116 | return value; 117 | } 118 | 119 | bool write_NVS(const char *nvs_key, int value) 120 | { 121 | nvs_handle_t my_handle; 122 | esp_err_t err = nvs_open("storage", NVS_READWRITE, &my_handle); 123 | err = nvs_set_i32(my_handle, nvs_key, value); 124 | ESP_LOGI(__func__, "Write value... %s", (err != ESP_OK) ? T_STATUS_FAILED : T_STATUS_DONE); 125 | 126 | // Commit written value. 127 | // After setting any values, nvs_commit() must be called to ensure changes are written 128 | // to flash storage. Implementations may write to storage at other times, 129 | // but this is not guaranteed. 130 | err = nvs_commit(my_handle); 131 | ESP_LOGI(__func__, "Commit updates... %s", (err != ESP_OK) ? T_STATUS_FAILED : T_STATUS_DONE); 132 | 133 | // Close 134 | nvs_close(my_handle); 135 | 136 | if (err != ESP_OK) 137 | { 138 | return false; 139 | } 140 | return true; 141 | } 142 | 143 | const char *get_endpoint_name(int endpoint) 144 | { 145 | switch (endpoint) 146 | { 147 | case SENSOR_ENDPOINT: 148 | return "SENSOR"; 149 | case INT_LED_ENDPOINT: 150 | return "INT_LED"; 151 | case EXT_LED_ENDPOINT: 152 | return "EXT_LED"; 153 | default: 154 | return "Unknown"; 155 | } 156 | } 157 | 158 | float random_float(float min, float max) 159 | { 160 | return min + (max - min) * ((float)rand() / RAND_MAX); 161 | } 162 | 163 | float round_to_4_decimals(float value) 164 | { 165 | return roundf(value * 10000) / 10000; 166 | } 167 | 168 | void set_zcl_string(char *buffer, char *value) 169 | { 170 | buffer[0] = (char)strlen(value); 171 | memcpy(buffer + 1, value, buffer[0]); 172 | } 173 | 174 | void print_chip_info() 175 | { 176 | esp_chip_info_t chip_info; 177 | uint32_t flash_size; 178 | uint8_t mac[6]; 179 | 180 | esp_chip_info(&chip_info); 181 | 182 | ESP_LOGW(__func__, "This is %s chip with %d CPU core(s), %s%s%s%s, ", 183 | CONFIG_IDF_TARGET, 184 | chip_info.cores, 185 | (chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "", 186 | (chip_info.features & CHIP_FEATURE_BT) ? "BT" : "", 187 | (chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "", 188 | (chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : ""); 189 | 190 | unsigned major_rev = chip_info.revision / 100; 191 | unsigned minor_rev = chip_info.revision % 100; 192 | ESP_LOGW(__func__, "Silicon revision v%d.%d", major_rev, minor_rev); 193 | 194 | if (esp_flash_get_size(NULL, &flash_size) != ESP_OK) 195 | { 196 | ESP_LOGE(__func__, "Get flash size failed"); 197 | return; 198 | } 199 | 200 | ESP_LOGW(__func__, "%" PRIu32 "MB %s flash", 201 | flash_size / (uint32_t)(1024 * 1024), 202 | (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); 203 | 204 | ESP_LOGW(__func__, "Minimum free heap size: %" PRIu32 " bytes", esp_get_minimum_free_heap_size()); 205 | 206 | if (esp_read_mac(mac, ESP_MAC_WIFI_STA) == ESP_OK) 207 | { 208 | ESP_LOGW(__func__, "Base MAC (WiFi STA): %02X:%02X:%02X:%02X:%02X:%02X", 209 | mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 210 | } 211 | else if (esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP) == ESP_OK) 212 | { 213 | ESP_LOGW(__func__, "Base MAC (WiFi SoftAP): %02X:%02X:%02X:%02X:%02X:%02X", 214 | mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 215 | } 216 | else if (esp_read_mac(mac, ESP_MAC_BT) == ESP_OK) 217 | { 218 | ESP_LOGW(__func__, "Base MAC (Bluetooth): %02X:%02X:%02X:%02X:%02X:%02X", 219 | mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 220 | } 221 | else if (esp_read_mac(mac, ESP_MAC_ETH) == ESP_OK) 222 | { 223 | ESP_LOGW(__func__, "Base MAC (Ethernet): %02X:%02X:%02X:%02X:%02X:%02X", 224 | mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 225 | } 226 | else 227 | { 228 | ESP_LOGE(__func__, "Failed to get any MAC address"); 229 | } 230 | } 231 | 232 | void heap_stats() 233 | { 234 | multi_heap_info_t heap_info; 235 | heap_caps_get_info(&heap_info, MALLOC_CAP_8BIT); 236 | 237 | size_t total_heap_size = heap_caps_get_total_size(MALLOC_CAP_8BIT); 238 | size_t free_heap_size = heap_caps_get_free_size(MALLOC_CAP_8BIT); 239 | size_t largest_free_block = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); 240 | size_t minimum_free_size = heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT); 241 | 242 | float frag_heap_percentage = 0.0; 243 | if (free_heap_size > 0) 244 | { 245 | frag_heap_percentage = (1.0 - ((float)largest_free_block / (float)free_heap_size)) * 100.0; 246 | } 247 | 248 | float free_heap_percentage = ((float)free_heap_size / (float)total_heap_size) * 100.0; 249 | 250 | ESP_LOGI(__func__, "total: %d, free: %d, largest free block: %d, minimum free size: %d", 251 | total_heap_size, 252 | free_heap_size, 253 | largest_free_block, 254 | minimum_free_size); 255 | 256 | ESP_LOGW(__func__, "free: %.2f%% (%d bytes), fragmentation: %.2f%%", 257 | free_heap_percentage, 258 | free_heap_size, 259 | frag_heap_percentage); 260 | } 261 | 262 | void debug_task(void *pvParameters) 263 | { 264 | ESP_LOGW(__func__, "started"); 265 | while (1) 266 | { 267 | heap_stats(); 268 | if (connected) 269 | { 270 | if (!time_updated) 271 | { 272 | ESP_LOGE(__func__, "Time not updated yet"); 273 | } 274 | } 275 | vTaskDelay(pdMS_TO_TICKS(DEBUG_TASK_INTERVAL)); 276 | } 277 | } -------------------------------------------------------------------------------- /components/ina219/ina219.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Ruslan V. Uss 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 3. Neither the name of the copyright holder nor the names of itscontributors 13 | * may be used to endorse or promote products derived from this software without 14 | * specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /** 29 | * @file ina219.c 30 | * 31 | * ESP-IDF driver for INA219/INA220 Zerø-Drift, Bidirectional 32 | * Current/Power Monitor 33 | * 34 | * Copyright (c) 2019 Ruslan V. Uss 35 | * 36 | * BSD Licensed as described in the file LICENSE 37 | */ 38 | #include 39 | #include 40 | #include 41 | #include "ina219.h" 42 | 43 | #define I2C_FREQ_HZ 1000000 // Max 1 MHz for esp-idf, but supports up to 2.56 MHz 44 | 45 | static const char *TAG = "ina219"; 46 | 47 | #define REG_CONFIG 0 48 | #define REG_SHUNT_U 1 49 | #define REG_BUS_U 2 50 | #define REG_POWER 3 51 | #define REG_CURRENT 4 52 | #define REG_CALIBRATION 5 53 | 54 | #define BIT_RST 15 55 | #define BIT_BRNG 13 56 | #define BIT_PG0 11 57 | #define BIT_BADC0 7 58 | #define BIT_SADC0 3 59 | #define BIT_MODE 0 60 | 61 | #define MASK_PG (3 << BIT_PG0) 62 | #define MASK_BADC (0xf << BIT_BADC0) 63 | #define MASK_SADC (0xf << BIT_SADC0) 64 | #define MASK_MODE (7 << BIT_MODE) 65 | #define MASK_BRNG (1 << BIT_BRNG) 66 | 67 | #define DEF_CONFIG 0x399f 68 | 69 | #define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) 70 | #define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) 71 | 72 | static const float u_shunt_max[] = { 73 | [INA219_GAIN_1] = 0.04, 74 | [INA219_GAIN_0_5] = 0.08, 75 | [INA219_GAIN_0_25] = 0.16, 76 | [INA219_GAIN_0_125] = 0.32, 77 | }; 78 | 79 | static esp_err_t read_reg_16(ina219_t *dev, uint8_t reg, uint16_t *val) 80 | { 81 | CHECK_ARG(val); 82 | 83 | I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); 84 | I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2)); 85 | I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); 86 | 87 | *val = (*val >> 8) | (*val << 8); 88 | 89 | return ESP_OK; 90 | } 91 | 92 | static esp_err_t write_reg_16(ina219_t *dev, uint8_t reg, uint16_t val) 93 | { 94 | uint16_t v = (val >> 8) | (val << 8); 95 | 96 | I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); 97 | I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2)); 98 | I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); 99 | 100 | return ESP_OK; 101 | } 102 | 103 | static esp_err_t read_conf_bits(ina219_t *dev, uint16_t mask, uint8_t bit, uint16_t *res) 104 | { 105 | uint16_t raw; 106 | CHECK(read_reg_16(dev, REG_CONFIG, &raw)); 107 | 108 | *res = (raw & mask) >> bit; 109 | 110 | return ESP_OK; 111 | } 112 | 113 | /////////////////////////////////////////////////////////////////////////////// 114 | 115 | esp_err_t ina219_init_desc(ina219_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) 116 | { 117 | CHECK_ARG(dev); 118 | 119 | if (addr < INA219_ADDR_GND_GND || addr > INA219_ADDR_SCL_SCL) 120 | { 121 | ESP_LOGE(TAG, "Invalid I2C address"); 122 | return ESP_ERR_INVALID_ARG; 123 | } 124 | 125 | dev->i2c_dev.port = port; 126 | dev->i2c_dev.addr = addr; 127 | dev->i2c_dev.cfg.sda_io_num = sda_gpio; 128 | dev->i2c_dev.cfg.scl_io_num = scl_gpio; 129 | #if HELPER_TARGET_IS_ESP32 130 | dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; 131 | #endif 132 | 133 | return i2c_dev_create_mutex(&dev->i2c_dev); 134 | } 135 | 136 | esp_err_t ina219_free_desc(ina219_t *dev) 137 | { 138 | CHECK_ARG(dev); 139 | 140 | return i2c_dev_delete_mutex(&dev->i2c_dev); 141 | } 142 | 143 | esp_err_t ina219_init(ina219_t *dev) 144 | { 145 | CHECK_ARG(dev); 146 | 147 | CHECK(read_reg_16(dev, REG_CONFIG, &dev->config)); 148 | 149 | ESP_LOGD(TAG, "Initialize, config: 0x%04x", dev->config); 150 | 151 | return ESP_OK; 152 | } 153 | 154 | esp_err_t ina219_reset(ina219_t *dev) 155 | { 156 | CHECK_ARG(dev); 157 | CHECK(write_reg_16(dev, REG_CONFIG, 1 << BIT_RST)); 158 | 159 | dev->config = DEF_CONFIG; 160 | 161 | ESP_LOGD(TAG, "Device reset"); 162 | 163 | return ESP_OK; 164 | } 165 | 166 | esp_err_t ina219_configure(ina219_t *dev, ina219_bus_voltage_range_t u_range, 167 | ina219_gain_t gain, ina219_resolution_t u_res, 168 | ina219_resolution_t i_res, ina219_mode_t mode) 169 | { 170 | CHECK_ARG(dev); 171 | CHECK_ARG(u_range <= INA219_BUS_RANGE_32V); 172 | CHECK_ARG(gain <= INA219_GAIN_0_125); 173 | CHECK_ARG(u_res <= INA219_RES_12BIT_128S); 174 | CHECK_ARG(i_res <= INA219_RES_12BIT_128S); 175 | CHECK_ARG(mode <= INA219_MODE_CONT_SHUNT_BUS); 176 | 177 | dev->config = (u_range << BIT_BRNG) | 178 | (gain << BIT_PG0) | 179 | (u_res << BIT_BADC0) | 180 | (i_res << BIT_SADC0) | 181 | (mode << BIT_MODE); 182 | 183 | ESP_LOGD(TAG, "Config: 0x%04x", dev->config); 184 | 185 | return write_reg_16(dev, REG_CONFIG, dev->config); 186 | } 187 | 188 | esp_err_t ina219_get_bus_voltage_range(ina219_t *dev, ina219_bus_voltage_range_t *range) 189 | { 190 | CHECK_ARG(dev && range); 191 | *range = 0; 192 | return read_conf_bits(dev, MASK_BRNG, BIT_BRNG, (uint16_t *)range); 193 | } 194 | 195 | esp_err_t ina219_get_gain(ina219_t *dev, ina219_gain_t *gain) 196 | { 197 | CHECK_ARG(dev && gain); 198 | *gain = 0; 199 | return read_conf_bits(dev, MASK_PG, BIT_PG0, (uint16_t *)gain); 200 | } 201 | 202 | esp_err_t ina219_get_bus_voltage_resolution(ina219_t *dev, ina219_resolution_t *res) 203 | { 204 | CHECK_ARG(dev && res); 205 | *res = 0; 206 | return read_conf_bits(dev, MASK_BADC, BIT_BADC0, (uint16_t *)res); 207 | } 208 | 209 | esp_err_t ina219_get_shunt_voltage_resolution(ina219_t *dev, ina219_resolution_t *res) 210 | { 211 | CHECK_ARG(dev && res); 212 | *res = 0; 213 | return read_conf_bits(dev, MASK_SADC, BIT_SADC0, (uint16_t *)res); 214 | } 215 | 216 | esp_err_t ina219_get_mode(ina219_t *dev, ina219_mode_t *mode) 217 | { 218 | CHECK_ARG(dev && mode); 219 | *mode = 0; 220 | return read_conf_bits(dev, MASK_MODE, BIT_MODE, (uint16_t *)mode); 221 | } 222 | 223 | esp_err_t ina219_calibrate(ina219_t *dev, float r_shunt) 224 | { 225 | CHECK_ARG(dev); 226 | 227 | ina219_gain_t gain; 228 | CHECK(ina219_get_gain(dev, &gain)); 229 | 230 | dev->i_lsb = (uint16_t)(u_shunt_max[gain] / r_shunt / 32767 * 100000000); 231 | dev->i_lsb /= 100000000; 232 | dev->i_lsb /= 0.0001; 233 | dev->i_lsb = ceil(dev->i_lsb); 234 | dev->i_lsb *= 0.0001; 235 | 236 | dev->p_lsb = dev->i_lsb * 20; 237 | 238 | uint16_t cal = (uint16_t)((0.04096) / (dev->i_lsb * r_shunt)); 239 | 240 | ESP_LOGD(TAG, "Calibration: %.04f Ohm, 0x%04x", r_shunt, cal); 241 | 242 | return write_reg_16(dev, REG_CALIBRATION, cal); 243 | } 244 | 245 | esp_err_t ina219_trigger(ina219_t *dev) 246 | { 247 | CHECK_ARG(dev); 248 | 249 | uint16_t mode = (dev->config & MASK_MODE) >> BIT_MODE; 250 | if (mode < INA219_MODE_TRIG_SHUNT || mode > INA219_MODE_TRIG_SHUNT_BUS) 251 | { 252 | ESP_LOGE(TAG, "Could not trigger conversion in this mode: %d", mode); 253 | return ESP_ERR_INVALID_STATE; 254 | } 255 | 256 | return write_reg_16(dev, REG_CONFIG, dev->config); 257 | } 258 | 259 | esp_err_t ina219_get_bus_voltage(ina219_t *dev, float *voltage) 260 | { 261 | CHECK_ARG(dev && voltage); 262 | 263 | uint16_t raw; 264 | CHECK(read_reg_16(dev, REG_BUS_U, &raw)); 265 | 266 | *voltage = (raw >> 3) * 0.004; 267 | 268 | return ESP_OK; 269 | } 270 | 271 | esp_err_t ina219_get_shunt_voltage(ina219_t *dev, float *voltage) 272 | { 273 | CHECK_ARG(dev && voltage); 274 | 275 | int16_t raw; 276 | CHECK(read_reg_16(dev, REG_SHUNT_U, (uint16_t *)&raw)); 277 | 278 | *voltage = raw / 100000.0; 279 | 280 | return ESP_OK; 281 | } 282 | 283 | esp_err_t ina219_get_current(ina219_t *dev, float *current) 284 | { 285 | CHECK_ARG(dev && current); 286 | 287 | int16_t raw; 288 | CHECK(read_reg_16(dev, REG_CURRENT, (uint16_t *)&raw)); 289 | 290 | *current = raw * dev->i_lsb; 291 | 292 | return ESP_OK; 293 | } 294 | 295 | esp_err_t ina219_get_power(ina219_t *dev, float *power) 296 | { 297 | CHECK_ARG(dev && power); 298 | 299 | int16_t raw; 300 | CHECK(read_reg_16(dev, REG_POWER, (uint16_t *)&raw)); 301 | 302 | *power = raw * dev->p_lsb; 303 | 304 | return ESP_OK; 305 | } 306 | 307 | -------------------------------------------------------------------------------- /components/ina219/ina219.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Ruslan V. Uss 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 3. Neither the name of the copyright holder nor the names of itscontributors 13 | * may be used to endorse or promote products derived from this software without 14 | * specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /** 29 | * @file ina219.h 30 | * @defgroup ina219 ina219 31 | * @{ 32 | * 33 | * ESP-IDF driver for INA219/INA220 Zerø-Drift, Bidirectional 34 | * Current/Power Monitor 35 | * 36 | * Copyright (c) 2019 Ruslan V. Uss 37 | * 38 | * BSD Licensed as described in the file LICENSE 39 | */ 40 | #ifndef __INA219_H__ 41 | #define __INA219_H__ 42 | 43 | #include 44 | #include 45 | 46 | #ifdef __cplusplus 47 | extern "C" { 48 | #endif 49 | 50 | #define INA219_ADDR_GND_GND 0x40 //!< I2C address, A1 pin - GND, A0 pin - GND 51 | #define INA219_ADDR_GND_VS 0x41 //!< I2C address, A1 pin - GND, A0 pin - VS+ 52 | #define INA219_ADDR_GND_SDA 0x42 //!< I2C address, A1 pin - GND, A0 pin - SDA 53 | #define INA219_ADDR_GND_SCL 0x43 //!< I2C address, A1 pin - GND, A0 pin - SCL 54 | #define INA219_ADDR_VS_GND 0x44 //!< I2C address, A1 pin - VS+, A0 pin - GND 55 | #define INA219_ADDR_VS_VS 0x45 //!< I2C address, A1 pin - VS+, A0 pin - VS+ 56 | #define INA219_ADDR_VS_SDA 0x46 //!< I2C address, A1 pin - VS+, A0 pin - SDA 57 | #define INA219_ADDR_VS_SCL 0x47 //!< I2C address, A1 pin - VS+, A0 pin - SCL 58 | #define INA219_ADDR_SDA_GND 0x48 //!< I2C address, A1 pin - SDA, A0 pin - GND 59 | #define INA219_ADDR_SDA_VS 0x49 //!< I2C address, A1 pin - SDA, A0 pin - VS+ 60 | #define INA219_ADDR_SDA_SDA 0x4a //!< I2C address, A1 pin - SDA, A0 pin - SDA 61 | #define INA219_ADDR_SDA_SCL 0x4b //!< I2C address, A1 pin - SDA, A0 pin - SCL 62 | #define INA219_ADDR_SCL_GND 0x4c //!< I2C address, A1 pin - SCL, A0 pin - GND 63 | #define INA219_ADDR_SCL_VS 0x4d //!< I2C address, A1 pin - SCL, A0 pin - VS+ 64 | #define INA219_ADDR_SCL_SDA 0x4e //!< I2C address, A1 pin - SCL, A0 pin - SDA 65 | #define INA219_ADDR_SCL_SCL 0x4f //!< I2C address, A1 pin - SCL, A0 pin - SCL 66 | 67 | /** 68 | * Bus voltage range 69 | */ 70 | typedef enum { 71 | INA219_BUS_RANGE_16V = 0, //!< 16V FSR 72 | INA219_BUS_RANGE_32V //!< 32V FSR (default) 73 | } ina219_bus_voltage_range_t; 74 | 75 | /** 76 | * PGA gain for shunt voltage 77 | */ 78 | typedef enum { 79 | INA219_GAIN_1 = 0, //!< Gain: 1, Range: +-40 mV 80 | INA219_GAIN_0_5, //!< Gain: 1/2, Range: +-80 mV 81 | INA219_GAIN_0_25, //!< Gain: 1/4, Range: +-160 mV 82 | INA219_GAIN_0_125 //!< Gain: 1/8, Range: +-320 mV (default) 83 | } ina219_gain_t; 84 | 85 | /** 86 | * ADC resolution/averaging 87 | */ 88 | typedef enum { 89 | INA219_RES_9BIT_1S = 0, //!< 9 bit, 1 sample, conversion time 84 us 90 | INA219_RES_10BIT_1S = 1, //!< 10 bit, 1 sample, conversion time 148 us 91 | INA219_RES_11BIT_1S = 2, //!< 11 bit, 1 sample, conversion time 276 us 92 | INA219_RES_12BIT_1S = 3, //!< 12 bit, 1 sample, conversion time 532 us (default) 93 | INA219_RES_12BIT_2S = 9, //!< 12 bit, 2 samples, conversion time 1.06 ms 94 | INA219_RES_12BIT_4S = 10, //!< 12 bit, 4 samples, conversion time 2.13 ms 95 | INA219_RES_12BIT_8S = 11, //!< 12 bit, 8 samples, conversion time 4.26 ms 96 | INA219_RES_12BIT_16S = 12, //!< 12 bit, 16 samples, conversion time 8.51 ms 97 | INA219_RES_12BIT_32S = 13, //!< 12 bit, 32 samples, conversion time 17.02 ms 98 | INA219_RES_12BIT_64S = 14, //!< 12 bit, 64 samples, conversion time 34.05 ms 99 | INA219_RES_12BIT_128S = 15, //!< 12 bit, 128 samples, conversion time 68.1 ms 100 | } ina219_resolution_t; 101 | 102 | /** 103 | * Operating mode 104 | */ 105 | typedef enum { 106 | INA219_MODE_POWER_DOWN = 0, //!< Power-done 107 | INA219_MODE_TRIG_SHUNT, //!< Shunt voltage, triggered 108 | INA219_MODE_TRIG_BUS, //!< Bus voltage, triggered 109 | INA219_MODE_TRIG_SHUNT_BUS, //!< Shunt and bus, triggered 110 | INA219_MODE_DISABLED, //!< ADC off (disabled) 111 | INA219_MODE_CONT_SHUNT, //!< Shunt voltage, continuous 112 | INA219_MODE_CONT_BUS, //!< Bus voltage, continuous 113 | INA219_MODE_CONT_SHUNT_BUS //!< Shunt and bus, continuous (default) 114 | } ina219_mode_t; 115 | 116 | /** 117 | * Device descriptor 118 | */ 119 | typedef struct 120 | { 121 | i2c_dev_t i2c_dev; 122 | 123 | uint16_t config; 124 | float i_lsb, p_lsb; 125 | } ina219_t; 126 | 127 | /** 128 | * @brief Initialize device descriptor 129 | * 130 | * @param dev Device descriptor 131 | * @param addr Device I2C address 132 | * @param port I2C port 133 | * @param sda_gpio SDA GPIO 134 | * @param scl_gpio SCL GPIO 135 | * @return `ESP_OK` on success 136 | */ 137 | esp_err_t ina219_init_desc(ina219_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); 138 | 139 | /** 140 | * @brief Free device descriptor 141 | * 142 | * @param dev Device descriptor 143 | * @return `ESP_OK` on success 144 | */ 145 | esp_err_t ina219_free_desc(ina219_t *dev); 146 | 147 | /** 148 | * @brief Init device 149 | * 150 | * Read current device configuration into `dev->config` 151 | * 152 | * @param dev Device descriptor 153 | * @return `ESP_OK` on success 154 | */ 155 | esp_err_t ina219_init(ina219_t *dev); 156 | 157 | /** 158 | * @brief Reset device 159 | * 160 | * Same as power-on reset. Resets all registers to default values. 161 | * You still need to calibrate device to read current, otherwise 162 | * only shunt voltage readings will be valid. 163 | * 164 | * @param dev Device descriptor 165 | * @return `ESP_OK` on success 166 | */ 167 | esp_err_t ina219_reset(ina219_t *dev); 168 | 169 | /** 170 | * @brief Set device configuration 171 | * 172 | * @param dev Device descriptor 173 | * @param u_range Bus voltage range 174 | * @param gain Shunt voltage gain 175 | * @param u_res Bus voltage resolution and averaging 176 | * @param i_res Shunt voltage resolution and averaging 177 | * @param mode Device operational mode 178 | * @return `ESP_OK` on success 179 | */ 180 | esp_err_t ina219_configure(ina219_t *dev, ina219_bus_voltage_range_t u_range, 181 | ina219_gain_t gain, ina219_resolution_t u_res, 182 | ina219_resolution_t i_res, ina219_mode_t mode); 183 | 184 | /** 185 | * @brief Get bus voltage range 186 | * 187 | * @param dev Device descriptor 188 | * @param[out] range Bus voltage range 189 | * @return `ESP_OK` on success 190 | */ 191 | esp_err_t ina219_get_bus_voltage_range(ina219_t *dev, ina219_bus_voltage_range_t *range); 192 | 193 | /** 194 | * @brief Get shunt voltage gain 195 | * 196 | * @param dev Device descriptor 197 | * @param[out] gain Shunt voltage gain 198 | * @return `ESP_OK` on success 199 | */ 200 | esp_err_t ina219_get_gain(ina219_t *dev, ina219_gain_t *gain); 201 | 202 | /** 203 | * @brief Get bus voltage resolution and averaging 204 | * 205 | * @param dev Device descriptor 206 | * @param[out] res Bus voltage resolution and averaging 207 | * @return `ESP_OK` on success 208 | */ 209 | esp_err_t ina219_get_bus_voltage_resolution(ina219_t *dev, ina219_resolution_t *res); 210 | 211 | /** 212 | * @brief Get shunt voltage resolution and averaging 213 | * 214 | * @param dev Device descriptor 215 | * @param[out] res Shunt voltage resolution and averaging 216 | * @return `ESP_OK` on success 217 | */ 218 | esp_err_t ina219_get_shunt_voltage_resolution(ina219_t *dev, ina219_resolution_t *res); 219 | 220 | /** 221 | * @brief Get operating mode 222 | * 223 | * @param dev Device descriptor 224 | * @param[out] mode Operating mode 225 | * @return `ESP_OK` on success 226 | */ 227 | esp_err_t ina219_get_mode(ina219_t *dev, ina219_mode_t *mode); 228 | 229 | /** 230 | * @brief Perform calibration 231 | * 232 | * Current readings will be valid only after calibration 233 | * 234 | * @param dev Device descriptor 235 | * @param r_shunt Shunt resistance, Ohm 236 | * @return `ESP_OK` on success 237 | */ 238 | esp_err_t ina219_calibrate(ina219_t *dev, float r_shunt); 239 | 240 | /** 241 | * @brief Trigger single conversion 242 | * 243 | * Function will return an error if current operating 244 | * mode is not `INA219_MODE_TRIG_SHUNT`/`INA219_MODE_TRIG_BUS`/`INA219_MODE_TRIG_SHUNT_BUS` 245 | * 246 | * @param dev Device descriptor 247 | * @return `ESP_OK` on success 248 | */ 249 | esp_err_t ina219_trigger(ina219_t *dev); 250 | 251 | /** 252 | * @brief Read bus voltage 253 | * 254 | * @param dev Device descriptor 255 | * @param[out] voltage Bus voltage, V 256 | * @return `ESP_OK` on success 257 | */ 258 | esp_err_t ina219_get_bus_voltage(ina219_t *dev, float *voltage); 259 | 260 | /** 261 | * @brief Read shunt voltage 262 | * 263 | * @param dev Device descriptor 264 | * @param[out] voltage Shunt voltage, V 265 | * @return `ESP_OK` on success 266 | */ 267 | esp_err_t ina219_get_shunt_voltage(ina219_t *dev, float *voltage); 268 | 269 | /** 270 | * @brief Read current 271 | * 272 | * This function works properly only after calibration. 273 | * 274 | * @param dev Device descriptor 275 | * @param[out] current Current, A 276 | * @return `ESP_OK` on success 277 | */ 278 | esp_err_t ina219_get_current(ina219_t *dev, float *current); 279 | 280 | /** 281 | * @brief Read power 282 | * 283 | * This function works properly only after calibration. 284 | * 285 | * @param dev Device descriptor 286 | * @param[out] power Power, W 287 | * @return `ESP_OK` on success 288 | */ 289 | esp_err_t ina219_get_power(ina219_t *dev, float *power); 290 | 291 | #ifdef __cplusplus 292 | } 293 | #endif 294 | 295 | /**@}*/ 296 | 297 | #endif /* __INA219_H__ */ 298 | -------------------------------------------------------------------------------- /main/perf.c: -------------------------------------------------------------------------------- 1 | #include "driver/gpio.h" 2 | #include "esp_log.h" 3 | #include "freertos/FreeRTOS.h" 4 | #include "freertos/task.h" 5 | #include "driver/temperature_sensor.h" 6 | #include "iot_button.h" 7 | #include "ina219.h" 8 | #include "string.h" 9 | 10 | #include "main.h" 11 | #include "const.h" 12 | #include "perf.h" 13 | #include "tools.h" 14 | #include "zigbee.h" 15 | 16 | void init_outputs() 17 | { 18 | ESP_LOGI(__func__, "setup LEDs gpios"); 19 | gpio_reset_pin(EXT_LED_GPIO); 20 | gpio_set_direction(EXT_LED_GPIO, GPIO_MODE_OUTPUT); 21 | gpio_pulldown_en(EXT_LED_GPIO); 22 | gpio_set_level(EXT_LED_GPIO, LED_OFF_STATE); 23 | 24 | gpio_reset_pin(INT_LED_GPIO); 25 | gpio_set_direction(INT_LED_GPIO, GPIO_MODE_OUTPUT); 26 | gpio_pulldown_en(INT_LED_GPIO); 27 | gpio_set_level(INT_LED_GPIO, LED_OFF_STATE); 28 | 29 | ESP_LOGI(__func__, "setup USB gpio"); 30 | gpio_reset_pin(USB_GPIO); 31 | gpio_set_direction(USB_GPIO, GPIO_MODE_OUTPUT); 32 | gpio_pullup_en(USB_GPIO); 33 | switch (data.start_up_on_off) 34 | { 35 | case 0: 36 | usb_driver_set_power(0); 37 | break; 38 | case 1: 39 | usb_driver_set_power(1); 40 | break; 41 | case 2: 42 | usb_driver_set_power(!data.USB_state); 43 | break; 44 | case 255: 45 | usb_driver_set_power(data.USB_state); 46 | break; 47 | default: 48 | break; 49 | } 50 | } 51 | 52 | /*(void init_pullup_i2c_pins() 53 | { 54 | ESP_LOGI(__func__, "setup I2C_SDA_GPIO"); 55 | gpio_reset_pin(I2C_SDA_GPIO); 56 | gpio_pullup_en(I2C_SDA_GPIO); 57 | ESP_LOGI(__func__, "setup I2C_SCL_GPIO"); 58 | gpio_reset_pin(I2C_SCL_GPIO); 59 | gpio_pullup_en(I2C_SCL_GPIO); 60 | }*/ 61 | 62 | void usb_driver_set_power(bool state) 63 | { 64 | gpio_set_level(USB_GPIO, state); 65 | ext_led_action(state); 66 | ESP_LOGI(__func__, "Setting USB power to %d", state); 67 | data.USB_state = state; 68 | if (data.start_up_on_off > 1) 69 | { 70 | write_NVS("USB_state", state); 71 | } 72 | } 73 | 74 | void led_task(void *pvParameters) 75 | { 76 | ESP_LOGI(__func__, "starting"); 77 | // led_hz = 1; 78 | while (1) 79 | { 80 | if (led_hz > 0) 81 | { 82 | int_led_blink(); 83 | vTaskDelay(pdMS_TO_TICKS(1000 / led_hz)); 84 | } 85 | else 86 | { 87 | // LED disabled 88 | 89 | vTaskDelay(pdMS_TO_TICKS(100)); 90 | } 91 | } 92 | } 93 | 94 | void int_led_blink() 95 | { 96 | if (data.int_led_mode) 97 | { 98 | gpio_set_level(INT_LED_GPIO, LED_ON_STATE); 99 | vTaskDelay(pdMS_TO_TICKS(150)); 100 | gpio_set_level(INT_LED_GPIO, LED_OFF_STATE); 101 | } 102 | } 103 | 104 | void ext_led_action(int mode) 105 | { 106 | // ESP_LOGW(__func__, "ext_led_action: mode %d, ex_led_m: %d", mode, data.ext_led_mode); 107 | if (data.ext_led_mode) 108 | { 109 | if (mode == 1) 110 | { 111 | // ESP_LOGW(__func__, "ext_led_action: LED ON"); 112 | gpio_set_level(EXT_LED_GPIO, 0); 113 | } 114 | else if (mode == 0) 115 | { 116 | // ESP_LOGW(__func__, "ext_led_action: LED OFF"); 117 | gpio_set_level(EXT_LED_GPIO, 1); 118 | } 119 | else if (mode == 1) 120 | { 121 | // ESP_LOGW(__func__, "ext_led_action: LED invert"); 122 | int level = gpio_get_level(EXT_LED_GPIO); 123 | gpio_set_level(EXT_LED_GPIO, !level); 124 | } 125 | else if (mode == 2) 126 | { 127 | // ESP_LOGW(__func__, "ext_led_action: LED blink"); 128 | int level = gpio_get_level(EXT_LED_GPIO); 129 | gpio_set_level(EXT_LED_GPIO, !level); 130 | vTaskDelay(pdMS_TO_TICKS(250)); 131 | gpio_set_level(EXT_LED_GPIO, level); 132 | } 133 | } 134 | } 135 | 136 | void register_alarm_input() 137 | { 138 | button_config_t gpio_alarm_cfg = { 139 | .type = BUTTON_TYPE_GPIO, 140 | .gpio_button_config = { 141 | .gpio_num = ALARM_GPIO, 142 | .active_level = 0, 143 | }, 144 | }; 145 | 146 | button_handle_t gpio_alarm = iot_button_create(&gpio_alarm_cfg); 147 | if (NULL == gpio_alarm) 148 | { 149 | ESP_LOGE(__func__, "Alarm input create failed"); 150 | } 151 | 152 | iot_button_register_cb(gpio_alarm, BUTTON_PRESS_UP, alarm_input_deactive_cb, NULL); 153 | iot_button_register_cb(gpio_alarm, BUTTON_PRESS_DOWN, alarm_input_active_cb, NULL); 154 | } 155 | 156 | static void alarm_input_active_cb(void *arg, void *usr_data) 157 | { 158 | ESP_LOGE(__func__, "active"); 159 | data.alarm_state = 1; 160 | send_alarm_state(data.alarm_state); 161 | } 162 | 163 | static void alarm_input_deactive_cb(void *arg, void *usr_data) 164 | { 165 | ESP_LOGE(__func__, "deactive"); 166 | data.alarm_state = 0; 167 | send_alarm_state(data.alarm_state); 168 | } 169 | 170 | void register_button(int pin) 171 | { 172 | button_config_t gpio_btn_cfg = { 173 | .type = BUTTON_TYPE_GPIO, 174 | .long_press_time = LONG_PRESS_TIME, 175 | .short_press_time = SHORT_PRESS_TIME, 176 | .gpio_button_config = { 177 | .gpio_num = pin, 178 | .active_level = 0, 179 | }, 180 | }; 181 | 182 | button_handle_t gpio_btn = iot_button_create(&gpio_btn_cfg); 183 | if (NULL == gpio_btn) 184 | { 185 | ESP_LOGE(__func__, "Button on pin %d create failed", pin); 186 | } 187 | 188 | iot_button_register_cb(gpio_btn, BUTTON_SINGLE_CLICK, button_single_click_cb, NULL); 189 | iot_button_register_cb(gpio_btn, BUTTON_LONG_PRESS_START, button_long_press_cb, NULL); 190 | ESP_LOGI(__func__, "Button on pin %d registered", pin); 191 | } 192 | 193 | static void button_single_click_cb(void *arg, void *usr_data) 194 | { 195 | ESP_LOGI(__func__, "single click"); 196 | bool new_state = !(data.USB_state); 197 | 198 | usb_driver_set_power(new_state); 199 | send_bin_cfg_option(SENSOR_ENDPOINT, new_state); 200 | } 201 | 202 | static void button_long_press_cb(void *arg, void *usr_data) 203 | { 204 | ESP_LOGI(__func__, "long press - leave & reset"); 205 | data.ext_led_mode = 1; // Force turn LED ON 206 | for (int i = 0; i < 5; i++) 207 | { 208 | ext_led_action(3); 209 | vTaskDelay(pdMS_TO_TICKS(500)); 210 | } 211 | esp_zb_bdb_reset_via_local_action(); 212 | esp_zb_factory_reset(); 213 | } 214 | 215 | void int_temp_task(void *pvParameters) 216 | { 217 | ESP_LOGI(__func__, "initializing temperature sensor"); 218 | temperature_sensor_handle_t temp_handle = NULL; 219 | temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(10, 60); 220 | ESP_ERROR_CHECK(temperature_sensor_install(&temp_sensor_config, &temp_handle)); 221 | 222 | // ESP_LOGI(__func__, "enable temperature sensor"); 223 | // ESP_ERROR_CHECK(temperature_sensor_enable(temp_handle)); 224 | 225 | ESP_LOGI(__func__, "starting the loop"); 226 | while (1) 227 | { 228 | // Enable temperature sensor 229 | ESP_ERROR_CHECK(temperature_sensor_enable(temp_handle)); 230 | 231 | // Get converted sensor data 232 | float tsens_out; 233 | uint16_t new_CPU_temp; 234 | ESP_ERROR_CHECK(temperature_sensor_get_celsius(temp_handle, &tsens_out)); 235 | // printf("Temperature in %f °C\n", tsens_out); 236 | 237 | // Disable the temperature sensor if it is not needed and save the power 238 | ESP_ERROR_CHECK(temperature_sensor_disable(temp_handle)); 239 | 240 | new_CPU_temp = (float)(tsens_out * 100); 241 | 242 | if (new_CPU_temp != CPU_temp) 243 | { 244 | CPU_temp = new_CPU_temp; 245 | update_attributes(ATTRIBUTE_TEMP); 246 | } 247 | 248 | vTaskDelay(pdMS_TO_TICKS(CPU_TEMP_INTERVAL)); 249 | 250 | #ifdef TEST_MODE 251 | get_rtc_time(); 252 | ESP_LOGI(__func__, "time %s", strftime_buf); 253 | #endif 254 | } 255 | } 256 | 257 | void ina219_task(void *pvParameters) 258 | { 259 | ina219_t dev; 260 | memset(&dev, 0, sizeof(ina219_t)); 261 | 262 | assert(SHUNT_RESISTOR_MILLI_OHM > 0); 263 | ESP_ERROR_CHECK(ina219_init_desc(&dev, I2C_ADDR, I2C_PORT, I2C_SDA_GPIO, I2C_SCL_GPIO)); 264 | ESP_LOGI(__func__, "initializing"); 265 | ESP_ERROR_CHECK(ina219_init(&dev)); 266 | 267 | ESP_LOGI(__func__, "configuring"); 268 | ESP_ERROR_CHECK(ina219_configure(&dev, INA219_BUS_RANGE_16V, INA219_GAIN_0_125, 269 | INA219_RES_12BIT_1S, INA219_RES_12BIT_1S, INA219_MODE_CONT_SHUNT_BUS)); 270 | 271 | ESP_LOGI(__func__, "calibrating"); 272 | 273 | ESP_ERROR_CHECK(ina219_calibrate(&dev, (float)SHUNT_RESISTOR_MILLI_OHM / 1000.0f)); 274 | 275 | ESP_LOGI(__func__, "starting the loop"); 276 | while (1) 277 | { 278 | ESP_ERROR_CHECK(ina219_get_bus_voltage(&dev, &ina_bus_voltage)); 279 | ESP_ERROR_CHECK(ina219_get_shunt_voltage(&dev, &ina_shunt_voltage)); 280 | ESP_ERROR_CHECK(ina219_get_current(&dev, &ina_current)); 281 | ESP_ERROR_CHECK(ina219_get_power(&dev, &ina_power)); 282 | 283 | ESP_LOGW(__func__, "VBUS: %.04f V, IBUS: %.04f A, PBUS: %.04f W", 284 | ina_bus_voltage, ina_current, ina_power); 285 | 286 | uint16_t new_voltage, new_current, new_power; 287 | // ZCL must be V,A,W = uint16. But we need more accuaracy - so use *100. 288 | new_voltage = (float)(ina_bus_voltage * 100); 289 | new_current = (float)(ina_current * 100); 290 | new_power = (float)(ina_power * 100); 291 | 292 | // Check if values have changed 293 | if (new_voltage != voltage || new_current != current || new_power != power) 294 | { 295 | // Update global variables 296 | voltage = new_voltage; 297 | current = new_current; 298 | power = new_power; 299 | 300 | // Send update attribute 301 | update_attributes(ATTRIBUTE_ELECTRO); 302 | } 303 | 304 | vTaskDelay(pdMS_TO_TICKS(INA219_INTERVAL)); 305 | } 306 | } 307 | 308 | void test_task(void *pvParameters) 309 | { 310 | 311 | ESP_LOGI(__func__, "starting test task"); 312 | while (1) 313 | { 314 | float ina_bus_voltage = round_to_4_decimals(random_float(4.5, 5.5)); 315 | float ina_current = round_to_4_decimals(random_float(0.0, 2.0)); 316 | float ina_power = round_to_4_decimals(ina_bus_voltage * ina_current); 317 | 318 | ESP_LOGW(__func__, "VBUS: %.04f V, IBUS: %.04f A, PBUS: %.04f W", 319 | ina_bus_voltage, ina_current, ina_power); 320 | 321 | // ZCL must be V,A,W = uint16. But we need more accuaracy - so use *100. 322 | voltage = (float)(ina_bus_voltage * 100); 323 | current = (float)(ina_current * 100); 324 | power = (float)(ina_power * 100); 325 | 326 | update_attributes(ATTRIBUTE_ELECTRO); 327 | 328 | vTaskDelay(pdMS_TO_TICKS(INA219_INTERVAL)); 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /components/i2cdev/i2cdev.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2018 Ruslan V. Uss 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | 24 | /** 25 | * @file i2cdev.c 26 | * 27 | * ESP-IDF I2C master thread-safe functions for communication with I2C slave 28 | * 29 | * Copyright (c) 2018 Ruslan V. Uss 30 | * 31 | * MIT Licensed as described in the file LICENSE 32 | */ 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include "i2cdev.h" 39 | 40 | static const char *TAG = "i2cdev"; 41 | 42 | typedef struct { 43 | SemaphoreHandle_t lock; 44 | i2c_config_t config; 45 | bool installed; 46 | } i2c_port_state_t; 47 | 48 | static i2c_port_state_t states[I2C_NUM_MAX]; 49 | 50 | #if CONFIG_I2CDEV_NOLOCK 51 | #define SEMAPHORE_TAKE(port) 52 | #else 53 | #define SEMAPHORE_TAKE(port) do { \ 54 | if (!xSemaphoreTake(states[port].lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT))) \ 55 | { \ 56 | ESP_LOGE(TAG, "Could not take port mutex %d", port); \ 57 | return ESP_ERR_TIMEOUT; \ 58 | } \ 59 | } while (0) 60 | #endif 61 | 62 | #if CONFIG_I2CDEV_NOLOCK 63 | #define SEMAPHORE_GIVE(port) 64 | #else 65 | #define SEMAPHORE_GIVE(port) do { \ 66 | if (!xSemaphoreGive(states[port].lock)) \ 67 | { \ 68 | ESP_LOGE(TAG, "Could not give port mutex %d", port); \ 69 | return ESP_FAIL; \ 70 | } \ 71 | } while (0) 72 | #endif 73 | 74 | esp_err_t i2cdev_init() 75 | { 76 | memset(states, 0, sizeof(states)); 77 | 78 | #if !CONFIG_I2CDEV_NOLOCK 79 | for (int i = 0; i < I2C_NUM_MAX; i++) 80 | { 81 | states[i].lock = xSemaphoreCreateMutex(); 82 | if (!states[i].lock) 83 | { 84 | ESP_LOGE(TAG, "Could not create port mutex %d", i); 85 | return ESP_FAIL; 86 | } 87 | } 88 | #endif 89 | 90 | return ESP_OK; 91 | } 92 | 93 | esp_err_t i2cdev_done() 94 | { 95 | for (int i = 0; i < I2C_NUM_MAX; i++) 96 | { 97 | if (!states[i].lock) continue; 98 | 99 | if (states[i].installed) 100 | { 101 | SEMAPHORE_TAKE(i); 102 | i2c_driver_delete(i); 103 | states[i].installed = false; 104 | SEMAPHORE_GIVE(i); 105 | } 106 | #if !CONFIG_I2CDEV_NOLOCK 107 | vSemaphoreDelete(states[i].lock); 108 | #endif 109 | states[i].lock = NULL; 110 | } 111 | return ESP_OK; 112 | } 113 | 114 | esp_err_t i2c_dev_create_mutex(i2c_dev_t *dev) 115 | { 116 | #if !CONFIG_I2CDEV_NOLOCK 117 | if (!dev) return ESP_ERR_INVALID_ARG; 118 | 119 | ESP_LOGV(TAG, "[0x%02x at %d] creating mutex", dev->addr, dev->port); 120 | 121 | dev->mutex = xSemaphoreCreateMutex(); 122 | if (!dev->mutex) 123 | { 124 | ESP_LOGE(TAG, "[0x%02x at %d] Could not create device mutex", dev->addr, dev->port); 125 | return ESP_FAIL; 126 | } 127 | #endif 128 | 129 | return ESP_OK; 130 | } 131 | 132 | esp_err_t i2c_dev_delete_mutex(i2c_dev_t *dev) 133 | { 134 | #if !CONFIG_I2CDEV_NOLOCK 135 | if (!dev) return ESP_ERR_INVALID_ARG; 136 | 137 | ESP_LOGV(TAG, "[0x%02x at %d] deleting mutex", dev->addr, dev->port); 138 | 139 | vSemaphoreDelete(dev->mutex); 140 | #endif 141 | return ESP_OK; 142 | } 143 | 144 | esp_err_t i2c_dev_take_mutex(i2c_dev_t *dev) 145 | { 146 | #if !CONFIG_I2CDEV_NOLOCK 147 | if (!dev) return ESP_ERR_INVALID_ARG; 148 | 149 | ESP_LOGV(TAG, "[0x%02x at %d] taking mutex", dev->addr, dev->port); 150 | 151 | if (!xSemaphoreTake(dev->mutex, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT))) 152 | { 153 | ESP_LOGE(TAG, "[0x%02x at %d] Could not take device mutex", dev->addr, dev->port); 154 | return ESP_ERR_TIMEOUT; 155 | } 156 | #endif 157 | return ESP_OK; 158 | } 159 | 160 | esp_err_t i2c_dev_give_mutex(i2c_dev_t *dev) 161 | { 162 | #if !CONFIG_I2CDEV_NOLOCK 163 | if (!dev) return ESP_ERR_INVALID_ARG; 164 | 165 | ESP_LOGV(TAG, "[0x%02x at %d] giving mutex", dev->addr, dev->port); 166 | 167 | if (!xSemaphoreGive(dev->mutex)) 168 | { 169 | ESP_LOGE(TAG, "[0x%02x at %d] Could not give device mutex", dev->addr, dev->port); 170 | return ESP_FAIL; 171 | } 172 | #endif 173 | return ESP_OK; 174 | } 175 | 176 | inline static bool cfg_equal(const i2c_config_t *a, const i2c_config_t *b) 177 | { 178 | return a->scl_io_num == b->scl_io_num 179 | && a->sda_io_num == b->sda_io_num 180 | #if HELPER_TARGET_IS_ESP32 181 | && a->master.clk_speed == b->master.clk_speed 182 | #elif HELPER_TARGET_IS_ESP8266 183 | && ((a->clk_stretch_tick && a->clk_stretch_tick == b->clk_stretch_tick) 184 | || (!a->clk_stretch_tick && b->clk_stretch_tick == I2CDEV_MAX_STRETCH_TIME) 185 | ) // see line 232 186 | #endif 187 | && a->scl_pullup_en == b->scl_pullup_en 188 | && a->sda_pullup_en == b->sda_pullup_en; 189 | } 190 | 191 | static esp_err_t i2c_setup_port(const i2c_dev_t *dev) 192 | { 193 | if (dev->port >= I2C_NUM_MAX) return ESP_ERR_INVALID_ARG; 194 | 195 | esp_err_t res; 196 | if (!cfg_equal(&dev->cfg, &states[dev->port].config) || !states[dev->port].installed) 197 | { 198 | ESP_LOGD(TAG, "Reconfiguring I2C driver on port %d", dev->port); 199 | i2c_config_t temp; 200 | memcpy(&temp, &dev->cfg, sizeof(i2c_config_t)); 201 | temp.mode = I2C_MODE_MASTER; 202 | 203 | // Driver reinstallation 204 | if (states[dev->port].installed) 205 | { 206 | i2c_driver_delete(dev->port); 207 | states[dev->port].installed = false; 208 | } 209 | #if HELPER_TARGET_IS_ESP32 210 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) 211 | // See https://github.com/espressif/esp-idf/issues/10163 212 | if ((res = i2c_driver_install(dev->port, temp.mode, 0, 0, 0)) != ESP_OK) 213 | return res; 214 | if ((res = i2c_param_config(dev->port, &temp)) != ESP_OK) 215 | return res; 216 | #else 217 | if ((res = i2c_param_config(dev->port, &temp)) != ESP_OK) 218 | return res; 219 | if ((res = i2c_driver_install(dev->port, temp.mode, 0, 0, 0)) != ESP_OK) 220 | return res; 221 | #endif 222 | #endif 223 | #if HELPER_TARGET_IS_ESP8266 224 | // Clock Stretch time, depending on CPU frequency 225 | temp.clk_stretch_tick = dev->timeout_ticks ? dev->timeout_ticks : I2CDEV_MAX_STRETCH_TIME; 226 | if ((res = i2c_driver_install(dev->port, temp.mode)) != ESP_OK) 227 | return res; 228 | if ((res = i2c_param_config(dev->port, &temp)) != ESP_OK) 229 | return res; 230 | #endif 231 | states[dev->port].installed = true; 232 | 233 | memcpy(&states[dev->port].config, &temp, sizeof(i2c_config_t)); 234 | ESP_LOGD(TAG, "I2C driver successfully reconfigured on port %d", dev->port); 235 | } 236 | #if HELPER_TARGET_IS_ESP32 237 | int t; 238 | if ((res = i2c_get_timeout(dev->port, &t)) != ESP_OK) 239 | return res; 240 | // Timeout cannot be 0 241 | uint32_t ticks = dev->timeout_ticks ? dev->timeout_ticks : I2CDEV_MAX_STRETCH_TIME; 242 | if ((ticks != t) && (res = i2c_set_timeout(dev->port, ticks)) != ESP_OK) 243 | return res; 244 | ESP_LOGD(TAG, "Timeout: ticks = %" PRIu32 " (%" PRIu32 " usec) on port %d", dev->timeout_ticks, dev->timeout_ticks / 80, dev->port); 245 | #endif 246 | 247 | return ESP_OK; 248 | } 249 | 250 | esp_err_t i2c_dev_probe(const i2c_dev_t *dev, i2c_dev_type_t operation_type) 251 | { 252 | if (!dev) return ESP_ERR_INVALID_ARG; 253 | 254 | SEMAPHORE_TAKE(dev->port); 255 | 256 | esp_err_t res = i2c_setup_port(dev); 257 | if (res == ESP_OK) 258 | { 259 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 260 | i2c_master_start(cmd); 261 | i2c_master_write_byte(cmd, dev->addr << 1 | (operation_type == I2C_DEV_READ ? 1 : 0), true); 262 | i2c_master_stop(cmd); 263 | 264 | res = i2c_master_cmd_begin(dev->port, cmd, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)); 265 | 266 | i2c_cmd_link_delete(cmd); 267 | } 268 | 269 | SEMAPHORE_GIVE(dev->port); 270 | 271 | return res; 272 | } 273 | 274 | esp_err_t i2c_dev_read(const i2c_dev_t *dev, const void *out_data, size_t out_size, void *in_data, size_t in_size) 275 | { 276 | if (!dev || !in_data || !in_size) return ESP_ERR_INVALID_ARG; 277 | 278 | SEMAPHORE_TAKE(dev->port); 279 | 280 | esp_err_t res = i2c_setup_port(dev); 281 | if (res == ESP_OK) 282 | { 283 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 284 | if (out_data && out_size) 285 | { 286 | i2c_master_start(cmd); 287 | i2c_master_write_byte(cmd, dev->addr << 1, true); 288 | i2c_master_write(cmd, (void *)out_data, out_size, true); 289 | } 290 | i2c_master_start(cmd); 291 | i2c_master_write_byte(cmd, (dev->addr << 1) | 1, true); 292 | i2c_master_read(cmd, in_data, in_size, I2C_MASTER_LAST_NACK); 293 | i2c_master_stop(cmd); 294 | 295 | res = i2c_master_cmd_begin(dev->port, cmd, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)); 296 | if (res != ESP_OK) 297 | ESP_LOGE(TAG, "Could not read from device [0x%02x at %d]: %d (%s)", dev->addr, dev->port, res, esp_err_to_name(res)); 298 | 299 | i2c_cmd_link_delete(cmd); 300 | } 301 | 302 | SEMAPHORE_GIVE(dev->port); 303 | return res; 304 | } 305 | 306 | esp_err_t i2c_dev_write(const i2c_dev_t *dev, const void *out_reg, size_t out_reg_size, const void *out_data, size_t out_size) 307 | { 308 | if (!dev || !out_data || !out_size) return ESP_ERR_INVALID_ARG; 309 | 310 | SEMAPHORE_TAKE(dev->port); 311 | 312 | esp_err_t res = i2c_setup_port(dev); 313 | if (res == ESP_OK) 314 | { 315 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 316 | i2c_master_start(cmd); 317 | i2c_master_write_byte(cmd, dev->addr << 1, true); 318 | if (out_reg && out_reg_size) 319 | i2c_master_write(cmd, (void *)out_reg, out_reg_size, true); 320 | i2c_master_write(cmd, (void *)out_data, out_size, true); 321 | i2c_master_stop(cmd); 322 | res = i2c_master_cmd_begin(dev->port, cmd, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)); 323 | if (res != ESP_OK) 324 | ESP_LOGE(TAG, "Could not write to device [0x%02x at %d]: %d (%s)", dev->addr, dev->port, res, esp_err_to_name(res)); 325 | i2c_cmd_link_delete(cmd); 326 | } 327 | 328 | SEMAPHORE_GIVE(dev->port); 329 | return res; 330 | } 331 | 332 | esp_err_t i2c_dev_read_reg(const i2c_dev_t *dev, uint8_t reg, void *in_data, size_t in_size) 333 | { 334 | return i2c_dev_read(dev, ®, 1, in_data, in_size); 335 | } 336 | 337 | esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, const void *out_data, size_t out_size) 338 | { 339 | return i2c_dev_write(dev, ®, 1, out_data, out_size); 340 | } 341 | -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | hide_title: true 4 | hide: true 5 | --- 6 | 7 |
8 | GitHub version 9 | GitHub Actions Workflow Status 10 | GitHub download 11 | GitHub Issues or Pull Requests 12 |
13 | 14 | The ZigUSB C6 project is an innovative solution designed to enhance the control and monitoring of USB-powered devices through Zigbee communication. This project aims to provide a seamless integration for smart home enthusiasts and professionals alike, enabling remote control, automation, and monitoring of USB devices in a Zigbee-enabled ecosystem. 15 | 16 | This project is based on the original ZigUSB but uses a modern chip and custom-developed firmware. 17 | 18 | Using this device, you can remotely control the power of the USB port to turn on or off the connected device. Additionally, you can monitor the current voltage and current. It also functions as a reliable Zigbee network router. 19 | 20 | Frequent use cases include converting a "dumb" USB lamp into a "smart" one, connecting modems/sticks/adapters that sometimes require a power reset, and monitoring the current consumption of any connected device. 21 | 22 | ### Key Features 23 | 24 | - **USB Power Control**: Remotely manage the power supply to USB devices, allowing for energy savings and enhanced device management. 25 | - **Zigbee Integration**: Fully compatible with Zigbee networks, facilitating easy integration into existing smart home setups. 26 | - **OTA Updates**: Support for Over-The-Air (OTA) firmware updates, ensuring the device remains up-to-date with the latest features and security enhancements. 27 | - **USB data transfer** is available. This may be needed when connecting a USB modem to a router that does not know how to manage USB power, and the modem may need to be rebooted. 28 | - Monitoring of voltage and current - INA219 chip. 29 | - For power management, USB switch - AP22804AW5-7 chip. 30 | - WT0132C6-S5 module ​​was used as the Zigbee chip. It's ESP32 C6 based module. 31 | - Designed for AK-N-12 case. 32 | 33 | #### To re-pairing or reset to factory defaults: 34 | 35 | **Hold touch button for more than 5 seconds** 36 | 37 | ### Project Goals 38 | 39 | ZigUSB C6 was created with the vision of making smart home automation more accessible and versatile. By providing a bridge between USB devices and Zigbee networks, it opens up new possibilities for device automation and control. Whether you're looking to remotely manage lighting, charge devices on a schedule, or integrate USB devices into complex automation routines, ZigUSB C6 offers the flexibility and reliability needed for modern smart homes. 40 | 41 | Stay tuned for updates as we continue to expand the capabilities of ZigUSB C6, and feel free to contribute to the project or suggest new features through our GitHub repository. 42 | 43 | ### Overview 44 | 45 |
46 | 47 | 48 |
49 | 50 | ### Photos 51 | 52 |
53 | 54 | 55 |
56 | 57 | ### Schematic 58 | 59 |
60 | 61 | #### zigbee2mqtt overview 62 | 63 |
64 | 65 | 66 |
67 | 68 | #### Home Assistant overview 69 | 70 |
71 | 72 |
73 | 74 | ### Hardware files 75 | 76 | - [iBOM page](./hardware/iBOM.html) 🌍 77 | - [BOM file](./hardware/BOM.csv) 📃 78 | - [Gerber zip](./hardware/Gerber.zip) 🗂 79 | 80 | This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License 81 | 82 | ### Web Flasher 83 | 84 | Flash your device using next options: 85 | 86 |
    87 |
  1. Connect your device using USB-TTL adapter.
  2. 88 |
  3. Hit "Connect" and select the correct COM port. No device found?
  4. 89 |
  5. Get firmware installed and connected in less than 3 minutes!
  6. 90 |
91 | 92 |
93 |
94 | 97 |
98 | 99 |
100 | 101 | 102 |
103 | 104 | 105 | 106 |
107 | 108 | 109 |
110 | 111 | 112 | 113 | 226 | 227 | 276 | 277 | #### Firmware Files 278 | 279 | All source files are available in this repository. Pre-built firmware files can be found in the [releases section](https://github.com/xyzroe/ZigUSB_C6/releases). 280 | 281 | ### OTA 282 | 283 | ##### zigbee2mqtt 284 | 285 | 1. Put firmware update (.\*ota) file next to config file 286 | 2. Create `index.json` file: 287 | 288 | ``` 289 | [ 290 | { 291 | "url": "ZigUSB_C6.ota", 292 | "force": true 293 | } 294 | ] 295 | ``` 296 | 297 | 3. Add config option to you zigbee2mqtt configuration.yaml file 298 | 299 | ``` 300 | ota: 301 | zigbee_ota_override_index_location: index.json 302 | ``` 303 | 304 | 4. Open OTA tab in z2m and click check update next to your device. 305 | 5. Check update process via web UI 306 | 307 | ##### homed 308 | 309 | 1. Put firmware update (.\*ota) file to "ota" folder next to config file 310 | 2. Open device page and click OTA button 311 | 3. Click refresh and then update 312 | 313 | ### Verified Supported Zigbee Systems 314 | 315 | - [zigbee2mqtt](https://www.zigbee2mqtt.io/) - Full support, no longer requires an [external converter](https://github.com/xyzroe/ZigUSB_C6/tree/main/external_converter/ZigUSB_C6.js) ⭐⭐⭐⭐⭐ 316 | - [HOMEd](https://wiki.homed.dev/page/HOMEd) - Partial support ⭐⭐⭐⭐ 317 | - [ZHA](https://www.home-assistant.io/integrations/zha/) - Partial support ⭐⭐⭐⭐ 318 | - Other systems must be tested. The device uses standard clusters and attributes, so most coordinators can support it out of the box. 319 | 320 | ### Where to buy? 321 |
322 | I sell on Lectronz 323 |
324 | I sell on Tindie 325 |
326 | 327 | ### Like ♥️? 328 | 329 | [![badges](https://badges.aleen42.com/src/buymeacoffee.svg)](https://www.buymeacoffee.com/xyzroe) 330 | [![badges](https://badges.aleen42.com/src/github.svg)](https://github.com/sponsors/xyzroe) 331 | [![badges](https://badges.aleen42.com/src/paypal.svg)](http://paypal.me/xyzroe) 332 | 333 | ### Contribute 🚀 334 | 335 | - [How-to](./CONTRIBUTE.md) 336 | 337 |
338 | ZigUSB_C6 is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License 339 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | Attribution-NonCommercial-ShareAlike 4.0 International 4 | 5 | ======================================================================= 6 | 7 | Creative Commons Corporation ("Creative Commons") is not a law firm and 8 | does not provide legal services or legal advice. Distribution of 9 | Creative Commons public licenses does not create a lawyer-client or 10 | other relationship. Creative Commons makes its licenses and related 11 | information available on an "as-is" basis. Creative Commons gives no 12 | warranties regarding its licenses, any material licensed under their 13 | terms and conditions, or any related information. Creative Commons 14 | disclaims all liability for damages resulting from their use to the 15 | fullest extent possible. 16 | 17 | Using Creative Commons Public Licenses 18 | 19 | Creative Commons public licenses provide a standard set of terms and 20 | conditions that creators and other rights holders may use to share 21 | original works of authorship and other material subject to copyright 22 | and certain other rights specified in the public license below. The 23 | following considerations are for informational purposes only, are not 24 | exhaustive, and do not form part of our licenses. 25 | 26 | Considerations for licensors: Our public licenses are 27 | intended for use by those authorized to give the public 28 | permission to use material in ways otherwise restricted by 29 | copyright and certain other rights. Our licenses are 30 | irrevocable. Licensors should read and understand the terms 31 | and conditions of the license they choose before applying it. 32 | Licensors should also secure all rights necessary before 33 | applying our licenses so that the public can reuse the 34 | material as expected. Licensors should clearly mark any 35 | material not subject to the license. This includes other CC- 36 | licensed material, or material used under an exception or 37 | limitation to copyright. More considerations for licensors: 38 | wiki.creativecommons.org/Considerations_for_licensors 39 | 40 | Considerations for the public: By using one of our public 41 | licenses, a licensor grants the public permission to use the 42 | licensed material under specified terms and conditions. If 43 | the licensor's permission is not necessary for any reason--for 44 | example, because of any applicable exception or limitation to 45 | copyright--then that use is not regulated by the license. Our 46 | licenses grant only permissions under copyright and certain 47 | other rights that a licensor has authority to grant. Use of 48 | the licensed material may still be restricted for other 49 | reasons, including because others have copyright or other 50 | rights in the material. A licensor may make special requests, 51 | such as asking that all changes be marked or described. 52 | Although not required by our licenses, you are encouraged to 53 | respect those requests where reasonable. More_considerations 54 | for the public: 55 | wiki.creativecommons.org/Considerations_for_licensees 56 | 57 | ======================================================================= 58 | 59 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International 60 | Public License 61 | 62 | By exercising the Licensed Rights (defined below), You accept and agree 63 | to be bound by the terms and conditions of this Creative Commons 64 | Attribution-NonCommercial-ShareAlike 4.0 International Public License 65 | ("Public License"). To the extent this Public License may be 66 | interpreted as a contract, You are granted the Licensed Rights in 67 | consideration of Your acceptance of these terms and conditions, and the 68 | Licensor grants You such rights in consideration of benefits the 69 | Licensor receives from making the Licensed Material available under 70 | these terms and conditions. 71 | 72 | 73 | Section 1 -- Definitions. 74 | 75 | a. Adapted Material means material subject to Copyright and Similar 76 | Rights that is derived from or based upon the Licensed Material 77 | and in which the Licensed Material is translated, altered, 78 | arranged, transformed, or otherwise modified in a manner requiring 79 | permission under the Copyright and Similar Rights held by the 80 | Licensor. For purposes of this Public License, where the Licensed 81 | Material is a musical work, performance, or sound recording, 82 | Adapted Material is always produced where the Licensed Material is 83 | synched in timed relation with a moving image. 84 | 85 | b. Adapter's License means the license You apply to Your Copyright 86 | and Similar Rights in Your contributions to Adapted Material in 87 | accordance with the terms and conditions of this Public License. 88 | 89 | c. BY-NC-SA Compatible License means a license listed at 90 | creativecommons.org/compatiblelicenses, approved by Creative 91 | Commons as essentially the equivalent of this Public License. 92 | 93 | d. Copyright and Similar Rights means copyright and/or similar rights 94 | closely related to copyright including, without limitation, 95 | performance, broadcast, sound recording, and Sui Generis Database 96 | Rights, without regard to how the rights are labeled or 97 | categorized. For purposes of this Public License, the rights 98 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 99 | Rights. 100 | 101 | e. Effective Technological Measures means those measures that, in the 102 | absence of proper authority, may not be circumvented under laws 103 | fulfilling obligations under Article 11 of the WIPO Copyright 104 | Treaty adopted on December 20, 1996, and/or similar international 105 | agreements. 106 | 107 | f. Exceptions and Limitations means fair use, fair dealing, and/or 108 | any other exception or limitation to Copyright and Similar Rights 109 | that applies to Your use of the Licensed Material. 110 | 111 | g. License Elements means the license attributes listed in the name 112 | of a Creative Commons Public License. The License Elements of this 113 | Public License are Attribution, NonCommercial, and ShareAlike. 114 | 115 | h. Licensed Material means the artistic or literary work, database, 116 | or other material to which the Licensor applied this Public 117 | License. 118 | 119 | i. Licensed Rights means the rights granted to You subject to the 120 | terms and conditions of this Public License, which are limited to 121 | all Copyright and Similar Rights that apply to Your use of the 122 | Licensed Material and that the Licensor has authority to license. 123 | 124 | j. Licensor means the individual(s) or entity(ies) granting rights 125 | under this Public License. 126 | 127 | k. NonCommercial means not primarily intended for or directed towards 128 | commercial advantage or monetary compensation. For purposes of 129 | this Public License, the exchange of the Licensed Material for 130 | other material subject to Copyright and Similar Rights by digital 131 | file-sharing or similar means is NonCommercial provided there is 132 | no payment of monetary compensation in connection with the 133 | exchange. 134 | 135 | l. Share means to provide material to the public by any means or 136 | process that requires permission under the Licensed Rights, such 137 | as reproduction, public display, public performance, distribution, 138 | dissemination, communication, or importation, and to make material 139 | available to the public including in ways that members of the 140 | public may access the material from a place and at a time 141 | individually chosen by them. 142 | 143 | m. Sui Generis Database Rights means rights other than copyright 144 | resulting from Directive 96/9/EC of the European Parliament and of 145 | the Council of 11 March 1996 on the legal protection of databases, 146 | as amended and/or succeeded, as well as other essentially 147 | equivalent rights anywhere in the world. 148 | 149 | n. You means the individual or entity exercising the Licensed Rights 150 | under this Public License. Your has a corresponding meaning. 151 | 152 | 153 | Section 2 -- Scope. 154 | 155 | a. License grant. 156 | 157 | 1. Subject to the terms and conditions of this Public License, 158 | the Licensor hereby grants You a worldwide, royalty-free, 159 | non-sublicensable, non-exclusive, irrevocable license to 160 | exercise the Licensed Rights in the Licensed Material to: 161 | 162 | a. reproduce and Share the Licensed Material, in whole or 163 | in part, for NonCommercial purposes only; and 164 | 165 | b. produce, reproduce, and Share Adapted Material for 166 | NonCommercial purposes only. 167 | 168 | 2. Exceptions and Limitations. For the avoidance of doubt, where 169 | Exceptions and Limitations apply to Your use, this Public 170 | License does not apply, and You do not need to comply with 171 | its terms and conditions. 172 | 173 | 3. Term. The term of this Public License is specified in Section 174 | 6(a). 175 | 176 | 4. Media and formats; technical modifications allowed. The 177 | Licensor authorizes You to exercise the Licensed Rights in 178 | all media and formats whether now known or hereafter created, 179 | and to make technical modifications necessary to do so. The 180 | Licensor waives and/or agrees not to assert any right or 181 | authority to forbid You from making technical modifications 182 | necessary to exercise the Licensed Rights, including 183 | technical modifications necessary to circumvent Effective 184 | Technological Measures. For purposes of this Public License, 185 | simply making modifications authorized by this Section 2(a) 186 | (4) never produces Adapted Material. 187 | 188 | 5. Downstream recipients. 189 | 190 | a. Offer from the Licensor -- Licensed Material. Every 191 | recipient of the Licensed Material automatically 192 | receives an offer from the Licensor to exercise the 193 | Licensed Rights under the terms and conditions of this 194 | Public License. 195 | 196 | b. Additional offer from the Licensor -- Adapted Material. 197 | Every recipient of Adapted Material from You 198 | automatically receives an offer from the Licensor to 199 | exercise the Licensed Rights in the Adapted Material 200 | under the conditions of the Adapter's License You apply. 201 | 202 | c. No downstream restrictions. You may not offer or impose 203 | any additional or different terms or conditions on, or 204 | apply any Effective Technological Measures to, the 205 | Licensed Material if doing so restricts exercise of the 206 | Licensed Rights by any recipient of the Licensed 207 | Material. 208 | 209 | 6. No endorsement. Nothing in this Public License constitutes or 210 | may be construed as permission to assert or imply that You 211 | are, or that Your use of the Licensed Material is, connected 212 | with, or sponsored, endorsed, or granted official status by, 213 | the Licensor or others designated to receive attribution as 214 | provided in Section 3(a)(1)(A)(i). 215 | 216 | b. Other rights. 217 | 218 | 1. Moral rights, such as the right of integrity, are not 219 | licensed under this Public License, nor are publicity, 220 | privacy, and/or other similar personality rights; however, to 221 | the extent possible, the Licensor waives and/or agrees not to 222 | assert any such rights held by the Licensor to the limited 223 | extent necessary to allow You to exercise the Licensed 224 | Rights, but not otherwise. 225 | 226 | 2. Patent and trademark rights are not licensed under this 227 | Public License. 228 | 229 | 3. To the extent possible, the Licensor waives any right to 230 | collect royalties from You for the exercise of the Licensed 231 | Rights, whether directly or through a collecting society 232 | under any voluntary or waivable statutory or compulsory 233 | licensing scheme. In all other cases the Licensor expressly 234 | reserves any right to collect such royalties, including when 235 | the Licensed Material is used other than for NonCommercial 236 | purposes. 237 | 238 | 239 | Section 3 -- License Conditions. 240 | 241 | Your exercise of the Licensed Rights is expressly made subject to the 242 | following conditions. 243 | 244 | a. Attribution. 245 | 246 | 1. If You Share the Licensed Material (including in modified 247 | form), You must: 248 | 249 | a. retain the following if it is supplied by the Licensor 250 | with the Licensed Material: 251 | 252 | i. identification of the creator(s) of the Licensed 253 | Material and any others designated to receive 254 | attribution, in any reasonable manner requested by 255 | the Licensor (including by pseudonym if 256 | designated); 257 | 258 | ii. a copyright notice; 259 | 260 | iii. a notice that refers to this Public License; 261 | 262 | iv. a notice that refers to the disclaimer of 263 | warranties; 264 | 265 | v. a URI or hyperlink to the Licensed Material to the 266 | extent reasonably practicable; 267 | 268 | b. indicate if You modified the Licensed Material and 269 | retain an indication of any previous modifications; and 270 | 271 | c. indicate the Licensed Material is licensed under this 272 | Public License, and include the text of, or the URI or 273 | hyperlink to, this Public License. 274 | 275 | 2. You may satisfy the conditions in Section 3(a)(1) in any 276 | reasonable manner based on the medium, means, and context in 277 | which You Share the Licensed Material. For example, it may be 278 | reasonable to satisfy the conditions by providing a URI or 279 | hyperlink to a resource that includes the required 280 | information. 281 | 3. If requested by the Licensor, You must remove any of the 282 | information required by Section 3(a)(1)(A) to the extent 283 | reasonably practicable. 284 | 285 | b. ShareAlike. 286 | 287 | In addition to the conditions in Section 3(a), if You Share 288 | Adapted Material You produce, the following conditions also apply. 289 | 290 | 1. The Adapter's License You apply must be a Creative Commons 291 | license with the same License Elements, this version or 292 | later, or a BY-NC-SA Compatible License. 293 | 294 | 2. You must include the text of, or the URI or hyperlink to, the 295 | Adapter's License You apply. You may satisfy this condition 296 | in any reasonable manner based on the medium, means, and 297 | context in which You Share Adapted Material. 298 | 299 | 3. You may not offer or impose any additional or different terms 300 | or conditions on, or apply any Effective Technological 301 | Measures to, Adapted Material that restrict exercise of the 302 | rights granted under the Adapter's License You apply. 303 | 304 | 305 | Section 4 -- Sui Generis Database Rights. 306 | 307 | Where the Licensed Rights include Sui Generis Database Rights that 308 | apply to Your use of the Licensed Material: 309 | 310 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 311 | to extract, reuse, reproduce, and Share all or a substantial 312 | portion of the contents of the database for NonCommercial purposes 313 | only; 314 | 315 | b. if You include all or a substantial portion of the database 316 | contents in a database in which You have Sui Generis Database 317 | Rights, then the database in which You have Sui Generis Database 318 | Rights (but not its individual contents) is Adapted Material, 319 | including for purposes of Section 3(b); and 320 | 321 | c. You must comply with the conditions in Section 3(a) if You Share 322 | all or a substantial portion of the contents of the database. 323 | 324 | For the avoidance of doubt, this Section 4 supplements and does not 325 | replace Your obligations under this Public License where the Licensed 326 | Rights include other Copyright and Similar Rights. 327 | 328 | 329 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 330 | 331 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 332 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 333 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 334 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 335 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 336 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 337 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 338 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 339 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 340 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 341 | 342 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 343 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 344 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 345 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 346 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 347 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 348 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 349 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 350 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 351 | 352 | c. The disclaimer of warranties and limitation of liability provided 353 | above shall be interpreted in a manner that, to the extent 354 | possible, most closely approximates an absolute disclaimer and 355 | waiver of all liability. 356 | 357 | 358 | Section 6 -- Term and Termination. 359 | 360 | a. This Public License applies for the term of the Copyright and 361 | Similar Rights licensed here. However, if You fail to comply with 362 | this Public License, then Your rights under this Public License 363 | terminate automatically. 364 | 365 | b. Where Your right to use the Licensed Material has terminated under 366 | Section 6(a), it reinstates: 367 | 368 | 1. automatically as of the date the violation is cured, provided 369 | it is cured within 30 days of Your discovery of the 370 | violation; or 371 | 372 | 2. upon express reinstatement by the Licensor. 373 | 374 | For the avoidance of doubt, this Section 6(b) does not affect any 375 | right the Licensor may have to seek remedies for Your violations 376 | of this Public License. 377 | 378 | c. For the avoidance of doubt, the Licensor may also offer the 379 | Licensed Material under separate terms or conditions or stop 380 | distributing the Licensed Material at any time; however, doing so 381 | will not terminate this Public License. 382 | 383 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 384 | License. 385 | 386 | 387 | Section 7 -- Other Terms and Conditions. 388 | 389 | a. The Licensor shall not be bound by any additional or different 390 | terms or conditions communicated by You unless expressly agreed. 391 | 392 | b. Any arrangements, understandings, or agreements regarding the 393 | Licensed Material not stated herein are separate from and 394 | independent of the terms and conditions of this Public License. 395 | 396 | 397 | Section 8 -- Interpretation. 398 | 399 | a. For the avoidance of doubt, this Public License does not, and 400 | shall not be interpreted to, reduce, limit, restrict, or impose 401 | conditions on any use of the Licensed Material that could lawfully 402 | be made without permission under this Public License. 403 | 404 | b. To the extent possible, if any provision of this Public License is 405 | deemed unenforceable, it shall be automatically reformed to the 406 | minimum extent necessary to make it enforceable. If the provision 407 | cannot be reformed, it shall be severed from this Public License 408 | without affecting the enforceability of the remaining terms and 409 | conditions. 410 | 411 | c. No term or condition of this Public License will be waived and no 412 | failure to comply consented to unless expressly agreed to by the 413 | Licensor. 414 | 415 | d. Nothing in this Public License constitutes or may be interpreted 416 | as a limitation upon, or waiver of, any privileges and immunities 417 | that apply to the Licensor or You, including from the legal 418 | processes of any jurisdiction or authority. 419 | 420 | ======================================================================= 421 | 422 | Creative Commons is not a party to its public 423 | licenses. Notwithstanding, Creative Commons may elect to apply one of 424 | its public licenses to material it publishes and in those instances 425 | will be considered the “Licensor.” The text of the Creative Commons 426 | public licenses is dedicated to the public domain under the CC0 Public 427 | Domain Dedication. Except for the limited purpose of indicating that 428 | material is shared under a Creative Commons public license or as 429 | otherwise permitted by the Creative Commons policies published at 430 | creativecommons.org/policies, Creative Commons does not authorize the 431 | use of the trademark "Creative Commons" or any other trademark or logo 432 | of Creative Commons without its prior written consent including, 433 | without limitation, in connection with any unauthorized modifications 434 | to any of its public licenses or any other arrangements, 435 | understandings, or agreements concerning use of licensed material. For 436 | the avoidance of doubt, this paragraph does not form part of the 437 | public licenses. 438 | 439 | Creative Commons may be contacted at creativecommons.org. 440 | -------------------------------------------------------------------------------- /main/zigbee.c: -------------------------------------------------------------------------------- 1 | 2 | #include "esp_check.h" 3 | #include "esp_err.h" 4 | #include "esp_log.h" 5 | #include "nvs_flash.h" 6 | #include "string.h" 7 | #include "freertos/FreeRTOS.h" 8 | #include "freertos/task.h" 9 | #include "zcl/esp_zigbee_zcl_common.h" 10 | #include 11 | #include 12 | 13 | #include "ha/esp_zigbee_ha_standard.h" 14 | #include "esp_timer.h" 15 | #include "esp_ota_ops.h" 16 | #include "zboss_api.h" 17 | #include "zcl/esp_zigbee_zcl_command.h" 18 | #include "zcl/zb_zcl_common.h" 19 | 20 | #include "iot_button.h" 21 | 22 | #include "const.h" 23 | #include "main.h" 24 | #include "perf.h" 25 | #include "tools.h" 26 | #include "zigbee.h" 27 | #include "ota.h" 28 | 29 | /*------ Global definitions -----------*/ 30 | 31 | static const esp_partition_t *s_ota_partition = NULL; 32 | static esp_ota_handle_t s_ota_handle = 0; 33 | 34 | static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) 35 | { 36 | ESP_ERROR_CHECK(esp_zb_bdb_start_top_level_commissioning(mode_mask)); 37 | } 38 | 39 | /* Manual reporting atribute to coordinator */ 40 | static void reportAttribute(uint8_t endpoint, uint16_t clusterID, uint16_t attributeID, void *value, uint8_t value_length) 41 | { 42 | esp_zb_zcl_report_attr_cmd_t cmd = { 43 | .zcl_basic_cmd = { 44 | .dst_addr_u.addr_short = 0x0000, 45 | .dst_endpoint = endpoint, 46 | .src_endpoint = endpoint, 47 | }, 48 | .address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT, 49 | .clusterID = clusterID, 50 | .attributeID = attributeID, 51 | .cluster_role = ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, 52 | }; 53 | esp_zb_zcl_attr_t *value_r = esp_zb_zcl_get_attribute(endpoint, clusterID, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, attributeID); 54 | memcpy(value_r->data_p, value, value_length); 55 | esp_zb_zcl_report_attr_cmd_req(&cmd); 56 | } 57 | 58 | static void update_attribute_value(uint8_t endpoint, uint16_t cluster_id, uint8_t role, uint16_t attr_id, void *value, const char *attr_name) 59 | { 60 | esp_zb_zcl_status_t status = esp_zb_zcl_set_attribute_val(endpoint, cluster_id, role, attr_id, value, false); 61 | if (status != ESP_ZB_ZCL_STATUS_SUCCESS) 62 | { 63 | ESP_LOGE(__func__, "Setting %s attribute failed!", attr_name); 64 | } 65 | } 66 | 67 | void update_attributes(attribute_t attribute) 68 | { 69 | if (connected) 70 | { 71 | ESP_LOGI(__func__, "updating %d", attribute); 72 | 73 | if (attribute == ATTRIBUTE_TEMP || attribute == ATTRIBUTE_ALL) 74 | { 75 | update_attribute_value(SENSOR_ENDPOINT, ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID, &CPU_temp, "temperature"); 76 | } 77 | 78 | if (attribute == ATTRIBUTE_ELECTRO || attribute == ATTRIBUTE_ALL) 79 | { 80 | update_attribute_value(SENSOR_ENDPOINT, ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_CURRENT_ID, ¤t, "DC current"); 81 | update_attribute_value(SENSOR_ENDPOINT, ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_RMSCURRENT_ID, ¤t, "RMS current"); 82 | 83 | update_attribute_value(SENSOR_ENDPOINT, ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_VOLTAGE_ID, &voltage, "DC voltage"); 84 | update_attribute_value(SENSOR_ENDPOINT, ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_RMSVOLTAGE_ID, &voltage, "RMS voltage"); 85 | 86 | update_attribute_value(SENSOR_ENDPOINT, ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DCPOWER_ID, &power, "DC power"); 87 | update_attribute_value(SENSOR_ENDPOINT, ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_ACTIVE_POWER_ID, &power, "active power"); 88 | } 89 | } 90 | } 91 | 92 | /* Task for update all values value */ 93 | void force_update() 94 | { 95 | while (1) 96 | { 97 | vTaskDelay(WAIT_BEFORE_FIRST_UPDATE / portTICK_PERIOD_MS); 98 | if (connected) 99 | { 100 | int_led_blink(); 101 | 102 | ESP_LOGI(__func__, "temperature = %d, current = %d, voltage = %d, power = %d", CPU_temp, current, voltage, power); 103 | 104 | send_bin_cfg_option(SENSOR_ENDPOINT, data.USB_state); 105 | send_bin_cfg_option(INT_LED_ENDPOINT, data.int_led_mode); 106 | send_bin_cfg_option(EXT_LED_ENDPOINT, data.ext_led_mode); 107 | send_bin_cfg_option(INV_USB_ENDPOINT, 0); 108 | 109 | send_alarm_state(data.alarm_state); 110 | 111 | update_attributes(ATTRIBUTE_ALL); 112 | } 113 | vTaskDelay((UPDATE_ATTRIBUTE_INTERVAL - WAIT_BEFORE_FIRST_UPDATE) / portTICK_PERIOD_MS); 114 | } 115 | } 116 | 117 | static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message) 118 | { 119 | esp_err_t ret = ESP_OK; 120 | ESP_RETURN_ON_FALSE(message, ESP_FAIL, __func__, "Empty message"); 121 | ESP_RETURN_ON_FALSE(message->info.status == ESP_ZB_ZCL_STATUS_SUCCESS, ESP_ERR_INVALID_ARG, __func__, "Received message: error status(%d)", 122 | message->info.status); 123 | ESP_LOGI(__func__, "Received message: endpoint(%d), cluster(0x%x), attribute(0x%x), data size(%d)", message->info.dst_endpoint, message->info.cluster, 124 | message->attribute.id, message->attribute.data.size); 125 | if (message->info.dst_endpoint == SENSOR_ENDPOINT) 126 | { 127 | switch (message->info.cluster) 128 | { 129 | case ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY: 130 | 131 | int time = *(int *)message->attribute.data.value; 132 | ESP_LOGI(__func__, "Identify pressed, time: %ds", time); 133 | 134 | if (time < 3) // Minimum time is 3 seconds 135 | { 136 | ESP_LOGW(__func__, "Identify time is too short, setting to 3 seconds"); 137 | time = 3; 138 | } 139 | 140 | float old_led_hz = led_hz; 141 | bool old_int_led_mode = data.int_led_mode; 142 | data.int_led_mode = 1; 143 | led_hz = 4; 144 | 145 | if (ledTaskHandle != NULL) 146 | { 147 | vTaskDelete(ledTaskHandle); 148 | ledTaskHandle = NULL; 149 | } 150 | xTaskCreate(led_task, "led_task", 4096, NULL, 3, &ledTaskHandle); 151 | 152 | vTaskDelay(time * 1000 / portTICK_PERIOD_MS); 153 | 154 | if (ledTaskHandle != NULL) 155 | { 156 | vTaskDelete(ledTaskHandle); 157 | ledTaskHandle = NULL; 158 | } 159 | led_hz = old_led_hz; 160 | int_led_blink(); 161 | 162 | data.int_led_mode = old_int_led_mode; 163 | 164 | uint16_t timer = 0; 165 | update_attribute_value(SENSOR_ENDPOINT, ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_CMD_IDENTIFY_IDENTIFY_ID, &timer, "Identify"); 166 | 167 | ESP_LOGI(__func__, "Identify exit"); 168 | break; 169 | case ESP_ZB_ZCL_CLUSTER_ID_ON_OFF: 170 | if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_START_UP_ON_OFF) 171 | { 172 | int value = *(int *)message->attribute.data.value; 173 | if (value == 0 || value == 1 || value == 2 || value == 255) 174 | { 175 | ESP_LOGW(__func__, "Power-on behavior %d", value); 176 | data.start_up_on_off = value; 177 | write_NVS("start_up_on_off", data.start_up_on_off); 178 | } 179 | else 180 | { 181 | ESP_LOGE(__func__, "Invalid power-on behavior value: %d", value); 182 | } 183 | } 184 | if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_TIME) 185 | { 186 | ESP_LOGI(__func__, "On time %d", *(int *)message->attribute.data.value); 187 | } 188 | if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_OFF_WAIT_TIME) 189 | { 190 | ESP_LOGI(__func__, "Off wait time %d", *(int *)message->attribute.data.value); 191 | } 192 | break; 193 | default: 194 | ESP_LOGI(__func__, "Message data: cluster(0x%x), attribute(0x%x) ", message->info.cluster, message->attribute.id); 195 | } 196 | } 197 | // bool usb_state = 0; 198 | /*if (message->info.dst_endpoint == HA_ONOFF_SWITCH_ENDPOINT) 199 | { 200 | if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) 201 | { 202 | if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) 203 | { 204 | usb_state = message->attribute.data.value ? *(bool *)message->attribute.data.value : usb_state; 205 | ESP_LOGI(__func__, "USB sets to %s", usb_state ? "On" : "Off"); 206 | usb_driver_set_power(usb_state); 207 | } 208 | } 209 | }*/ 210 | if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) 211 | { 212 | // ESP_LOGI(__func__, "Message data: cluster(0x%x), attribute(0x%x) ", message->info.cluster, message->attribute.id); 213 | if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) 214 | { 215 | if (message->info.dst_endpoint == SENSOR_ENDPOINT) 216 | { 217 | bool usb_state = 218 | message->attribute.data.value ? *(bool *)message->attribute.data.value : 0; 219 | ESP_LOGI(__func__, "USB sets to %s", usb_state ? "On" : "Off"); 220 | usb_driver_set_power(usb_state); 221 | } 222 | else 223 | { 224 | bool switch_state = 225 | message->attribute.data.value ? *(bool *)message->attribute.data.value : 0; 226 | ESP_LOGI(__func__, "Endpoint %d, sets to %s", message->info.dst_endpoint, switch_state ? "On" : "Off"); 227 | if (message->info.dst_endpoint == INT_LED_ENDPOINT) 228 | { 229 | if (switch_state == 0) 230 | { 231 | gpio_set_level(INT_LED_GPIO, LED_OFF_STATE); 232 | } 233 | data.int_led_mode = switch_state; 234 | write_NVS("int_led_mode", switch_state); 235 | } 236 | else if (message->info.dst_endpoint == EXT_LED_ENDPOINT) 237 | { 238 | if (switch_state == 0) 239 | { 240 | ESP_LOGI(__func__, "cmd is OFF, so EXT_LED_GPIO (1)"); 241 | gpio_set_level(EXT_LED_GPIO, 1); 242 | } 243 | else if (data.USB_state == 1) 244 | { 245 | ESP_LOGI(__func__, "USB is ON, so EXT_LED_GPIO (0)"); 246 | gpio_set_level(EXT_LED_GPIO, 0); 247 | } 248 | data.ext_led_mode = switch_state; 249 | write_NVS("ext_led_mode", switch_state); 250 | } 251 | else if (message->info.dst_endpoint == INV_USB_ENDPOINT) 252 | { 253 | // inverted logic to make possible onWithOff work 254 | bool new_state = !(switch_state); 255 | usb_driver_set_power(new_state); 256 | send_bin_cfg_option(SENSOR_ENDPOINT, new_state); 257 | } 258 | } 259 | } 260 | } 261 | return ret; 262 | } 263 | 264 | static esp_err_t zb_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_resp_message_t *message) 265 | { 266 | 267 | ESP_RETURN_ON_FALSE(message, ESP_FAIL, __func__, "Empty message"); 268 | ESP_RETURN_ON_FALSE(message->info.status == ESP_ZB_ZCL_STATUS_SUCCESS, ESP_ERR_INVALID_ARG, __func__, "Received message: error status(%d)", 269 | message->info.status); 270 | 271 | esp_zb_zcl_read_attr_resp_variable_t *variable = message->variables; 272 | while (variable) 273 | { 274 | ESP_LOGI(__func__, "Read attribute response: status(%d), cluster(0x%x), attribute(0x%x), type(0x%x), value(%d)", variable->status, 275 | message->info.cluster, variable->attribute.id, variable->attribute.data.type, 276 | variable->attribute.data.value ? *(uint8_t *)variable->attribute.data.value : 0); 277 | 278 | if (message->info.dst_endpoint == SENSOR_ENDPOINT) 279 | { 280 | switch (message->info.cluster) 281 | { 282 | case ESP_ZB_ZCL_CLUSTER_ID_TIME: 283 | ESP_LOGW(__func__, "Server time recieved %lu", *(uint32_t *)variable->attribute.data.value); 284 | struct timeval tv; 285 | tv.tv_sec = *(uint32_t *)variable->attribute.data.value + 946684800; 286 | settimeofday(&tv, NULL); 287 | time_updated = true; 288 | 289 | uint32_t boot_time = *(uint32_t *)variable->attribute.data.value; 290 | ESP_LOGI(__func__, "Write new boot time %lu", boot_time); 291 | esp_zb_zcl_status_t state_boot_time = esp_zb_zcl_set_attribute_val(SENSOR_ENDPOINT, ESP_ZB_ZCL_CLUSTER_ID_TIME, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_TIME_LAST_SET_TIME_ID, &boot_time, false); 292 | if (state_boot_time != ESP_ZB_ZCL_STATUS_SUCCESS) 293 | { 294 | ESP_LOGE(__func__, "Setting boot time attribute failed!"); 295 | } 296 | 297 | break; 298 | default: 299 | ESP_LOGI(__func__, "Message data: cluster(0x%x), attribute(0x%x) ", message->info.cluster, variable->attribute.id); 300 | } 301 | } 302 | 303 | variable = variable->next; 304 | } 305 | 306 | return ESP_OK; 307 | } 308 | 309 | size_t ota_data_len_; 310 | size_t ota_header_len_; 311 | bool ota_upgrade_subelement_; 312 | uint8_t ota_header_[6]; 313 | 314 | /* 315 | static esp_err_t zb_ota_upgrade_status_handler(esp_zb_zcl_ota_upgrade_value_message_t message) 316 | { 317 | static uint32_t total_size = 0; 318 | static uint32_t offset = 0; 319 | static int64_t start_time = 0; 320 | esp_err_t ret = ESP_OK; 321 | 322 | if (message.info.status == ESP_ZB_ZCL_STATUS_SUCCESS) 323 | { 324 | switch (message.upgrade_status) 325 | { 326 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_START: 327 | ESP_LOGI(__func__, "-- OTA upgrade start"); 328 | start_time = esp_timer_get_time(); 329 | s_ota_partition = esp_ota_get_next_update_partition(NULL); 330 | assert(s_ota_partition); 331 | ret = esp_ota_begin(s_ota_partition, 0, &s_ota_handle); 332 | ESP_RETURN_ON_ERROR(ret, __func__, "Failed to begin OTA partition, status: %s", esp_err_to_name(ret)); 333 | break; 334 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_RECEIVE: 335 | size_t payload_size = message.payload_size; 336 | const uint8_t *payload = message.payload; 337 | 338 | total_size = message.ota_header.image_size; 339 | offset += payload_size; 340 | 341 | ESP_LOGI(__func__, "-- OTA Client receives data: progress [%ld/%ld]", offset, total_size); 342 | 343 | // Read and process the first sub-element, ignoring everything else 344 | while (ota_header_len_ < 6 && payload_size > 0) 345 | { 346 | ota_header_[ota_header_len_] = payload[0]; 347 | ota_header_len_++; 348 | payload++; 349 | payload_size--; 350 | } 351 | 352 | if (!ota_upgrade_subelement_ && ota_header_len_ == 6) 353 | { 354 | if (ota_header_[0] == 0 && ota_header_[1] == 0) 355 | { 356 | ota_upgrade_subelement_ = true; 357 | ota_data_len_ = 358 | (((int)ota_header_[5] & 0xFF) << 24) | (((int)ota_header_[4] & 0xFF) << 16) | (((int)ota_header_[3] & 0xFF) << 8) | ((int)ota_header_[2] & 0xFF); 359 | ESP_LOGD(__func__, "OTA sub-element size %zu", ota_data_len_); 360 | } 361 | else 362 | { 363 | ESP_LOGE(__func__, "OTA sub-element type %02x%02x not supported", ota_header_[0], ota_header_[1]); 364 | return ESP_FAIL; 365 | } 366 | } 367 | 368 | if (ota_data_len_) 369 | { 370 | payload_size = fmin(ota_data_len_, payload_size); 371 | ota_data_len_ -= payload_size; 372 | 373 | if (message.payload_size && message.payload) 374 | { 375 | ret = esp_ota_write(s_ota_handle, payload, payload_size); 376 | ESP_RETURN_ON_ERROR(ret, __func__, "Failed to write OTA data to partition, status: %s", esp_err_to_name(ret)); 377 | } 378 | } 379 | break; 380 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_APPLY: 381 | ESP_LOGI(__func__, "-- OTA upgrade apply"); 382 | break; 383 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_CHECK: 384 | ret = offset == total_size ? ESP_OK : ESP_FAIL; 385 | ESP_LOGI(__func__, "-- OTA upgrade check status: %s", esp_err_to_name(ret)); 386 | break; 387 | case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_FINISH: 388 | ESP_LOGI(__func__, "-- OTA Finish"); 389 | ESP_LOGI(__func__, "-- OTA Information: version: 0x%lx, manufacturer code: 0x%x, image type: 0x%x, total size: %ld bytes, cost time: %lld ms,", 390 | message.ota_header.file_version, message.ota_header.manufacturer_code, message.ota_header.image_type, 391 | message.ota_header.image_size, (esp_timer_get_time() - start_time) / 1000); 392 | ret = esp_ota_end(s_ota_handle); 393 | ESP_RETURN_ON_ERROR(ret, __func__, "Failed to end OTA partition, status: %s", esp_err_to_name(ret)); 394 | ret = esp_ota_set_boot_partition(s_ota_partition); 395 | ESP_RETURN_ON_ERROR(ret, __func__, "Failed to set OTA boot partition, status: %s", esp_err_to_name(ret)); 396 | ESP_LOGW(__func__, "Prepare to restart system"); 397 | esp_restart(); 398 | break; 399 | default: 400 | ESP_LOGI(__func__, "OTA status: %d", message.upgrade_status); 401 | break; 402 | } 403 | } 404 | return ret; 405 | } 406 | */ 407 | 408 | static esp_err_t zb_ota_upgrade_query_image_resp_handler(esp_zb_zcl_ota_upgrade_query_image_resp_message_t message) 409 | { 410 | esp_err_t ret = ESP_OK; 411 | if (message.info.status == ESP_ZB_ZCL_STATUS_SUCCESS) 412 | { 413 | ESP_LOGI(__func__, "Queried OTA image from address: 0x%04hx, endpoint: %d", message.server_addr.u.short_addr, message.server_endpoint); 414 | ESP_LOGI(__func__, "Image version: 0x%lx, manufacturer code: 0x%x, image size: %ld", message.file_version, message.manufacturer_code, 415 | message.image_size); 416 | } 417 | if (ret == ESP_OK) 418 | { 419 | ESP_LOGI(__func__, "Approving OTA image upgrade"); 420 | } 421 | else 422 | { 423 | ESP_LOGI(__func__, "Rejecting OTA image upgrade, status: %s", esp_err_to_name(ret)); 424 | } 425 | return ret; 426 | } 427 | 428 | 429 | static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message) 430 | { 431 | esp_err_t ret = ESP_OK; 432 | switch (callback_id) 433 | { 434 | // case ESP_ZB_CORE_IDENTIFY_EFFECT_CB_ID: 435 | // ret = esp_zb_zcl_identify_cmd_req((esp_zb_zcl_identify_cmd_t *)message); 436 | // ESP_LOGW(__func__, "ESP_ZB_CORE_IDENTIFY_EFFECT_CB_ID"); 437 | // break; 438 | case ESP_ZB_CORE_CMD_WRITE_ATTR_RESP_CB_ID: 439 | ESP_LOGW(__func__, "ESP_ZB_CORE_CMD_WRITE_ATTR_RESP_CB_ID"); 440 | break; 441 | case ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID: 442 | ret = zb_attribute_handler((esp_zb_zcl_set_attr_value_message_t *)message); 443 | ESP_LOGW(__func__, "CORE_SET_ATTR_VALUE_CB action(0x%x) callback", callback_id); 444 | break; 445 | case ESP_ZB_CORE_CMD_READ_ATTR_RESP_CB_ID: 446 | ret = zb_read_attr_resp_handler((esp_zb_zcl_cmd_read_attr_resp_message_t *)message); 447 | ESP_LOGW(__func__, "CORE_CMD_READ_ATTR_RESP_CB action(0x%x) callback", callback_id); 448 | break; 449 | case ESP_ZB_CORE_OTA_UPGRADE_VALUE_CB_ID: 450 | ret = zb_ota_upgrade_status_handler(*(esp_zb_zcl_ota_upgrade_value_message_t *)message); 451 | break; 452 | case ESP_ZB_CORE_OTA_UPGRADE_SRV_QUERY_IMAGE_CB_ID: 453 | ret = zb_ota_upgrade_query_image_resp_handler(*(esp_zb_zcl_ota_upgrade_query_image_resp_message_t *)message); 454 | break; 455 | default: 456 | ESP_LOGD(__func__, "Receive Zigbee action(0x%x) callback", callback_id); 457 | break; 458 | } 459 | return ret; 460 | } 461 | 462 | void read_server_time() 463 | { 464 | esp_zb_zcl_read_attr_cmd_t read_req; 465 | read_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; 466 | uint16_t attributes[] = {ESP_ZB_ZCL_ATTR_TIME_LOCAL_TIME_ID}; 467 | 468 | read_req.attr_number = sizeof(attributes) / sizeof(uint16_t); 469 | read_req.attr_field = attributes; 470 | 471 | read_req.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TIME; 472 | 473 | read_req.zcl_basic_cmd.dst_endpoint = 1; // Coordinator 474 | read_req.zcl_basic_cmd.src_endpoint = SENSOR_ENDPOINT; // Device main endpoint 475 | read_req.zcl_basic_cmd.dst_addr_u.addr_short = 0x0000; 476 | esp_zb_zcl_read_attr_cmd_req(&read_req); 477 | } 478 | 479 | void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) 480 | { 481 | uint32_t *p_sg_p = signal_struct->p_app_signal; 482 | esp_err_t err_status = signal_struct->esp_err_status; 483 | esp_zb_app_signal_type_t sig_type = *p_sg_p; 484 | esp_zb_zdo_signal_leave_params_t *leave_params = NULL; 485 | switch (sig_type) 486 | { 487 | case ESP_ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY: 488 | esp_zb_set_node_descriptor_manufacturer_code(manuf_id); 489 | break; 490 | case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START: 491 | case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT: 492 | case ESP_ZB_BDB_SIGNAL_STEERING: 493 | if (err_status != ESP_OK) 494 | { 495 | connected = false; 496 | led_hz = 2; 497 | ESP_LOGW(__func__, "Stack %s failure with %s status, steering", esp_zb_zdo_signal_to_string(sig_type), esp_err_to_name(err_status)); 498 | esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000); 499 | } 500 | else 501 | { 502 | /* device auto start successfully and on a formed network */ 503 | connected = true; 504 | led_hz = 0; 505 | esp_zb_ieee_addr_t extended_pan_id; 506 | esp_zb_get_extended_pan_id(extended_pan_id); 507 | ESP_LOGI(__func__, "Joined network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: 0x%04hx, Channel:%d)", 508 | extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], 509 | extended_pan_id[3], extended_pan_id[2], extended_pan_id[1], extended_pan_id[0], 510 | esp_zb_get_pan_id(), esp_zb_get_current_channel()); 511 | read_server_time(); 512 | } 513 | break; 514 | case ESP_ZB_ZDO_SIGNAL_LEAVE: 515 | leave_params = (esp_zb_zdo_signal_leave_params_t *)esp_zb_app_signal_get_params(p_sg_p); 516 | if (leave_params->leave_type == ESP_ZB_NWK_LEAVE_TYPE_RESET) 517 | { 518 | ESP_LOGI(__func__, "Reset device"); 519 | esp_zb_factory_reset(); 520 | } 521 | break; 522 | default: 523 | ESP_LOGI(__func__, "ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type, 524 | esp_err_to_name(err_status)); 525 | break; 526 | } 527 | } 528 | 529 | static void zigbee_config_endpoints_attributes() 530 | { 531 | uint32_t undefined_value; 532 | undefined_value = 0x8000; 533 | 534 | uint32_t hundred_value; 535 | hundred_value = 100; 536 | 537 | uint32_t one_value; 538 | one_value = 1; 539 | 540 | uint32_t zero_value; 541 | zero_value = 0; 542 | 543 | // basic cluster create with fully customized 544 | set_zcl_string(manufacturer, HW_MANUFACTURER); 545 | set_zcl_string(model, HW_MODEL); 546 | char ota_upgrade_file_version[10]; 547 | sprintf(ota_upgrade_file_version, "%d", OTA_FW_VERSION); 548 | set_zcl_string(firmware_version, ota_upgrade_file_version); 549 | set_zcl_string(firmware_date, FW_BUILD_DATE); 550 | uint8_t dc_power_source; 551 | dc_power_source = 4; 552 | esp_zb_attribute_list_t *esp_zb_basic_cluster = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_BASIC); 553 | esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, manufacturer); 554 | esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, model); 555 | esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_POWER_SOURCE_ID, &dc_power_source); /**< DC source. */ 556 | 557 | esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_SW_BUILD_ID, firmware_version); 558 | esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_DATE_CODE_ID, firmware_date); 559 | 560 | // identify cluster create with fully customized 561 | uint16_t identify_id = 0; 562 | esp_zb_attribute_list_t *esp_zb_identify_cluster = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY); 563 | esp_zb_identify_cluster_add_attr(esp_zb_identify_cluster, ESP_ZB_ZCL_CMD_IDENTIFY_IDENTIFY_ID, &identify_id); 564 | 565 | // Electrical cluster 566 | esp_zb_attribute_list_t *esp_zb_electrical_meas_cluster = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_ELECTRICAL_MEASUREMENT); 567 | 568 | // DC Electrical attributes 569 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_CURRENT_ID, &undefined_value); 570 | // esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_CURRENT_MAX_ID, &undefined_value); 571 | // esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_CURRENT_MIN_ID, &undefined_value); 572 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DCPOWER_ID, &undefined_value); 573 | // esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_POWER_MAX_ID, &undefined_value); 574 | // esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_POWER_MIN_ID, &undefined_value); 575 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_VOLTAGE_ID, &undefined_value); 576 | // esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_VOLTAGE_MAX_ID, &undefined_value); 577 | // esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_VOLTAGE_MIN_ID, &undefined_value); 578 | 579 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_CURRENT_DIVISOR_ID, &hundred_value); 580 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_POWER_DIVISOR_ID, &hundred_value); 581 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_VOLTAGE_DIVISOR_ID, &hundred_value); 582 | 583 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_CURRENT_MULTIPLIER_ID, &one_value); 584 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_POWER_MULTIPLIER_ID, &one_value); 585 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_DC_VOLTAGE_MULTIPLIER_ID, &one_value); 586 | 587 | // AC Electrical attributes 588 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_RMSCURRENT_ID, &undefined_value); 589 | // esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_RMS_CURRENT_MAX_ID, &undefined_value); 590 | // esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_RMS_CURRENT_MIN_ID, &undefined_value); 591 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_ACTIVE_POWER_ID, &undefined_value); 592 | // esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_ACTIVE_POWER_MAX_ID, &undefined_value); 593 | // esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_ACTIVE_POWER_MIN_ID, &undefined_value); 594 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_RMSVOLTAGE_ID, &undefined_value); 595 | // esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_RMS_VOLTAGE_MAX_ID, &undefined_value); 596 | // esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_RMS_VOLTAGE_MIN_ID, &undefined_value); 597 | 598 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_ACCURRENT_DIVISOR_ID, &hundred_value); 599 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_ACPOWER_DIVISOR_ID, &hundred_value); 600 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_ACVOLTAGE_DIVISOR_ID, &hundred_value); 601 | 602 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_ACCURRENT_MULTIPLIER_ID, &one_value); 603 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_ACPOWER_MULTIPLIER_ID, &one_value); 604 | esp_zb_electrical_meas_cluster_add_attr(esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_ATTR_ELECTRICAL_MEASUREMENT_ACVOLTAGE_MULTIPLIER_ID, &one_value); 605 | 606 | // Temperature cluster 607 | esp_zb_attribute_list_t *esp_zb_temperature_meas_cluster = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT); 608 | esp_zb_temperature_meas_cluster_add_attr(esp_zb_temperature_meas_cluster, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID, &undefined_value); 609 | esp_zb_temperature_meas_cluster_add_attr(esp_zb_temperature_meas_cluster, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_MIN_VALUE_ID, &undefined_value); 610 | esp_zb_temperature_meas_cluster_add_attr(esp_zb_temperature_meas_cluster, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_MAX_VALUE_ID, &undefined_value); 611 | 612 | /* 613 | esp_zb_attribute_list_t *esp_zb_ias_zone_cluster = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_IAS_ZONE); 614 | esp_zb_ias_zone_cluster_add_attr(esp_zb_ias_zone_cluster, ESP_ZB_ZCL_ATTR_IAS_ZONE_ZONESTATE_ID, &undefined_value); 615 | esp_zb_ias_zone_cluster_add_attr(esp_zb_ias_zone_cluster, ESP_ZB_ZCL_ATTR_IAS_ZONE_ZONESTATUS_ID, &undefined_value); 616 | esp_zb_ias_zone_cluster_add_attr(esp_zb_ias_zone_cluster, ESP_ZB_ZCL_ATTR_IAS_ZONE_ZONETYPE_ID, &undefined_value); 617 | */ 618 | 619 | /* 620 | esp_zb_attribute_list_t *esp_zb_on_off_switch_config_cluster = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_ON_OFF_SWITCH_CONFIG); 621 | 622 | esp_zb_on_off_switch_config_cluster_add_attr(esp_zb_on_off_switch_config_cluster, esp_zb_on_off_switch_config_cluster 623 | esp_zb_on_off_switch_config_cluster_add_attr(esp_zb_on_off_switch_config_cluster, ESP_ZB_ZCL_ATTR_IAS_ZONE_ZONESTATUS_ID, &undefined_value); 624 | esp_zb_on_off_switch_config_cluster_add_attr(esp_zb_on_off_switch_config_cluster, ESP_ZB_ZCL_ATTR_IAS_ZONE_ZONETYPE_ID, &undefined_value); 625 | 626 | esp_zb_cluster_list_add_on_off_switch_config_cluster 627 | */ 628 | 629 | // Time cluster 630 | esp_zb_attribute_list_t *esp_zb_server_time_cluster = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_TIME); 631 | esp_zb_attribute_list_t *esp_zb_client_time_cluster = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_TIME); 632 | esp_zb_time_cluster_add_attr(esp_zb_client_time_cluster, ESP_ZB_ZCL_ATTR_TIME_LAST_SET_TIME_ID, &undefined_value); 633 | 634 | // IAS cluster 635 | esp_zb_ias_zone_cluster_cfg_t ias_cluster_cfg = { 636 | .zone_type = ESP_ZB_ZCL_IAS_ZONE_ZONETYPE_STANDARD_CIE, 637 | .zone_state = ESP_ZB_ZCL_IAS_ZONE_ZONESTATE_NOT_ENROLLED, 638 | .zone_status = ESP_ZB_ZCL_IAS_ZONE_ZONE_STATUS_ALARM1, 639 | .ias_cie_addr = ESP_ZB_ZCL_ZONE_IAS_CIE_ADDR_DEFAULT, 640 | .zone_id = 0, 641 | }; 642 | esp_zb_attribute_list_t *esp_zb_ias_zone_cluster = esp_zb_ias_zone_cluster_create(&ias_cluster_cfg); 643 | 644 | /** Create ota client cluster with attributes. 645 | * Manufacturer code, image type and file version should match with configured values for server. 646 | * If the client values do not match with configured values then it shall discard the command and 647 | * no further processing shall continue. 648 | */ 649 | esp_zb_ota_cluster_cfg_t ota_cluster_cfg = { 650 | .ota_upgrade_file_version = OTA_FW_VERSION, // OTA_UPGRADE_RUNNING_FILE_VERSION, 651 | .ota_upgrade_downloaded_file_ver = OTA_FW_VERSION, // OTA_UPGRADE_DOWNLOADED_FILE_VERSION, 652 | .ota_upgrade_manufacturer = manuf_id, 653 | .ota_upgrade_image_type = OTA_UPGRADE_IMAGE_TYPE, 654 | }; 655 | esp_zb_attribute_list_t *esp_zb_ota_client_cluster = esp_zb_ota_cluster_create(&ota_cluster_cfg); 656 | 657 | /** add client parameters to ota client cluster */ 658 | esp_zb_zcl_ota_upgrade_client_variable_t variable_config = { 659 | .timer_query = ESP_ZB_ZCL_OTA_UPGRADE_QUERY_TIMER_COUNT_DEF, 660 | .hw_version = OTA_UPGRADE_HW_VERSION, 661 | .max_data_size = OTA_UPGRADE_MAX_DATA_SIZE, 662 | }; 663 | 664 | uint16_t ota_upgrade_server_addr = 0xffff; 665 | uint8_t ota_upgrade_server_ep = 0xff; 666 | esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create(); 667 | esp_zb_ep_list_t *ep_list = esp_zb_ep_list_create(); 668 | esp_zb_endpoint_config_t endpoint_config = { 669 | .endpoint = 1, 670 | .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, 671 | .app_device_id = ESP_ZB_HA_TEST_DEVICE_ID, 672 | .app_device_version = 0, 673 | }; 674 | 675 | esp_zb_ota_cluster_add_attr(esp_zb_ota_client_cluster, ESP_ZB_ZCL_ATTR_OTA_UPGRADE_CLIENT_DATA_ID, (void *)&variable_config); 676 | esp_zb_ota_cluster_add_attr(esp_zb_ota_client_cluster, ESP_ZB_ZCL_ATTR_OTA_UPGRADE_SERVER_ADDR_ID, (void *)&ota_upgrade_server_addr); 677 | esp_zb_ota_cluster_add_attr(esp_zb_ota_client_cluster, ESP_ZB_ZCL_ATTR_OTA_UPGRADE_SERVER_ENDPOINT_ID, (void *)&ota_upgrade_server_ep); 678 | 679 | /* create cluster list with ota cluster */ 680 | 681 | esp_zb_on_off_cluster_cfg_t on_off_cfg = {}; 682 | esp_zb_attribute_list_t *esp_zb_on_off_cluster = esp_zb_on_off_cluster_create(&on_off_cfg); 683 | 684 | // uint8_t default_value = 0xff; 685 | esp_zb_on_off_cluster_add_attr(esp_zb_on_off_cluster, ESP_ZB_ZCL_ATTR_ON_OFF_START_UP_ON_OFF, &data.start_up_on_off); 686 | 687 | esp_zb_on_off_cluster_add_attr(esp_zb_on_off_cluster, ESP_ZB_ZCL_ATTR_ON_OFF_ON_TIME, &undefined_value); 688 | esp_zb_on_off_cluster_add_attr(esp_zb_on_off_cluster, ESP_ZB_ZCL_ATTR_ON_OFF_OFF_WAIT_TIME, &undefined_value); 689 | // esp_zb_on_off_cluster_cfg_t 690 | // esp_zb_on_off_cluster_add_attr(&on_off_cfg, 691 | 692 | /* Create full cluster list enabled on device */ 693 | esp_zb_cluster_list_t *esp_zb_cluster_list = esp_zb_zcl_cluster_list_create(); 694 | esp_zb_cluster_list_add_basic_cluster(esp_zb_cluster_list, esp_zb_basic_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); 695 | esp_zb_cluster_list_add_identify_cluster(esp_zb_cluster_list, esp_zb_identify_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); 696 | esp_zb_cluster_list_add_on_off_cluster(esp_zb_cluster_list, esp_zb_on_off_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); 697 | esp_zb_cluster_list_add_electrical_meas_cluster(esp_zb_cluster_list, esp_zb_electrical_meas_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); 698 | esp_zb_cluster_list_add_temperature_meas_cluster(esp_zb_cluster_list, esp_zb_temperature_meas_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); 699 | esp_zb_cluster_list_add_time_cluster(esp_zb_cluster_list, esp_zb_server_time_cluster, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE); 700 | esp_zb_cluster_list_add_time_cluster(esp_zb_cluster_list, esp_zb_client_time_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); 701 | esp_zb_cluster_list_add_ota_cluster(esp_zb_cluster_list, esp_zb_ota_client_cluster, ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE); 702 | // esp_zb_cluster_list_add_groups_cluster(esp_zb_cluster_list, 703 | esp_zb_cluster_list_add_ias_zone_cluster(esp_zb_cluster_list, esp_zb_ias_zone_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); 704 | // esp_zb_cluster_list_add_on_off_switch_config_cluster(esp_zb_cluster_list, 705 | // esp_zb_cluster_list_add_power_config_cluster(esp_zb_cluster_list, 706 | // esp_zb_cluster_list_add_scenes_cluster(esp_zb_cluster_list, 707 | 708 | esp_zb_ep_list_t *esp_zb_ep_list = esp_zb_ep_list_create(); 709 | 710 | // esp_zb_mains_power_outlet_cfg_t switch_cfg = ESP_ZB_DEFAULT_MAINS_POWER_OUTLET_CONFIG(); 711 | 712 | // esp_zb_on_off_switch_cfg_t switch_cfg = ESP_ZB_DEFAULT_ON_OFF_SWITCH_CONFIG(); 713 | 714 | // esp_zb_ep_list_t *esp_zb_on_off_switch_ep = esp_zb_on_off_switch_ep_create(HA_ONOFF_SWITCH_ENDPOINT, &switch_cfg); 715 | 716 | // esp_zb_ep_list_t *esp_zb_ep_list = esp_zb_mains_power_outlet_ep_create(4, &switch_cfg); 717 | 718 | // esp_zb_ep_list_t *esp_zb_ep_list = esp_zb_on_off_switch_cfg_cluster_create(switch_cfg) 719 | // esp_zb_ep_list_t *esp_zb_ep_list = esp_zb_on_off_switch_ep_create(4, &switch_cfg); 720 | 721 | esp_zb_on_off_cluster_cfg_t on_off_cfg2 = {}; 722 | esp_zb_attribute_list_t *esp_zb_on_off_cluster2 = esp_zb_on_off_cluster_create(&on_off_cfg2); 723 | esp_zb_cluster_list_t *esp_zb_cluster_list2 = esp_zb_zcl_cluster_list_create(); 724 | esp_zb_cluster_list_add_on_off_cluster(esp_zb_cluster_list2, esp_zb_on_off_cluster2, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); 725 | 726 | esp_zb_on_off_cluster_cfg_t on_off_cfg3 = {}; 727 | esp_zb_attribute_list_t *esp_zb_on_off_cluster3 = esp_zb_on_off_cluster_create(&on_off_cfg3); 728 | esp_zb_cluster_list_t *esp_zb_cluster_list3 = esp_zb_zcl_cluster_list_create(); 729 | esp_zb_cluster_list_add_on_off_cluster(esp_zb_cluster_list3, esp_zb_on_off_cluster3, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); 730 | 731 | esp_zb_on_off_cluster_cfg_t on_off_cfg4 = {}; 732 | esp_zb_attribute_list_t *esp_zb_on_off_cluster4 = esp_zb_on_off_cluster_create(&on_off_cfg4); 733 | esp_zb_on_off_cluster_add_attr(esp_zb_on_off_cluster4, ESP_ZB_ZCL_ATTR_ON_OFF_ON_TIME, &zero_value); 734 | esp_zb_on_off_cluster_add_attr(esp_zb_on_off_cluster4, ESP_ZB_ZCL_ATTR_ON_OFF_OFF_WAIT_TIME, &zero_value); 735 | esp_zb_cluster_list_t *esp_zb_cluster_list4 = esp_zb_zcl_cluster_list_create(); 736 | esp_zb_cluster_list_add_on_off_cluster(esp_zb_cluster_list4, esp_zb_on_off_cluster4, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); 737 | 738 | esp_zb_endpoint_config_t endpoint_config1 = { 739 | .endpoint = SENSOR_ENDPOINT, 740 | .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, 741 | .app_device_id = ESP_ZB_HA_ON_OFF_OUTPUT_DEVICE_ID, 742 | .app_device_version = 0}; 743 | esp_zb_ep_list_add_ep(esp_zb_ep_list, esp_zb_cluster_list, endpoint_config1); 744 | 745 | esp_zb_endpoint_config_t endpoint_config2 = { 746 | .endpoint = INT_LED_ENDPOINT, 747 | .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, 748 | .app_device_id = ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID, 749 | .app_device_version = 0}; 750 | esp_zb_ep_list_add_ep(esp_zb_ep_list, esp_zb_cluster_list2, endpoint_config2); 751 | 752 | esp_zb_endpoint_config_t endpoint_config3 = { 753 | .endpoint = EXT_LED_ENDPOINT, 754 | .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, 755 | .app_device_id = ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID, 756 | .app_device_version = 0}; 757 | esp_zb_ep_list_add_ep(esp_zb_ep_list, esp_zb_cluster_list3, endpoint_config3); 758 | 759 | esp_zb_endpoint_config_t endpoint_config4 = { 760 | .endpoint = INV_USB_ENDPOINT, 761 | .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, 762 | .app_device_id = ESP_ZB_HA_ON_OFF_OUTPUT_DEVICE_ID, 763 | .app_device_version = 0}; 764 | esp_zb_ep_list_add_ep(esp_zb_ep_list, esp_zb_cluster_list4, endpoint_config4); 765 | 766 | esp_zb_device_register(esp_zb_ep_list); 767 | } 768 | 769 | static void esp_zb_task(void *pvParameters) 770 | { 771 | /* initialize Zigbee stack */ 772 | esp_zb_cfg_t zb_nwk_cfg = ESP_ZB_ZR_CONFIG(); 773 | 774 | esp_zb_init(&zb_nwk_cfg); 775 | 776 | zigbee_config_endpoints_attributes(); 777 | 778 | esp_zb_core_action_handler_register(zb_action_handler); 779 | 780 | esp_zb_set_primary_network_channel_set(ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK); 781 | 782 | ESP_ERROR_CHECK(esp_zb_start(true)); 783 | 784 | esp_zb_main_loop_iteration(); 785 | } 786 | 787 | void send_bin_cfg_option(int endpoint, bool value) 788 | { 789 | if (connected) 790 | { 791 | const char *endpoint_name = get_endpoint_name(endpoint); 792 | ESP_LOGI(__func__, "attribute to %d on %s (endpoint %d)", value, endpoint_name, endpoint); 793 | 794 | esp_zb_zcl_status_t state_tmp = esp_zb_zcl_set_attribute_val(endpoint, 795 | ESP_ZB_ZCL_CLUSTER_ID_ON_OFF, 796 | ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, 797 | ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID, 798 | &value, 799 | false); 800 | 801 | if (state_tmp != ESP_ZB_ZCL_STATUS_SUCCESS) 802 | { 803 | ESP_LOGE(__func__, "Setting cfg option On/Off attribute failed! error %d", state_tmp); 804 | } 805 | } 806 | else 807 | { 808 | ESP_LOGW(__func__, "no connection! attribute to %d", value); 809 | } 810 | } 811 | 812 | void send_alarm_state(bool alarm) 813 | { 814 | 815 | uint16_t new_zone_status = 0x0001; 816 | if (alarm == 0) 817 | { 818 | new_zone_status = 0x0000; 819 | } 820 | if (connected) 821 | { 822 | esp_zb_zcl_ias_zone_status_change_notif_cmd_t cmd = { 823 | .zcl_basic_cmd = { 824 | .dst_addr_u.addr_short = 0x0000, 825 | .dst_endpoint = 1, 826 | .src_endpoint = 1, 827 | }, 828 | .address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT, 829 | .zone_status = new_zone_status, 830 | .zone_id = 0, 831 | .delay = 0, 832 | }; 833 | esp_zb_zcl_status_t state_tmp = esp_zb_zcl_ias_zone_status_change_notif_cmd_req(&cmd); 834 | 835 | /*if (state_tmp != ESP_ZB_ZCL_STATUS_SUCCESS) // why every time failed? 836 | { 837 | ESP_LOGE(__func__, "ias_zone_status_change_notif_cmd failed! error %d", state_tmp); 838 | } 839 | else 840 | { 841 | ESP_LOGI(__func__, "alarm %d", alarm); 842 | }*/ 843 | } 844 | else 845 | { 846 | ESP_LOGW(__func__, "no connection! alarm %d", alarm); 847 | } 848 | } 849 | 850 | void zigbee_setup() 851 | { 852 | ESP_LOGI("Zigbee", "Start Zigbee stack"); 853 | 854 | esp_zb_platform_config_t config = { 855 | .radio_config = ESP_ZB_DEFAULT_RADIO_CONFIG(), 856 | .host_config = ESP_ZB_DEFAULT_HOST_CONFIG(), 857 | }; 858 | 859 | ESP_ERROR_CHECK(esp_zb_platform_config(&config)); 860 | 861 | // esp_zb_aps_src_binding_table_size_set(SRC_BINDING_TABLE_SIZE); 862 | // esp_zb_aps_dst_binding_table_size_set(DST_BINDING_TABLE_SIZE); 863 | 864 | xTaskCreate(esp_zb_task, "esp_zb_task", 4096, NULL, 1, NULL); 865 | } --------------------------------------------------------------------------------