From 4deeae744bbbccf22de72f328c6e60686e5c3340 Mon Sep 17 00:00:00 2001 From: Ali Hassan Shah Date: Tue, 31 Oct 2023 16:53:50 +0800 Subject: [PATCH] ESP-Button --- .github/scripts/check_versions.sh | 136 ++++++ .github/workflows/arduino_lint.yml | 15 + .github/workflows/build_test.yml | 28 ++ .github/workflows/check_versions.yml | 30 ++ .github/workflows/pre-commit.yml | 14 + .gitignore | 71 +++ .pre-commit-config.yaml | 26 + CHANGELOG.md | 11 + CMakeLists.txt | 38 ++ README.md | 182 ++++++- check_copyright_config.yaml | 41 ++ examples/example/example.ino | 42 ++ library.properties | 10 + license.txt | 202 ++++++++ src/Button.cpp | 519 ++++++++++++++++++++ src/Button.h | 79 +++ src/original/arduino_config.h | 24 + src/original/button_adc.c | 311 ++++++++++++ src/original/button_adc.h | 76 +++ src/original/button_gpio.c | 48 ++ src/original/button_gpio.h | 54 +++ src/original/button_matrix.c | 59 +++ src/original/button_matrix.h | 80 +++ src/original/iot_button.c | 702 +++++++++++++++++++++++++++ src/original/iot_button.h | 291 +++++++++++ test_apps/CMakeLists.txt | 9 + test_apps/main/CMakeLists.txt | 3 + test_apps/main/button_test.cpp | 105 ++++ test_apps/sdkconfig.defaults | 9 + 29 files changed, 3214 insertions(+), 1 deletion(-) create mode 100644 .github/scripts/check_versions.sh create mode 100644 .github/workflows/arduino_lint.yml create mode 100644 .github/workflows/build_test.yml create mode 100644 .github/workflows/check_versions.yml create mode 100644 .github/workflows/pre-commit.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 CHANGELOG.md create mode 100644 CMakeLists.txt create mode 100644 check_copyright_config.yaml create mode 100644 examples/example/example.ino create mode 100644 library.properties create mode 100644 license.txt create mode 100644 src/Button.cpp create mode 100644 src/Button.h create mode 100644 src/original/arduino_config.h create mode 100644 src/original/button_adc.c create mode 100644 src/original/button_adc.h create mode 100644 src/original/button_gpio.c create mode 100644 src/original/button_gpio.h create mode 100644 src/original/button_matrix.c create mode 100644 src/original/button_matrix.h create mode 100644 src/original/iot_button.c create mode 100644 src/original/iot_button.h create mode 100644 test_apps/CMakeLists.txt create mode 100644 test_apps/main/CMakeLists.txt create mode 100644 test_apps/main/button_test.cpp create mode 100644 test_apps/sdkconfig.defaults diff --git a/.github/scripts/check_versions.sh b/.github/scripts/check_versions.sh new file mode 100644 index 0000000..d458178 --- /dev/null +++ b/.github/scripts/check_versions.sh @@ -0,0 +1,136 @@ +#!/bin/bash + +# Function: Check version format +# Input parameters: $1 The version number +# Return value: 0 if the version numbers are correct, 1 if the first version is incorrect, +check_version_format() { + version_regex="^v[0-9]+\.[0-9]+\.[0-9]+$" + + if [[ ! $1 =~ $version_regex ]]; then + return 1 + fi + + return 0 +} + +if [ $# -lt 1 ]; then + latest_version="0.0.0" + echo "Don't get the lastest version, use \"0.0.0\" as default" +else + # Get the first input parameter as the version to be compared + latest_version="$1" + # Check the version format + check_version_format "${latest_version}" + result=$? + if [ ${result} -ne 0 ]; then + echo "The latest release version (${latest_version}) format is incorrect." + exit 1 + fi +fi + +# Specify the directory path +target_directory="./" + +echo "Checking directory: ${target_directory}" + +# Function: Check if a file exists +# Input parameters: $1 The file to check +# Return value: 0 if the file exists, 1 if the file does not exist +check_file_exists() { + if [ ! -f "$1" ]; then + echo "File '$1' not found." + return 1 + fi + return 0 +} + +# Function: Compare version numbers +# Input parameters: $1 The first version number, $2 The second version number +# Return value: 0 if the version numbers are equal, 1 if the first version is greater, +# 2 if the second version is greater, +compare_versions() { + version_regex="^v[0-9]+\.[0-9]+\.[0-9]+$" + + version1=$(echo "$1" | cut -c 2-) # Remove the 'v' at the beginning of the version number + version2=$(echo "$2" | cut -c 2-) + + IFS='.' read -ra v1_parts <<< "$version1" + IFS='.' read -ra v2_parts <<< "$version2" + + for ((i=0; i<${#v1_parts[@]}; i++)); do + if [[ "${v1_parts[$i]}" -lt "${v2_parts[$i]}" ]]; then + return 2 + elif [[ "${v1_parts[$i]}" -gt "${v2_parts[$i]}" ]]; then + return 1 + fi + done + + return 0 +} + +echo "Checking file: library.properties" +# Check if "library.properties" file exists +check_file_exists "${target_directory}/library.properties" +if [ $? -ne 0 ]; then + exit 1 +fi +# Read the version information from the file +arduino_version=v$(grep -E '^version=' "${target_directory}/library.properties" | cut -d '=' -f 2) +echo "Get Arduino version: ${arduino_version}" +# Check the version format +check_version_format "${arduino_version}" +result=$? +if [ ${result} -ne 0 ]; then + echo "Arduino version (${arduino_version}) format is incorrect." + exit 1 +fi + +# Compare Arduino Library version with the latest release version +compare_versions "${arduino_version}" "${latest_version}" +result=$? +if [ ${result} -ne 1 ]; then + if [ ${result} -eq 3 ]; then + echo "Arduino version (${arduino_version}) is incorrect." + else + echo "Arduino version (${arduino_version}) is not greater than the latest release version (${latest_version})." + exit 1 + fi +fi + +echo "Checking file: idf_component.yml" +# Check if "idf_component.yml" file exists +check_file_exists "${target_directory}/idf_component.yml" +if [ $? -eq 0 ]; then + # Read the version information from the file + idf_version=v$(grep -E '^version:' "${target_directory}/idf_component.yml" | awk -F'"' '{print $2}') + echo "Get IDF component version: ${idf_version}" + # Check the version format + check_version_format "${idf_version}" + result=$? + if [ ${result} -ne 0 ]; then + echo "IDF component (${idf_version}) format is incorrect." + exit 1 + fi + # Compare IDF Component version with Arduino Library version + compare_versions ${idf_version} ${arduino_version} + result=$? + if [ ${result} -ne 0 ]; then + if [ ${result} -eq 3 ]; then + echo "IDF component version (${idf_version}) is incorrect." + else + echo "IDF component version (${idf_version}) is not equal to the Arduino version (${arduino_version})." + exit 1 + fi + fi + # Compare IDF Component version with the latest release version + compare_versions ${idf_version} ${latest_version} + result=$? + if [ ${result} -ne 1 ]; then + if [ ${result} -eq 3 ]; then + echo "IDF component version (${idf_version}) is incorrect." + else + echo "IDF component version (${idf_version}) is not greater than the latest release version (${latest_version})." + exit 1 + fi + fi +fi diff --git a/.github/workflows/arduino_lint.yml b/.github/workflows/arduino_lint.yml new file mode 100644 index 0000000..0a6b9a8 --- /dev/null +++ b/.github/workflows/arduino_lint.yml @@ -0,0 +1,15 @@ +name: Arduino Lint Action + +on: + workflow_dispatch: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: arduino/arduino-lint-action@v1 + with: + library-manager: update diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml new file mode 100644 index 0000000..0f287cd --- /dev/null +++ b/.github/workflows/build_test.yml @@ -0,0 +1,28 @@ +name: Build Test Application + +on: + workflow_dispatch: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + build: + strategy: + matrix: + idf_ver: ["v4.4.5", "v5.0"] + idf_target: ["esp32"] + runs-on: ubuntu-20.04 + container: espressif/idf:${{ matrix.idf_ver }} + steps: + - uses: actions/checkout@v3 + - name: Build ESP_Button Test Application + env: + IDF_TARGET: ${{ matrix.idf_target }} + working-directory: test_apps + shell: bash + run: | + . ${IDF_PATH}/export.sh + export PEDANTIC_FLAGS="-DIDF_CI_BUILD -Werror -Werror=deprecated-declarations -Werror=unused-variable -Werror=unused-but-set-variable -Werror=unused-function" + export EXTRA_CFLAGS="${PEDANTIC_FLAGS} -Wstrict-prototypes" + export EXTRA_CXXFLAGS="${PEDANTIC_FLAGS}" + idf.py build \ No newline at end of file diff --git a/.github/workflows/check_versions.yml b/.github/workflows/check_versions.yml new file mode 100644 index 0000000..eb2d116 --- /dev/null +++ b/.github/workflows/check_versions.yml @@ -0,0 +1,30 @@ +name: Check Versions + +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + check_versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Get latest release info of repository + id: last_release + uses: InsonusK/get-latest-release@v1.1.0 + with: + myToken: ${{ github.token }} + exclude_types: "draft|prerelease" + view_top: 1 + - name: Print result + run: | + echo "id: ${{ steps.last_release.outputs.id }}" + echo "name: ${{ steps.last_release.outputs.name }}" + echo "tag_name: ${{ steps.last_release.outputs.tag_name }}" + echo "created_at: ${{ steps.last_release.outputs.created_at }}" + echo "draft: ${{ steps.last_release.outputs.draft }}" + echo "prerelease: ${{ steps.last_release.outputs.prerelease }}" + echo "url: ${{ steps.last_release.outputs.url }}" + - name: Check & Compare versions + run: bash ./.github/scripts/check_versions.sh ${{ steps.last_release.outputs.tag_name }} + diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..51f77cd --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,14 @@ +name: pre-commit + +on: + workflow_dispatch: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - uses: pre-commit/action@v2.0.3 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..657ffab --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ +.config +*.o +*.pyc +*.orig + +# gtags +GTAGS +GRTAGS +GPATH + +# emacs +.dir-locals.el + +# emacs temp file suffixes +*~ +.#* +\#*# + +# eclipse setting +.settings + +# MacOS directory files +.DS_Store + +# Unit Test CMake compile log folder +log_ut_cmake + +TEST_LOGS + +# gcov coverage reports +*.gcda +*.gcno +coverage.info +coverage_report/ + +test_multi_heap_host + +# VS Code Settings +.vscode/ + +# VIM files +*.swp +*.swo + +# Clion IDE CMake build & config +.idea/ +cmake-build-*/ + +# Results for the checking of the Python coding style and static analysis +.mypy_cache +flake8_output.txt + +# esp-idf default build directory name +build +build_esp*/ +build_linux*/ +size_info.txt +sdkconfig +sdkconfig.old + +# lock files for examples and components +dependencies.lock + +# managed_components for examples +managed_components + +# pytest log +pytest_embedded_log/ +pytest_log/ +.pytest_cache/ +XUNIT_RESULT.xml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..88b4d93 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +exclude: 'src/original' +repos: +- repo: https://github.com/igrr/astyle_py.git + rev: master + hooks: + - id: astyle_py + args: ['--style=otbs', '--attach-namespaces', '--attach-classes', '--indent=spaces=4', '--convert-tabs', '--align-pointer=name', '--align-reference=name', '--keep-one-line-statements', '--pad-header', '--pad-oper'] + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: trailing-whitespace + types_or: [c, c++] + - id: end-of-file-fixer + types_or: [c, c++] + - id: check-merge-conflict + - id: mixed-line-ending + types_or: [c, c++] + args: ['--fix=lf'] + description: Forces to replace line ending by the UNIX 'lf' character + +- repo: https://github.com/espressif/check-copyright/ + rev: v1.0.3 + hooks: + - id: check-copyright + args: ['--config', 'check_copyright_config.yaml'] \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..59a3770 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# ChangeLog + +## v0.0.1 - [2023-11-10] + +### Enhancements: + +* Only support for ESP32-S2 and ESP32-S3 SoCs. +* Support video stream through UVC Stream interface. +* Support microphone stream and speaker stream through the UAC Stream interface +* Support volume, mute and other features control through the UAC Control interface +* Support stream separately suspend and resume \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8f016e0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,38 @@ +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0") + list(APPEND PRIVREQ esp_adc) +else() + list(APPEND PRIVREQ esp_adc_cal) +endif() + +idf_component_register(SRCS "src/original/button_adc.c" + "src/original/button_gpio.c" + "src/original/button_matrix.c" + "src/original/iot_button.c" + # "src/original/adc_oneshot.c" + "src/Button.cpp" + INCLUDE_DIRS "src" + REQUIRES driver ${PRIVREQ} + PRIV_REQUIRES esp_timer) + + +# set(PRIVREQ esp_timer) + +# if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0") +# list(APPEND REQ esp_adc) +# else() +# list(APPEND REQ esp_adc_cal) +# endif() + +# idf_component_register(SRCS "src/original/button_adc.c" +# "src/original/button_gpio.c" +# "src/original/button_matrix.c" +# "src/original/iot_button.c" +# "src/Button.cpp" +# INCLUDE_DIRS "src" +# REQUIRES driver ${REQ} +# PRIV_REQUIRES ${PRIVREQ}) +# # REQUIRES driver +# # PRIV_REQUIRES esp_timer) + +# # include(package_manager) +# # cu_pkg_define_version(${CMAKE_CURRENT_LIST_DIR}) \ No newline at end of file diff --git a/README.md b/README.md index 7f46327..ee9fae1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,183 @@ +[![Arduino Lint](https://github.com/esp-arduino-libs/[xxx]/actions/workflows/arduino_lint.yml/badge.svg)](https://github.com/esp-arduino-libs/[xxx]/actions/workflows/arduino_lint.yml) [![pre-commit](https://github.com/esp-arduino-libs/[xxx]/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/esp-arduino-libs/[xxx]/actions/workflows/pre-commit.yml) + # ESP32_Button -Arduino library of driving button for the ESP32 SoCs +ESP32_Button is an Arduino library designed for creating both GPIO and ADC buttons using ESP32 System-on-Chip (SoC) devices. This versatile library simplifies the process of configuring and working with buttons on ESP32-based projects. + +ESP32_Button builds upon the functionality provided by the [ESP-IOT-SOLUTION/Components/Button](https://github.com/espressif/esp-iot-solution/tree/master/components/button) library, offering an accessible interface for button-related tasks in the Arduino ecosystem. + +## Features + +- Support for all ESP SoCs. +- Supported multiple events. + +| Events | Description | Arduino Function | +| --------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `Button pressed` | This event is triggered when the button is initially pressed down. | attachPressDownEventCb | +| `Button released` | This event is triggered when the button is released after being pressed down. | attachPressUpEventCb | +| `Button pressed - repeated` | This event is triggered when the button is pressed repeatedly in a short span of time. | attachPressRepeatEventCb | +| `Button single click` | This event is triggered when the button is clicked once without any repeated presses. | attachSingleClickEventCb | +| `Button double click` | This event is triggered when the button is clicked twice in quick succession. | attachDoubleClickEventCb | +| `Button long press start` | This event is triggered when the button is held down for a specified amount of time, indicating a potential long press. | attachLongPressStartEventCb | +| `Button long press hold` | This event is triggered repeatedly while the button is held down after a long press has been detected. | attachLongPressHoldEventCb | +| `Button press repeat done` | This event is triggered when the button repetition is completed | attachPressRepeatDoneEventCb | +| `Button long press up` | This event is triggered after the button long press is completed and button is up | attachLongPressUpEventCb | +| `Button multiple click` | This event is triggered when button is designed to click multiple times | attachMultipleClickEventCb | + +## Supported Drivers + +| **Driver** | **Version** | +| ------------------------------------------------------------------ | ----------- | +| [knob](https://components.espressif.com/components/espressif/button)| 3.1.2 | + +## How to Use + +For information on how to use the library in the Arduino IDE, please refer to the documentation for [Arduino IDE v1.x.x](https://docs.arduino.cc/software/ide-v1/tutorials/installing-libraries) or [Arduino IDE v2.x.x](https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-installing-a-library). + +### Examples + +* [example.ino](./examples/example/example.ino): Demonstrates how to use ESP32-Button. + +## Detailed Usage + +### Initializing a GPIO Button + +``` + Button *btn = new Button(pin number, pullupActive) +``` +This creates a new button object and assigns it to the pointer variable btn. The button::button() constructor is then defined to take two arguments: + +- **pin :** which specifies the pin number that the button is connected to +- **pullupActive :** which is a boolean value indicating whether the internal pull-up resistor of the pin should be enabled or not. If pullupActive is true, the pinMode() function sets the pin as an input with its internal pull-up resistor enabled. This means that the pin will read as HIGH when the button is not pressed, and LOW when the button is pressed. If pullupActive is false, the pinMode() function sets the pin as an input with its internal pull-up resistor disabled. In this case, the pin will read as a floating state, which may be either HIGH or LOW when the button is not pressed. When the button is pressed, it will again momentarily connect the pin to ground, causing it to read as LOW. + +### Initializing an ADC Button + +``` + Button *btn = new Button(pin number, pullupActive, adc_channel, button_index, min, max); +``` +This creates a new button object and assigns it to the pointer variable btn. The Button::Button() constructor is then defined arguments: + +- **pin :** which specifies the pin number that the button is connected to, +- **pullupActive :** which is a boolean value indicating whether the internal pull-up resistor of the pin should be enabled or not. If pullupActive is true, the pinMode() function sets the pin as an input with its internal pull-up resistor enabled. This means that the pin will read as HIGH when the button is not pressed, and LOW when the button is pressed. If pullupActive is false, the pinMode() function sets the pin as an input with its internal pull-up resistor disabled. In this case, the pin will read as a floating state, which may be either HIGH or LOW when the button is not pressed. When the button is pressed, it will again momentarily connect the pin to ground, causing it to read as LOW. +- **adc_channel :** Channel of ADC +- **button_index :** button index on the channel +- **min :** min voltage in millivolt corresponding to the button +- **max :** max voltage in millivolt corresponding to the button + +### Attach Callback Function + +``` + btn-> attachPressDownEventCb(&Callback Function name, usr_data) +``` + +This attaches a callback function to the button's `attachPressDownEventCb`, which triggers when the button is pressed down. + +To use this code, you should replace & "Callback Function name" with the actual name of the function that you want to be called when the attachPressDown event occurs. This function will be executed whenever the button is pressed down. Such as toggling an LED or executing a sequence of instructions. The usr_data parameter can be used to pass any additional user-defined data to the callback function, allowing for more flexibility in response to button presses. + +### Detach Callback Function + +``` + btn->detachPressDownEvent() +``` + +The code detaches any event handler that was previously attached to the button instance's `attachPressDownEvent`. This means that if a callback function was previously attached to this event using `btn->attachPressDownEventCb(& Callback Function name, usr_data)`, it will no longer be executed when the button is pressed down. + +Detaching an event handler is useful when you want to change the behavior of a button or disable a specific event without affecting the other events attached to the button. For example, if you have multiple functions attached to the button's events and you want to disable only the attachPressDown event temporarily, you can use btn->detachPressDown() to remove the attachPressDown callback function without affecting the other event handlers. + +### Unregistered Event Function + +``` + btn->unregisterPressDownEvent(callbackFunction newFunction); +``` + +This function unregister a callback function which is previously attached to the event. The `newFunction` parameter specifies the callback function to be removed from the event's callback list. +Note: One Event can have multiple callbacks attached + +### Delete Button + +``` + btn->del() +``` + +The `btn->del()` code deletes the button instance created by `new button(pin number, pullupActive)`, which means all the events attached to the button instance will be detached as well. + +So, you don't need to detach the events manually before deleting the button instance using `btn->del()`. The function will automatically detach all the events and remove the instance from memory. + +Deleting the button instance is useful when you no longer need the button and want to free up memory space. + +### Count Call Back + +``` + btn->countCallBack() +``` + +This function returns the number of callbacks **currently** attached to the button. This functions is useful for debugging and monitoring the state of the button instance, especially if you have multiple event handlers attached to the button and want to ensure that they are all being executed correctly. + +### Get Event + +``` + btn->getEvent() +``` + +This event is used to retrieve the current event type of a button instance. This can be helpful for monitoring the current state of the button instance and for triggering different actions or behaviors depending on the current event type. + +``` +typedef enum { + BUTTON_PRESS_DOWN = 0, + BUTTON_PRESS_UP, + BUTTON_PRESS_REPEAT, + BUTTON_PRESS_REPEAT_DONE, + BUTTON_SINGLE_CLICK, + BUTTON_DOUBLE_CLICK, + BUTTON_MULTIPLE_CLICK, + BUTTON_LONG_PRESS_START, + BUTTON_LONG_PRESS_HOLD, + BUTTON_LONG_PRESS_UP, + BUTTON_EVENT_MAX, + BUTTON_NONE_PRESS, +} button_event_t; +``` + +### Get Repeat + +``` + btn->getRepeat() +``` + +This function is used to retrieve the number of times a button has been repeatedly pressed since the last button event was triggered. + +### Get Tick Time + +``` + btn->getTickTime() +``` + + This function is used to retrieve the elapsed time since the last button event in milliseconds. + +### Get Long Press Hold Count + +``` + btn->getLongPressHoldCount() +``` + +This function is used to retrieve the number of times the button has been held down during a long press event. + +### Set Parameter + +``` +btn->setParam(button_param_t param, void *value); +``` + +This function allows you to set a parameter value for the button. The parameter includes: + +``` +typedef enum { + BUTTON_LONG_PRESS_TIME_MS = 0, + BUTTON_SHORT_PRESS_TIME_MS, + BUTTON_PARAM_MAX, +} button_param_t; +``` + +--- +Note: +For additional details and information about the button functionality, please refer to the documentation provided by [ESP-IOT Solutions](https://github.com/espressif/esp-iot-solution/tree/master/components/button). \ No newline at end of file diff --git a/check_copyright_config.yaml b/check_copyright_config.yaml new file mode 100644 index 0000000..d0c1e09 --- /dev/null +++ b/check_copyright_config.yaml @@ -0,0 +1,41 @@ +DEFAULT: + perform_check: yes # should the check be performed? + # Sections setting this to 'no' don't need to include any other options as they are ignored + # When a file is using a section with the option set to 'no', no checks are performed. + + # what licenses (or license expressions) are allowed for files in this section + # when setting this option in a section, you need to list all the allowed licenses + allowed_licenses: + - Apache-2.0 + license_for_new_files: Apache-2.0 # license to be used when inserting a new copyright notice + new_notice_c: | # notice for new C, CPP, H, HPP and LD files + /* + * SPDX-FileCopyrightText: {years} Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: {license} + */ + new_notice_python: | # notice for new python files + # SPDX-FileCopyrightText: {years} Espressif Systems (Shanghai) CO LTD + # SPDX-License-Identifier: {license} + + # comment lines matching: + # SPDX-FileCopyrightText: year[-year] Espressif Systems + # or + # SPDX-FileContributor: year[-year] Espressif Systems + # are replaced with this template prefixed with the correct comment notation (# or // or *) and SPDX- notation + espressif_copyright: '{years} Espressif Systems (Shanghai) CO LTD' + +# You can create your own rules for files or group of files +examples_and_unit_tests: + include: + - 'test_apps/' + allowed_licenses: + - Apache-2.0 + - Unlicense + - CC0-1.0 + license_for_new_files: CC0-1.0 + +ignore: # You can also select ignoring files here + perform_check: no # Don't check files from that block + include: + - 'examples/' \ No newline at end of file diff --git a/examples/example/example.ino b/examples/example/example.ino new file mode 100644 index 0000000..593d25a --- /dev/null +++ b/examples/example/example.ino @@ -0,0 +1,42 @@ +#include +#include "Button.h" + +static void onButtonPressDownCb(void *button_handle, void *usr_data) { + Serial.println("Button pressed down"); +} +static void onButtonPressDownRepeatCb(void *button_handle, void *usr_data) +{ + Serial.println("Button press down repeat"); +} +static void onButtonPressUpCb(void *button_handle, void *usr_data) { + Serial.println("Button press Up"); +} +static void onButtonSingleClickCb(void *button_handle, void *usr_data) { + Serial.println("Button single click"); +} +static void onButtonSingleClickRepeatCb(void *button_handle, void *usr_data) +{ + Serial.println("Button single click repeat"); +} + +void setup() +{ + // put your setup code here, to run once: + Serial.begin(115200); + + // initializing a button + Button *btn = new Button(GPIO_NUM_9, false); + + btn->attachPressDownEventCb(&onButtonPressDownCb, NULL); + btn->attachPressUpEventCb(&onButtonPressUpCb, NULL); + btn->attachPressDownEventCb(&onButtonPressDownRepeatCb, NULL); + btn->attachSingleClickEventCb(&onButtonSingleClickCb,NULL); + btn->attachSingleClickEventCb(&onButtonSingleClickRepeatCb,NULL); + btn->unregisterPressDownEventCb(&onButtonPressDownCb); + btn->detachSingleClickEvent(); +} + +void loop() +{ + delay(10); +} diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..0b64c57 --- /dev/null +++ b/library.properties @@ -0,0 +1,10 @@ +name=ESP32_Button +version=0.0.1 +author=espressif +maintainer=alibukharai +sentence=ESP32_Button is a specialized library created to facilitate the implementation of ADC/GPIO button functionality on ESP SoCs. +paragraph=It currently enables the management of multiple button instances based on various events. +category=Other +url=https://github.com/esp-arduino-libs/ESP32_Button +architectures=esp32 +includes=Button.h \ No newline at end of file diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/license.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/Button.cpp b/src/Button.cpp new file mode 100644 index 0000000..bde6eb0 --- /dev/null +++ b/src/Button.cpp @@ -0,0 +1,519 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "original/button_gpio.h" +#include "original/button_adc.h" +#include "original/iot_button.h" +#include "Button.h" + +static const char *TAG = "arduino-button"; + +#define CHECK_ESP_ERROR(result, message) \ + if (result != ESP_OK) { \ + ESP_LOGE(TAG, "%s(%d): %s, Error Code: %d", __FUNCTION__, __LINE__, message, result); \ + } + +// Constructor for button using GPIO pin +Button::Button(gpio_num_t pin, bool pullup) +{ + _button_pin = pin; + + // Set pin mode based on pullup parameter + gpio_set_pull_mode(_button_pin, pullup ? GPIO_PULLUP_ONLY : GPIO_PULLDOWN_ONLY); + + + // Configure button using GPIO + button_config_t cfg = { + .type = BUTTON_TYPE_GPIO, // Set button type as GPIO + .long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS, // Set long press time + .short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS, // Set short press time + .gpio_button_config = { + .gpio_num = pin, // Set GPIO pin number + .active_level = pullup // Set active level based on pullup parameter + } + }; + _handle = iot_button_create(&cfg); + // Print button created message for debugging purposes + ESP_LOGI(TAG, "Button created"); +} + +// Constructor for button using ADC pin +Button::Button(gpio_num_t pin, bool pullup, uint8_t adc_channel, uint8_t button_index, uint16_t min, uint16_t max) +{ + _button_pin = pin; + + // Set pin mode based on pullup parameter + gpio_set_pull_mode(_button_pin, pullup ? GPIO_PULLUP_ONLY : GPIO_PULLDOWN_ONLY); + + // Configure button using ADC + button_config_t cfg = { + .type = BUTTON_TYPE_ADC, // Set button type as ADC + .long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS, // Set long press time + .short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS, // Set short press time + .adc_button_config = { + .adc_channel = adc_channel, // Set ADC channel + .button_index = button_index, // Set button index + .min = min, // Set minimum ADC reading + .max = max, // Set maximum ADC reading +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + .adc_handle = NULL /**< handle of adc unit, if NULL will create new one internal, else will use the handle */ +#endif + } + }; + // Create button handle + _handle = iot_button_create(&cfg); + // Print button created message for debugging purposes + ESP_LOGI(TAG, "Button created"); +} + +/* 0. Button Press Down*/ + +// Method to attach callback function for button press down event +void Button::attachPressDownEventCb(callbackFunction newFunction, void *usr_data) +{ + if (newFunction == NULL) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + + CHECK_ESP_ERROR(iot_button_register_cb(_handle, BUTTON_PRESS_DOWN, newFunction, usr_data), "attach callback fail"); +} +// Method to unregister button press down event +void Button::unregisterPressDownEventCb(callbackFunction newFunction) +{ + if (!_handle) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + button_event_config_t btn_cfg; + btn_cfg.event = BUTTON_PRESS_DOWN; + CHECK_ESP_ERROR(iot_button_unregister_event(_handle, btn_cfg, newFunction), "unregister callback fail"); +} +// Method to detach button press down event +void Button::detachPressDownEvent(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return; + } + CHECK_ESP_ERROR(iot_button_unregister_cb(_handle, BUTTON_PRESS_DOWN), "detach pressdown event fail"); +} + +/* 1. Button Press Up*/ + +// Method to attach callback function for button press up event +void Button::attachPressUpEventCb(callbackFunction newFunction, void *usr_data) +{ + if (newFunction == NULL) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + + CHECK_ESP_ERROR(iot_button_register_cb(_handle, BUTTON_PRESS_UP, newFunction, usr_data), "attach callback fail"); +} + +// Method to unregister button press up event +void Button::unregisterPressUpEventCb(callbackFunction newFunction) +{ + if (!_handle) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + + button_event_config_t btn_cfg; + btn_cfg.event = BUTTON_PRESS_UP; + + CHECK_ESP_ERROR(iot_button_unregister_event(_handle, btn_cfg, newFunction), "unregister callback fail"); +} +// Method to detach button press up event +void Button::detachPressUpEvent(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return; + } + + CHECK_ESP_ERROR(iot_button_unregister_cb(_handle, BUTTON_PRESS_UP), "detach press up event fail"); +} + +/*2. Button Press Repeat*/ + +// Method to attach callback function for button press repeat event +void Button::attachPressRepeatEventCb(callbackFunction newFunction, void *usr_data) +{ + if (newFunction == NULL) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + + CHECK_ESP_ERROR(iot_button_register_cb(_handle, BUTTON_PRESS_REPEAT, newFunction, usr_data), "attach callback fail"); +} +// Method to unregister button press down event +void Button::unregisterPressRepeatEventCb(callbackFunction newFunction) +{ + if (!_handle) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + + button_event_config_t btn_cfg; + btn_cfg.event = BUTTON_PRESS_REPEAT; + + CHECK_ESP_ERROR(iot_button_unregister_event(_handle, btn_cfg, newFunction), "unregister callback fail"); +} +// Method to detach button press repeat event +void Button::detachPressRepeatEvent(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return; + } + + CHECK_ESP_ERROR(iot_button_unregister_cb(_handle, BUTTON_PRESS_REPEAT), "detach press repeat event fail"); +} + +/*3. Button Single Click*/ + +// Method to attach callback function for button single click event +void Button::attachSingleClickEventCb(callbackFunction newFunction, void *usr_data) +{ + if (newFunction == NULL) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + CHECK_ESP_ERROR(iot_button_register_cb(_handle, BUTTON_SINGLE_CLICK, newFunction, usr_data), "attach callback fail"); +} +// Method to unregister button press down event +void Button::unregisterSingleClickEventCb(callbackFunction newFunction) +{ + if (!_handle) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + + button_event_config_t btn_cfg; + btn_cfg.event = BUTTON_SINGLE_CLICK; + + CHECK_ESP_ERROR(iot_button_unregister_event(_handle, btn_cfg, newFunction), "unregister callback fail"); +} +// Method to detach button single click event +void Button::detachSingleClickEvent(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return; + } + + CHECK_ESP_ERROR(iot_button_unregister_cb(_handle, BUTTON_SINGLE_CLICK), "detach single click event fail"); +} + +/*4. Button Double Click*/ + +// Method to attach callback function for button double click event +void Button::attachDoubleClickEventCb(callbackFunction newFunction, void *usr_data) +{ + if (newFunction == NULL) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + CHECK_ESP_ERROR(iot_button_register_cb(_handle, BUTTON_DOUBLE_CLICK, newFunction, usr_data), "attach callback fail"); +} +// Method to unregister double click event +void Button::unregisterDoubleClickEventCb(callbackFunction newFunction) +{ + if (!_handle) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + + button_event_config_t btn_cfg; + btn_cfg.event = BUTTON_DOUBLE_CLICK; + + CHECK_ESP_ERROR(iot_button_unregister_event(_handle, btn_cfg, newFunction), "unregister callback fail"); +} +// Method to detach button double click event +void Button::detachDoubleClickEvent(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return; + } + + CHECK_ESP_ERROR(iot_button_unregister_cb(_handle, BUTTON_DOUBLE_CLICK), "detach double click event Fail"); +} + +/*5. Button Long Press Start*/ + +// Method to attach callback function for button long press start event +void Button::attachLongPressStartEventCb(callbackFunction newFunction, void *usr_data) +{ + if (newFunction == NULL) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + CHECK_ESP_ERROR(iot_button_register_cb(_handle, BUTTON_LONG_PRESS_START, newFunction, usr_data), "attach callback fail"); +} +// Method to unregister long press event +void Button::unregisterLongPressStartEventCb(callbackFunction newFunction) +{ + if (!_handle) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + + button_event_config_t btn_cfg; + btn_cfg.event = BUTTON_LONG_PRESS_START; + CHECK_ESP_ERROR(iot_button_unregister_event(_handle, btn_cfg, newFunction), "unregister callback fail"); +} +// Method to detach button long press start event +void Button::detachLongPressStartEvent(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return; + } + + CHECK_ESP_ERROR(iot_button_unregister_cb(_handle, BUTTON_LONG_PRESS_START), "Detach Callback Fail"); +} + +/*6. Button Long Press Hold*/ + +// Method to attach callback function for button long press hold event +void Button::attachLongPressHoldEventCb(callbackFunction newFunction, void *usr_data) +{ + if (newFunction == NULL) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + CHECK_ESP_ERROR(iot_button_register_cb(_handle, BUTTON_LONG_PRESS_HOLD, newFunction, usr_data), "attach callback fail"); +} +// Method to unregister long press hold event +void Button::unregisterLongPressHoldEventCb(callbackFunction newFunction) +{ + if (!_handle) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + + button_event_config_t btn_cfg; + btn_cfg.event = BUTTON_LONG_PRESS_HOLD; + + CHECK_ESP_ERROR(iot_button_unregister_event(_handle, btn_cfg, newFunction), "unregister callback fail"); +} +// Method to detach button long press hold event +void Button::detachLongPressHoldEvent(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return; + } + + CHECK_ESP_ERROR(iot_button_unregister_cb(_handle, BUTTON_LONG_PRESS_HOLD), "detach long press hold event fail"); +} + +/*7. Button Press Repeat Done*/ + +// Method to attach callback function for button press repeat done event +void Button::attachPressRepeatDoneEventCb(callbackFunction newFunction, void *usr_data) +{ + if (newFunction == NULL) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + CHECK_ESP_ERROR(iot_button_register_cb(_handle, BUTTON_PRESS_REPEAT_DONE, newFunction, usr_data), "attach callback fail"); +} +// Method to unregister press repeat done event +void Button::unregisterPressRepeatDoneEventCb(callbackFunction newFunction) +{ + if (!_handle) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + + button_event_config_t btn_cfg; + btn_cfg.event = BUTTON_PRESS_REPEAT_DONE; + + CHECK_ESP_ERROR(iot_button_unregister_event(_handle, btn_cfg, newFunction), "unregister callback fail"); +} +// Method to detach button repeat down event +void Button::detachPressRepeatDoneEvent(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return; + } + + CHECK_ESP_ERROR(iot_button_unregister_cb(_handle, BUTTON_PRESS_REPEAT_DONE), "detach press repeat done event fail"); +} + +/*8. Button Multiple Clicks*/ + +// Method to attach callback function for button press down event +void Button::attachMultipleClickEventCb(callbackFunction newFunction, int clicks, void *usr_data) +{ + if (newFunction == NULL) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + + button_event_config_t btn_cfg; + btn_cfg.event = BUTTON_MULTIPLE_CLICK; + btn_cfg.event_data.multiple_clicks.clicks = clicks; + + CHECK_ESP_ERROR(iot_button_register_event_cb(_handle, btn_cfg, newFunction, usr_data), "attach callback fail"); +} +// Method to unregistered multiclick event +void Button::unregisterMultiClickEventCb(callbackFunction newFunction, int clicks) +{ + if (!_handle) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + + button_event_config_t btn_cfg; + btn_cfg.event = BUTTON_MULTIPLE_CLICK; // Set the event to unregister + btn_cfg.event_data.multiple_clicks.clicks = clicks; + + CHECK_ESP_ERROR(iot_button_unregister_event(_handle, btn_cfg, newFunction), "unregister callback fail"); +} + +/*9. Button Long Press Up*/ + +// Method to attach callback function for button press down event +void Button::attachLongPressUpEventCb(callbackFunction newFunction, void *usr_data) +{ + if (newFunction == NULL) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + CHECK_ESP_ERROR(iot_button_register_cb(_handle, BUTTON_LONG_PRESS_UP, newFunction, usr_data), "attach callback fail"); +} +// Method to unregistered long press up event +void Button::unregisterLongPressUpEventCb(callbackFunction newFunction) +{ + if (!_handle) { + ESP_LOGE(TAG, "Callback function not defined"); + return; + } + + button_event_config_t btn_cfg; + btn_cfg.event = BUTTON_LONG_PRESS_UP; + + CHECK_ESP_ERROR(iot_button_unregister_event(_handle, btn_cfg, newFunction), "unregister callback fail"); +} +// Method to detach Button Long Pres Up event +void Button::detachLongPressUpEvent(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return; + } + + CHECK_ESP_ERROR(iot_button_unregister_cb(_handle, BUTTON_LONG_PRESS_UP), "detach long press up event Fail"); +} + +/*Other Methods*/ + +// Method to delete button +void Button::del(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return; + } + CHECK_ESP_ERROR(iot_button_delete(_handle), "delete button fail"); + _handle = NULL; +} + +// Method to count call back +int Button::countCallBack(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return 0; + } else { + return iot_button_count_cb(_handle); + } +} + +// Method to count call back events +int Button::countEvent(button_event_t event) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return 0; + } else { + return iot_button_count_event(_handle, event); + } +} + +// Method to get event +int Button::getEvent(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return 0; + } else { + return iot_button_get_event(_handle); + } +} + +// Method to get repeat +int Button::getRepeat(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return 0; + } else { + return iot_button_get_repeat(_handle); + } +} + +// Method to get tick time +int Button::getTickTime(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return 0; + } else { + return iot_button_get_ticks_time(_handle); + } +} + +// Method to get long press hold count +int Button::getLongPressHoldCount(void) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return 0; + } else { + return iot_button_get_long_press_hold_cnt(_handle); + } +} + +// Method to set parameter +void Button::setParam(button_param_t param, void *value) +{ + if (!_handle) { + ESP_LOGE(TAG, "Button not created"); + return; + } else { + CHECK_ESP_ERROR(iot_button_set_param(_handle, param, value), "paramater set fail"); + } +} + +// Method to button resume +void Button::resume(void) +{ + CHECK_ESP_ERROR(iot_button_resume(), "button resume fail"); +} + +//Method to button stop +void Button::stop(void) +{ + CHECK_ESP_ERROR(iot_button_stop(), "button stop fail"); +} diff --git a/src/Button.h b/src/Button.h new file mode 100644 index 0000000..d5a3156 --- /dev/null +++ b/src/Button.h @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef BUTTON_H +#define BUTTON_H +#include "original/iot_button.h" +#include "original/arduino_config.h" + +// Define a function pointer type for callbacks +typedef void (*callbackFunction)(void *button_handle, void *usr_data); + +// Define the Button class +class Button { +public: + // Constructors for gpio button + Button(gpio_num_t pin, bool pullup); + + // Constructors for gpio button + Button(gpio_num_t pin, bool pullup, uint8_t adc_channel, uint8_t button_index, uint16_t min, uint16_t max); + + /*Attach Methods*/ + void attachPressDownEventCb(callbackFunction newFunction, void *usr_data); + void attachPressUpEventCb(callbackFunction newFunction, void *usr_data); + void attachPressRepeatEventCb(callbackFunction newFunction, void *usr_data); + void attachSingleClickEventCb(callbackFunction newFunction, void *usr_data); + void attachDoubleClickEventCb(callbackFunction newFunction, void *usr_data); + void attachLongPressStartEventCb(callbackFunction newFunction, void *usr_data); + void attachLongPressHoldEventCb(callbackFunction newFunction, void *usr_data); + void attachPressRepeatDoneEventCb(callbackFunction newFunction, void *usr_data); + void attachLongPressUpEventCb(callbackFunction newFunction, void *usr_data); + void attachButtonNonePressEventCb(callbackFunction newFunction, void *usr_data); + void attachMultipleClickEventCb(callbackFunction newFunction, int clicks, void *usr_data); + + /*Detach Methods*/ + void detachPressDownEvent(void); + void detachPressUpEvent(void); + void detachPressRepeatEvent(void); + void detachSingleClickEvent(void); + void detachDoubleClickEvent(void); + void detachLongPressStartEvent(void); + void detachLongPressHoldEvent(void); + void detachPressRepeatDoneEvent(void); + void detachLongPressUpEvent(void); + + /*Unregister Methods*/ + void unregisterPressDownEventCb(callbackFunction newFunction); + void unregisterPressUpEventCb(callbackFunction newFunction); + void unregisterPressRepeatEventCb(callbackFunction newFunction); + void unregisterSingleClickEventCb(callbackFunction newFunction); + void unregisterDoubleClickEventCb(callbackFunction newFunction); + void unregisterLongPressStartEventCb(callbackFunction newFunction); + void unregisterLongPressHoldEventCb(callbackFunction newFunction); + void unregisterPressRepeatDoneEventCb(callbackFunction newFunction); + void unregisterLongPressUpEventCb(callbackFunction newFunction); + void unregisterButtonNonePressEventCb(callbackFunction newFunction); + void unregisterMultiClickEventCb(callbackFunction newFunction, int clicks); + + // Other methods + void del(void); + int countCallBack(void); + int countEvent(button_event_t event); + int getEvent(void); + int getRepeat(void); + int getTickTime(void); + int getLongPressHoldCount(void); + void setParam(button_param_t param, void *value); + void resume(void); + void stop(void); + +private: + // Private variables + gpio_num_t _button_pin; + button_handle_t _handle; +}; + +#endif diff --git a/src/original/arduino_config.h b/src/original/arduino_config.h new file mode 100644 index 0000000..8ae3f5c --- /dev/null +++ b/src/original/arduino_config.h @@ -0,0 +1,24 @@ +/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ARDUINO_CONFIG_H +#define ARDUINO_CONFIG_H + +//Configuration parameters +#define CONFIG_ADC_BUTTON_MAX_BUTTON_PER_CHANNEL 8 //range 1 10 +#define CONFIG_ADC_BUTTON_MAX_CHANNEL 3 // range 1 5 +#define CONFIG_ADC_BUTTON_SAMPLE_TIMES 1 // range 1 4 +#define CONFIG_BUTTON_DEBOUNCE_TICKS 2 //range 1 8 +#define CONFIG_BUTTON_SHORT_PRESS_TIME_MS 180 //range 50-800 +#define CONFIG_BUTTON_LONG_PRESS_TIME_MS 1500 //range 500-5000 +#define CONFIG_BUTTON_PERIOD_TIME_MS 5 // range 2-20 +#define CONFIG_BUTTON_SERIAL_TIME_MS 20 //range 2-1000 +#define CONFIG_BUTTON_LONG_PRESS_TOLERANCE_MS 20 + +#define BUTTON_VER_MINOR (1) // ignore this +#define BUTTON_VER_PATCH (1) // ignore this +#define BUTTON_VER_MAJOR (1) //ignore this + +#endif // BUTTON_CONFIG_H diff --git a/src/original/button_adc.c b/src/original/button_adc.c new file mode 100644 index 0000000..6eb7d3a --- /dev/null +++ b/src/original/button_adc.c @@ -0,0 +1,311 @@ +/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "esp_log.h" +#include "esp_timer.h" +#include "esp_idf_version.h" +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#include "soc/soc_caps.h" +#include "esp_adc/adc_oneshot.h" +#include "esp_adc/adc_cali.h" +#include "esp_adc/adc_cali_scheme.h" +#else +#include "driver/gpio.h" +#include "driver/adc.h" +#include "esp_adc_cal.h" +#endif +#include "button_adc.h" +#include "arduino_config.h" + + +static const char *TAG = "adc button"; + +#define ADC_BTN_CHECK(a, str, ret_val) \ + if (!(a)) \ + { \ + ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret_val); \ + } + +#define DEFAULT_VREF 1100 +#define NO_OF_SAMPLES CONFIG_ADC_BUTTON_SAMPLE_TIMES //Multisampling + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#define ADC_BUTTON_WIDTH SOC_ADC_RTC_MAX_BITWIDTH +#define ADC1_BUTTON_CHANNEL_MAX SOC_ADC_MAX_CHANNEL_NUM +#define ADC_BUTTON_ATTEN ADC_ATTEN_DB_11 +#else +#define ADC_BUTTON_WIDTH ADC_WIDTH_MAX-1 +#define ADC1_BUTTON_CHANNEL_MAX ADC1_CHANNEL_MAX +#define ADC_BUTTON_ATTEN ADC_ATTEN_DB_11 +#endif +#define ADC_BUTTON_ADC_UNIT ADC_UNIT_1 +#define ADC_BUTTON_MAX_CHANNEL CONFIG_ADC_BUTTON_MAX_CHANNEL +#define ADC_BUTTON_MAX_BUTTON CONFIG_ADC_BUTTON_MAX_BUTTON_PER_CHANNEL + +typedef struct { + uint16_t min; + uint16_t max; +} button_data_t; + +typedef struct { + uint8_t channel; + uint8_t is_init; + button_data_t btns[ADC_BUTTON_MAX_BUTTON]; /* all button on the channel */ + uint64_t last_time; /* the last time of adc sample */ +} btn_adc_channel_t; + +typedef struct { + bool is_configured; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + adc_cali_handle_t adc1_cali_handle; + adc_oneshot_unit_handle_t adc1_handle; +#else + esp_adc_cal_characteristics_t adc_chars; +#endif + btn_adc_channel_t ch[ADC_BUTTON_MAX_CHANNEL]; + uint8_t ch_num; +} adc_button_t; + +static adc_button_t g_button = {0}; + +static int find_unused_channel(void) +{ + for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) { + if (0 == g_button.ch[i].is_init) { + return i; + } + } + return -1; +} + +static int find_channel(uint8_t channel) +{ + for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) { + if (channel == g_button.ch[i].channel) { + return i; + } + } + return -1; +} + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +static esp_err_t adc_calibration_init(adc_unit_t unit, adc_atten_t atten, adc_cali_handle_t *out_handle) +{ + adc_cali_handle_t handle = NULL; + esp_err_t ret = ESP_FAIL; + bool calibrated = false; + +#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + if (!calibrated) { + ESP_LOGI(TAG, "calibration scheme version is %s", "Curve Fitting"); + adc_cali_curve_fitting_config_t cali_config = { + .unit_id = unit, + .atten = atten, + .bitwidth = ADC_BUTTON_WIDTH, + }; + ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle); + if (ret == ESP_OK) { + calibrated = true; + } + } +#endif + +#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + if (!calibrated) { + ESP_LOGI(TAG, "calibration scheme version is %s", "Line Fitting"); + adc_cali_line_fitting_config_t cali_config = { + .unit_id = unit, + .atten = atten, + .bitwidth = ADC_BUTTON_WIDTH, + }; + ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle); + if (ret == ESP_OK) { + calibrated = true; + } + } +#endif + + *out_handle = handle; + if (ret == ESP_OK) { + ESP_LOGI(TAG, "Calibration Success"); + } else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) { + ESP_LOGW(TAG, "eFuse not burnt, skip software calibration"); + } else { + ESP_LOGE(TAG, "Invalid arg or no memory"); + } + + return calibrated?ESP_OK:ESP_FAIL; +} +#endif + +esp_err_t button_adc_init(const button_adc_config_t *config) +{ + ADC_BTN_CHECK(NULL != config, "Pointer of config is invalid", ESP_ERR_INVALID_ARG); + ADC_BTN_CHECK(config->adc_channel < ADC1_BUTTON_CHANNEL_MAX, "channel out of range", ESP_ERR_NOT_SUPPORTED); + ADC_BTN_CHECK(config->button_index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", ESP_ERR_NOT_SUPPORTED); + ADC_BTN_CHECK(config->max > 0, "key max voltage invalid", ESP_ERR_INVALID_ARG); + + int ch_index = find_channel(config->adc_channel); + if (ch_index >= 0) { /**< the channel has been initialized */ + ADC_BTN_CHECK(g_button.ch[ch_index].btns[config->button_index].max == 0, "The button_index has been used", ESP_ERR_INVALID_STATE); + } else { /**< this is a new channel */ + int unused_ch_index = find_unused_channel(); + ADC_BTN_CHECK(unused_ch_index >= 0, "exceed max channel number, can't create a new channel", ESP_ERR_INVALID_STATE); + ch_index = unused_ch_index; + } + + /** initialize adc */ + if (0 == g_button.is_configured) { +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + esp_err_t ret; + if (NULL == config->adc_handle) { + //ADC1 Init + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_1, + }; + ret = adc_oneshot_new_unit(&init_config, &g_button.adc1_handle); + ADC_BTN_CHECK(ret == ESP_OK, "adc oneshot new unit fail!", ESP_FAIL); + } else { + g_button.adc1_handle = *config->adc_handle ; + ESP_LOGI(TAG, "ADC1 has been initialized"); + } +#else + //Configure ADC + adc1_config_width(ADC_BUTTON_WIDTH); + //Characterize ADC + esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_BUTTON_ADC_UNIT, ADC_BUTTON_ATTEN, ADC_BUTTON_WIDTH, DEFAULT_VREF, &g_button.adc_chars); + if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) { + ESP_LOGI(TAG, "Characterized using Two Point Value"); + } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) { + ESP_LOGI(TAG, "Characterized using eFuse Vref"); + } else { + ESP_LOGI(TAG, "Characterized using Default Vref"); + } +#endif + g_button.is_configured = 1; + } + + /** initialize adc channel */ + if (0 == g_button.ch[ch_index].is_init) { +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + //ADC1 Config + adc_oneshot_chan_cfg_t oneshot_config = { + .bitwidth = ADC_BUTTON_WIDTH, + .atten = ADC_BUTTON_ATTEN, + }; + esp_err_t ret = adc_oneshot_config_channel(g_button.adc1_handle, config->adc_channel, &oneshot_config); + ADC_BTN_CHECK(ret == ESP_OK, "adc oneshot config channel fail!", ESP_FAIL); + //-------------ADC1 Calibration Init---------------// + ret = adc_calibration_init(ADC_BUTTON_ADC_UNIT, ADC_BUTTON_ATTEN, &g_button.adc1_cali_handle); + ADC_BTN_CHECK(ret == ESP_OK, "ADC1 Calibration Init False", 0); +#else + adc1_config_channel_atten(config->adc_channel, ADC_BUTTON_ATTEN); +#endif + g_button.ch[ch_index].channel = config->adc_channel; + g_button.ch[ch_index].is_init = 1; + g_button.ch[ch_index].last_time = 0; + } + g_button.ch[ch_index].btns[config->button_index].max = config->max; + g_button.ch[ch_index].btns[config->button_index].min = config->min; + g_button.ch_num++; + + return ESP_OK; +} + +esp_err_t button_adc_deinit(uint8_t channel, int button_index) +{ + ADC_BTN_CHECK(channel < ADC1_BUTTON_CHANNEL_MAX, "channel out of range", ESP_ERR_INVALID_ARG); + ADC_BTN_CHECK(button_index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", ESP_ERR_INVALID_ARG); + + int ch_index = find_channel(channel); + ADC_BTN_CHECK(ch_index >= 0, "can't find the channel", ESP_ERR_INVALID_ARG); + + g_button.ch[ch_index].btns[button_index].max = 0; + g_button.ch[ch_index].btns[button_index].min = 0; + + /** check button usage on the channel*/ + uint8_t unused_button = 0; + for (size_t i = 0; i < ADC_BUTTON_MAX_BUTTON; i++) { + if (0 == g_button.ch[ch_index].btns[i].max) { + unused_button++; + } + } + if (unused_button == ADC_BUTTON_MAX_BUTTON && g_button.ch[ch_index].is_init) { /**< if all button is unused, deinit the channel */ + g_button.ch[ch_index].is_init = 0; + g_button.ch[ch_index].channel = ADC1_BUTTON_CHANNEL_MAX; + ESP_LOGD(TAG, "all button is unused on channel%d, deinit the channel", g_button.ch[ch_index].channel); + } + + /** check channel usage on the adc*/ + uint8_t unused_ch = 0; + for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) { + if (0 == g_button.ch[i].is_init) { + unused_ch++; + } + } + if (unused_ch == ADC_BUTTON_MAX_CHANNEL && g_button.is_configured) { /**< if all channel is unused, deinit the adc */ + g_button.is_configured = false; + memset(&g_button, 0, sizeof(adc_button_t)); + ESP_LOGD(TAG, "all channel is unused, , deinit adc"); + } +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + esp_err_t ret = adc_oneshot_del_unit(g_button.adc1_handle); + ADC_BTN_CHECK(ret == ESP_OK, "adc oneshot deinit fail", ESP_FAIL); +#endif + return ESP_OK; +} + +static uint32_t get_adc_volatge(uint8_t channel) +{ + uint32_t adc_reading = 0; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + int adc_raw = 0; + for (int i = 0; i < NO_OF_SAMPLES; i++) { + adc_oneshot_read(g_button.adc1_handle, channel, &adc_raw); + adc_reading += adc_raw; + } + adc_reading /= NO_OF_SAMPLES; + //Convert adc_reading to voltage in mV + int voltage = 0; + adc_cali_raw_to_voltage(g_button.adc1_cali_handle, adc_reading, &voltage); + ESP_LOGV(TAG, "Raw: %"PRIu32"\tVoltage: %dmV", adc_reading, voltage); +#else + //Multisampling + for (int i = 0; i < NO_OF_SAMPLES; i++) { + adc_reading += adc1_get_raw(channel); + } + adc_reading /= NO_OF_SAMPLES; + //Convert adc_reading to voltage in mV + uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, &g_button.adc_chars); + ESP_LOGV(TAG, "Raw: %"PRIu32"\tVoltage: %"PRIu32"mV", adc_reading, voltage); +#endif + return voltage; +} + +uint8_t button_adc_get_key_level(void *button_index) +{ + static uint16_t vol = 0; + uint32_t ch = ADC_BUTTON_SPLIT_CHANNEL(button_index); + uint32_t index = ADC_BUTTON_SPLIT_INDEX(button_index); + ADC_BTN_CHECK(ch < ADC1_BUTTON_CHANNEL_MAX, "channel out of range", 0); + ADC_BTN_CHECK(index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", 0); + int ch_index = find_channel(ch); + ADC_BTN_CHECK(ch_index >= 0, "The button_index is not init", 0); + + /** It starts only when the elapsed time is more than 1ms */ + if ((esp_timer_get_time() - g_button.ch[ch_index].last_time) > 1000) { + vol = get_adc_volatge(ch); + g_button.ch[ch_index].last_time = esp_timer_get_time(); + } + + if (vol <= g_button.ch[ch_index].btns[index].max && + vol > g_button.ch[ch_index].btns[index].min) { + return 1; + } + return 0; +} diff --git a/src/original/button_adc.h b/src/original/button_adc.h new file mode 100644 index 0000000..7ba1270 --- /dev/null +++ b/src/original/button_adc.h @@ -0,0 +1,76 @@ +/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_idf_version.h" +#include "driver/gpio.h" +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#include "esp_adc/adc_oneshot.h" +#else +#include "driver/adc.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define ADC_BUTTON_COMBINE(channel, index) ((channel)<<8 | (index)) +#define ADC_BUTTON_SPLIT_INDEX(data) ((uint32_t)(data)&0xff) +#define ADC_BUTTON_SPLIT_CHANNEL(data) (((uint32_t)(data) >> 8) & 0xff) + +/** + * @brief adc button configuration + * + */ +typedef struct { + uint8_t adc_channel; /**< Channel of ADC */ + uint8_t button_index; /**< button index on the channel */ + uint16_t min; /**< min voltage in mv corresponding to the button */ + uint16_t max; /**< max voltage in mv corresponding to the button */ +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + adc_oneshot_unit_handle_t *adc_handle; /**< handle of adc unit, if NULL will create new one internal, else will use the handle */ +#endif +} button_adc_config_t; + +/** + * @brief Initialize gpio button + * + * @param config pointer of configuration struct + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG Arguments is NULL. + * - ESP_ERR_NOT_SUPPORTED Arguments out of range. + * - ESP_ERR_INVALID_STATE State is error. + */ +esp_err_t button_adc_init(const button_adc_config_t *config); + +/** + * @brief Deinitialize gpio button + * + * @param channel ADC channel + * @param button_index Button index on the channel + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG Arguments is invalid. + */ +esp_err_t button_adc_deinit(uint8_t channel, int button_index); + +/** + * @brief Get the adc button level + * + * @param button_index It is compressed by ADC channel and button index, use the macro ADC_BUTTON_COMBINE to generate. It will be treated as a uint32_t variable. + * + * @return + * - 0 Not pressed + * - 1 Pressed + */ +uint8_t button_adc_get_key_level(void *button_index); + +#ifdef __cplusplus +} +#endif diff --git a/src/original/button_gpio.c b/src/original/button_gpio.c new file mode 100644 index 0000000..a0a5604 --- /dev/null +++ b/src/original/button_gpio.c @@ -0,0 +1,48 @@ +/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "driver/gpio.h" +#include "button_gpio.h" + +static const char *TAG = "gpio button"; + +#define GPIO_BTN_CHECK(a, str, ret_val) \ + if (!(a)) \ + { \ + ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret_val); \ + } + +esp_err_t button_gpio_init(const button_gpio_config_t *config) +{ + GPIO_BTN_CHECK(NULL != config, "Pointer of config is invalid", ESP_ERR_INVALID_ARG); + GPIO_BTN_CHECK(GPIO_IS_VALID_GPIO(config->gpio_num), "GPIO number error", ESP_ERR_INVALID_ARG); + + gpio_config_t gpio_conf; + gpio_conf.intr_type = GPIO_INTR_DISABLE; + gpio_conf.mode = GPIO_MODE_INPUT; + gpio_conf.pin_bit_mask = (1ULL << config->gpio_num); + if (config->active_level) { + gpio_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; + gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE; + } else { + gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE; + } + gpio_config(&gpio_conf); + + return ESP_OK; +} + +esp_err_t button_gpio_deinit(int gpio_num) +{ + return gpio_reset_pin(gpio_num);; +} + +uint8_t button_gpio_get_key_level(void *gpio_num) +{ + return (uint8_t)gpio_get_level((uint32_t)gpio_num); +} diff --git a/src/original/button_gpio.h b/src/original/button_gpio.h new file mode 100644 index 0000000..0a66478 --- /dev/null +++ b/src/original/button_gpio.h @@ -0,0 +1,54 @@ +/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "driver/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief gpio button configuration + * + */ +typedef struct { + int32_t gpio_num; /**< num of gpio */ + uint8_t active_level; /**< gpio level when press down */ +} button_gpio_config_t; + +/** + * @brief Initialize gpio button + * + * @param config pointer of configuration struct + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG Arguments is NULL. + */ +esp_err_t button_gpio_init(const button_gpio_config_t *config); + +/** + * @brief Deinitialize gpio button + * + * @param gpio_num gpio number of button + * + * @return Always return ESP_OK + */ +esp_err_t button_gpio_deinit(int gpio_num); + +/** + * @brief Get current level on button gpio + * + * @param gpio_num gpio number of button, it will be treated as a uint32_t variable. + * + * @return Level on gpio + */ +uint8_t button_gpio_get_key_level(void *gpio_num); + +#ifdef __cplusplus +} +#endif diff --git a/src/original/button_matrix.c b/src/original/button_matrix.c new file mode 100644 index 0000000..113754e --- /dev/null +++ b/src/original/button_matrix.c @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "driver/gpio.h" +#include "button_matrix.h" + +static const char *TAG = "matrix button"; + +#define MATRIX_BTN_CHECK(a, str, ret_val) \ + if (!(a)) \ + { \ + ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret_val); \ + } + +esp_err_t button_matrix_init(const button_matrix_config_t *config) +{ + MATRIX_BTN_CHECK(NULL != config, "Pointer of config is invalid", ESP_ERR_INVALID_ARG); + MATRIX_BTN_CHECK(GPIO_IS_VALID_GPIO(config->row_gpio_num), "row GPIO number error", ESP_ERR_INVALID_ARG); + MATRIX_BTN_CHECK(GPIO_IS_VALID_GPIO(config->col_gpio_num), "col GPIO number error", ESP_ERR_INVALID_ARG); + + // set row gpio as output + gpio_config_t gpio_conf = {0}; + gpio_conf.intr_type = GPIO_INTR_DISABLE; + gpio_conf.mode = GPIO_MODE_OUTPUT; + gpio_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; + gpio_conf.pin_bit_mask = (1ULL << config->row_gpio_num); + gpio_config(&gpio_conf); + + // set col gpio as input + gpio_conf.mode = GPIO_MODE_INPUT; + gpio_conf.pin_bit_mask = (1ULL << config->col_gpio_num); + gpio_config(&gpio_conf); + + return ESP_OK; +} + +esp_err_t button_matrix_deinit(int row_gpio_num, int col_gpio_num) +{ + //Reset an gpio to default state (select gpio function, enable pullup and disable input and output). + gpio_reset_pin(row_gpio_num); + gpio_reset_pin(col_gpio_num); + return ESP_OK; +} + +uint8_t button_matrix_get_key_level(void *hardware_data) +{ + uint32_t row = MATRIX_BUTTON_SPLIT_ROW(hardware_data); + uint32_t col = MATRIX_BUTTON_SPLIT_COL(hardware_data); + gpio_set_level(row, 1); + uint8_t level = gpio_get_level(col); + gpio_set_level(row, 0); + + return level; +} diff --git a/src/original/button_matrix.h b/src/original/button_matrix.h new file mode 100644 index 0000000..f7f45af --- /dev/null +++ b/src/original/button_matrix.h @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define MATRIX_BUTTON_COMBINE(row_gpio, col_gpio) ((row_gpio)<<8 | (col_gpio)) +#define MATRIX_BUTTON_SPLIT_COL(data) ((uint32_t)(data)&0xff) +#define MATRIX_BUTTON_SPLIT_ROW(data) (((uint32_t)(data) >> 8) & 0xff) + +/** + * @brief Button matrix key configuration. + * Just need to configure the GPIO associated with this GPIO in the matrix keyboard. + * + * Matrix Keyboard Layout (3x3): + * ---------------------------------------- + * | Button 1 | Button 2 | Button 3 | + * | (R1-C1) | (R1-C2) | (R1-C3) | + * |--------------------------------------| + * | Button 4 | Button 5 | Button 6 | + * | (R2-C1) | (R2-C2) | (R2-C3) | + * |--------------------------------------| + * | Button 7 | Button 8 | Button 9 | + * | (R3-C1) | (R3-C2) | (R3-C3) | + * ---------------------------------------- + * + * - Button matrix key is driven using row scanning. + * - Buttons within the same column cannot be detected simultaneously, + * but buttons within the same row can be detected without conflicts. + */ +typedef struct { + int32_t row_gpio_num; /**< GPIO number associated with the row */ + int32_t col_gpio_num; /**< GPIO number associated with the column */ +} button_matrix_config_t; + +/** + * @brief Initialize a button matrix keyboard. + * + * @param config Pointer to the button matrix key configuration. + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the argument is NULL. + * + * @note When initializing the button matrix keyboard, the row GPIO pins will be set as outputs, + * and the column GPIO pins will be set as inputs, both with pull-down resistors enabled. + */ +esp_err_t button_matrix_init(const button_matrix_config_t *config); + +/** + * @brief Deinitialize a button in the matrix keyboard. + * + * @param row_gpio_num GPIO number of the row where the button is located. + * @param col_gpio_num GPIO number of the column where the button is located. + * @return + * - ESP_OK if the button is successfully deinitialized + * + * @note When deinitializing a button, please exercise caution and avoid deinitializing a button individually, as it may affect the proper functioning of other buttons in the same row or column. + */ +esp_err_t button_matrix_deinit(int row_gpio_num, int col_gpio_num); + +/** + * @brief Get the key level from the button matrix hardware. + * + * @param hardware_data Pointer to hardware-specific data containing information about row GPIO and column GPIO. + * @return uint8_t[out] The key level read from the hardware. + * + * @note This function retrieves the key level from the button matrix hardware. + * The `hardware_data` parameter should contain information about the row and column GPIO pins, + * and you can access this information using the `MATRIX_BUTTON_SPLIT_COL` and `MATRIX_BUTTON_SPLIT_ROW` macros. + */ +uint8_t button_matrix_get_key_level(void *hardware_data); + +#ifdef __cplusplus +} +#endif diff --git a/src/original/iot_button.c b/src/original/iot_button.c new file mode 100644 index 0000000..d1f43dc --- /dev/null +++ b/src/original/iot_button.c @@ -0,0 +1,702 @@ +/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/timers.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "iot_button.h" +#include "esp_timer.h" +#include "sdkconfig.h" +#include "arduino_config.h" + +static const char *TAG = "button"; +static portMUX_TYPE s_button_lock = portMUX_INITIALIZER_UNLOCKED; +#define BUTTON_ENTER_CRITICAL() portENTER_CRITICAL(&s_button_lock) +#define BUTTON_EXIT_CRITICAL() portEXIT_CRITICAL(&s_button_lock) + +#define BTN_CHECK(a, str, ret_val) \ + if (!(a)) { \ + ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret_val); \ + } + +/** + * @brief Structs to store callback info + * + */ +typedef struct { + button_cb_t cb; + void *usr_data; + button_event_data_t event_data; +} button_cb_info_t; + +/** + * @brief Structs to record individual key parameters + * + */ +typedef struct Button { + uint16_t ticks; + uint16_t long_press_ticks; /*! Trigger ticks for long press*/ + uint16_t short_press_ticks; /*! Trigger ticks for repeat press*/ + uint16_t long_press_hold_cnt; /*! Record long press hold count*/ + uint16_t long_press_ticks_default; + uint8_t repeat; + uint8_t state: 3; + uint8_t debounce_cnt: 3; + uint8_t active_level: 1; + uint8_t button_level: 1; + button_event_t event; + uint8_t (*hal_button_Level)(void *hardware_data); + esp_err_t (*hal_button_deinit)(void *hardware_data); + void *hardware_data; + button_type_t type; + button_cb_info_t *cb_info[BUTTON_EVENT_MAX]; + size_t size[BUTTON_EVENT_MAX]; + int count[2]; + struct Button *next; +} button_dev_t; + +//button handle list head. +static button_dev_t *g_head_handle = NULL; +static esp_timer_handle_t g_button_timer_handle = NULL; +static bool g_is_timer_running = false; + +#define TICKS_INTERVAL CONFIG_BUTTON_PERIOD_TIME_MS +#define DEBOUNCE_TICKS CONFIG_BUTTON_DEBOUNCE_TICKS //MAX 8 +#define SHORT_TICKS (CONFIG_BUTTON_SHORT_PRESS_TIME_MS /TICKS_INTERVAL) +#define LONG_TICKS (CONFIG_BUTTON_LONG_PRESS_TIME_MS /TICKS_INTERVAL) +#define SERIAL_TICKS (CONFIG_BUTTON_SERIAL_TIME_MS /TICKS_INTERVAL) +#define TOLERANCE CONFIG_BUTTON_LONG_PRESS_TOLERANCE_MS + +#define CALL_EVENT_CB(ev) \ + if (btn->cb_info[ev]) { \ + for (int i = 0; i < btn->size[ev]; i++) { \ + btn->cb_info[ev][i].cb(btn, btn->cb_info[ev][i].usr_data); \ + } \ + } \ + +#define TIME_TO_TICKS(time, congfig_time) (0 == (time))?congfig_time:(((time) / TICKS_INTERVAL))?((time) / TICKS_INTERVAL):1 + +/** + * @brief Button driver core function, driver state machine. + */ +static void button_handler(button_dev_t *btn) +{ + uint8_t read_gpio_level = btn->hal_button_Level(btn->hardware_data); + + /** ticks counter working.. */ + if ((btn->state) > 0) { + btn->ticks++; + } + + /**< button debounce handle */ + if (read_gpio_level != btn->button_level) { + if (++(btn->debounce_cnt) >= DEBOUNCE_TICKS) { + btn->button_level = read_gpio_level; + btn->debounce_cnt = 0; + } + } else { + btn->debounce_cnt = 0; + } + + /** State machine */ + switch (btn->state) { + case 0: + if (btn->button_level == btn->active_level) { + btn->event = (uint8_t)BUTTON_PRESS_DOWN; + CALL_EVENT_CB(BUTTON_PRESS_DOWN); + btn->ticks = 0; + btn->repeat = 1; + btn->state = 1; + } else { + btn->event = (uint8_t)BUTTON_NONE_PRESS; + } + break; + + case 1: + if (btn->button_level != btn->active_level) { + btn->event = (uint8_t)BUTTON_PRESS_UP; + CALL_EVENT_CB(BUTTON_PRESS_UP); + btn->ticks = 0; + btn->state = 2; + + } else if (btn->ticks > btn->long_press_ticks) { + btn->event = (uint8_t)BUTTON_LONG_PRESS_START; + btn->state = 4; + /** Calling callbacks for BUTTON_LONG_PRESS_START */ + uint16_t ticks_time = iot_button_get_ticks_time(btn); + if (btn->cb_info[btn->event] && btn->count[0] == 0) { + if (abs(ticks_time - (btn->long_press_ticks * TICKS_INTERVAL)) <= TOLERANCE && btn->cb_info[btn->event][btn->count[0]].event_data.long_press.press_time == (btn->long_press_ticks * TICKS_INTERVAL)) { + do { + btn->cb_info[btn->event][btn->count[0]].cb(btn, btn->cb_info[btn->event][btn->count[0]].usr_data); + btn->count[0]++; + if (btn->count[0] >= btn->size[btn->event]) + break; + } while (btn->cb_info[btn->event][btn->count[0]].event_data.long_press.press_time == btn->long_press_ticks * TICKS_INTERVAL); + } + } + } + break; + + case 2: + if (btn->button_level == btn->active_level) { + btn->event = (uint8_t)BUTTON_PRESS_DOWN; + CALL_EVENT_CB(BUTTON_PRESS_DOWN); + btn->event = (uint8_t)BUTTON_PRESS_REPEAT; + btn->repeat++; + CALL_EVENT_CB(BUTTON_PRESS_REPEAT); // repeat hit + btn->ticks = 0; + btn->state = 3; + } else if (btn->ticks > btn->short_press_ticks) { + if (btn->repeat == 1) { + btn->event = (uint8_t)BUTTON_SINGLE_CLICK; + CALL_EVENT_CB(BUTTON_SINGLE_CLICK); + } else if (btn->repeat == 2) { + btn->event = (uint8_t)BUTTON_DOUBLE_CLICK; + CALL_EVENT_CB(BUTTON_DOUBLE_CLICK); // repeat hit + } + + btn->event = (uint8_t)BUTTON_MULTIPLE_CLICK; + + /** Calling the callbacks for MULTIPLE BUTTON CLICKS */ + for (int i = 0; i < btn->size[btn->event]; i++) { + if (btn->repeat == btn->cb_info[btn->event][i].event_data.multiple_clicks.clicks) { + do { + btn->cb_info[btn->event][i].cb(btn, btn->cb_info[btn->event][i].usr_data); + i++; + if (i >= btn->size[btn->event]) + break; + } while (btn->cb_info[btn->event][i].event_data.multiple_clicks.clicks == btn->repeat); + } + } + + btn->event = (uint8_t)BUTTON_PRESS_REPEAT_DONE; + CALL_EVENT_CB(BUTTON_PRESS_REPEAT_DONE); // repeat hit + btn->repeat = 0; + btn->state = 0; + } + break; + + case 3: + if (btn->button_level != btn->active_level) { + btn->event = (uint8_t)BUTTON_PRESS_UP; + CALL_EVENT_CB(BUTTON_PRESS_UP); + if (btn->ticks < SHORT_TICKS) { + btn->ticks = 0; + btn->state = 2; //repeat press + } else { + btn->state = 0; + } + } + break; + + case 4: + if (btn->button_level == btn->active_level) { + //continue hold trigger + if (btn->ticks >= (btn->long_press_hold_cnt + 1) * SERIAL_TICKS + btn->long_press_ticks) { + btn->event = (uint8_t)BUTTON_LONG_PRESS_HOLD; + btn->long_press_hold_cnt++; + CALL_EVENT_CB(BUTTON_LONG_PRESS_HOLD); + + /** Calling callbacks for BUTTON_LONG_PRESS_START based on press_time */ + uint16_t ticks_time = iot_button_get_ticks_time(btn); + if (btn->cb_info[BUTTON_LONG_PRESS_START]) { + button_cb_info_t *cb_info = btn->cb_info[BUTTON_LONG_PRESS_START]; + uint16_t time = cb_info[btn->count[0]].event_data.long_press.press_time; + if (btn->long_press_ticks * TICKS_INTERVAL > time) { + for (int i = btn->count[0] + 1; i < btn->size[BUTTON_LONG_PRESS_START]; i++) { + time = cb_info[i].event_data.long_press.press_time; + if (btn->long_press_ticks * TICKS_INTERVAL <= time) { + btn->count[0] = i; + break; + } + } + } + if (btn->count[0] < btn->size[BUTTON_LONG_PRESS_START] && abs(ticks_time - time) <= TOLERANCE) { + do { + cb_info[btn->count[0]].cb(btn, cb_info[btn->count[0]].usr_data); + btn->count[0]++; + if (btn->count[0] >= btn->size[BUTTON_LONG_PRESS_START]) + break; + } while (time == cb_info[btn->count[0]].event_data.long_press.press_time); + } + } + + /** Updating counter for BUTTON_LONG_PRESS_UP press_time */ + if (btn->cb_info[BUTTON_LONG_PRESS_UP]) { + button_cb_info_t *cb_info = btn->cb_info[BUTTON_LONG_PRESS_UP]; + uint16_t time = cb_info[btn->count[1] + 1].event_data.long_press.press_time; + if (btn->long_press_ticks * TICKS_INTERVAL > time) { + for (int i = btn->count[1] + 1; i < btn->size[BUTTON_LONG_PRESS_UP]; i++) { + time = cb_info[i].event_data.long_press.press_time; + if (btn->long_press_ticks * TICKS_INTERVAL <= time) { + btn->count[1] = i; + break; + } + } + } + if(btn->count[1] + 1 < btn->size[BUTTON_LONG_PRESS_UP] && abs(ticks_time - time) <= TOLERANCE) { + do { + btn->count[1]++; + if (btn->count[1] + 1 >= btn->size[BUTTON_LONG_PRESS_UP]) + break; + } while (time == cb_info[btn->count[1] + 1].event_data.long_press.press_time); + } + } + } + } else { //releasd + + btn->event = BUTTON_LONG_PRESS_UP; + + /** calling callbacks for BUTTON_LONG_PRESS_UP press_time */ + if (btn->cb_info[btn->event] && btn->count[1] >= 0) { + button_cb_info_t *cb_info = btn->cb_info[btn->event]; + do { + cb_info[btn->count[1]].cb(btn, cb_info[btn->count[1]].usr_data); + if (!btn->count[1]) + break; + btn->count[1]--; + } while (cb_info[btn->count[1]].event_data.long_press.press_time == cb_info[btn->count[1] + 1].event_data.long_press.press_time); + + /** Reset the counter */ + btn->count[1] = -1; + } + /** Reset counter */ + if (btn->cb_info[BUTTON_LONG_PRESS_START]) { + btn->count[0] = 0; + } + + btn->event = (uint8_t)BUTTON_PRESS_UP; + CALL_EVENT_CB(BUTTON_PRESS_UP); + btn->state = 0; //reset + btn->long_press_hold_cnt = 0; + } + break; + } +} + +static void button_cb(void *args) +{ + button_dev_t *target; + for (target = g_head_handle; target; target = target->next) { + button_handler(target); + } +} + +static button_dev_t *button_create_com(uint8_t active_level, uint8_t (*hal_get_key_state)(void *hardware_data), void *hardware_data, uint16_t long_press_ticks, uint16_t short_press_ticks) +{ + BTN_CHECK(NULL != hal_get_key_state, "Function pointer is invalid", NULL); + + button_dev_t *btn = (button_dev_t *) calloc(1, sizeof(button_dev_t)); + BTN_CHECK(NULL != btn, "Button memory alloc failed", NULL); + btn->hardware_data = hardware_data; + btn->event = BUTTON_NONE_PRESS; + btn->active_level = active_level; + btn->hal_button_Level = hal_get_key_state; + btn->button_level = !active_level; + btn->long_press_ticks = long_press_ticks; + btn->long_press_ticks_default = btn->long_press_ticks; + btn->short_press_ticks = short_press_ticks; + + /** Add handle to list */ + btn->next = g_head_handle; + g_head_handle = btn; + + if (false == g_is_timer_running) { + esp_timer_create_args_t button_timer; + button_timer.arg = NULL; + button_timer.callback = button_cb; + button_timer.dispatch_method = ESP_TIMER_TASK; + button_timer.name = "button_timer"; + esp_timer_create(&button_timer, &g_button_timer_handle); + esp_timer_start_periodic(g_button_timer_handle, TICKS_INTERVAL * 1000U); + g_is_timer_running = true; + } + + return btn; +} + +static esp_err_t button_delete_com(button_dev_t *btn) +{ + BTN_CHECK(NULL != btn, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG); + + button_dev_t **curr; + for (curr = &g_head_handle; *curr; ) { + button_dev_t *entry = *curr; + if (entry == btn) { + *curr = entry->next; + free(entry); + } else { + curr = &entry->next; + } + } + + /* count button number */ + uint16_t number = 0; + button_dev_t *target = g_head_handle; + while (target) { + target = target->next; + number++; + } + ESP_LOGD(TAG, "remain btn number=%d", number); + + if (0 == number && g_is_timer_running) { /**< if all button is deleted, stop the timer */ + esp_timer_stop(g_button_timer_handle); + esp_timer_delete(g_button_timer_handle); + g_is_timer_running = false; + } + return ESP_OK; +} + +button_handle_t iot_button_create(const button_config_t *config) +{ + ESP_LOGI(TAG, "IoT Button Version: %d.%d.%d", BUTTON_VER_MAJOR, BUTTON_VER_MINOR, BUTTON_VER_PATCH); + BTN_CHECK(config, "Invalid button config", NULL); + + esp_err_t ret = ESP_OK; + button_dev_t *btn = NULL; + uint16_t long_press_time = 0; + uint16_t short_press_time = 0; + long_press_time = TIME_TO_TICKS(config->long_press_time, LONG_TICKS); + short_press_time = TIME_TO_TICKS(config->short_press_time, SHORT_TICKS); + switch (config->type) { + case BUTTON_TYPE_GPIO: { + const button_gpio_config_t *cfg = &(config->gpio_button_config); + ret = button_gpio_init(cfg); + BTN_CHECK(ESP_OK == ret, "gpio button init failed", NULL); + btn = button_create_com(cfg->active_level, button_gpio_get_key_level, (void *)cfg->gpio_num, long_press_time, short_press_time); + } break; + case BUTTON_TYPE_ADC: { + const button_adc_config_t *cfg = &(config->adc_button_config); + ret = button_adc_init(cfg); + BTN_CHECK(ESP_OK == ret, "adc button init failed", NULL); + btn = button_create_com(1, button_adc_get_key_level, (void *)ADC_BUTTON_COMBINE(cfg->adc_channel, cfg->button_index), long_press_time, short_press_time); + } break; + case BUTTON_TYPE_MATRIX: { + const button_matrix_config_t *cfg = &(config->matrix_button_config); + ret = button_matrix_init(cfg); + BTN_CHECK(ESP_OK == ret, "matrix button init failed", NULL); + btn = button_create_com(1, button_matrix_get_key_level, (void *)MATRIX_BUTTON_COMBINE(cfg->row_gpio_num, cfg->col_gpio_num), long_press_time, short_press_time); + } break; + case BUTTON_TYPE_CUSTOM: { + if (config->custom_button_config.button_custom_init) { + ret = config->custom_button_config.button_custom_init(config->custom_button_config.priv); + BTN_CHECK(ESP_OK == ret, "custom button init failed", NULL); + } + + btn = button_create_com(config->custom_button_config.active_level, + config->custom_button_config.button_custom_get_key_value, + config->custom_button_config.priv, + long_press_time, short_press_time); + if (btn) { + btn->hal_button_deinit = config->custom_button_config.button_custom_deinit; + } + } break; + + default: + ESP_LOGE(TAG, "Unsupported button type"); + break; + } + BTN_CHECK(NULL != btn, "button create failed", NULL); + btn->type = config->type; + return (button_handle_t)btn; +} + +esp_err_t iot_button_delete(button_handle_t btn_handle) +{ + esp_err_t ret = ESP_OK; + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG); + button_dev_t *btn = (button_dev_t *)btn_handle; + switch (btn->type) { + case BUTTON_TYPE_GPIO: + ret = button_gpio_deinit((int)(btn->hardware_data)); + break; + case BUTTON_TYPE_ADC: + ret = button_adc_deinit(ADC_BUTTON_SPLIT_CHANNEL(btn->hardware_data), ADC_BUTTON_SPLIT_INDEX(btn->hardware_data)); + break; + case BUTTON_TYPE_MATRIX: + ret = button_matrix_deinit(MATRIX_BUTTON_SPLIT_ROW(btn->hardware_data), MATRIX_BUTTON_SPLIT_COL(btn->hardware_data)); + break; + case BUTTON_TYPE_CUSTOM: + if (btn->hal_button_deinit) { + ret = btn->hal_button_deinit(btn->hardware_data); + } + + break; + default: + break; + } + BTN_CHECK(ESP_OK == ret, "button deinit failed", ESP_FAIL); + for (int i = 0; i < BUTTON_EVENT_MAX; i++) { + if (btn->cb_info[i]) { + free(btn->cb_info[i]); + } + } + button_delete_com(btn); + return ESP_OK; +} + +esp_err_t iot_button_register_cb(button_handle_t btn_handle, button_event_t event, button_cb_t cb, void *usr_data) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG); + button_dev_t *btn = (button_dev_t *) btn_handle; + BTN_CHECK(event != BUTTON_MULTIPLE_CLICK, "event argument is invalid", ESP_ERR_INVALID_ARG); + button_event_config_t event_cfg = { + .event = event, + }; + + if ((event == BUTTON_LONG_PRESS_START || event == BUTTON_LONG_PRESS_UP) && !event_cfg.event_data.long_press.press_time) { + event_cfg.event_data.long_press.press_time = btn->long_press_ticks_default * TICKS_INTERVAL; + } + + return iot_button_register_event_cb(btn_handle, event_cfg, cb, usr_data); +} + +esp_err_t iot_button_register_event_cb(button_handle_t btn_handle, button_event_config_t event_cfg, button_cb_t cb, void *usr_data) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG); + button_dev_t *btn = (button_dev_t *) btn_handle; + button_event_t event = event_cfg.event; + BTN_CHECK(event < BUTTON_EVENT_MAX, "event is invalid", ESP_ERR_INVALID_ARG); + BTN_CHECK(!(event == BUTTON_LONG_PRESS_START || event == BUTTON_LONG_PRESS_UP) || event_cfg.event_data.long_press.press_time > btn->short_press_ticks * TICKS_INTERVAL, "event_data is invalid", ESP_ERR_INVALID_ARG); + BTN_CHECK(event != BUTTON_MULTIPLE_CLICK || event_cfg.event_data.multiple_clicks.clicks, "event_data is invalid", ESP_ERR_INVALID_ARG); + + if (!btn->cb_info[event]) { + btn->cb_info[event] = calloc(1, sizeof(button_cb_info_t)); + BTN_CHECK(NULL != btn->cb_info[event], "calloc cb_info failed", ESP_ERR_NO_MEM); + if (event == BUTTON_LONG_PRESS_START) { + btn->count[0] = 0; + } else if (event == BUTTON_LONG_PRESS_UP) { + btn->count[1] = -1; + } + } + else { + button_cb_info_t *p = realloc(btn->cb_info[event], sizeof(button_cb_info_t) * (btn->size[event] + 1)); + BTN_CHECK(NULL != p, "realloc cb_info failed", ESP_ERR_NO_MEM); + btn->cb_info[event] = p; + } + + btn->cb_info[event][btn->size[event]].cb = cb; + btn->cb_info[event][btn->size[event]].usr_data = usr_data; + btn->size[event]++; + + /** Inserting the event_data in sorted manner */ + if (event == BUTTON_LONG_PRESS_START || event == BUTTON_LONG_PRESS_UP) { + uint16_t press_time = event_cfg.event_data.long_press.press_time; + BTN_CHECK(press_time / TICKS_INTERVAL > btn->short_press_ticks, "press_time event_data is less than short_press_ticks", ESP_ERR_INVALID_ARG); + if (btn->size[event] >= 2) { + for (int i = btn->size[event] - 2; i >= 0; i--) { + if (btn->cb_info[event][i].event_data.long_press.press_time > press_time) { + btn->cb_info[event][i + 1] = btn->cb_info[event][i]; + + btn->cb_info[event][i].event_data.long_press.press_time = press_time; + btn->cb_info[event][i].cb = cb; + btn->cb_info[event][i].usr_data = usr_data; + } else { + btn->cb_info[event][i + 1].event_data.long_press.press_time = press_time; + btn->cb_info[event][i + 1].cb = cb; + btn->cb_info[event][i + 1].usr_data = usr_data; + break; + } + } + } else { + btn->cb_info[event][btn->size[event] - 1].event_data.long_press.press_time = press_time; + } + + int32_t press_ticks = press_time / TICKS_INTERVAL; + if (btn->short_press_ticks < press_ticks && press_ticks < btn->long_press_ticks) { + iot_button_set_param(btn, BUTTON_LONG_PRESS_TIME_MS, (void*)(intptr_t)press_time); + } + } + + if (event == BUTTON_MULTIPLE_CLICK) { + if (btn->size[event] >= 2) { + for (int i = btn->size[event] - 2; i >= 0; i--) { + if (btn->cb_info[event][i].event_data.multiple_clicks.clicks > event_cfg.event_data.multiple_clicks.clicks) { + btn->cb_info[event][i + 1] = btn->cb_info[event][i]; + + btn->cb_info[event][i].event_data.multiple_clicks.clicks = event_cfg.event_data.multiple_clicks.clicks; + btn->cb_info[event][i].cb = cb; + btn->cb_info[event][i].usr_data = usr_data; + } else { + btn->cb_info[event][i + 1].event_data.multiple_clicks.clicks = event_cfg.event_data.multiple_clicks.clicks; + btn->cb_info[event][i + 1].cb = cb; + btn->cb_info[event][i + 1].usr_data = usr_data; + break; + } + } + } else { + btn->cb_info[event][btn->size[event] - 1].event_data.multiple_clicks.clicks = event_cfg.event_data.multiple_clicks.clicks; + } + } + + return ESP_OK; +} + +esp_err_t iot_button_unregister_cb(button_handle_t btn_handle, button_event_t event) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG); + BTN_CHECK(event < BUTTON_EVENT_MAX, "event is invalid", ESP_ERR_INVALID_ARG); + button_dev_t *btn = (button_dev_t *) btn_handle; + BTN_CHECK(NULL != btn->cb_info[event], "No callbacks registered for the event", ESP_ERR_INVALID_STATE); + + if (btn->cb_info[event]) { + free(btn->cb_info[event]); + + /** Reset the counter */ + if (event == BUTTON_LONG_PRESS_START) { + btn->count[0] = 0; + } else if (event == BUTTON_LONG_PRESS_UP) { + btn->count[1] = -1; + } + + } + + btn->cb_info[event] = NULL; + btn->size[event] = 0; + return ESP_OK; +} + +esp_err_t iot_button_unregister_event(button_handle_t btn_handle, button_event_config_t event_cfg, button_cb_t cb) +{ + + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG); + button_event_t event = event_cfg.event; + BTN_CHECK(event < BUTTON_EVENT_MAX, "event is invalid", ESP_ERR_INVALID_ARG); + BTN_CHECK(NULL != cb, "Pointer to function callback is invalid", ESP_ERR_INVALID_ARG); + button_dev_t *btn = (button_dev_t *) btn_handle; + + int check = -1; + + for (int i = 0; i < btn->size[event]; i++) { + if (cb == btn->cb_info[event][i].cb) { + if ((event == BUTTON_LONG_PRESS_START || event == BUTTON_LONG_PRESS_UP) && event_cfg.event_data.long_press.press_time) { + if (event_cfg.event_data.long_press.press_time != btn->cb_info[event][i].event_data.long_press.press_time) { + continue; + } + } + + if (event == BUTTON_MULTIPLE_CLICK && event_cfg.event_data.multiple_clicks.clicks) { + if (event_cfg.event_data.multiple_clicks.clicks != btn->cb_info[event][i].event_data.multiple_clicks.clicks) { + continue; + } + } + check = i; + for (int j = i; j <= btn->size[event]-1; j++) { + btn->cb_info[event][j] = btn->cb_info[event][j + 1]; + } + + if (btn->size[event] != 1) { + button_cb_info_t *p = realloc(btn->cb_info[event], sizeof(button_cb_info_t) * (btn->size[event] - 1)); + BTN_CHECK(NULL != p, "realloc cb_info failed", ESP_ERR_NO_MEM); + btn->cb_info[event] = p; + btn->size[event]--; + } else { + free(btn->cb_info[event]); + btn->cb_info[event] = NULL; + btn->size[event] = 0; + } + break; + } + } + + BTN_CHECK(check != -1, "No such callback registered for the event", ESP_ERR_INVALID_STATE); + + return ESP_OK; +} + +size_t iot_button_count_cb(button_handle_t btn_handle) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG); + button_dev_t *btn = (button_dev_t *) btn_handle; + size_t ret = 0; + for (size_t i = 0; i < BUTTON_EVENT_MAX; i++) { + if (btn->cb_info[i]) { + ret+=btn->size[i]; + } + } + return ret; +} + +size_t iot_button_count_event(button_handle_t btn_handle, button_event_t event) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG); + button_dev_t *btn = (button_dev_t *) btn_handle; + return btn->size[event]; +} + +button_event_t iot_button_get_event(button_handle_t btn_handle) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", BUTTON_NONE_PRESS); + button_dev_t *btn = (button_dev_t *) btn_handle; + return btn->event; +} + +uint8_t iot_button_get_repeat(button_handle_t btn_handle) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", 0); + button_dev_t *btn = (button_dev_t *) btn_handle; + return btn->repeat; +} + +uint16_t iot_button_get_ticks_time(button_handle_t btn_handle) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", 0); + button_dev_t *btn = (button_dev_t *) btn_handle; + return (btn->ticks * TICKS_INTERVAL); +} + +uint16_t iot_button_get_long_press_hold_cnt(button_handle_t btn_handle) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", 0); + button_dev_t *btn = (button_dev_t *) btn_handle; + return btn->long_press_hold_cnt; +} + +esp_err_t iot_button_set_param(button_handle_t btn_handle, button_param_t param, void *value) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG); + button_dev_t *btn = (button_dev_t *) btn_handle; + BUTTON_ENTER_CRITICAL(); + switch (param) { + case BUTTON_LONG_PRESS_TIME_MS: + btn->long_press_ticks = (int32_t)value / TICKS_INTERVAL; + break; + case BUTTON_SHORT_PRESS_TIME_MS: + btn->short_press_ticks = (int32_t)value / TICKS_INTERVAL; + break; + default: + break; + } + BUTTON_EXIT_CRITICAL(); + return ESP_OK; +} + +esp_err_t iot_button_resume(void) +{ + BTN_CHECK(g_button_timer_handle, "Button timer handle is invalid", ESP_ERR_INVALID_STATE); + BTN_CHECK(!g_is_timer_running, "Button timer is already running", ESP_ERR_INVALID_STATE); + + esp_err_t err = esp_timer_start_periodic(g_button_timer_handle, TICKS_INTERVAL * 1000U); + BTN_CHECK(ESP_OK == err, "Button timer start failed", ESP_FAIL); + g_is_timer_running = true; + return ESP_OK; +} + +esp_err_t iot_button_stop(void) +{ + BTN_CHECK(g_button_timer_handle, "Button timer handle is invalid", ESP_ERR_INVALID_STATE); + BTN_CHECK(g_is_timer_running, "Button timer is not running", ESP_ERR_INVALID_STATE); + + esp_err_t err = esp_timer_stop(g_button_timer_handle); + BTN_CHECK(ESP_OK == err, "Button timer stop failed", ESP_FAIL); + g_is_timer_running = false; + return ESP_OK; +} diff --git a/src/original/iot_button.h b/src/original/iot_button.h new file mode 100644 index 0000000..610c17a --- /dev/null +++ b/src/original/iot_button.h @@ -0,0 +1,291 @@ +/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "button_adc.h" +#include "button_gpio.h" +#include "button_matrix.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (* button_cb_t)(void *button_handle, void *usr_data); +typedef void *button_handle_t; + +/** + * @brief Button events + * + */ +typedef enum { + BUTTON_PRESS_DOWN = 0, + BUTTON_PRESS_UP, + BUTTON_PRESS_REPEAT, + BUTTON_PRESS_REPEAT_DONE, + BUTTON_SINGLE_CLICK, + BUTTON_DOUBLE_CLICK, + BUTTON_MULTIPLE_CLICK, + BUTTON_LONG_PRESS_START, + BUTTON_LONG_PRESS_HOLD, + BUTTON_LONG_PRESS_UP, + BUTTON_EVENT_MAX, + BUTTON_NONE_PRESS, +} button_event_t; + +/** + * @brief Button events data + * + */ +typedef union { + /** + * @brief Long press time event data + * + */ + struct long_press_t { + uint16_t press_time; /**< press time(ms) for the corresponding callback to trigger */ + } long_press; /**< long press struct, for event BUTTON_LONG_PRESS_START and BUTTON_LONG_PRESS_UP */ + + /** + * @brief Multiple clicks event data + * + */ + struct multiple_clicks_t { + uint16_t clicks; /**< number of clicks, to trigger the callback */ + } multiple_clicks; /**< multiple clicks struct, for event BUTTON_MULTIPLE_CLICK */ +}button_event_data_t; + +/** + * @brief Button events configuration + * + */ +typedef struct { + button_event_t event; /**< button event type */ + button_event_data_t event_data; /**< event data corresponding to the event */ +} button_event_config_t; + +/** + * @brief Supported button type + * + */ +typedef enum { + BUTTON_TYPE_GPIO, + BUTTON_TYPE_ADC, + BUTTON_TYPE_MATRIX, + BUTTON_TYPE_CUSTOM +} button_type_t; + +/** + * @brief Button parameter + * + */ +typedef enum { + BUTTON_LONG_PRESS_TIME_MS = 0, + BUTTON_SHORT_PRESS_TIME_MS, + BUTTON_PARAM_MAX, +} button_param_t; + +/** + * @brief custom button configuration + * + */ +typedef struct { + uint8_t active_level; /**< active level when press down */ + esp_err_t (*button_custom_init)(void *param); /**< user defined button init */ + uint8_t (*button_custom_get_key_value)(void *param); /**< user defined button get key value */ + esp_err_t (*button_custom_deinit)(void *param); /**< user defined button deinit */ + void *priv; /**< private data used for custom button, MUST be allocated dynamically and will be auto freed in iot_button_delete*/ +} button_custom_config_t; + +/** + * @brief Button configuration + * + */ +typedef struct { + button_type_t type; /**< button type, The corresponding button configuration must be filled */ + uint16_t long_press_time; /**< Trigger time(ms) for long press, if 0 default to BUTTON_LONG_PRESS_TIME_MS */ + uint16_t short_press_time; /**< Trigger time(ms) for short press, if 0 default to BUTTON_SHORT_PRESS_TIME_MS */ + union { + button_gpio_config_t gpio_button_config; /**< gpio button configuration */ + button_adc_config_t adc_button_config; /**< adc button configuration */ + button_matrix_config_t matrix_button_config; /**< matrix key button configuration */ + button_custom_config_t custom_button_config; /**< custom button configuration */ + }; /**< button configuration */ +} button_config_t; + +/** + * @brief Create a button + * + * @param config pointer of button configuration, must corresponding the button type + * + * @return A handle to the created button, or NULL in case of error. + */ +button_handle_t iot_button_create(const button_config_t *config); + +/** + * @brief Delete a button + * + * @param btn_handle A button handle to delete + * + * @return + * - ESP_OK Success + * - ESP_FAIL Failure + */ +esp_err_t iot_button_delete(button_handle_t btn_handle); + +/** + * @brief Register the button event callback function. + * + * @param btn_handle A button handle to register + * @param event Button event + * @param cb Callback function. + * @param usr_data user data + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG Arguments is invalid. + * - ESP_ERR_INVALID_STATE The Callback is already registered. No free Space for another Callback. + * - ESP_ERR_NO_MEM No more memory allocation for the event + */ +esp_err_t iot_button_register_cb(button_handle_t btn_handle, button_event_t event, button_cb_t cb, void *usr_data); + +/** + * @brief Register the button event callback function. + * + * @param btn_handle A button handle to register + * @param event_cfg Button event configuration + * @param cb Callback function. + * @param usr_data user data + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG Arguments is invalid. + * - ESP_ERR_INVALID_STATE The Callback is already registered. No free Space for another Callback. + * - ESP_ERR_NO_MEM No more memory allocation for the event + */ +esp_err_t iot_button_register_event_cb(button_handle_t btn_handle, button_event_config_t event_cfg, button_cb_t cb, void *usr_data); + +/** + * @brief Unregister the button event callback function. + * In case event_data is also passed it will unregister function for that particular event_data only. + * + * @param btn_handle A button handle to unregister + * @param event_cfg Button event + * @param cb callback to unregister + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG Arguments is invalid. + * - ESP_ERR_INVALID_STATE The Callback was never registered with the event + */ +esp_err_t iot_button_unregister_event(button_handle_t btn_handle, button_event_config_t event_cfg, button_cb_t cb); + +/** + * @brief Unregister all the callbacks associated with the event. + * + * @param btn_handle A button handle to unregister + * @param event Button event + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG Arguments is invalid. + * - ESP_ERR_INVALID_STATE No callbacks registered for the event + */ +esp_err_t iot_button_unregister_cb(button_handle_t btn_handle, button_event_t event); + +/** + * @brief counts total callbacks registered + * + * @param btn_handle A button handle to the button + * + * @return + * - 0 if no callbacks registered, or 1 .. (BUTTON_EVENT_MAX-1) for the number of Registered Buttons. + * - ESP_ERR_INVALID_ARG if btn_handle is invalid + */ +size_t iot_button_count_cb(button_handle_t btn_handle); + +/** + * @brief how many callbacks are registered for the event + * + * @param btn_handle A button handle to the button + * + * @param event Button event + * + * @return + * - 0 if no callbacks registered, or 1 .. (BUTTON_EVENT_MAX-1) for the number of Registered Buttons. + * - ESP_ERR_INVALID_ARG if btn_handle is invalid + */ +size_t iot_button_count_event(button_handle_t btn_handle, button_event_t event); + +/** + * @brief Get button event + * + * @param btn_handle Button handle + * + * @return Current button event. See button_event_t + */ +button_event_t iot_button_get_event(button_handle_t btn_handle); + +/** + * @brief Get button repeat times + * + * @param btn_handle Button handle + * + * @return button pressed times. For example, double-click return 2, triple-click return 3, etc. + */ +uint8_t iot_button_get_repeat(button_handle_t btn_handle); + +/** + * @brief Get button ticks time + * + * @param btn_handle Button handle + * + * @return Actual time from press down to up (ms). + */ +uint16_t iot_button_get_ticks_time(button_handle_t btn_handle); + +/** + * @brief Get button long press hold count + * + * @param btn_handle Button handle + * + * @return Count of trigger cb(BUTTON_LONG_PRESS_HOLD) + */ +uint16_t iot_button_get_long_press_hold_cnt(button_handle_t btn_handle); + +/** + * @brief Dynamically change the parameters of the iot button + * + * @param btn_handle Button handle + * @param param Button parameter + * @param value new value + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG Arguments is invalid. + */ +esp_err_t iot_button_set_param(button_handle_t btn_handle, button_param_t param, void *value); + +/** + * @brief resume button timer, if button timer is stopped. Make sure iot_button_create() is called before calling this API. + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE timer state is invalid. + */ +esp_err_t iot_button_resume(void); + +/** + * @brief stop button timer, if button timer is running. Make sure iot_button_create() is called before calling this API. + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE timer state is invalid + */ +esp_err_t iot_button_stop(void); + +#ifdef __cplusplus +} +#endif diff --git a/test_apps/CMakeLists.txt b/test_apps/CMakeLists.txt new file mode 100644 index 0000000..ea345ef --- /dev/null +++ b/test_apps/CMakeLists.txt @@ -0,0 +1,9 @@ + +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components" + "../../ESP32_Button") +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(button_test) \ No newline at end of file diff --git a/test_apps/main/CMakeLists.txt b/test_apps/main/CMakeLists.txt new file mode 100644 index 0000000..5b9a69b --- /dev/null +++ b/test_apps/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRC_DIRS "." + INCLUDE_DIRS "." + REQUIRES ESP32_Button unity test_utils) \ No newline at end of file diff --git a/test_apps/main/button_test.cpp b/test_apps/main/button_test.cpp new file mode 100644 index 0000000..7035749 --- /dev/null +++ b/test_apps/main/button_test.cpp @@ -0,0 +1,105 @@ +/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "stdio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/timers.h" +#include "freertos/semphr.h" +#include "freertos/event_groups.h" +#include "esp_idf_version.h" +#include "esp_log.h" +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#include "esp_adc/adc_cali.h" +#endif +#include "unity.h" +#include "Button.h" +#include "sdkconfig.h" + +static const char *TAG = "ESP32 Arduino BUTTON TEST"; + +#define TEST_MEMORY_LEAK_THRESHOLD (-400) +#define BUTTON_IO_NUM 0 +#define BUTTON_ACTIVE_LEVEL 0 +#define BUTTON_NUM 16 + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#define ADC_BUTTON_WIDTH SOC_ADC_RTC_MAX_BITWIDTH +#else +#define ADC_BUTTON_WIDTH ADC_WIDTH_MAX - 1 +#endif + + +static void onButtonPressDownCb(void *button_handle, void *usr_data) +{ + ESP_LOGI(TAG, "Button pressed down"); +} +static void onButtonPressDownRepeatCb(void *button_handle, void *usr_data) +{ + ESP_LOGI(TAG, "Button press down repeat"); +} +static void onButtonPressUpCb(void *button_handle, void *usr_data) +{ + ESP_LOGI(TAG, "Button press Up"); +} +static void onButtonSingleClickCb(void *button_handle, void *usr_data) +{ + ESP_LOGI(TAG, "Button single click"); +} +static void onButtonSingleClickRepeatCb(void *button_handle, void *usr_data) +{ + ESP_LOGI(TAG, "Button single click repeat"); +} + + +TEST_CASE("gpio button test", "[button][iot]") +{ + // Button* btn = new Button(9, false); + Button btn(GPIO_NUM_9, false); + + btn.attachPressDownEventCb(&onButtonPressDownCb, NULL); + btn.attachPressUpEventCb(&onButtonPressUpCb, NULL); + btn.attachPressDownEventCb(&onButtonPressDownRepeatCb, NULL); + btn.attachSingleClickEventCb(&onButtonSingleClickCb, NULL); + btn.attachSingleClickEventCb(&onButtonSingleClickRepeatCb, NULL); + btn.unregisterPressDownEventCb(&onButtonPressDownCb); + btn.detachSingleClickEvent(); + btn.stop(); + vTaskDelay(pdMS_TO_TICKS(5000)); + btn.resume(); + vTaskDelay(pdMS_TO_TICKS(10000)); + btn.del(); +} + +static size_t before_free_8bit; +static size_t before_free_32bit; + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +extern "C" void app_main(void) +{ + printf("USB STREAM TEST \n"); + unity_run_menu(); +} diff --git a/test_apps/sdkconfig.defaults b/test_apps/sdkconfig.defaults new file mode 100644 index 0000000..3778471 --- /dev/null +++ b/test_apps/sdkconfig.defaults @@ -0,0 +1,9 @@ +# For IDF 5.0 +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT_EN=n + +# For IDF4.4 +CONFIG_ESP32S2_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP_TASK_WDT=n