diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 75a2b46d619..64d241ba20a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -11,6 +11,7 @@ # CI /.github/ @lucasssvaz @me-no-dev @P-R-O-C-H-Y +/.gitlab/ @lucasssvaz /tests/ @lucasssvaz @P-R-O-C-H-Y # Tools diff --git a/.github/ISSUE_TEMPLATE/Issue-report.yml b/.github/ISSUE_TEMPLATE/Issue-report.yml index 9dba5e0ca8f..6dc1b0de171 100644 --- a/.github/ISSUE_TEMPLATE/Issue-report.yml +++ b/.github/ISSUE_TEMPLATE/Issue-report.yml @@ -43,6 +43,7 @@ body: - latest stable Release (if not listed below) - latest development Release Candidate (RC-X) - latest master (checkout manually) + - v3.2.1 - v3.2.0 - v3.1.3 - v3.1.2 diff --git a/.github/scripts/merge_packages.py b/.github/scripts/merge_packages.py index 7e4f47ca8b3..8d1f200ec5c 100755 --- a/.github/scripts/merge_packages.py +++ b/.github/scripts/merge_packages.py @@ -4,6 +4,7 @@ # Usage: # python merge_packages.py package_esp8266com_index.json version/new/package_esp8266com_index.json # Written by Ivan Grokhotkov, 2015 +# Updated by lucasssvaz to handle Chinese version sorting, 2025 # from __future__ import print_function @@ -36,20 +37,19 @@ def merge_objects(versions, obj): # Normalize ESP release version string (x.x.x) by adding '-rc' (x.x.x-rc9223372036854775807) -# to ensure having REL above any RC +# to ensure having REL above any RC. CN version will be sorted after the official version if they happen +# to be mixed (normally, CN and non-CN versions should not be mixed) # Dummy approach, functional anyway for current ESP package versioning # (unlike NormalizedVersion/LooseVersion/StrictVersion & similar crap) def pkgVersionNormalized(versionString): - - verStr = str(versionString) + verStr = str(versionString).replace("-cn", "") verParts = re.split(r"\.|-rc|-alpha", verStr, flags=re.IGNORECASE) if len(verParts) == 3: - if sys.version_info > (3, 0): # Python 3 - verStr = str(versionString) + "-rc" + str(sys.maxsize) - else: # Python 2 - verStr = str(versionString) + "-rc" + str(sys.maxint) - + if "-cn" in str(versionString): + verStr = verStr + "-rc" + str(sys.maxsize // 2) + else: + verStr = verStr + "-rc" + str(sys.maxsize) elif len(verParts) != 4: print("pkgVersionNormalized WARNING: unexpected version format: {0})".format(verStr), file=sys.stderr) diff --git a/.github/scripts/package_esptool.sh b/.github/scripts/package_esptool.sh deleted file mode 100755 index 32b87b277e9..00000000000 --- a/.github/scripts/package_esptool.sh +++ /dev/null @@ -1,129 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -# Check version argument -if [[ $# -ne 3 ]]; then - echo "Usage: $0 " - echo "Example: $0 5.0.dev1 /tmp/esptool /tmp/esptool-5.0.dev1.json" - exit 1 -fi - -VERSION=$1 -BASE_FOLDER=$2 -JSON_PATH=$3 - -export COPYFILE_DISABLE=1 - -shopt -s nullglob # So for loop doesn't run if no matches - -# Function to update JSON for a given host -function update_json_for_host { - local host=$1 - local archive=$2 - - # Extract the old url from the JSON for this host, then replace only the filename - old_url=$(jq -r --arg host "$host" ' - .packages[].tools[] | select(.name == "esptool_py") | .systems[] | select(.host == $host) | .url // empty - ' "$tmp_json") - if [[ -n "$old_url" ]]; then - base_url="${old_url%/*}" - url="$base_url/$archive" - else - echo "No old url found for $host" - exit 1 - fi - - archiveFileName="$archive" - checksum="SHA-256:$(shasum -a 256 "$archive" | awk '{print $1}')" - size=$(stat -f%z "$archive") - - # Use jq to update the JSON - jq --arg host "$host" \ - --arg url "$url" \ - --arg archiveFileName "$archiveFileName" \ - --arg checksum "$checksum" \ - --arg size "$size" \ - ' - .packages[].tools[] - |= if .name == "esptool_py" then - .systems = ( - ((.systems // []) | map(select(.host != $host))) + [{ - host: $host, - url: $url, - archiveFileName: $archiveFileName, - checksum: $checksum, - size: $size - }] - ) - else - . - end - ' "$tmp_json" > "$tmp_json.new" && mv "$tmp_json.new" "$tmp_json" -} - -cd "$BASE_FOLDER" - -# Delete all archives before starting -rm -f esptool-*.tar.gz esptool-*.zip - -for dir in esptool-*; do - # Check if directory exists and is a directory - if [[ ! -d "$dir" ]]; then - continue - fi - - base="${dir#esptool-}" - - # Add 'linux-' prefix if base doesn't contain linux/macos/win64 - if [[ "$base" != *linux* && "$base" != *macos* && "$base" != *win64* ]]; then - base="linux-${base}" - fi - - if [[ "$dir" == esptool-win* ]]; then - # Windows zip archive - zipfile="esptool-v${VERSION}-${base}.zip" - echo "Creating $zipfile from $dir ..." - zip -r "$zipfile" "$dir" - else - # Non-Windows: set permissions and tar.gz archive - tarfile="esptool-v${VERSION}-${base}.tar.gz" - echo "Setting permissions and creating $tarfile from $dir ..." - chmod -R u=rwx,g=rx,o=rx "$dir" - tar -cvzf "$tarfile" "$dir" - fi -done - -# After the for loop, update the JSON for each archive -# Create a temporary JSON file to accumulate changes -tmp_json="${JSON_PATH}.tmp" -cp "$JSON_PATH" "$tmp_json" - -for archive in esptool-v"${VERSION}"-*.tar.gz esptool-v"${VERSION}"-*.zip; do - [ -f "$archive" ] || continue - - echo "Updating JSON for $archive" - - # Determine host from archive name - case "$archive" in - *linux-amd64*) host="x86_64-pc-linux-gnu" ;; - *linux-armv7*) host="arm-linux-gnueabihf" ;; - *linux-aarch64*) host="aarch64-linux-gnu" ;; - *macos-amd64*) host="x86_64-apple-darwin" ;; - *macos-arm64*) host="arm64-apple-darwin" ;; - *win64*) hosts=("x86_64-mingw32" "i686-mingw32") ;; - *) echo "Unknown host for $archive"; continue ;; - esac - - # For win64, loop over both hosts; otherwise, use a single host - if [[ "$archive" == *win64* ]]; then - for host in "${hosts[@]}"; do - update_json_for_host "$host" "$archive" - done - else - update_json_for_host "$host" "$archive" - fi -done - -# After all archives are processed, move the temporary JSON to the final file -mv "$tmp_json" "$JSON_PATH" diff --git a/.github/scripts/release_append_cn.py b/.github/scripts/release_append_cn.py index b29fe0c31ba..2342834bb7e 100755 --- a/.github/scripts/release_append_cn.py +++ b/.github/scripts/release_append_cn.py @@ -17,8 +17,9 @@ def append_cn_to_versions(obj): if isinstance(obj, dict): - # dfu-util comes from arduino.cc and not from the Chinese mirrors, so we skip it - if obj.get("name") == "dfu-util": + # Skip tools that are not from the esp32 package + packager = obj.get("packager") + if packager is not None and packager != "esp32": return for key, value in obj.items(): diff --git a/.github/scripts/update-version.sh b/.github/scripts/update-version.sh index 9a38b27a57a..59a95d01105 100755 --- a/.github/scripts/update-version.sh +++ b/.github/scripts/update-version.sh @@ -1,4 +1,5 @@ #!/bin/bash +# Disable shellcheck warning about using 'cat' to read a file. # shellcheck disable=SC2002 # For reference: add tools for all boards by replacing one line in each board @@ -23,7 +24,15 @@ ESP_ARDUINO_VERSION_MINOR="$2" ESP_ARDUINO_VERSION_PATCH="$3" ESP_ARDUINO_VERSION="$ESP_ARDUINO_VERSION_MAJOR.$ESP_ARDUINO_VERSION_MINOR.$ESP_ARDUINO_VERSION_PATCH" +# Get ESP-IDF version from push.yml (this way we can ensure that the version is correct even if the local libs are not up to date) +ESP_IDF_VERSION=$(grep "idf_ver:" .github/workflows/push.yml | sed 's/.*release-v\([^"]*\).*/\1/') +if [ -z "$ESP_IDF_VERSION" ]; then + echo "Error: ESP-IDF version not found in push.yml" >&2 + exit 1 +fi + echo "New Arduino Version: $ESP_ARDUINO_VERSION" +echo "ESP-IDF Version: $ESP_IDF_VERSION" echo "Updating platform.txt..." cat platform.txt | sed "s/version=.*/version=$ESP_ARDUINO_VERSION/g" > __platform.txt && mv __platform.txt platform.txt @@ -31,6 +40,16 @@ cat platform.txt | sed "s/version=.*/version=$ESP_ARDUINO_VERSION/g" > __platfor echo "Updating package.json..." cat package.json | sed "s/.*\"version\":.*/ \"version\": \"$ESP_ARDUINO_VERSION\",/g" > __package.json && mv __package.json package.json +echo "Updating docs/conf_common.py..." +cat docs/conf_common.py | \ +sed "s/.. |version| replace:: .*/.. |version| replace:: $ESP_ARDUINO_VERSION/g" | \ +sed "s/.. |idf_version| replace:: .*/.. |idf_version| replace:: $ESP_IDF_VERSION/g" > docs/__conf_common.py && mv docs/__conf_common.py docs/conf_common.py + +echo "Updating .gitlab/workflows/common.yml..." +cat .gitlab/workflows/common.yml | \ +sed "s/ESP_IDF_VERSION:.*/ESP_IDF_VERSION: \"$ESP_IDF_VERSION\"/g" | \ +sed "s/ESP_ARDUINO_VERSION:.*/ESP_ARDUINO_VERSION: \"$ESP_ARDUINO_VERSION\"/g" > .gitlab/workflows/__common.yml && mv .gitlab/workflows/__common.yml .gitlab/workflows/common.yml + echo "Updating cores/esp32/esp_arduino_version.h..." cat cores/esp32/esp_arduino_version.h | \ sed "s/#define ESP_ARDUINO_VERSION_MAJOR.*/#define ESP_ARDUINO_VERSION_MAJOR $ESP_ARDUINO_VERSION_MAJOR/g" | \ diff --git a/.github/scripts/update_esptool.py b/.github/scripts/update_esptool.py new file mode 100644 index 00000000000..d99462fcb8f --- /dev/null +++ b/.github/scripts/update_esptool.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python3 + +# This script is used to re-package the esptool if needed and update the JSON file +# for the Arduino ESP32 platform. +# +# The script has only been tested on macOS. +# +# For regular esptool releases, the generated packages already contain the correct permissions, +# extensions and are uploaded to the GitHub release assets. In this case, the script will only +# update the JSON file with the information from the GitHub release. +# +# The script can be used in two modes: +# 1. Local build: The build artifacts must be already downloaded and extracted in the base_folder. +# This is useful for esptool versions that are not yet released and that are grabbed from the +# GitHub build artifacts. +# 2. Release build: The script will get the release information from GitHub and update the JSON file. +# This is useful for esptool versions that are already released and that are uploaded to the +# GitHub release assets. +# +# For local build, the artifacts must be already downloaded and extracted in the base_folder +# set with the -l option. +# For example, a base folder "esptool" should contain the following folders extracted directly +# from the GitHub build artifacts: +# esptool/esptool-linux-aarch64 +# esptool/esptool-linux-amd64 +# esptool/esptool-linux-armv7 +# esptool/esptool-macos-amd64 +# esptool/esptool-macos-arm64 +# esptool/esptool-windows-amd64 + +import argparse +import json +import os +import shutil +import stat +import tarfile +import zipfile +import hashlib +import requests +from pathlib import Path + +def compute_sha256(filepath): + sha256 = hashlib.sha256() + with open(filepath, "rb") as f: + for block in iter(lambda: f.read(4096), b""): + sha256.update(block) + return f"SHA-256:{sha256.hexdigest()}" + +def get_file_size(filepath): + return os.path.getsize(filepath) + +def update_json_for_host(tmp_json_path, version, host, url, archiveFileName, checksum, size): + with open(tmp_json_path) as f: + data = json.load(f) + + for pkg in data.get("packages", []): + for tool in pkg.get("tools", []): + if tool.get("name") == "esptool_py": + tool["version"] = version + + if url is None: + # If the URL is not set, we need to find the old URL and update it + for system in tool.get("systems", []): + if system.get("host") == host: + url = system.get("url").replace(system.get("archiveFileName"), archiveFileName) + break + else: + print(f"No old URL found for host {host}. Using empty URL.") + url = "" + + # Preserve existing systems order and update or append the new system + systems = tool.get("systems", []) + system_updated = False + for i, system in enumerate(systems): + if system.get("host") == host: + systems[i] = { + "host": host, + "url": url, + "archiveFileName": archiveFileName, + "checksum": checksum, + "size": str(size), + } + system_updated = True + break + + if not system_updated: + systems.append({ + "host": host, + "url": url, + "archiveFileName": archiveFileName, + "checksum": checksum, + "size": str(size), + }) + tool["systems"] = systems + + with open(tmp_json_path, "w") as f: + json.dump(data, f, indent=2, sort_keys=False, ensure_ascii=False) + f.write("\n") + +def update_tools_dependencies(tmp_json_path, version): + with open(tmp_json_path) as f: + data = json.load(f) + + for pkg in data.get("packages", []): + for platform in pkg.get("platforms", []): + for dep in platform.get("toolsDependencies", []): + if dep.get("name") == "esptool_py": + dep["version"] = version + + with open(tmp_json_path, "w") as f: + json.dump(data, f, indent=2, sort_keys=False, ensure_ascii=False) + f.write("\n") + +def create_archives(version, base_folder): + archive_files = [] + + for dirpath in Path(base_folder).glob("esptool-*"): + if not dirpath.is_dir(): + continue + + base = dirpath.name[len("esptool-"):] + + if "windows" in dirpath.name: + zipfile_name = f"esptool-v{version}-{base}.zip" + print(f"Creating {zipfile_name} from {dirpath} ...") + with zipfile.ZipFile(zipfile_name, "w", zipfile.ZIP_DEFLATED) as zipf: + for root, _, files in os.walk(dirpath): + for file in files: + full_path = os.path.join(root, file) + zipf.write(full_path, os.path.relpath(full_path, start=dirpath)) + archive_files.append(zipfile_name) + else: + tarfile_name = f"esptool-v{version}-{base}.tar.gz" + print(f"Creating {tarfile_name} from {dirpath} ...") + for root, dirs, files in os.walk(dirpath): + for name in dirs + files: + os.chmod(os.path.join(root, name), stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | + stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH) + with tarfile.open(tarfile_name, "w:gz") as tar: + tar.add(dirpath, arcname=dirpath.name) + archive_files.append(tarfile_name) + + return archive_files + +def determine_hosts(archive_name): + if "linux-amd64" in archive_name: + return ["x86_64-pc-linux-gnu"] + elif "linux-armv7" in archive_name: + return ["arm-linux-gnueabihf"] + elif "linux-aarch64" in archive_name: + return ["aarch64-linux-gnu"] + elif "macos-amd64" in archive_name: + return ["x86_64-apple-darwin"] + elif "macos-arm64" in archive_name: + return ["arm64-apple-darwin"] + elif "windows-amd64" in archive_name: + return ["x86_64-mingw32", "i686-mingw32"] + else: + return [] + +def update_json_from_local_build(tmp_json_path, version, base_folder, archive_files): + for archive in archive_files: + print(f"Processing archive: {archive}") + hosts = determine_hosts(archive) + if not hosts: + print(f"Skipping unknown archive type: {archive}") + continue + + archive_path = Path(archive) + checksum = compute_sha256(archive_path) + size = get_file_size(archive_path) + + for host in hosts: + update_json_for_host(tmp_json_path, version, host, None, archive_path.name, checksum, size) + +def update_json_from_release(tmp_json_path, version, release_info): + assets = release_info.get("assets", []) + for asset in assets: + if (asset.get("name").endswith(".tar.gz") or asset.get("name").endswith(".zip")) and "esptool" in asset.get("name"): + asset_fname = asset.get("name") + print(f"Processing asset: {asset_fname}") + hosts = determine_hosts(asset_fname) + if not hosts: + print(f"Skipping unknown archive type: {asset_fname}") + continue + + asset_url = asset.get("browser_download_url") + asset_checksum = asset.get("digest").replace("sha256:", "SHA-256:") + asset_size = asset.get("size") + if asset_checksum is None: + asset_checksum = "" + print(f"Asset {asset_fname} has no checksum. Please set the checksum in the JSON file.") + + for host in hosts: + update_json_for_host(tmp_json_path, version, host, asset_url, asset_fname, asset_checksum, asset_size) + +def get_release_info(version): + url = f"https://api.github.com/repos/espressif/esptool/releases/tags/v{version}" + response = requests.get(url) + response.raise_for_status() + return response.json() + +def main(): + parser = argparse.ArgumentParser(description="Repack esptool and update JSON metadata.") + parser.add_argument("version", help="Version of the esptool (e.g. 5.0.dev1)") + parser.add_argument("-l", "--local", dest="base_folder", help="Enable local build mode and set the base folder with unpacked artifacts") + args = parser.parse_args() + + script_dir = Path(__file__).resolve().parent + json_path = (script_dir / "../../package/package_esp32_index.template.json").resolve() + tmp_json_path = Path(str(json_path) + ".tmp") + shutil.copy(json_path, tmp_json_path) + + local_build = args.base_folder is not None + + if local_build: + os.chdir(args.base_folder) + os.environ['COPYFILE_DISABLE'] = 'true' # this disables including resource forks in tar files on macOS + # Clear any existing archive files + for file in Path(args.base_folder).glob("esptool-*.*"): + file.unlink() + archive_files = create_archives(args.version, args.base_folder) + update_json_from_local_build(tmp_json_path, args.version, args.base_folder, archive_files) + else: + release_info = get_release_info(args.version) + update_json_from_release(tmp_json_path, args.version, release_info) + + print(f"Updating esptool version fields to {args.version}") + update_tools_dependencies(tmp_json_path, args.version) + + shutil.move(tmp_json_path, json_path) + print(f"Done. JSON updated at {json_path}") + +if __name__ == "__main__": + main() diff --git a/.github/workflows/dangerjs.yml b/.github/workflows/dangerjs.yml index 13bc907566b..bba96bfedee 100644 --- a/.github/workflows/dangerjs.yml +++ b/.github/workflows/dangerjs.yml @@ -24,4 +24,5 @@ jobs: instructions-cla-link: "https://cla-assistant.io/espressif/arduino-esp32" instructions-contributions-file: "docs/en/contributing.rst" rule-max-commits: "false" + rule-target-branch: "false" commit-messages-min-summary-length: "10" diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000000..89a45022bc2 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,25 @@ +workflow: + rules: + # Disable those non-protected push triggered pipelines + - if: '$CI_COMMIT_REF_NAME != "master" && $CI_COMMIT_BRANCH !~ /^release\/v/ && $CI_COMMIT_TAG !~ /^\d+\.\d+(\.\d+)?($|-)/ && $CI_PIPELINE_SOURCE == "push"' + when: never + # when running merged result pipelines, CI_COMMIT_SHA represents the temp commit it created. + # Please use PIPELINE_COMMIT_SHA at all places that require a commit sha of the original commit. + - if: $CI_OPEN_MERGE_REQUESTS != null + variables: + PIPELINE_COMMIT_SHA: $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA + IS_MR_PIPELINE: 1 + - if: $CI_OPEN_MERGE_REQUESTS == null + variables: + PIPELINE_COMMIT_SHA: $CI_COMMIT_SHA + IS_MR_PIPELINE: 0 + - if: '$CI_PIPELINE_SOURCE == "schedule"' + variables: + IS_SCHEDULED_RUN: "true" + - when: always + +# Place the default settings in `.gitlab/workflows/common.yml` instead + +include: + - ".gitlab/workflows/common.yml" + - ".gitlab/workflows/sample.yml" diff --git a/.gitlab/workflows/common.yml b/.gitlab/workflows/common.yml new file mode 100644 index 00000000000..9086da018ab --- /dev/null +++ b/.gitlab/workflows/common.yml @@ -0,0 +1,26 @@ +##################### +# Default Variables # +##################### + +stages: + - pre_check + - build + - test + - result + +variables: + ESP_IDF_VERSION: "5.4" + ESP_ARDUINO_VERSION: "3.2.1" + +############# +# `default` # +############# + +default: + retry: + max: 2 + when: + # In case of a runner failure we could hop to another one, or a network error could go away. + - runner_system_failure + # Job execution timeout may be caused by a network issue. + - job_execution_timeout diff --git a/.gitlab/workflows/sample.yml b/.gitlab/workflows/sample.yml new file mode 100644 index 00000000000..32b6fce042d --- /dev/null +++ b/.gitlab/workflows/sample.yml @@ -0,0 +1,6 @@ +hello-world: + stage: test + script: + - echo "Hello, World from GitLab CI!" + rules: + - if: $CI_PIPELINE_SOURCE == "push" diff --git a/CMakeLists.txt b/CMakeLists.txt index e8f44ac5ee0..f21183ee11e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -305,6 +305,7 @@ set(ARDUINO_LIBRARY_Zigbee_SRCS libraries/Zigbee/src/ep/ZigbeeElectricalMeasurement.cpp libraries/Zigbee/src/ep/ZigbeeBinary.cpp libraries/Zigbee/src/ep/ZigbeePowerOutlet.cpp + libraries/Zigbee/src/ep/ZigbeeFanControl.cpp ) set(ARDUINO_LIBRARY_BLE_SRCS diff --git a/boards.txt b/boards.txt index 4198f3856b0..ac2b1a336e4 100644 --- a/boards.txt +++ b/boards.txt @@ -33287,6 +33287,229 @@ deneyapkart.menu.EraseFlash.all.upload.erase_cmd=-e ############################################################## +deneyapkartv2.name=Deneyap Kart v2 + +deneyapkartv2.vid.0=0x303a +deneyapkartv2.pid.0=0x82EB + +deneyapkartv2.bootloader.tool=esptool_py +deneyapkartv2.bootloader.tool.default=esptool_py + +deneyapkartv2.upload.tool=esptool_py +deneyapkartv2.upload.tool.default=esptool_py +deneyapkartv2.upload.tool.network=esp_ota + +deneyapkartv2.upload.maximum_size=1310720 +deneyapkartv2.upload.maximum_data_size=327680 +deneyapkartv2.upload.flags= +deneyapkartv2.upload.extra_flags= +deneyapkartv2.upload.use_1200bps_touch=false +deneyapkartv2.upload.wait_for_upload_port=false + +deneyapkartv2.serial.disableDTR=false +deneyapkartv2.serial.disableRTS=false + +deneyapkartv2.build.tarch=xtensa +deneyapkartv2.build.bootloader_addr=0x0 +deneyapkartv2.build.target=esp32s3 +deneyapkartv2.build.mcu=esp32s3 +deneyapkartv2.build.core=esp32 +deneyapkartv2.build.variant=deneyapkartv2 +deneyapkartv2.build.board=DYDKV2 + +deneyapkartv2.build.usb_mode=1 +deneyapkartv2.build.cdc_on_boot=1 +deneyapkartv2.build.msc_on_boot=0 +deneyapkartv2.build.dfu_on_boot=0 +deneyapkartv2.build.f_cpu=240000000L +deneyapkartv2.build.flash_size=4MB +deneyapkartv2.build.flash_freq=80m +deneyapkartv2.build.flash_mode=dio +deneyapkartv2.build.boot=qio +deneyapkartv2.build.boot_freq=80m +deneyapkartv2.build.partitions=default +deneyapkartv2.build.defines=-DBOARD_HAS_PSRAM +deneyapkartv2.build.loop_core= +deneyapkartv2.build.event_core= +deneyapkartv2.build.psram_type=opi +deneyapkartv2.build.memory_type={build.boot}_{build.psram_type} + +## IDE 2.0 Seems to not update the value +deneyapkartv2.menu.JTAGAdapter.default=Disabled +deneyapkartv2.menu.JTAGAdapter.default.build.copy_jtag_files=0 +deneyapkartv2.menu.JTAGAdapter.builtin=Integrated USB JTAG +deneyapkartv2.menu.JTAGAdapter.builtin.build.openocdscript=esp32s3-builtin.cfg +deneyapkartv2.menu.JTAGAdapter.builtin.build.copy_jtag_files=1 +deneyapkartv2.menu.JTAGAdapter.external=FTDI Adapter +deneyapkartv2.menu.JTAGAdapter.external.build.openocdscript=esp32s3-ftdi.cfg +deneyapkartv2.menu.JTAGAdapter.external.build.copy_jtag_files=1 +deneyapkartv2.menu.JTAGAdapter.bridge=ESP USB Bridge +deneyapkartv2.menu.JTAGAdapter.bridge.build.openocdscript=esp32s3-bridge.cfg +deneyapkartv2.menu.JTAGAdapter.bridge.build.copy_jtag_files=1 + +deneyapkartv2.menu.PSRAM.opi=OPI PSRAM +deneyapkartv2.menu.PSRAM.opi.build.defines=-DBOARD_HAS_PSRAM +deneyapkartv2.menu.PSRAM.opi.build.psram_type=opi +deneyapkartv2.menu.PSRAM.disabled=Disabled +deneyapkartv2.menu.PSRAM.disabled.build.defines= +deneyapkartv2.menu.PSRAM.disabled.build.psram_type=qspi + +deneyapkartv2.menu.FlashMode.qio=QIO 80MHz +deneyapkartv2.menu.FlashMode.qio.build.flash_mode=dio +deneyapkartv2.menu.FlashMode.qio.build.boot=qio +deneyapkartv2.menu.FlashMode.qio.build.boot_freq=80m +deneyapkartv2.menu.FlashMode.qio.build.flash_freq=80m +deneyapkartv2.menu.FlashMode.qio120=QIO 120MHz +deneyapkartv2.menu.FlashMode.qio120.build.flash_mode=dio +deneyapkartv2.menu.FlashMode.qio120.build.boot=qio +deneyapkartv2.menu.FlashMode.qio120.build.boot_freq=120m +deneyapkartv2.menu.FlashMode.qio120.build.flash_freq=80m +deneyapkartv2.menu.FlashMode.dio=DIO 80MHz +deneyapkartv2.menu.FlashMode.dio.build.flash_mode=dio +deneyapkartv2.menu.FlashMode.dio.build.boot=dio +deneyapkartv2.menu.FlashMode.dio.build.boot_freq=80m +deneyapkartv2.menu.FlashMode.dio.build.flash_freq=80m +deneyapkartv2.menu.FlashMode.opi=OPI 80MHz +deneyapkartv2.menu.FlashMode.opi.build.flash_mode=dout +deneyapkartv2.menu.FlashMode.opi.build.boot=opi +deneyapkartv2.menu.FlashMode.opi.build.boot_freq=80m +deneyapkartv2.menu.FlashMode.opi.build.flash_freq=80m + +deneyapkartv2.menu.FlashSize.4M=4MB (32Mb) +deneyapkartv2.menu.FlashSize.4M.build.flash_size=4MB + +deneyapkartv2.menu.LoopCore.1=Core 1 +deneyapkartv2.menu.LoopCore.1.build.loop_core=-DARDUINO_RUNNING_CORE=1 +deneyapkartv2.menu.LoopCore.0=Core 0 +deneyapkartv2.menu.LoopCore.0.build.loop_core=-DARDUINO_RUNNING_CORE=0 + +deneyapkartv2.menu.EventsCore.1=Core 1 +deneyapkartv2.menu.EventsCore.1.build.event_core=-DARDUINO_EVENT_RUNNING_CORE=1 +deneyapkartv2.menu.EventsCore.0=Core 0 +deneyapkartv2.menu.EventsCore.0.build.event_core=-DARDUINO_EVENT_RUNNING_CORE=0 + +deneyapkartv2.menu.USBMode.hwcdc=Hardware CDC and JTAG +deneyapkartv2.menu.USBMode.hwcdc.build.usb_mode=1 +deneyapkartv2.menu.USBMode.default=USB-OTG (TinyUSB) +deneyapkartv2.menu.USBMode.default.build.usb_mode=0 + +deneyapkartv2.menu.CDCOnBoot.cdc=Enabled +deneyapkartv2.menu.CDCOnBoot.cdc.build.cdc_on_boot=1 +deneyapkartv2.menu.CDCOnBoot.default=Disabled +deneyapkartv2.menu.CDCOnBoot.default.build.cdc_on_boot=0 + +deneyapkartv2.menu.MSCOnBoot.default=Disabled +deneyapkartv2.menu.MSCOnBoot.default.build.msc_on_boot=0 +deneyapkartv2.menu.MSCOnBoot.msc=Enabled (Requires USB-OTG Mode) +deneyapkartv2.menu.MSCOnBoot.msc.build.msc_on_boot=1 + +deneyapkartv2.menu.DFUOnBoot.default=Disabled +deneyapkartv2.menu.DFUOnBoot.default.build.dfu_on_boot=0 +deneyapkartv2.menu.DFUOnBoot.dfu=Enabled (Requires USB-OTG Mode) +deneyapkartv2.menu.DFUOnBoot.dfu.build.dfu_on_boot=1 + +deneyapkartv2.menu.UploadMode.default=UART0 / Hardware CDC +deneyapkartv2.menu.UploadMode.default.upload.use_1200bps_touch=false +deneyapkartv2.menu.UploadMode.default.upload.wait_for_upload_port=false +deneyapkartv2.menu.UploadMode.cdc=USB-OTG CDC (TinyUSB) +deneyapkartv2.menu.UploadMode.cdc.upload.use_1200bps_touch=true +deneyapkartv2.menu.UploadMode.cdc.upload.wait_for_upload_port=true + +deneyapkartv2.menu.PartitionScheme.default=Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS) +deneyapkartv2.menu.PartitionScheme.default.build.partitions=default +deneyapkartv2.menu.PartitionScheme.defaultffat=Default 4MB with ffat (1.2MB APP/1.5MB FATFS) +deneyapkartv2.menu.PartitionScheme.defaultffat.build.partitions=default_ffat +deneyapkartv2.menu.PartitionScheme.minimal=Minimal (1.3MB APP/700KB SPIFFS) +deneyapkartv2.menu.PartitionScheme.minimal.build.partitions=minimal +deneyapkartv2.menu.PartitionScheme.no_fs=No FS 4MB (2MB APP x2) +deneyapkartv2.menu.PartitionScheme.no_fs.build.partitions=no_fs +deneyapkartv2.menu.PartitionScheme.no_fs.upload.maximum_size=2031616 +deneyapkartv2.menu.PartitionScheme.no_ota=No OTA (2MB APP/2MB SPIFFS) +deneyapkartv2.menu.PartitionScheme.no_ota.build.partitions=no_ota +deneyapkartv2.menu.PartitionScheme.no_ota.upload.maximum_size=2097152 +deneyapkartv2.menu.PartitionScheme.noota_3g=No OTA (1MB APP/3MB SPIFFS) +deneyapkartv2.menu.PartitionScheme.noota_3g.build.partitions=noota_3g +deneyapkartv2.menu.PartitionScheme.noota_3g.upload.maximum_size=1048576 +deneyapkartv2.menu.PartitionScheme.noota_ffat=No OTA (2MB APP/2MB FATFS) +deneyapkartv2.menu.PartitionScheme.noota_ffat.build.partitions=noota_ffat +deneyapkartv2.menu.PartitionScheme.noota_ffat.upload.maximum_size=2097152 +deneyapkartv2.menu.PartitionScheme.noota_3gffat=No OTA (1MB APP/3MB FATFS) +deneyapkartv2.menu.PartitionScheme.noota_3gffat.build.partitions=noota_3gffat +deneyapkartv2.menu.PartitionScheme.noota_3gffat.upload.maximum_size=1048576 +deneyapkartv2.menu.PartitionScheme.huge_app=Huge APP (3MB No OTA/1MB SPIFFS) +deneyapkartv2.menu.PartitionScheme.huge_app.build.partitions=huge_app +deneyapkartv2.menu.PartitionScheme.huge_app.upload.maximum_size=3145728 +deneyapkartv2.menu.PartitionScheme.min_spiffs=Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS) +deneyapkartv2.menu.PartitionScheme.min_spiffs.build.partitions=min_spiffs +deneyapkartv2.menu.PartitionScheme.min_spiffs.upload.maximum_size=1966080 +deneyapkartv2.menu.PartitionScheme.rainmaker=RainMaker 4MB +deneyapkartv2.menu.PartitionScheme.rainmaker.build.partitions=rainmaker +deneyapkartv2.menu.PartitionScheme.rainmaker.upload.maximum_size=1966080 +deneyapkartv2.menu.PartitionScheme.rainmaker_4MB=RainMaker 4MB No OTA +deneyapkartv2.menu.PartitionScheme.rainmaker_4MB.build.partitions=rainmaker_4MB_no_ota +deneyapkartv2.menu.PartitionScheme.rainmaker_4MB.upload.maximum_size=4038656 +deneyapkartv2.menu.PartitionScheme.zigbee_zczr=Zigbee ZCZR 4MB with spiffs +deneyapkartv2.menu.PartitionScheme.zigbee_zczr.build.partitions=zigbee_zczr +deneyapkartv2.menu.PartitionScheme.zigbee_zczr.upload.maximum_size=1310720 +deneyapkartv2.menu.PartitionScheme.custom=Custom +deneyapkartv2.menu.PartitionScheme.custom.build.partitions= +deneyapkartv2.menu.PartitionScheme.custom.upload.maximum_size=16777216 + +deneyapkartv2.menu.CPUFreq.240=240MHz (WiFi) +deneyapkartv2.menu.CPUFreq.240.build.f_cpu=240000000L +deneyapkartv2.menu.CPUFreq.160=160MHz (WiFi) +deneyapkartv2.menu.CPUFreq.160.build.f_cpu=160000000L +deneyapkartv2.menu.CPUFreq.80=80MHz (WiFi) +deneyapkartv2.menu.CPUFreq.80.build.f_cpu=80000000L +deneyapkartv2.menu.CPUFreq.40=40MHz +deneyapkartv2.menu.CPUFreq.40.build.f_cpu=40000000L +deneyapkartv2.menu.CPUFreq.20=20MHz +deneyapkartv2.menu.CPUFreq.20.build.f_cpu=20000000L +deneyapkartv2.menu.CPUFreq.10=10MHz +deneyapkartv2.menu.CPUFreq.10.build.f_cpu=10000000L + +deneyapkartv2.menu.UploadSpeed.921600=921600 +deneyapkartv2.menu.UploadSpeed.921600.upload.speed=921600 +deneyapkartv2.menu.UploadSpeed.115200=115200 +deneyapkartv2.menu.UploadSpeed.115200.upload.speed=115200 +deneyapkartv2.menu.UploadSpeed.256000.windows=256000 +deneyapkartv2.menu.UploadSpeed.256000.upload.speed=256000 +deneyapkartv2.menu.UploadSpeed.230400.windows.upload.speed=256000 +deneyapkartv2.menu.UploadSpeed.230400=230400 +deneyapkartv2.menu.UploadSpeed.230400.upload.speed=230400 +deneyapkartv2.menu.UploadSpeed.460800.linux=460800 +deneyapkartv2.menu.UploadSpeed.460800.macosx=460800 +deneyapkartv2.menu.UploadSpeed.460800.upload.speed=460800 +deneyapkartv2.menu.UploadSpeed.512000.windows=512000 +deneyapkartv2.menu.UploadSpeed.512000.upload.speed=512000 + +deneyapkartv2.menu.DebugLevel.none=None +deneyapkartv2.menu.DebugLevel.none.build.code_debug=0 +deneyapkartv2.menu.DebugLevel.error=Error +deneyapkartv2.menu.DebugLevel.error.build.code_debug=1 +deneyapkartv2.menu.DebugLevel.warn=Warn +deneyapkartv2.menu.DebugLevel.warn.build.code_debug=2 +deneyapkartv2.menu.DebugLevel.info=Info +deneyapkartv2.menu.DebugLevel.info.build.code_debug=3 +deneyapkartv2.menu.DebugLevel.debug=Debug +deneyapkartv2.menu.DebugLevel.debug.build.code_debug=4 +deneyapkartv2.menu.DebugLevel.verbose=Verbose +deneyapkartv2.menu.DebugLevel.verbose.build.code_debug=5 + +deneyapkartv2.menu.EraseFlash.none=Disabled +deneyapkartv2.menu.EraseFlash.none.upload.erase_cmd= +deneyapkartv2.menu.EraseFlash.all=Enabled +deneyapkartv2.menu.EraseFlash.all.upload.erase_cmd=-e + +deneyapkartv2.menu.ZigbeeMode.default=Disabled +deneyapkartv2.menu.ZigbeeMode.default.build.zigbee_mode= +deneyapkartv2.menu.ZigbeeMode.default.build.zigbee_libs= +deneyapkartv2.menu.ZigbeeMode.zczr=Zigbee ZCZR (coordinator/router) +deneyapkartv2.menu.ZigbeeMode.zczr.build.zigbee_mode=-DZIGBEE_MODE_ZCZR +deneyapkartv2.menu.ZigbeeMode.zczr.build.zigbee_libs=-lesp_zb_api.zczr -lzboss_stack.zczr -lzboss_port.remote + +############################################################## + deneyapkart1A.name=Deneyap Kart 1A deneyapkart1A.bootloader.tool=esptool_py @@ -50582,10 +50805,12 @@ rakwireless_rak3112.build.dfu_on_boot=0 rakwireless_rak3112.build.f_cpu=240000000L rakwireless_rak3112.build.flash_size=16MB rakwireless_rak3112.build.flash_freq=80m -rakwireless_rak3112.build.flash_mode=dio -rakwireless_rak3112.build.boot=dio +rakwireless_rak3112.build.flash_mode=qio +rakwireless_rak3112.build.boot=qio rakwireless_rak3112.build.partitions=default rakwireless_rak3112.build.defines= +rakwireless_rak3112.build.psram_type=opi +rakwireless_rak3112.build.memory_type={build.boot}_{build.psram_type} rakwireless_rak3112.menu.PSRAM.enabled=Enabled rakwireless_rak3112.menu.PSRAM.enabled.build.defines=-DBOARD_HAS_PSRAM diff --git a/cores/esp32/HardwareI2C.h b/cores/esp32/HardwareI2C.h index 65b7e2036b2..c44f34e1ee7 100644 --- a/cores/esp32/HardwareI2C.h +++ b/cores/esp32/HardwareI2C.h @@ -20,6 +20,7 @@ #include #include "Stream.h" +#include class HardwareI2C : public Stream { public: @@ -36,6 +37,7 @@ class HardwareI2C : public Stream { virtual size_t requestFrom(uint8_t address, size_t len, bool stopBit) = 0; virtual size_t requestFrom(uint8_t address, size_t len) = 0; - virtual void onReceive(void (*)(int)) = 0; - virtual void onRequest(void (*)(void)) = 0; + // Update base class to use std::function + virtual void onReceive(const std::function &) = 0; + virtual void onRequest(const std::function &) = 0; }; diff --git a/cores/esp32/esp32-hal-i2c-ng.c b/cores/esp32/esp32-hal-i2c-ng.c index 8e48d0e0397..a3b2307b8a8 100644 --- a/cores/esp32/esp32-hal-i2c-ng.c +++ b/cores/esp32/esp32-hal-i2c-ng.c @@ -56,6 +56,13 @@ static bool i2cDetachBus(void *bus_i2c_num) { return true; } +void *i2cBusHandle(uint8_t i2c_num) { + if (i2c_num >= SOC_I2C_NUM) { + return NULL; + } + return bus[i2c_num].bus_handle; +} + bool i2cIsInit(uint8_t i2c_num) { if (i2c_num >= SOC_I2C_NUM) { return false; diff --git a/cores/esp32/esp32-hal-i2c.h b/cores/esp32/esp32-hal-i2c.h index 35783d350b0..0e4f484bb46 100644 --- a/cores/esp32/esp32-hal-i2c.h +++ b/cores/esp32/esp32-hal-i2c.h @@ -19,6 +19,7 @@ #include "soc/soc_caps.h" #if SOC_I2C_SUPPORTED +#include "esp_idf_version.h" #ifdef __cplusplus extern "C" { @@ -39,6 +40,10 @@ esp_err_t i2cWriteReadNonStop( ); bool i2cIsInit(uint8_t i2c_num); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) +void *i2cBusHandle(uint8_t i2c_num); +#endif + #ifdef __cplusplus } #endif diff --git a/docs/conf_common.py b/docs/conf_common.py index 6945c0d190d..af1d615f753 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -4,7 +4,7 @@ # Used for substituting variables in the documentation rst_prolog = """ -.. |version| replace:: 3.2.0 +.. |version| replace:: 3.2.1 .. |idf_version| replace:: 5.4 """ diff --git a/docs/en/api/i2c.rst b/docs/en/api/i2c.rst index eac04b76a23..06d4d1953a6 100644 --- a/docs/en/api/i2c.rst +++ b/docs/en/api/i2c.rst @@ -347,20 +347,147 @@ This function will return ``true`` if the peripheral was initialized correctly. onReceive ^^^^^^^^^ -The ``onReceive`` function is used to define the callback for the data received from the master. +The ``onReceive`` function is used to define the callback for data received from the master device. .. code-block:: arduino - void onReceive( void (*)(int) ); + void onReceive(const std::function& callback); + +**Function Signature:** + +The callback function must have the signature ``void(int numBytes)`` where ``numBytes`` indicates how many bytes were received from the master. + +**Usage Examples:** + +.. code-block:: arduino + + // Method 1: Regular function + void handleReceive(int numBytes) { + Serial.printf("Received %d bytes: ", numBytes); + while (Wire.available()) { + char c = Wire.read(); + Serial.print(c); + } + Serial.println(); + } + Wire.onReceive(handleReceive); + + // Method 2: Lambda function + Wire.onReceive([](int numBytes) { + Serial.printf("Master sent %d bytes\n", numBytes); + while (Wire.available()) { + uint8_t data = Wire.read(); + // Process received data + Serial.printf("Data: 0x%02X\n", data); + } + }); + + // Method 3: Lambda with capture (for accessing variables) + int deviceId = 42; + Wire.onReceive([deviceId](int numBytes) { + Serial.printf("Device %d received %d bytes\n", deviceId, numBytes); + // Process data... + }); + + // Method 4: Using std::function variable + std::function receiveHandler = [](int bytes) { + Serial.printf("Handling %d received bytes\n", bytes); + }; + Wire.onReceive(receiveHandler); + + // Method 5: Class member function (using lambda wrapper) + class I2CDevice { + private: + int deviceAddress; + public: + I2CDevice(int addr) : deviceAddress(addr) {} + + void handleReceive(int numBytes) { + Serial.printf("Device 0x%02X received %d bytes\n", deviceAddress, numBytes); + } + + void setup() { + Wire.onReceive([this](int bytes) { + this->handleReceive(bytes); + }); + } + }; + +.. note:: + The ``onReceive`` callback is triggered when the I2C master sends data to this slave device. + Use ``Wire.available()`` and ``Wire.read()`` inside the callback to retrieve the received data. onRequest ^^^^^^^^^ -The ``onRequest`` function is used to define the callback for the data to be send to the master. +The ``onRequest`` function is used to define the callback for responding to master read requests. + +.. code-block:: arduino + + void onRequest(const std::function& callback); + +**Function Signature:** + +The callback function must have the signature ``void()`` with no parameters. This callback is triggered when the master requests data from this slave device. + +**Usage Examples:** .. code-block:: arduino - void onRequest( void (*)(void) ); + // Method 1: Regular function + void handleRequest() { + static int counter = 0; + Wire.printf("Response #%d", counter++); + } + Wire.onRequest(handleRequest); + + // Method 2: Lambda function + Wire.onRequest([]() { + // Send sensor data to master + int sensorValue = analogRead(A0); + Wire.write(sensorValue >> 8); // High byte + Wire.write(sensorValue & 0xFF); // Low byte + }); + + // Method 3: Lambda with capture (for accessing variables) + int deviceStatus = 1; + String deviceName = "Sensor1"; + Wire.onRequest([&deviceStatus, &deviceName]() { + Wire.write(deviceStatus); + Wire.write(deviceName.c_str(), deviceName.length()); + }); + + // Method 4: Using std::function variable + std::function requestHandler = []() { + Wire.write("Hello Master!"); + }; + Wire.onRequest(requestHandler); + + // Method 5: Class member function (using lambda wrapper) + class TemperatureSensor { + private: + float temperature; + public: + void updateTemperature() { + temperature = 25.5; // Read from actual sensor + } + + void sendTemperature() { + // Convert float to bytes and send + uint8_t* tempBytes = (uint8_t*)&temperature; + Wire.write(tempBytes, sizeof(float)); + } + + void setup() { + Wire.onRequest([this]() { + this->sendTemperature(); + }); + } + }; + +.. note:: + The ``onRequest`` callback is triggered when the I2C master requests data from this slave device. + Use ``Wire.write()`` inside the callback to send response data back to the master. slaveWrite ^^^^^^^^^^ diff --git a/libraries/AsyncUDP/src/AsyncUDP.cpp b/libraries/AsyncUDP/src/AsyncUDP.cpp index cab9c951921..2d533831cd5 100644 --- a/libraries/AsyncUDP/src/AsyncUDP.cpp +++ b/libraries/AsyncUDP/src/AsyncUDP.cpp @@ -582,8 +582,8 @@ bool AsyncUDP::listen(const ip_addr_t *addr, uint16_t port) { } close(); if (addr) { - IP_SET_TYPE_VAL(_pcb->local_ip, addr->type); - IP_SET_TYPE_VAL(_pcb->remote_ip, addr->type); + IP_SET_TYPE_VAL(_pcb->local_ip, IP_GET_TYPE(addr)); + IP_SET_TYPE_VAL(_pcb->remote_ip, IP_GET_TYPE(addr)); } if (_udp_bind(_pcb, addr, port) != ERR_OK) { return false; @@ -692,17 +692,8 @@ bool AsyncUDP::listenMulticast(const ip_addr_t *addr, uint16_t port, uint8_t ttl return false; } -#if CONFIG_LWIP_IPV6 - if (IP_IS_V6(addr)) { - IP_SET_TYPE(&bind_addr, IPADDR_TYPE_V6); - ip6_addr_set_any(&bind_addr.u_addr.ip6); - } else { -#endif - IP_SET_TYPE(&bind_addr, IPADDR_TYPE_V4); - ip4_addr_set_any(&bind_addr.u_addr.ip4); -#if CONFIG_LWIP_IPV6 - } -#endif + IP_SET_TYPE(&bind_addr, IP_GET_TYPE(addr)); + ip_addr_set_any(IP_IS_V6(addr), &bind_addr); if (!listen(&bind_addr, port)) { return false; } diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino new file mode 100644 index 00000000000..57d35383f17 --- /dev/null +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/FunctionalInterruptLambda.ino @@ -0,0 +1,157 @@ +/* + SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + + SPDX-License-Identifier: Apache-2.0 + + 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. + + ESP32 Lambda FunctionalInterrupt Example + ======================================== + + This example demonstrates how to use lambda functions with FunctionalInterrupt + for GPIO pin interrupt callbacks on ESP32. It shows CHANGE mode detection + with LED toggle functionality and proper debouncing. + + Hardware Setup: + - Use BOOT Button or connect a button between BUTTON_PIN and GND (with internal pullup) + - Use Builtin Board LED or connect an LED with resistor to GPIO 2 (LED_PIN) + + Features Demonstrated: + 1. CHANGE mode lambda to detect both RISING and FALLING edges + 2. LED toggle on button press (FALLING edge) + 3. Edge type detection using digitalRead() within ISR + 4. Hardware debouncing with configurable timeout + + IMPORTANT NOTE ABOUT ESP32 INTERRUPT BEHAVIOR: + - Only ONE interrupt handler can be attached per GPIO pin at a time + - Calling attachInterrupt() on a pin that already has an interrupt will override the previous one + - This applies regardless of edge type (RISING, FALLING, CHANGE) + - If you need both RISING and FALLING detection on the same pin, use CHANGE mode + and determine the edge type within your handler by reading the pin state +*/ + +#include +#include + +// Pin definitions +#define BUTTON_PIN BOOT_PIN // BOOT BUTTON - change as needed +#ifdef LED_BUILTIN +#define LED_PIN LED_BUILTIN +#else +#warning Using LED_PIN = GPIO 2 as default - change as needed +#define LED_PIN 2 // change as needed +#endif + +// Global variables for interrupt handling (volatile for ISR safety) +volatile uint32_t buttonPressCount = 0; +volatile uint32_t buttonReleaseCount = 0; +volatile bool buttonPressed = false; +volatile bool buttonReleased = false; +volatile bool ledState = false; +volatile bool ledStateChanged = false; // Flag to indicate LED needs updating + +// Debouncing variables (volatile for ISR safety) +volatile unsigned long lastButtonInterruptTime = 0; +const unsigned long DEBOUNCE_DELAY_MS = 50; // 50ms debounce delay + +// State-based debouncing to prevent hysteresis issues +volatile bool lastButtonState = HIGH; // Track last stable state (HIGH = released) + +// Global lambda function (declared at file scope) - ISR in IRAM +IRAM_ATTR std::function changeModeLambda = []() { + // Simple debouncing: check if enough time has passed since last interrupt + unsigned long currentTime = millis(); + if (currentTime - lastButtonInterruptTime < DEBOUNCE_DELAY_MS) { + return; // Ignore this interrupt due to bouncing + } + + // Read current pin state to determine edge type + bool currentState = digitalRead(BUTTON_PIN); + + // State-based debouncing: only process if state actually changed + if (currentState == lastButtonState) { + return; // No real state change, ignore (hysteresis/noise) + } + + // Update timing and state + lastButtonInterruptTime = currentTime; + lastButtonState = currentState; + + if (currentState == LOW) { + // FALLING edge detected (button pressed) - set flag for main loop + // volatile variables require use of temporary value transfer + uint32_t temp = buttonPressCount + 1; + buttonPressCount = temp; + buttonPressed = true; + ledStateChanged = true; // Signal main loop to toggle LED + } else { + // RISING edge detected (button released) - set flag for main loop + // volatile variables require use of temporary value transfer + uint32_t temp = buttonReleaseCount + 1; + buttonReleaseCount = temp; + buttonReleased = true; + } +}; + +void setup() { + Serial.begin(115200); + delay(1000); // Allow serial monitor to connect + + Serial.println("ESP32 Lambda FunctionalInterrupt Example"); + Serial.println("========================================"); + + // Configure pins + pinMode(BUTTON_PIN, INPUT_PULLUP); + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + + // CHANGE mode lambda to handle both RISING and FALLING edges + // This toggles the LED on button press (FALLING edge) + Serial.println("Setting up CHANGE mode lambda for LED toggle"); + + // Use the global lambda function + attachInterrupt(BUTTON_PIN, changeModeLambda, CHANGE); + + Serial.println(); + Serial.printf("Lambda interrupt configured on Pin %d (CHANGE mode)\r\n", BUTTON_PIN); + Serial.printf("Debounce delay: %lu ms\r\n", DEBOUNCE_DELAY_MS); + Serial.println(); + Serial.println("Press the button to toggle the LED!"); + Serial.println("Button press (FALLING edge) will toggle the LED."); + Serial.println("Button release (RISING edge) will be detected and reported."); + Serial.println("Button includes debouncing to prevent mechanical bounce issues."); + Serial.println(); +} + +void loop() { + // Handle LED state changes (ISR-safe approach) + if (ledStateChanged) { + ledStateChanged = false; + ledState = !ledState; // Toggle LED state in main loop + digitalWrite(LED_PIN, ledState); + } + + // Check for button presses + if (buttonPressed) { + buttonPressed = false; + Serial.printf("==> Button PRESSED! Count: %lu, LED: %s (FALLING edge)\r\n", buttonPressCount, ledState ? "ON" : "OFF"); + } + + // Check for button releases + if (buttonReleased) { + buttonReleased = false; + Serial.printf("==> Button RELEASED! Count: %lu (RISING edge)\r\n", buttonReleaseCount); + } + + delay(10); +} diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md new file mode 100644 index 00000000000..9488c317fde --- /dev/null +++ b/libraries/ESP32/examples/GPIO/FunctionalInterruptLambda/README.md @@ -0,0 +1,147 @@ +# ESP32 Lambda FunctionalInterrupt Example + +This example demonstrates how to use lambda functions with FunctionalInterrupt for GPIO pin interrupt callbacks on ESP32. It shows CHANGE mode detection with LED toggle functionality and proper debouncing. + +## Features Demonstrated + +1. **CHANGE mode lambda** to detect both RISING and FALLING edges +2. **LED toggle on button press** (FALLING edge) +3. **Edge type detection** using digitalRead() within ISR +4. **Hardware debouncing** with configurable timeout +5. **IRAM_ATTR lambda declaration** for optimal ISR performance in RAM + +## Hardware Setup + +- Use BOOT Button or connect a button between BUTTON_PIN and GND (with internal pullup) +- Use Builtin Board LED (no special hardware setup) or connect an LED with resistor to GPIO assigned as LED_PIN.\ + Some boards have an RGB LED that needs no special hardware setup to work as a simple white on/off LED. + +``` +ESP32 Board Button/LED +----------- --------- +BOOT_PIN ------------ [BUTTON] ---- GND +LED_PIN --------------- [LED] ----- GND + ¦ + [330O] (*) Only needed when using an external LED attached to the GPIO. + ¦ + 3V3 +``` + +## Important ESP32 Interrupt Behavior + +**CRITICAL:** Only ONE interrupt handler can be attached per GPIO pin at a time on ESP32. + +- Calling `attachInterrupt()` on a pin that already has an interrupt will **override** the previous one +- This applies regardless of edge type (RISING, FALLING, CHANGE) +- If you need both RISING and FALLING detection on the same pin, use **CHANGE mode** and determine the edge type within your handler by reading the pin state + +## Code Overview + +This example demonstrates a simple CHANGE mode lambda interrupt that: + +- **Detects both button press and release** using a single interrupt handler +- **Toggles LED only on button press** (FALLING edge) +- **Reports both press and release events** to Serial output +- **Uses proper debouncing** to prevent switch bounce issues +- **Implements minimal lambda captures** for simplicity + +## Lambda Function Pattern + +### CHANGE Mode Lambda with IRAM Declaration +```cpp +// Global lambda declared with IRAM_ATTR for optimal ISR performance +IRAM_ATTR std::function changeModeLambda = []() { + // Debouncing check + unsigned long currentTime = millis(); + if (currentTime - lastButtonInterruptTime < DEBOUNCE_DELAY_MS) { + return; // Ignore bouncing + } + + // Determine edge type + bool currentState = digitalRead(BUTTON_PIN); + if (currentState == lastButtonState) { + return; // No real state change + } + + // Update state and handle edges + lastButtonInterruptTime = currentTime; + lastButtonState = currentState; + + if (currentState == LOW) { + // Button pressed (FALLING edge) + buttonPressCount++; + buttonPressed = true; + ledStateChanged = true; // Signal LED toggle + } else { + // Button released (RISING edge) + buttonReleaseCount++; + buttonReleased = true; + } +}; + +attachInterrupt(BUTTON_PIN, changeModeLambda, CHANGE); +``` + +## Key Concepts + +### Edge Detection in CHANGE Mode +```cpp +if (digitalRead(pin) == LOW) { + // FALLING edge detected (button pressed) +} else { + // RISING edge detected (button released) +} +``` + +### Debouncing Strategy +This example implements dual-layer debouncing: +1. **Time-based**: Ignores interrupts within 50 ms of previous one +2. **State-based**: Only processes actual state changes + +### Main Loop Processing +```cpp +void loop() { + // Handle LED changes safely outside ISR + if (ledStateChanged) { + ledStateChanged = false; + ledState = !ledState; + digitalWrite(LED_PIN, ledState); + } + + // Report button events + if (buttonPressed) { + // Handle press event + } + if (buttonReleased) { + // Handle release event + } +} +``` + +## Expected Output + +``` +ESP32 Lambda FunctionalInterrupt Example +======================================== +Setting up CHANGE mode lambda for LED toggle + +Lambda interrupt configured on Pin 0 (CHANGE mode) +Debounce delay: 50 ms + +Press the button to toggle the LED! +Button press (FALLING edge) will toggle the LED. +Button release (RISING edge) will be detected and reported. +Button includes debouncing to prevent mechanical bounce issues. + +==> Button PRESSED! Count: 1, LED: ON (FALLING edge) +==> Button RELEASED! Count: 1 (RISING edge) +==> Button PRESSED! Count: 2, LED: OFF (FALLING edge) +==> Button RELEASED! Count: 2 (RISING edge) +``` + +## Pin Configuration + +The example uses these default pins: + +- `BUTTON_PIN`: BOOT_PIN (automatically assigned by the Arduino Core) +- `LED_PIN`: LED_BUILTIN (may not be available for your board - please verify it) diff --git a/libraries/Matter/examples/MatterLambdaSingleCallbackManyEPs/MatterLambdaSingleCallbackManyEPs.ino b/libraries/Matter/examples/MatterLambdaSingleCallbackManyEPs/MatterLambdaSingleCallbackManyEPs.ino new file mode 100644 index 00000000000..4992771d925 --- /dev/null +++ b/libraries/Matter/examples/MatterLambdaSingleCallbackManyEPs/MatterLambdaSingleCallbackManyEPs.ino @@ -0,0 +1,112 @@ +// Copyright 2025 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +/* + This example creates 6 on-off light endpoints that share the same onChangeOnOff() callback code. + It uses Lambda Function with an extra Lambda Capture information that links the Endpoint to its individual information. + After the Matter example is commissioned, the expected Serial output shall be similar to this: + +Matter App Control: 'Room 1' (OnOffLight[0], Endpoint 1, GPIO 2) changed to: OFF +Matter App Control: 'Room 1' (OnOffLight[0], Endpoint 1, GPIO 2) changed to: ON +Matter App Control: 'Room 5' (OnOffLight[4], Endpoint 5, GPIO 10) changed to: ON +Matter App Control: 'Room 2' (OnOffLight[1], Endpoint 2, GPIO 4) changed to: ON +Matter App Control: 'Room 4' (OnOffLight[3], Endpoint 4, GPIO 8) changed to: ON +Matter App Control: 'Room 6' (OnOffLight[5], Endpoint 6, GPIO 12) changed to: ON +Matter App Control: 'Room 3' (OnOffLight[2], Endpoint 3, GPIO 6) changed to: ON +Matter App Control: 'Room 5' (OnOffLight[4], Endpoint 5, GPIO 10) changed to: OFF +*/ + +// Matter Manager +#include +#include + +// WiFi is manually set and started +const char *ssid = "your-ssid"; // Change this to your WiFi SSID +const char *password = "your-password"; // Change this to your WiFi password + +//number of On-Off Lights: +const uint8_t MAX_LIGHT_NUMBER = 6; + +// array of OnOffLight endpoints +MatterOnOffLight OnOffLight[MAX_LIGHT_NUMBER]; + +// all pins, one for each on-off light +uint8_t lightPins[MAX_LIGHT_NUMBER] = {2, 4, 6, 8, 10, 12}; // must replace it by the real pin for the target SoC and application + +// friendly OnOffLights names used for printing a message in the callback +const char *lightName[MAX_LIGHT_NUMBER] = { + "Room 1", "Room 2", "Room 3", "Room 4", "Room 5", "Room 6", +}; + +// simple setup() function +void setup() { + Serial.begin(115200); // callback will just print a message in the console + + // We start by connecting to a WiFi network + Serial.print("Connecting to "); + Serial.println(ssid); + // Manually connect to WiFi + WiFi.begin(ssid, password); + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\r\nWiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + delay(500); + + // setup all the OnOff Light endpoint and their lambda callback functions + for (uint8_t i = 0; i < MAX_LIGHT_NUMBER; i++) { + pinMode(lightPins[i], OUTPUT); // set the GPIO function + OnOffLight[i].begin(false); // off + + // inline lambda function using capture array index -> it will just print a message in the console + OnOffLight[i].onChangeOnOff([i](bool state) -> bool { + // Display message with the specific light name and details + Serial.printf( + "Matter App Control: '%s' (OnOffLight[%d], Endpoint %d, GPIO %d) changed to: %s\r\n", lightName[i], i, OnOffLight[i].getEndPointId(), lightPins[i], + state ? "ON" : "OFF" + ); + + return true; + }); + } + // last step, starting Matter Stack + Matter.begin(); +} + +void loop() { + // Check Matter Plugin Commissioning state, which may change during execution of loop() + if (!Matter.isDeviceCommissioned()) { + Serial.println(""); + Serial.println("Matter Node is not commissioned yet."); + Serial.println("Initiate the device discovery in your Matter environment."); + Serial.println("Commission it to your Matter hub with the manual pairing code or QR code"); + Serial.printf("Manual pairing code: %s\r\n", Matter.getManualPairingCode().c_str()); + Serial.printf("QR code URL: %s\r\n", Matter.getOnboardingQRCodeUrl().c_str()); + // waits for Matter Plugin Commissioning. + uint32_t timeCount = 0; + while (!Matter.isDeviceCommissioned()) { + delay(100); + if ((timeCount++ % 50) == 0) { // 50*100ms = 5 sec + Serial.println("Matter Node not commissioned yet. Waiting for commissioning."); + } + } + Serial.println("Matter Node is commissioned and connected to the WiFi network. Ready for use."); + } + + delay(500); +} diff --git a/libraries/Matter/examples/MatterLambdaSingleCallbackManyEPs/ci.json b/libraries/Matter/examples/MatterLambdaSingleCallbackManyEPs/ci.json new file mode 100644 index 00000000000..556a8a9ee6b --- /dev/null +++ b/libraries/Matter/examples/MatterLambdaSingleCallbackManyEPs/ci.json @@ -0,0 +1,7 @@ +{ + "fqbn_append": "PartitionScheme=huge_app", + "requires": [ + "CONFIG_SOC_WIFI_SUPPORTED=y", + "CONFIG_ESP_MATTER_ENABLE_DATA_MODEL=y" + ] +} diff --git a/libraries/Matter/keywords.txt b/libraries/Matter/keywords.txt index 68aaebb1d4d..edba06083bd 100644 --- a/libraries/Matter/keywords.txt +++ b/libraries/Matter/keywords.txt @@ -36,8 +36,10 @@ EndPointSpeedCB KEYWORD1 EndPointOnOffCB KEYWORD1 EndPointBrightnessCB KEYWORD1 EndPointRGBColorCB KEYWORD1 +EndPointIdentifyCB KEYWORD1 matterEvent_t KEYWORD1 matterEventCB KEYWORD1 +attrOperation_t KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -111,6 +113,10 @@ onChangeLocalTemperature KEYWORD2 onChangeCoolingSetpoint KEYWORD2 onChangeHeatingSetpoint KEYWORD2 onEvent KEYWORD2 +setEndPointId KEYWORD2 +getEndPointId KEYWORD2 +onIdentify KEYWORD2 +endpointIdentifyCB KEYWORD2 ####################################### # Constants (LITERAL1) diff --git a/libraries/OpenThread/examples/Native/SimpleThreadNetwork/LeaderNode/LeaderNode.ino b/libraries/OpenThread/examples/Native/SimpleThreadNetwork/LeaderNode/LeaderNode.ino index dfea9776838..b3c4091e1dc 100644 --- a/libraries/OpenThread/examples/Native/SimpleThreadNetwork/LeaderNode/LeaderNode.ino +++ b/libraries/OpenThread/examples/Native/SimpleThreadNetwork/LeaderNode/LeaderNode.ino @@ -3,6 +3,9 @@ OpenThread threadLeaderNode; DataSet dataset; +// Track last known device role for state change detection +ot_device_role_t lastKnownRole = OT_ROLE_DISABLED; + void setup() { Serial.begin(115200); @@ -27,8 +30,84 @@ void setup() { } void loop() { - // Print network information every 5 seconds - Serial.println("=============================================="); - threadLeaderNode.otPrintNetworkInformation(Serial); + // Get current device role + ot_device_role_t currentRole = threadLeaderNode.otGetDeviceRole(); + + // Only print network information when not detached + if (currentRole != OT_ROLE_DETACHED && currentRole != OT_ROLE_DISABLED) { + Serial.println("=============================================="); + Serial.println("OpenThread Network Information:"); + + // Basic network information + Serial.printf("Role: %s\r\n", threadLeaderNode.otGetStringDeviceRole()); + Serial.printf("RLOC16: 0x%04x\r\n", threadLeaderNode.getRloc16()); + Serial.printf("Network Name: %s\r\n", threadLeaderNode.getNetworkName().c_str()); + Serial.printf("Channel: %d\r\n", threadLeaderNode.getChannel()); + Serial.printf("PAN ID: 0x%04x\r\n", threadLeaderNode.getPanId()); + + // Extended PAN ID + const uint8_t *extPanId = threadLeaderNode.getExtendedPanId(); + if (extPanId) { + Serial.print("Extended PAN ID: "); + for (int i = 0; i < OT_EXT_PAN_ID_SIZE; i++) { + Serial.printf("%02x", extPanId[i]); + } + Serial.println(); + } + + // Network Key + const uint8_t *networkKey = threadLeaderNode.getNetworkKey(); + if (networkKey) { + Serial.print("Network Key: "); + for (int i = 0; i < OT_NETWORK_KEY_SIZE; i++) { + Serial.printf("%02x", networkKey[i]); + } + Serial.println(); + } + + // Mesh Local EID + IPAddress meshLocalEid = threadLeaderNode.getMeshLocalEid(); + Serial.printf("Mesh Local EID: %s\r\n", meshLocalEid.toString().c_str()); + + // Leader RLOC + IPAddress leaderRloc = threadLeaderNode.getLeaderRloc(); + Serial.printf("Leader RLOC: %s\r\n", leaderRloc.toString().c_str()); + + // Node RLOC + IPAddress nodeRloc = threadLeaderNode.getRloc(); + Serial.printf("Node RLOC: %s\r\n", nodeRloc.toString().c_str()); + + // Demonstrate address listing with two different methods: + // Method 1: Unicast addresses using counting API (individual access) + Serial.println("\r\n--- Unicast Addresses (Using Count + Index API) ---"); + size_t unicastCount = threadLeaderNode.getUnicastAddressCount(); + for (size_t i = 0; i < unicastCount; i++) { + IPAddress addr = threadLeaderNode.getUnicastAddress(i); + Serial.printf(" [%zu]: %s\r\n", i, addr.toString().c_str()); + } + + // Method 2: Multicast addresses using std::vector (bulk access) + Serial.println("\r\n--- Multicast Addresses (Using std::vector API) ---"); + std::vector allMulticast = threadLeaderNode.getAllMulticastAddresses(); + for (size_t i = 0; i < allMulticast.size(); i++) { + Serial.printf(" [%zu]: %s\r\n", i, allMulticast[i].toString().c_str()); + } + + // Check for role change and clear cache if needed (only when active) + if (currentRole != lastKnownRole) { + Serial.printf( + "Role changed from %s to %s - clearing address cache\r\n", (lastKnownRole < 5) ? otRoleString[lastKnownRole] : "Unknown", + threadLeaderNode.otGetStringDeviceRole() + ); + threadLeaderNode.clearAllAddressCache(); + lastKnownRole = currentRole; + } + } else { + Serial.printf("Thread Node Status: %s - Waiting for thread network start...\r\n", threadLeaderNode.otGetStringDeviceRole()); + + // Update role tracking even when detached/disabled, but don't clear cache + lastKnownRole = currentRole; + } + delay(5000); } diff --git a/libraries/OpenThread/examples/Native/SimpleThreadNetwork/RouterNode/RouterNode.ino b/libraries/OpenThread/examples/Native/SimpleThreadNetwork/RouterNode/RouterNode.ino index 5ffa535ad51..a8959792f5b 100644 --- a/libraries/OpenThread/examples/Native/SimpleThreadNetwork/RouterNode/RouterNode.ino +++ b/libraries/OpenThread/examples/Native/SimpleThreadNetwork/RouterNode/RouterNode.ino @@ -22,8 +22,63 @@ void setup() { } void loop() { - // Print network information every 5 seconds - Serial.println("=============================================="); - threadChildNode.otPrintNetworkInformation(Serial); + // Get current device role + ot_device_role_t currentRole = threadChildNode.otGetDeviceRole(); + + // Only print detailed network information when node is active + if (currentRole != OT_ROLE_DETACHED && currentRole != OT_ROLE_DISABLED) { + Serial.println("=============================================="); + Serial.println("OpenThread Network Information (Active Dataset):"); + + // Get and display the current active dataset + const DataSet &activeDataset = threadChildNode.getCurrentDataSet(); + + Serial.printf("Role: %s\r\n", threadChildNode.otGetStringDeviceRole()); + Serial.printf("RLOC16: 0x%04x\r\n", threadChildNode.getRloc16()); + + // Dataset information + Serial.printf("Network Name: %s\r\n", activeDataset.getNetworkName()); + Serial.printf("Channel: %d\r\n", activeDataset.getChannel()); + Serial.printf("PAN ID: 0x%04x\r\n", activeDataset.getPanId()); + + // Extended PAN ID from dataset + const uint8_t *extPanId = activeDataset.getExtendedPanId(); + if (extPanId) { + Serial.print("Extended PAN ID: "); + for (int i = 0; i < OT_EXT_PAN_ID_SIZE; i++) { + Serial.printf("%02x", extPanId[i]); + } + Serial.println(); + } + + // Network Key from dataset + const uint8_t *networkKey = activeDataset.getNetworkKey(); + if (networkKey) { + Serial.print("Network Key: "); + for (int i = 0; i < OT_NETWORK_KEY_SIZE; i++) { + Serial.printf("%02x", networkKey[i]); + } + Serial.println(); + } + + // Additional runtime information + IPAddress meshLocalEid = threadChildNode.getMeshLocalEid(); + Serial.printf("Mesh Local EID: %s\r\n", meshLocalEid.toString().c_str()); + + IPAddress nodeRloc = threadChildNode.getRloc(); + Serial.printf("Node RLOC: %s\r\n", nodeRloc.toString().c_str()); + + IPAddress leaderRloc = threadChildNode.getLeaderRloc(); + Serial.printf("Leader RLOC: %s\r\n", leaderRloc.toString().c_str()); + + Serial.println(); + + } else { + Serial.println("=============================================="); + Serial.printf("Thread Node Status: %s - Waiting for thread network start...\r\n", threadChildNode.otGetStringDeviceRole()); + + Serial.println(); + } + delay(5000); } diff --git a/libraries/OpenThread/keywords.txt b/libraries/OpenThread/keywords.txt index b62c2c23ddc..99821ce401c 100644 --- a/libraries/OpenThread/keywords.txt +++ b/libraries/OpenThread/keywords.txt @@ -13,6 +13,7 @@ OpenThread KEYWORD1 DataSet KEYWORD1 ot_cmd_return_t KEYWORD1 ot_device_role_t KEYWORD1 +OnReceiveCb_t KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -59,6 +60,23 @@ stop KEYWORD2 networkInterfaceUp KEYWORD2 networkInterfaceDown KEYWORD2 commitDataSet KEYWORD2 +getInstance KEYWORD2 +getCurrentDataSet KEYWORD2 +getMeshLocalPrefix KEYWORD2 +getMeshLocalEid KEYWORD2 +getLeaderRloc KEYWORD2 +getRloc KEYWORD2 +getRloc16 KEYWORD2 +getUnicastAddressCount KEYWORD2 +getUnicastAddress KEYWORD2 +getAllUnicastAddresses KEYWORD2 +getMulticastAddressCount KEYWORD2 +getMulticastAddress KEYWORD2 +getAllMulticastAddresses KEYWORD2 +clearUnicastAddressCache KEYWORD2 +clearMulticastAddressCache KEYWORD2 +clearAllAddressCache KEYWORD2 +end KEYWORD2 ####################################### # Constants (LITERAL1) diff --git a/libraries/OpenThread/src/OThread.cpp b/libraries/OpenThread/src/OThread.cpp index 7714d870cce..87b748d104d 100644 --- a/libraries/OpenThread/src/OThread.cpp +++ b/libraries/OpenThread/src/OThread.cpp @@ -2,6 +2,8 @@ #if SOC_IEEE802154_SUPPORTED #if CONFIG_OPENTHREAD_ENABLED +#include "IPAddress.h" +#include #include "esp_err.h" #include "esp_event.h" #include "esp_netif.h" @@ -132,16 +134,29 @@ const otOperationalDataset &DataSet::getDataset() const { } void DataSet::setNetworkName(const char *name) { - strncpy(mDataset.mNetworkName.m8, name, sizeof(mDataset.mNetworkName.m8)); + if (!name) { + log_w("Network name is null"); + return; + } + // char m8[OT_NETWORK_KEY_SIZE + 1] bytes space by definition + strncpy(mDataset.mNetworkName.m8, name, OT_NETWORK_KEY_SIZE); mDataset.mComponents.mIsNetworkNamePresent = true; } void DataSet::setExtendedPanId(const uint8_t *extPanId) { + if (!extPanId) { + log_w("Extended PAN ID is null"); + return; + } memcpy(mDataset.mExtendedPanId.m8, extPanId, OT_EXT_PAN_ID_SIZE); mDataset.mComponents.mIsExtendedPanIdPresent = true; } void DataSet::setNetworkKey(const uint8_t *key) { + if (!key) { + log_w("Network key is null"); + return; + } memcpy(mDataset.mNetworkKey.m8, key, OT_NETWORK_KEY_SIZE); mDataset.mComponents.mIsNetworkKeyPresent = true; } @@ -181,10 +196,18 @@ void DataSet::apply(otInstance *instance) { } // OpenThread Implementation -bool OpenThread::otStarted = false; +bool OpenThread::otStarted; +otInstance *OpenThread::mInstance; +DataSet OpenThread::mCurrentDataset; +otNetworkKey OpenThread::mNetworkKey; -otInstance *OpenThread::mInstance = nullptr; -OpenThread::OpenThread() {} +OpenThread::OpenThread() { + // static initialization (node data and stack starting information) + otStarted = false; + mCurrentDataset.clear(); // Initialize the current dataset + memset(&mNetworkKey, 0, sizeof(mNetworkKey)); // Initialize the network key + mInstance = nullptr; +} OpenThread::~OpenThread() { end(); @@ -214,13 +237,7 @@ void OpenThread::begin(bool OThreadAutoStart) { return; } log_d("OpenThread task created successfully"); - // get the OpenThread instance that will be used for all operations - mInstance = esp_openthread_get_instance(); - if (!mInstance) { - log_e("Error: Failed to initialize OpenThread instance"); - end(); - return; - } + // starts Thread with default dataset from NVS or from IDF default settings if (OThreadAutoStart) { otOperationalDatasetTlvs dataset; @@ -238,23 +255,46 @@ void OpenThread::begin(bool OThreadAutoStart) { log_i("AUTO start OpenThread done"); } } + + // get the OpenThread instance that will be used for all operations + mInstance = esp_openthread_get_instance(); + if (!mInstance) { + log_e("Error: Failed to initialize OpenThread instance"); + end(); + return; + } + otStarted = true; } void OpenThread::end() { + if (!otStarted) { + log_w("OpenThread already stopped"); + return; + } + if (s_ot_task != NULL) { vTaskDelete(s_ot_task); s_ot_task = NULL; - // Clean up - esp_openthread_deinit(); - esp_openthread_netif_glue_deinit(); -#if CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM - ot_lwip_netif = NULL; -#endif + } + + // Clean up in reverse order of initialization + if (openthread_netif != NULL) { esp_netif_destroy(openthread_netif); - esp_vfs_eventfd_unregister(); + openthread_netif = NULL; } + + esp_openthread_netif_glue_deinit(); + esp_openthread_deinit(); + esp_vfs_eventfd_unregister(); + +#if CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM + ot_lwip_netif = NULL; +#endif + + mInstance = nullptr; otStarted = false; + log_d("OpenThread ended successfully"); } void OpenThread::start() { @@ -262,6 +302,7 @@ void OpenThread::start() { log_w("Error: OpenThread instance not initialized"); return; } + clearAllAddressCache(); // Clear cache when starting network otThreadSetEnabled(mInstance, true); log_d("Thread network started"); } @@ -271,6 +312,7 @@ void OpenThread::stop() { log_w("Error: OpenThread instance not initialized"); return; } + clearAllAddressCache(); // Clear cache when stopping network otThreadSetEnabled(mInstance, false); log_d("Thread network stopped"); } @@ -285,6 +327,7 @@ void OpenThread::networkInterfaceUp() { if (error != OT_ERROR_NONE) { log_e("Error: Failed to enable Thread interface (error code: %d)\n", error); } + clearAllAddressCache(); // Clear cache when interface comes up log_d("OpenThread Network Interface is up"); } @@ -312,6 +355,7 @@ void OpenThread::commitDataSet(const DataSet &dataset) { log_e("Error: Failed to commit dataset (error code: %d)\n", error); return; } + clearAllAddressCache(); // Clear cache when dataset changes log_d("Dataset committed successfully"); } @@ -360,6 +404,289 @@ void OpenThread::otPrintNetworkInformation(Stream &output) { output.println(); } +// Get the Node Network Name +String OpenThread::getNetworkName() const { + if (!mInstance) { + log_w("Error: OpenThread instance not initialized"); + return String(); // Return empty String, not nullptr + } + const char *networkName = otThreadGetNetworkName(mInstance); + return networkName ? String(networkName) : String(); +} + +// Get the Node Extended PAN ID +const uint8_t *OpenThread::getExtendedPanId() const { + if (!mInstance) { + log_w("Error: OpenThread instance not initialized"); + return nullptr; + } + const otExtendedPanId *extPanId = otThreadGetExtendedPanId(mInstance); + return extPanId ? extPanId->m8 : nullptr; +} + +// Get the Node Network Key +const uint8_t *OpenThread::getNetworkKey() const { + if (!mInstance) { + log_w("Error: OpenThread instance not initialized"); + return nullptr; + } + otThreadGetNetworkKey(mInstance, &mNetworkKey); + return mNetworkKey.m8; +} + +// Get the Node Channel +uint8_t OpenThread::getChannel() const { + if (!mInstance) { + log_w("Error: OpenThread instance not initialized"); + return 0; + } + return otLinkGetChannel(mInstance); +} + +// Get the Node PAN ID +uint16_t OpenThread::getPanId() const { + if (!mInstance) { + log_w("Error: OpenThread instance not initialized"); + return 0; + } + return otLinkGetPanId(mInstance); +} + +// Get the OpenThread instance +otInstance *OpenThread::getInstance() { + if (!mInstance) { + log_w("Error: OpenThread instance not initialized"); + return nullptr; + } + return mInstance; +} + +// Get the current dataset +const DataSet &OpenThread::getCurrentDataSet() const { + + if (!mInstance) { + log_w("Error: OpenThread instance not initialized"); + mCurrentDataset.clear(); + return mCurrentDataset; + } + + otOperationalDataset dataset; + otError error = otDatasetGetActive(mInstance, &dataset); + + if (error == OT_ERROR_NONE) { + mCurrentDataset.clear(); + + if (dataset.mComponents.mIsNetworkNamePresent) { + mCurrentDataset.setNetworkName(dataset.mNetworkName.m8); + } + if (dataset.mComponents.mIsExtendedPanIdPresent) { + mCurrentDataset.setExtendedPanId(dataset.mExtendedPanId.m8); + } + if (dataset.mComponents.mIsNetworkKeyPresent) { + mCurrentDataset.setNetworkKey(dataset.mNetworkKey.m8); + } + if (dataset.mComponents.mIsChannelPresent) { + mCurrentDataset.setChannel(dataset.mChannel); + } + if (dataset.mComponents.mIsPanIdPresent) { + mCurrentDataset.setPanId(dataset.mPanId); + } + } else { + log_w("Failed to get active dataset (error: %d)", error); + mCurrentDataset.clear(); + } + + return mCurrentDataset; +} + +// Get the Mesh Local Prefix +const otMeshLocalPrefix *OpenThread::getMeshLocalPrefix() const { + if (!mInstance) { + log_w("Error: OpenThread instance not initialized"); + return nullptr; + } + return otThreadGetMeshLocalPrefix(mInstance); +} + +// Get the Mesh-Local EID +IPAddress OpenThread::getMeshLocalEid() const { + if (!mInstance) { + log_w("Error: OpenThread instance not initialized"); + return IPAddress(IPv6); // Return empty IPv6 address + } + const otIp6Address *otAddr = otThreadGetMeshLocalEid(mInstance); + if (!otAddr) { + log_w("Failed to get Mesh Local EID"); + return IPAddress(IPv6); + } + return IPAddress(IPv6, otAddr->mFields.m8); +} + +// Get the Thread Leader RLOC +IPAddress OpenThread::getLeaderRloc() const { + if (!mInstance) { + log_w("Error: OpenThread instance not initialized"); + return IPAddress(IPv6); // Return empty IPv6 address + } + otIp6Address otAddr; + otError error = otThreadGetLeaderRloc(mInstance, &otAddr); + if (error != OT_ERROR_NONE) { + log_w("Failed to get Leader RLOC"); + return IPAddress(IPv6); + } + return IPAddress(IPv6, otAddr.mFields.m8); +} + +// Get the Node RLOC +IPAddress OpenThread::getRloc() const { + if (!mInstance) { + log_w("Error: OpenThread instance not initialized"); + return IPAddress(IPv6); // Return empty IPv6 address + } + const otIp6Address *otAddr = otThreadGetRloc(mInstance); + if (!otAddr) { + log_w("Failed to get Node RLOC"); + return IPAddress(IPv6); + } + return IPAddress(IPv6, otAddr->mFields.m8); +} + +// Get the RLOC16 ID +uint16_t OpenThread::getRloc16() const { + if (!mInstance) { + log_w("Error: OpenThread instance not initialized"); + return 0; + } + return otThreadGetRloc16(mInstance); +} + +// Populate unicast address cache from OpenThread +void OpenThread::populateUnicastAddressCache() const { + if (!mInstance) { + return; + } + + // Clear existing cache + mCachedUnicastAddresses.clear(); + + // Populate unicast addresses cache + const otNetifAddress *addr = otIp6GetUnicastAddresses(mInstance); + while (addr != nullptr) { + mCachedUnicastAddresses.push_back(IPAddress(IPv6, addr->mAddress.mFields.m8)); + addr = addr->mNext; + } + + log_d("Populated unicast address cache with %zu addresses", mCachedUnicastAddresses.size()); +} + +// Populate multicast address cache from OpenThread +void OpenThread::populateMulticastAddressCache() const { + if (!mInstance) { + return; + } + + // Clear existing cache + mCachedMulticastAddresses.clear(); + + // Populate multicast addresses cache + const otNetifMulticastAddress *mAddr = otIp6GetMulticastAddresses(mInstance); + while (mAddr != nullptr) { + mCachedMulticastAddresses.push_back(IPAddress(IPv6, mAddr->mAddress.mFields.m8)); + mAddr = mAddr->mNext; + } + + log_d("Populated multicast address cache with %zu addresses", mCachedMulticastAddresses.size()); +} + +// Clear unicast address cache +void OpenThread::clearUnicastAddressCache() const { + mCachedUnicastAddresses.clear(); + log_d("Cleared unicast address cache"); +} + +// Clear multicast address cache +void OpenThread::clearMulticastAddressCache() const { + mCachedMulticastAddresses.clear(); + log_d("Cleared multicast address cache"); +} + +// Clear all address caches +void OpenThread::clearAllAddressCache() const { + mCachedUnicastAddresses.clear(); + mCachedMulticastAddresses.clear(); + log_d("Cleared all address caches"); +} + +// Get count of unicast addresses +size_t OpenThread::getUnicastAddressCount() const { + // Populate cache if empty + if (mCachedUnicastAddresses.empty()) { + populateUnicastAddressCache(); + } + + return mCachedUnicastAddresses.size(); +} + +// Get unicast address by index +IPAddress OpenThread::getUnicastAddress(size_t index) const { + // Populate cache if empty + if (mCachedUnicastAddresses.empty()) { + populateUnicastAddressCache(); + } + + if (index >= mCachedUnicastAddresses.size()) { + log_w("Unicast address index %zu out of range (max: %zu)", index, mCachedUnicastAddresses.size()); + return IPAddress(IPv6); + } + + return mCachedUnicastAddresses[index]; +} + +// Get all unicast addresses +std::vector OpenThread::getAllUnicastAddresses() const { + // Populate cache if empty + if (mCachedUnicastAddresses.empty()) { + populateUnicastAddressCache(); + } + + return mCachedUnicastAddresses; // Return copy of cached vector +} + +// Get count of multicast addresses +size_t OpenThread::getMulticastAddressCount() const { + // Populate cache if empty + if (mCachedMulticastAddresses.empty()) { + populateMulticastAddressCache(); + } + + return mCachedMulticastAddresses.size(); +} + +// Get multicast address by index +IPAddress OpenThread::getMulticastAddress(size_t index) const { + // Populate cache if empty + if (mCachedMulticastAddresses.empty()) { + populateMulticastAddressCache(); + } + + if (index >= mCachedMulticastAddresses.size()) { + log_w("Multicast address index %zu out of range (max: %zu)", index, mCachedMulticastAddresses.size()); + return IPAddress(IPv6); + } + + return mCachedMulticastAddresses[index]; +} + +// Get all multicast addresses +std::vector OpenThread::getAllMulticastAddresses() const { + // Populate cache if empty + if (mCachedMulticastAddresses.empty()) { + populateMulticastAddressCache(); + } + + return mCachedMulticastAddresses; // Return copy of cached vector +} + OpenThread OThread; #endif /* CONFIG_OPENTHREAD_ENABLED */ diff --git a/libraries/OpenThread/src/OThread.h b/libraries/OpenThread/src/OThread.h index 359d581bb9d..6e21b854574 100644 --- a/libraries/OpenThread/src/OThread.h +++ b/libraries/OpenThread/src/OThread.h @@ -25,6 +25,8 @@ #include #include #include +#include "IPAddress.h" +#include typedef enum { OT_ROLE_DISABLED = 0, ///< The Thread stack is disabled. @@ -96,9 +98,68 @@ class OpenThread { // Set the dataset void commitDataSet(const DataSet &dataset); + // Get the Node Network Name + String getNetworkName() const; + + // Get the Node Extended PAN ID + const uint8_t *getExtendedPanId() const; + + // Get the Node Network Key + const uint8_t *getNetworkKey() const; + + // Get the Node Channel + uint8_t getChannel() const; + + // Get the Node PAN ID + uint16_t getPanId() const; + + // Get the OpenThread instance + otInstance *getInstance(); + + // Get the current dataset + const DataSet &getCurrentDataSet() const; + + // Get the Mesh Local Prefix + const otMeshLocalPrefix *getMeshLocalPrefix() const; + + // Get the Mesh-Local EID + IPAddress getMeshLocalEid() const; + + // Get the Thread Leader RLOC + IPAddress getLeaderRloc() const; + + // Get the Node RLOC + IPAddress getRloc() const; + + // Get the RLOC16 ID + uint16_t getRloc16() const; + + // Address management with caching + size_t getUnicastAddressCount() const; + IPAddress getUnicastAddress(size_t index) const; + std::vector getAllUnicastAddresses() const; + + size_t getMulticastAddressCount() const; + IPAddress getMulticastAddress(size_t index) const; + std::vector getAllMulticastAddresses() const; + + // Cache management + void clearUnicastAddressCache() const; + void clearMulticastAddressCache() const; + void clearAllAddressCache() const; + private: static otInstance *mInstance; - DataSet mCurrentDataSet; + static DataSet mCurrentDataset; // Current dataset being used by the OpenThread instance. + static otNetworkKey mNetworkKey; // Static storage to persist after function return + + // Address caching for performance (user-controlled) + mutable std::vector mCachedUnicastAddresses; + mutable std::vector mCachedMulticastAddresses; + + // Internal cache management + void populateUnicastAddressCache() const; + void populateMulticastAddressCache() const; }; extern OpenThread OThread; diff --git a/libraries/Wire/examples/WireSlaveFunctionalCallback/WireSlaveFunctionalCallback.ino b/libraries/Wire/examples/WireSlaveFunctionalCallback/WireSlaveFunctionalCallback.ino new file mode 100644 index 00000000000..a18fd2f023e --- /dev/null +++ b/libraries/Wire/examples/WireSlaveFunctionalCallback/WireSlaveFunctionalCallback.ino @@ -0,0 +1,37 @@ +// This example demonstrates the use of functional callbacks with the Wire library +// for I2C slave communication. It shows how to handle requests and data reception + +#include "Wire.h" + +#define I2C_DEV_ADDR 0x55 + +uint32_t i = 0; + +void setup() { + Serial.begin(115200); + Serial.setDebugOutput(true); + + Wire.onRequest([]() { + Wire.print(i++); + Wire.print(" Packets."); + Serial.println("onRequest"); + }); + + Wire.onReceive([](int len) { + Serial.printf("onReceive[%d]: ", len); + while (Wire.available()) { + Serial.write(Wire.read()); + } + Serial.println(); + }); + + Wire.begin((uint8_t)I2C_DEV_ADDR); + +#if CONFIG_IDF_TARGET_ESP32 + char message[64]; + snprintf(message, 64, "%lu Packets.", i++); + Wire.slaveWrite((uint8_t *)message, strlen(message)); +#endif +} + +void loop() {} diff --git a/libraries/Wire/examples/WireSlaveFunctionalCallback/ci.json b/libraries/Wire/examples/WireSlaveFunctionalCallback/ci.json new file mode 100644 index 00000000000..3c877975d62 --- /dev/null +++ b/libraries/Wire/examples/WireSlaveFunctionalCallback/ci.json @@ -0,0 +1,5 @@ +{ + "requires": [ + "CONFIG_SOC_I2C_SUPPORT_SLAVE=y" + ] +} diff --git a/libraries/Wire/src/Wire.cpp b/libraries/Wire/src/Wire.cpp index f8d9496389f..cda098d2d5b 100644 --- a/libraries/Wire/src/Wire.cpp +++ b/libraries/Wire/src/Wire.cpp @@ -39,7 +39,7 @@ extern "C" { #include "Arduino.h" TwoWire::TwoWire(uint8_t bus_num) - : num(bus_num & 1), sda(-1), scl(-1), bufferSize(I2C_BUFFER_LENGTH) // default Wire Buffer Size + : num(bus_num), sda(-1), scl(-1), bufferSize(I2C_BUFFER_LENGTH) // default Wire Buffer Size , rxBuffer(NULL), rxIndex(0), rxLength(0), txBuffer(NULL), txLength(0), txAddress(0), _timeOutMillis(50), nonStop(false) #if !CONFIG_DISABLE_HAL_LOCKS @@ -48,7 +48,7 @@ TwoWire::TwoWire(uint8_t bus_num) #endif #if SOC_I2C_SUPPORT_SLAVE , - is_slave(false), user_onRequest(NULL), user_onReceive(NULL) + is_slave(false), user_onRequest(nullptr), user_onReceive(nullptr) #endif /* SOC_I2C_SUPPORT_SLAVE */ { } @@ -62,6 +62,10 @@ TwoWire::~TwoWire() { #endif } +uint8_t TwoWire::getBusNum() { + return num; +} + bool TwoWire::initPins(int sdaPin, int sclPin) { if (sdaPin < 0) { // default param passed if (num == 0) { @@ -592,14 +596,14 @@ void TwoWire::flush() { //i2cFlush(num); // cleanup } -void TwoWire::onReceive(void (*function)(int)) { +void TwoWire::onReceive(const std::function &function) { #if SOC_I2C_SUPPORT_SLAVE user_onReceive = function; #endif } // sets function called on slave read -void TwoWire::onRequest(void (*function)(void)) { +void TwoWire::onRequest(const std::function &function) { #if SOC_I2C_SUPPORT_SLAVE user_onRequest = function; #endif diff --git a/libraries/Wire/src/Wire.h b/libraries/Wire/src/Wire.h index 0deab7d4a57..9cebdfaa304 100644 --- a/libraries/Wire/src/Wire.h +++ b/libraries/Wire/src/Wire.h @@ -48,10 +48,6 @@ #ifndef I2C_BUFFER_LENGTH #define I2C_BUFFER_LENGTH 128 // Default size, if none is set using Wire::setBuffersize(size_t) #endif -#if SOC_I2C_SUPPORT_SLAVE -typedef void (*user_onRequest)(void); -typedef void (*user_onReceive)(uint8_t *, int); -#endif /* SOC_I2C_SUPPORT_SLAVE */ class TwoWire : public HardwareI2C { protected: @@ -77,8 +73,8 @@ class TwoWire : public HardwareI2C { private: #if SOC_I2C_SUPPORT_SLAVE bool is_slave; - void (*user_onRequest)(void); - void (*user_onReceive)(int); + std::function user_onRequest; + std::function user_onReceive; static void onRequestService(uint8_t, void *); static void onReceiveService(uint8_t, uint8_t *, size_t, bool, void *); #endif /* SOC_I2C_SUPPORT_SLAVE */ @@ -105,6 +101,8 @@ class TwoWire : public HardwareI2C { bool end() override; + uint8_t getBusNum(); + bool setClock(uint32_t freq) override; void beginTransmission(uint8_t address) override; @@ -114,8 +112,8 @@ class TwoWire : public HardwareI2C { size_t requestFrom(uint8_t address, size_t len, bool stopBit) override; size_t requestFrom(uint8_t address, size_t len) override; - void onReceive(void (*)(int)) override; - void onRequest(void (*)(void)) override; + void onReceive(const std::function &) override; + void onRequest(const std::function &) override; //call setPins() first, so that begin() can be called without arguments from libraries bool setPins(int sda, int scl); diff --git a/libraries/Zigbee/examples/Zigbee_Fan_Control/README.md b/libraries/Zigbee/examples/Zigbee_Fan_Control/README.md new file mode 100644 index 00000000000..91700b669a0 --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_Fan_Control/README.md @@ -0,0 +1,83 @@ +# Arduino-ESP32 Zigbee Fan Control Example + +This example demonstrates how to use the Zigbee library to create a router device fan control and use it as a Home Automation (HA) fan control device. + +# Supported Targets + +Currently, this example supports the following targets. + +| Supported Targets | ESP32-C6 | ESP32-H2 | +| ----------------- | -------- | -------- | + +## Fan Control Functions + +1. Initialize a Zigbee fan control device. +2. Control fan modes (OFF, LOW, MEDIUM, HIGH, ON). +3. Respond to fan control commands from the Zigbee network. + +## Hardware Required + +* ESP32-H2 or ESP32-C6 development board +* A USB cable for power supply and programming +* RGB LED for visual feedback (built-in on most development boards) + +### Configure the Project + +In this example the RGB LED is used to indicate the current fan control mode. +The LED colors represent different fan modes: +- OFF: No light +- LOW: Blue +- MEDIUM: Yellow +- HIGH: Red +- ON: White + +Set the button GPIO by changing the `button` variable. By default, it's the pin `BOOT_PIN` (BOOT button on ESP32-C6 and ESP32-H2). + +#### Using Arduino IDE + +To get more information about the Espressif boards see [Espressif Development Kits](https://www.espressif.com/en/products/devkits). + +* Before Compile/Verify, select the correct board: `Tools -> Board`. +* Select the End device Zigbee mode: `Tools -> Zigbee mode: Zigbee ZCZR (coordinator/router)` +* Select Partition Scheme for Zigbee: `Tools -> Partition Scheme: Zigbee ZCZR 4MB with spiffs` +* Select the COM port: `Tools -> Port: xxx` where the `xxx` is the detected COM port. +* Optional: Set debug level to verbose to see all logs from Zigbee stack: `Tools -> Core Debug Level: Verbose`. + +## Troubleshooting + +If the End device flashed with this example is not connecting to the coordinator, erase the flash of the End device before flashing the example to the board. It is recommended to do this if you re-flash the coordinator. +You can do the following: + +* In the Arduino IDE go to the Tools menu and set `Erase All Flash Before Sketch Upload` to `Enabled`. +* Add to the sketch `Zigbee.factoryReset();` to reset the device and Zigbee stack. + +By default, the coordinator network is closed after rebooting or flashing new firmware. +To open the network you have 2 options: + +* Open network after reboot by setting `Zigbee.setRebootOpenNetwork(time);` before calling `Zigbee.begin();`. +* In application you can anytime call `Zigbee.openNetwork(time);` to open the network for devices to join. + + +***Important: Make sure you are using a good quality USB cable and that you have a reliable power source*** + +* **LED not blinking:** Check the wiring connection and the IO selection. +* **Programming Fail:** If the programming/flash procedure fails, try reducing the serial connection speed. +* **COM port not detected:** Check the USB cable and the USB to Serial driver installation. + +If the error persists, you can ask for help at the official [ESP32 forum](https://esp32.com) or see [Contribute](#contribute). + +## Contribute + +To know how to contribute to this project, see [How to contribute.](https://github.com/espressif/arduino-esp32/blob/master/CONTRIBUTING.rst) + +If you have any **feedback** or **issue** to report on this example/library, please open an issue or fix it by creating a new PR. Contributions are more than welcome! + +Before creating a new issue, be sure to try Troubleshooting and check if the same issue was already created by someone else. + +## Resources + +* Official ESP32 Forum: [Link](https://esp32.com) +* Arduino-ESP32 Official Repository: [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32) +* ESP32-C6 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf) +* ESP32-H2 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-h2_datasheet_en.pdf) +* Official ESP-IDF documentation: [ESP-IDF](https://idf.espressif.com) diff --git a/libraries/Zigbee/examples/Zigbee_Fan_Control/Zigbee_Fan_Control.ino b/libraries/Zigbee/examples/Zigbee_Fan_Control/Zigbee_Fan_Control.ino new file mode 100644 index 00000000000..4c0d15aa563 --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_Fan_Control/Zigbee_Fan_Control.ino @@ -0,0 +1,129 @@ +// Copyright 2025 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +/** + * @brief This example demonstrates simple Zigbee fan control. + * + * The example demonstrates how to use Zigbee library to create a router device fan control. + * The fan control is a Zigbee router device, which is controlled by a Zigbee coordinator. + * + * Proper Zigbee mode must be selected in Tools->Zigbee mode + * and also the correct partition scheme must be selected in Tools->Partition Scheme. + * + * Please check the README.md for instructions and more detailed description. + * + * Created by Jan Procházka (https://github.com/P-R-O-C-H-Y/) + */ + +#ifndef ZIGBEE_MODE_ZCZR +#error "Zigbee coordinator mode is not selected in Tools->Zigbee mode" +#endif + +#include "Zigbee.h" + +/* Zigbee light bulb configuration */ +#define ZIGBEE_FAN_CONTROL_ENDPOINT 1 + +#ifdef RGB_BUILTIN +uint8_t led = RGB_BUILTIN; // To demonstrate the current fan control mode +#else +uint8_t led = 2; +#endif + +uint8_t button = BOOT_PIN; + +ZigbeeFanControl zbFanControl = ZigbeeFanControl(ZIGBEE_FAN_CONTROL_ENDPOINT); + +/********************* fan control callback function **************************/ +void setFan(ZigbeeFanMode mode) { + switch (mode) { + case FAN_MODE_OFF: + rgbLedWrite(led, 0, 0, 0); // Off + Serial.println("Fan mode: OFF"); + break; + case FAN_MODE_LOW: + rgbLedWrite(led, 0, 0, 255); // Blue + Serial.println("Fan mode: LOW"); + break; + case FAN_MODE_MEDIUM: + rgbLedWrite(led, 255, 255, 0); // Yellow + Serial.println("Fan mode: MEDIUM"); + break; + case FAN_MODE_HIGH: + rgbLedWrite(led, 255, 0, 0); // Red + Serial.println("Fan mode: HIGH"); + break; + case FAN_MODE_ON: + rgbLedWrite(led, 255, 255, 255); // White + Serial.println("Fan mode: ON"); + break; + default: log_e("Unhandled fan mode: %d", mode); break; + } +} + +/********************* Arduino functions **************************/ +void setup() { + Serial.begin(115200); + + // Init LED that will be used to indicate the current fan control mode + rgbLedWrite(led, 0, 0, 0); + + // Init button for factory reset + pinMode(button, INPUT_PULLUP); + + //Optional: set Zigbee device name and model + zbFanControl.setManufacturerAndModel("Espressif", "ZBFanControl"); + + // Set the fan mode sequence to LOW_MED_HIGH + zbFanControl.setFanModeSequence(FAN_MODE_SEQUENCE_LOW_MED_HIGH); + + // Set callback function for fan mode change + zbFanControl.onFanModeChange(setFan); + + //Add endpoint to Zigbee Core + Serial.println("Adding ZigbeeFanControl endpoint to Zigbee Core"); + Zigbee.addEndpoint(&zbFanControl); + + // When all EPs are registered, start Zigbee in ROUTER mode + if (!Zigbee.begin(ZIGBEE_ROUTER)) { + Serial.println("Zigbee failed to start!"); + Serial.println("Rebooting..."); + ESP.restart(); + } + Serial.println("Connecting to network"); + while (!Zigbee.connected()) { + Serial.print("."); + delay(100); + } + Serial.println(); +} + +void loop() { + // Checking button for factory reset + if (digitalRead(button) == LOW) { // Push button pressed + // Key debounce handling + delay(100); + int startTime = millis(); + while (digitalRead(button) == LOW) { + delay(50); + if ((millis() - startTime) > 3000) { + // If key pressed for more than 3secs, factory reset Zigbee and reboot + Serial.println("Resetting Zigbee to factory and rebooting in 1s."); + delay(1000); + Zigbee.factoryReset(); + } + } + } + delay(100); +} diff --git a/libraries/Zigbee/examples/Zigbee_Fan_Control/ci.json b/libraries/Zigbee/examples/Zigbee_Fan_Control/ci.json new file mode 100644 index 00000000000..15d6190e4ae --- /dev/null +++ b/libraries/Zigbee/examples/Zigbee_Fan_Control/ci.json @@ -0,0 +1,6 @@ +{ + "fqbn_append": "PartitionScheme=zigbee_zczr,ZigbeeMode=zczr", + "requires": [ + "CONFIG_ZB_ENABLED=y" + ] +} diff --git a/libraries/Zigbee/keywords.txt b/libraries/Zigbee/keywords.txt index 556b6408ea2..23f3af3bf02 100644 --- a/libraries/Zigbee/keywords.txt +++ b/libraries/Zigbee/keywords.txt @@ -12,25 +12,31 @@ Zigbee KEYWORD1 ZigbeeEP KEYWORD1 # Endpoint Classes -ZigbeeLight KEYWORD1 -ZigbeeSwitch KEYWORD1 -ZigbeeColorDimmableLight KEYWORD1 -ZigbeeColorDimmerSwitch KEYWORD1 -ZigbeeTempSensor KEYWORD1 -ZigbeeThermostat KEYWORD1 -ZigbeeFlowSensor KEYWORD1 -ZigbeePressureSensor KEYWORD1 -ZigbeeOccupancySensor KEYWORD1 ZigbeeAnalog KEYWORD1 +ZigbeeBinary KEYWORD1 ZigbeeCarbonDioxideSensor KEYWORD1 +ZigbeeColorDimmableLight KEYWORD1 +ZigbeeColorDimmerSwitch KEYWORD1 ZigbeeContactSwitch KEYWORD1 +ZigbeeDimableLight KEYWORD1 ZigbeeDoorWindowHandle KEYWORD1 +ZigbeeElectricalMeasurement KEYWORD1 +ZigbeeFanControl KEYWORD1 +ZigbeeFlowSensor KEYWORD1 ZigbeeGateway KEYWORD1 +ZigbeeIlluminanceSensor KEYWORD1 +ZigbeeLight KEYWORD1 +ZigbeeOccupancySensor KEYWORD1 +ZigbeePM25Sensor KEYWORD1 +ZigbeePowerOutlet KEYWORD1 +ZigbeePressureSensor KEYWORD1 ZigbeeRangeExtender KEYWORD1 +ZigbeeSwitch KEYWORD1 +ZigbeeTempSensor KEYWORD1 +ZigbeeThermostat KEYWORD1 ZigbeeVibrationSensor KEYWORD1 ZigbeeWindowCovering KEYWORD1 -ZigbeeIlluminanceSensor KEYWORD1 -ZigbeePowerOutlet KEYWORD1 +ZigbeeWindSpeedSensor KEYWORD1 # Other zigbee_role_t KEYWORD1 @@ -39,6 +45,8 @@ zb_device_params_t KEYWORD1 zigbee_scan_result_t KEYWORD1 zb_power_source_t KEYWORD1 ZigbeeWindowCoveringType KEYWORD1 +ZigbeeFanMode KEYWORD1 +ZigbeeFanModeSequence KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -73,6 +81,7 @@ printBoundDevices KEYWORD2 getBoundDevices KEYWORD2 bound KEYWORD2 allowMultipleBinding KEYWORD2 +setManualBinding KEYWORD2 setManufacturerAndModel KEYWORD2 setPowerSource KEYWORD2 setBatteryPercentage KEYWORD2 @@ -80,6 +89,13 @@ reportBatteryPercentage KEYWORD2 readManufacturer KEYWORD2 readModel KEYWORD2 onIdentify KEYWORD2 +addTimeCluster KEYWORD2 +setTime KEYWORD2 +setTimezone KEYWORD2 +getTime KEYWORD2 +getTimezone KEYWORD2 +addOTAClient KEYWORD2 +clearBoundDevices KEYWORD2 # ZigbeeLight + ZigbeeColorDimmableLight onLightChange KEYWORD2 @@ -171,7 +187,7 @@ setTilted KEYWORD2 # ZigbeeVibrationSensor setVibration KEYWORD2 -ZigbeeWindowCovering +# ZigbeeWindowCovering onOpen KEYWORD2 onClose KEYWORD2 onGoToLiftPercentage KEYWORD2 @@ -186,6 +202,26 @@ setConfigStatus KEYWORD2 setMode KEYWORD2 setLimits KEYWORD2 +# ZigbeeBinary +addBinaryInput KEYWORD2 +addBinaryOutput KEYWORD2 +onBinaryOutputChange KEYWORD2 +setBinaryInput KEYWORD2 +setBinaryOutput KEYWORD2 +getBinaryOutput KEYWORD2 +reportBinaryInput KEYWORD2 +reportBinaryOutput KEYWORD2 +setBinaryInputApplication KEYWORD2 +setBinaryInputDescription KEYWORD2 +setBinaryOutputApplication KEYWORD2 +setBinaryOutputDescription KEYWORD2 + +# ZigbeeFanControl +setFanModeSequence KEYWORD2 +getFanMode KEYWORD2 +getFanModeSequence KEYWORD2 +onFanModeChange KEYWORD2 + ####################################### # Constants (LITERAL1) ####################################### diff --git a/libraries/Zigbee/src/Zigbee.h b/libraries/Zigbee/src/Zigbee.h index b2e2e5dd027..65c9e7f0daa 100644 --- a/libraries/Zigbee/src/Zigbee.h +++ b/libraries/Zigbee/src/Zigbee.h @@ -16,6 +16,7 @@ #include "ep/ZigbeeLight.h" //// Controllers #include "ep/ZigbeeThermostat.h" +#include "ep/ZigbeeFanControl.h" ////Outlets #include "ep/ZigbeePowerOutlet.h" //// Sensors diff --git a/libraries/Zigbee/src/ep/ZigbeeFanControl.cpp b/libraries/Zigbee/src/ep/ZigbeeFanControl.cpp new file mode 100644 index 00000000000..f4b32ce1200 --- /dev/null +++ b/libraries/Zigbee/src/ep/ZigbeeFanControl.cpp @@ -0,0 +1,60 @@ +#include "ZigbeeFanControl.h" +#if CONFIG_ZB_ENABLED + +ZigbeeFanControl::ZigbeeFanControl(uint8_t endpoint) : ZigbeeEP(endpoint) { + _device_id = ESP_ZB_HA_THERMOSTAT_DEVICE_ID; //There is no FAN_CONTROL_DEVICE_ID in the Zigbee spec + + //Create basic analog sensor clusters without configuration + _cluster_list = esp_zb_zcl_cluster_list_create(); + esp_zb_cluster_list_add_basic_cluster(_cluster_list, esp_zb_basic_cluster_create(NULL), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_identify_cluster(_cluster_list, esp_zb_identify_cluster_create(NULL), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_zb_cluster_list_add_fan_control_cluster(_cluster_list, esp_zb_fan_control_cluster_create(NULL), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + + _ep_config = { + .endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_HEATING_COOLING_UNIT_DEVICE_ID, .app_device_version = 0 + }; +} + +bool ZigbeeFanControl::setFanModeSequence(ZigbeeFanModeSequence sequence) { + esp_zb_attribute_list_t *fan_control_cluster = + esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_FAN_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + esp_err_t ret = esp_zb_cluster_update_attr(fan_control_cluster, ESP_ZB_ZCL_ATTR_FAN_CONTROL_FAN_MODE_SEQUENCE_ID, (void *)&sequence); + if (ret != ESP_OK) { + log_e("Failed to set min value: 0x%x: %s", ret, esp_err_to_name(ret)); + return false; + } + _current_fan_mode_sequence = sequence; + _current_fan_mode = FAN_MODE_OFF; + // Set initial fan mode to OFF + ret = esp_zb_cluster_update_attr(fan_control_cluster, ESP_ZB_ZCL_ATTR_FAN_CONTROL_FAN_MODE_ID, (void *)&_current_fan_mode); + if (ret != ESP_OK) { + log_e("Failed to set fan mode: 0x%x: %s", ret, esp_err_to_name(ret)); + return false; + } + return true; +} + +//set attribute method -> method overridden in child class +void ZigbeeFanControl::zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) { + //check the data and call right method + if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_FAN_CONTROL) { + if (message->attribute.id == ESP_ZB_ZCL_ATTR_FAN_CONTROL_FAN_MODE_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_8BIT_ENUM) { + _current_fan_mode = *(ZigbeeFanMode *)message->attribute.data.value; + fanModeChanged(); + } else { + log_w("Received message ignored. Attribute ID: %d not supported for Fan Control", message->attribute.id); + } + } else { + log_w("Received message ignored. Cluster ID: %d not supported for Fan Control", message->info.cluster); + } +} + +void ZigbeeFanControl::fanModeChanged() { + if (_on_fan_mode_change) { + _on_fan_mode_change(_current_fan_mode); + } else { + log_w("No callback function set for fan mode change"); + } +} + +#endif // CONFIG_ZB_ENABLED diff --git a/libraries/Zigbee/src/ep/ZigbeeFanControl.h b/libraries/Zigbee/src/ep/ZigbeeFanControl.h new file mode 100644 index 00000000000..25b5862c5c4 --- /dev/null +++ b/libraries/Zigbee/src/ep/ZigbeeFanControl.h @@ -0,0 +1,65 @@ +/* Class of Zigbee Pressure sensor endpoint inherited from common EP class */ + +#pragma once + +#include "soc/soc_caps.h" +#include "sdkconfig.h" +#if CONFIG_ZB_ENABLED + +#include "ZigbeeEP.h" +#include "ha/esp_zigbee_ha_standard.h" + +// Custom Arduino-friendly enums for fan mode values +enum ZigbeeFanMode { + FAN_MODE_OFF = ESP_ZB_ZCL_FAN_CONTROL_FAN_MODE_OFF, + FAN_MODE_LOW = ESP_ZB_ZCL_FAN_CONTROL_FAN_MODE_LOW, + FAN_MODE_MEDIUM = ESP_ZB_ZCL_FAN_CONTROL_FAN_MODE_MEDIUM, + FAN_MODE_HIGH = ESP_ZB_ZCL_FAN_CONTROL_FAN_MODE_HIGH, + FAN_MODE_ON = ESP_ZB_ZCL_FAN_CONTROL_FAN_MODE_ON, + FAN_MODE_AUTO = ESP_ZB_ZCL_FAN_CONTROL_FAN_MODE_AUTO, + FAN_MODE_SMART = ESP_ZB_ZCL_FAN_CONTROL_FAN_MODE_SMART, +}; + +// Custom Arduino-friendly enums for fan mode sequence +enum ZigbeeFanModeSequence { + FAN_MODE_SEQUENCE_LOW_MED_HIGH = ESP_ZB_ZCL_FAN_CONTROL_FAN_MODE_SEQUENCE_LOW_MED_HIGH, + FAN_MODE_SEQUENCE_LOW_HIGH = ESP_ZB_ZCL_FAN_CONTROL_FAN_MODE_SEQUENCE_LOW_HIGH, + FAN_MODE_SEQUENCE_LOW_MED_HIGH_AUTO = ESP_ZB_ZCL_FAN_CONTROL_FAN_MODE_SEQUENCE_LOW_MED_HIGH_AUTO, + FAN_MODE_SEQUENCE_LOW_HIGH_AUTO = ESP_ZB_ZCL_FAN_CONTROL_FAN_MODE_SEQUENCE_LOW_HIGH_AUTO, + FAN_MODE_SEQUENCE_ON_AUTO = ESP_ZB_ZCL_FAN_CONTROL_FAN_MODE_SEQUENCE_ON_AUTO, +}; + +class ZigbeeFanControl : public ZigbeeEP { +public: + ZigbeeFanControl(uint8_t endpoint); + ~ZigbeeFanControl() {} + + // Set the fan mode sequence value + bool setFanModeSequence(ZigbeeFanModeSequence sequence); + + // Use to get fan mode + ZigbeeFanMode getFanMode() { + return _current_fan_mode; + } + + // Use to get fan mode sequence + ZigbeeFanModeSequence getFanModeSequence() { + return _current_fan_mode_sequence; + } + + // On fan mode change callback + void onFanModeChange(void (*callback)(ZigbeeFanMode mode)) { + _on_fan_mode_change = callback; + } + +private: + void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) override; + //callback function to be called on fan mode change + void (*_on_fan_mode_change)(ZigbeeFanMode mode); + void fanModeChanged(); + + ZigbeeFanMode _current_fan_mode; + ZigbeeFanModeSequence _current_fan_mode_sequence; +}; + +#endif // CONFIG_ZB_ENABLED diff --git a/package/package_esp32_index.template.json b/package/package_esp32_index.template.json index bbb77f8ef5a..5f5d1f0e08b 100644 --- a/package/package_esp32_index.template.json +++ b/package/package_esp32_index.template.json @@ -81,7 +81,7 @@ { "packager": "esp32", "name": "esptool_py", - "version": "5.0.dev1" + "version": "5.0.0" }, { "packager": "esp32", @@ -469,56 +469,56 @@ }, { "name": "esptool_py", - "version": "5.0.dev1", + "version": "5.0.0", "systems": [ { "host": "aarch64-linux-gnu", - "url": "https://github.com/espressif/arduino-esp32/releases/download/3.2.0/esptool-v5.0.dev1-linux-aarch64.tar.gz", - "archiveFileName": "esptool-v5.0.dev1-linux-aarch64.tar.gz", - "checksum": "SHA-256:bfafa7a7723ebbabfd8b6e3ca5ae00bfead0331de923754aeddb43b2c116a078", - "size": "58241736" + "url": "https://github.com/espressif/esptool/releases/download/v5.0.0/esptool-v5.0.0-linux-aarch64.tar.gz", + "archiveFileName": "esptool-v5.0.0-linux-aarch64.tar.gz", + "checksum": "SHA-256:2bf239f3ed76141a957cadb205b94414ec6da9ace4e85f285e247d20a92b83e3", + "size": "58231895" }, { "host": "x86_64-pc-linux-gnu", - "url": "https://github.com/espressif/arduino-esp32/releases/download/3.2.0/esptool-v5.0.dev1-linux-amd64.tar.gz", - "archiveFileName": "esptool-v5.0.dev1-linux-amd64.tar.gz", - "checksum": "SHA-256:acd0486e96586b99d053a1479acbbbfcae8667227c831cdc53a171f9ccfa27ee", - "size": "100740042" + "url": "https://github.com/espressif/esptool/releases/download/v5.0.0/esptool-v5.0.0-linux-amd64.tar.gz", + "archiveFileName": "esptool-v5.0.0-linux-amd64.tar.gz", + "checksum": "SHA-256:3b3835d266ac61f3242758f2fe34e3b33dbe6ee4b5acde005da793356f9f7043", + "size": "100783748" }, { "host": "arm-linux-gnueabihf", - "url": "https://github.com/espressif/arduino-esp32/releases/download/3.2.0/esptool-v5.0.dev1-linux-armv7.tar.gz", - "archiveFileName": "esptool-v5.0.dev1-linux-armv7.tar.gz", - "checksum": "SHA-256:ea77a38681506761bbb7b0b39c130811ed565667b67ebbdb4d6dcc6cb6e07368", - "size": "53451939" + "url": "https://github.com/espressif/esptool/releases/download/v5.0.0/esptool-v5.0.0-linux-armv7.tar.gz", + "archiveFileName": "esptool-v5.0.0-linux-armv7.tar.gz", + "checksum": "SHA-256:e55cd321abecfcf27f72a2bff5d5e19a5365fd400de66d71c5e7218e77556315", + "size": "53461760" }, { "host": "x86_64-apple-darwin", - "url": "https://github.com/espressif/arduino-esp32/releases/download/3.2.0/esptool-v5.0.dev1-macos-amd64.tar.gz", - "archiveFileName": "esptool-v5.0.dev1-macos-amd64.tar.gz", - "checksum": "SHA-256:900a8e90731208bee96647e0e207a43612b9452c2120c4fdc0ff4c6be226257b", - "size": "59631998" + "url": "https://github.com/espressif/esptool/releases/download/v5.0.0/esptool-v5.0.0-macos-amd64.tar.gz", + "archiveFileName": "esptool-v5.0.0-macos-amd64.tar.gz", + "checksum": "SHA-256:424da2bdf0435257ad81bcb7eae6fd8dd7f675ce5b2ee60032f4ecec4d6a5d45", + "size": "59629533" }, { "host": "arm64-apple-darwin", - "url": "https://github.com/espressif/arduino-esp32/releases/download/3.2.0/esptool-v5.0.dev1-macos-arm64.tar.gz", - "archiveFileName": "esptool-v5.0.dev1-macos-arm64.tar.gz", - "checksum": "SHA-256:3653f4de73cb4fc6a25351eaf663708e91c65ae3265d75bd54ca4315a4350bb4", - "size": "56349992" + "url": "https://github.com/espressif/esptool/releases/download/v5.0.0/esptool-v5.0.0-macos-arm64.tar.gz", + "archiveFileName": "esptool-v5.0.0-macos-arm64.tar.gz", + "checksum": "SHA-256:b91dfe1da7b0041376683dec10a91dfb266fbda2fb86ed87c4a034ff7182ee56", + "size": "56343104" }, { "host": "x86_64-mingw32", - "url": "https://github.com/espressif/arduino-esp32/releases/download/3.2.0/esptool-v5.0.dev1-win64.zip", - "archiveFileName": "esptool-v5.0.dev1-win64.zip", - "checksum": "SHA-256:1e8fd89645daf94f2d4406ec73c9004e617ea921079515f9fd749205eece4d6d", - "size": "59102658" + "url": "https://github.com/espressif/esptool/releases/download/v5.0.0/esptool-v5.0.0-windows-amd64.zip", + "archiveFileName": "esptool-v5.0.0-windows-amd64.zip", + "checksum": "SHA-256:2294107f66db6f09b886b337728a981173c9e7eab45a030928a8a5a1370611ca", + "size": "59105322" }, { "host": "i686-mingw32", - "url": "https://github.com/espressif/arduino-esp32/releases/download/3.2.0/esptool-v5.0.dev1-win64.zip", - "archiveFileName": "esptool-v5.0.dev1-win64.zip", - "checksum": "SHA-256:1e8fd89645daf94f2d4406ec73c9004e617ea921079515f9fd749205eece4d6d", - "size": "59102658" + "url": "https://github.com/espressif/esptool/releases/download/v5.0.0/esptool-v5.0.0-windows-amd64.zip", + "archiveFileName": "esptool-v5.0.0-windows-amd64.zip", + "checksum": "SHA-256:2294107f66db6f09b886b337728a981173c9e7eab45a030928a8a5a1370611ca", + "size": "59105322" } ] }, diff --git a/tools/pioarduino-build.py b/tools/pioarduino-build.py index b67580e264c..4d4161dd9ca 100644 --- a/tools/pioarduino-build.py +++ b/tools/pioarduino-build.py @@ -95,7 +95,7 @@ def generate_bootloader_image(bootloader_elf): env.VerboseAction( " ".join( [ - '"$PYTHONEXE" "$OBJCOPY"', + "$OBJCOPY", "--chip", build_mcu, "elf2image", diff --git a/variants/deneyapkartv2/pins_arduino.h b/variants/deneyapkartv2/pins_arduino.h new file mode 100644 index 00000000000..f7eccadb13c --- /dev/null +++ b/variants/deneyapkartv2/pins_arduino.h @@ -0,0 +1,123 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include +#include "soc/soc_caps.h" + +#define USB_VID 0x303A +#define USB_PID 0x82EB +#define USB_MANUFACTURER "Turkish Technology Team Foundation (T3)" +#define USB_PRODUCT "DENEYAP KART v2" +#define USB_SERIAL "" // Empty string for MAC address + +static const uint8_t LED_BUILTIN = SOC_GPIO_PIN_COUNT + 46; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN +#define RGB_BUILTIN LED_BUILTIN +#define RGBLED LED_BUILTIN +#define RGB_BRIGHTNESS 64 + +static const uint8_t GPKEY = 0; +#define KEY_BUILTIN GPKEY +#define BUILTIN_KEY GPKEY + +static const uint8_t TX = 43; +static const uint8_t RX = 44; +#define TX1 TX +#define RX1 RX + +static const uint8_t SDA = 47; +static const uint8_t SCL = 21; + +static const uint8_t SS = 42; +static const uint8_t MOSI = 39; +static const uint8_t MISO = 40; +static const uint8_t SCK = 41; + +static const uint8_t A0 = 4; +static const uint8_t A1 = 5; +static const uint8_t A2 = 6; +static const uint8_t A3 = 7; +static const uint8_t A4 = 15; +static const uint8_t A5 = 16; +static const uint8_t A6 = 17; +static const uint8_t A7 = 18; +static const uint8_t A8 = 8; +static const uint8_t A9 = 9; +static const uint8_t A10 = 10; +static const uint8_t A11 = 11; +static const uint8_t A12 = 2; +static const uint8_t A13 = 1; +static const uint8_t A14 = 3; +static const uint8_t A15 = 12; +static const uint8_t A16 = 13; +static const uint8_t A17 = 14; + +static const uint8_t T0 = 4; +static const uint8_t T1 = 5; +static const uint8_t T2 = 6; +static const uint8_t T3 = 7; +static const uint8_t T4 = 8; +static const uint8_t T5 = 9; +static const uint8_t T6 = 10; +static const uint8_t T7 = 11; +static const uint8_t T8 = 2; +static const uint8_t T9 = 1; +static const uint8_t T10 = 3; +static const uint8_t T11 = 12; +static const uint8_t T12 = 13; +static const uint8_t T13 = 14; + +static const uint8_t D0 = 1; +static const uint8_t D1 = 2; +static const uint8_t D2 = 43; +static const uint8_t D3 = 44; +static const uint8_t D4 = 42; +static const uint8_t D5 = 41; +static const uint8_t D6 = 40; +static const uint8_t D7 = 39; +static const uint8_t D8 = 38; +static const uint8_t D9 = 48; +static const uint8_t D10 = 47; +static const uint8_t D11 = 21; +static const uint8_t D12 = 11; +static const uint8_t D13 = 10; +static const uint8_t D14 = 9; +static const uint8_t D15 = 8; +static const uint8_t D16 = 18; +static const uint8_t D17 = 17; +static const uint8_t D18 = 16; +static const uint8_t D19 = 15; +static const uint8_t D20 = 7; +static const uint8_t D21 = 6; +static const uint8_t D22 = 5; +static const uint8_t D23 = 4; +static const uint8_t D24 = 46; +static const uint8_t D25 = 0; +static const uint8_t D26 = 3; +static const uint8_t D27 = 12; +static const uint8_t D28 = 13; +static const uint8_t D29 = 14; + +static const uint8_t CAMSD = 4; +static const uint8_t CAMSC = 5; +static const uint8_t CAMD2 = 41; +static const uint8_t CAMD3 = 2; +static const uint8_t CAMD4 = 1; +static const uint8_t CAMD5 = 42; +static const uint8_t CAMD6 = 40; +static const uint8_t CAMD7 = 38; +static const uint8_t CAMD8 = 17; +static const uint8_t CAMD9 = 15; +static const uint8_t CAMPC = 39; +static const uint8_t CAMXC = 16; +static const uint8_t CAMH = 7; +static const uint8_t CAMV = 6; + +static const uint8_t SDCM = 12; +static const uint8_t SDCK = 13; +static const uint8_t SDDA = 14; + +static const uint8_t BAT = 3; + +#endif /* Pins_Arduino_h */ diff --git a/variants/rakwireless_rak3112/pins_arduino.h b/variants/rakwireless_rak3112/pins_arduino.h index 5d1e451494a..f1bcc7a6120 100644 --- a/variants/rakwireless_rak3112/pins_arduino.h +++ b/variants/rakwireless_rak3112/pins_arduino.h @@ -47,4 +47,17 @@ static const uint8_t SCK = 13; #define LORA_BUSY 48 #define LORA_IRQ LORA_DIO1 +// For WisBlock modules, see: https://docs.rakwireless.com/Product-Categories/WisBlock/ +#define WB_IO1 21 +#define WB_IO2 14 +#define WB_IO3 41 +#define WB_IO4 42 +#define WB_IO5 38 +#define WB_IO6 39 +#define WB_A0 1 +#define WB_A1 2 +#define WB_CS 12 +#define WB_LED1 46 +#define WB_LED2 45 + #endif /* Pins_Arduino_h */