Merge branch 'master' into release/v3.3.x

This commit is contained in:
Me No Dev 2025-06-20 12:25:31 +03:00 committed by GitHub
commit 05fbda2d6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 1428 additions and 266 deletions

129
.github/scripts/package_esptool.sh vendored Executable file
View file

@ -0,0 +1,129 @@
#!/bin/bash
set -euo pipefail
# Check version argument
if [[ $# -ne 3 ]]; then
echo "Usage: $0 <version> <base_folder> <json_path>"
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"

View file

@ -165,6 +165,7 @@ set(ARDUINO_LIBRARY_LittleFS_SRCS libraries/LittleFS/src/LittleFS.cpp)
set(ARDUINO_LIBRARY_NetBIOS_SRCS libraries/NetBIOS/src/NetBIOS.cpp)
set(ARDUINO_LIBRARY_OpenThread_SRCS
libraries/OpenThread/src/OThread.cpp
libraries/OpenThread/src/OThreadCLI.cpp
libraries/OpenThread/src/OThreadCLI_Util.cpp)

View file

@ -36262,12 +36262,12 @@ XIAO_ESP32S3_Plus.build.cdc_on_boot=1
XIAO_ESP32S3_Plus.build.msc_on_boot=0
XIAO_ESP32S3_Plus.build.dfu_on_boot=0
XIAO_ESP32S3_Plus.build.f_cpu=240000000L
XIAO_ESP32S3_Plus.build.flash_size=8MB
XIAO_ESP32S3_Plus.build.flash_size=16MB
XIAO_ESP32S3_Plus.build.flash_freq=80m
XIAO_ESP32S3_Plus.build.flash_mode=dio
XIAO_ESP32S3_Plus.build.boot=qio
XIAO_ESP32S3_Plus.build.boot_freq=80m
XIAO_ESP32S3_Plus.build.partitions=default_8MB
XIAO_ESP32S3_Plus.build.partitions=ffat
XIAO_ESP32S3_Plus.build.defines=
XIAO_ESP32S3_Plus.build.loop_core=
XIAO_ESP32S3_Plus.build.event_core=
@ -36304,8 +36304,6 @@ XIAO_ESP32S3_Plus.menu.FlashMode.dio.build.boot=dio
XIAO_ESP32S3_Plus.menu.FlashMode.dio.build.boot_freq=80m
XIAO_ESP32S3_Plus.menu.FlashMode.dio.build.flash_freq=80m
XIAO_ESP32S3_Plus.menu.FlashSize.8M=8MB (64Mb)
XIAO_ESP32S3_Plus.menu.FlashSize.8M.build.flash_size=8MB
XIAO_ESP32S3_Plus.menu.FlashSize.16M=16MB (128Mb)
XIAO_ESP32S3_Plus.menu.FlashSize.16M.build.flash_size=16MB

View file

@ -22,6 +22,9 @@
#include "soc/gpio_sig_map.h"
#include "esp_rom_gpio.h"
#include "hal/ledc_ll.h"
#if SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED
#include <math.h>
#endif
#ifdef SOC_LEDC_SUPPORT_HS_MODE
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM << 1)
@ -56,7 +59,7 @@ static bool find_matching_timer(uint8_t speed_mode, uint32_t freq, uint8_t resol
peripheral_bus_type_t type = perimanGetPinBusType(i);
if (type == ESP32_BUS_TYPE_LEDC) {
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(i, ESP32_BUS_TYPE_LEDC);
if (bus != NULL && (bus->channel / 8) == speed_mode && bus->freq_hz == freq && bus->channel_resolution == resolution) {
if (bus != NULL && (bus->channel / SOC_LEDC_CHANNEL_NUM) == speed_mode && bus->freq_hz == freq && bus->channel_resolution == resolution) {
log_d("Found matching timer %u for freq=%u, resolution=%u", bus->timer_num, freq, resolution);
*timer_num = bus->timer_num;
return true;
@ -78,7 +81,7 @@ static bool find_free_timer(uint8_t speed_mode, uint8_t *timer_num) {
peripheral_bus_type_t type = perimanGetPinBusType(i);
if (type == ESP32_BUS_TYPE_LEDC) {
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(i, ESP32_BUS_TYPE_LEDC);
if (bus != NULL && (bus->channel / 8) == speed_mode) {
if (bus != NULL && (bus->channel / SOC_LEDC_CHANNEL_NUM) == speed_mode) {
log_d("Timer %u is in use by channel %u", bus->timer_num, bus->channel);
used_timers |= (1 << bus->timer_num);
}
@ -110,7 +113,7 @@ static void remove_channel_from_timer(uint8_t speed_mode, uint8_t timer_num, uin
peripheral_bus_type_t type = perimanGetPinBusType(i);
if (type == ESP32_BUS_TYPE_LEDC) {
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(i, ESP32_BUS_TYPE_LEDC);
if (bus != NULL && (bus->channel / 8) == speed_mode && bus->timer_num == timer_num && bus->channel != channel) {
if (bus != NULL && (bus->channel / SOC_LEDC_CHANNEL_NUM) == speed_mode && bus->timer_num == timer_num && bus->channel != channel) {
log_d("Timer %u is still in use by channel %u", timer_num, bus->channel);
timer_in_use = true;
break;
@ -168,8 +171,8 @@ static bool ledcDetachBus(void *bus) {
}
pinMatrixOutDetach(handle->pin, false, false);
if (!channel_found) {
uint8_t group = (handle->channel / 8);
remove_channel_from_timer(group, handle->timer_num, handle->channel % 8);
uint8_t group = (handle->channel / SOC_LEDC_CHANNEL_NUM);
remove_channel_from_timer(group, handle->timer_num, handle->channel % SOC_LEDC_CHANNEL_NUM);
ledc_handle.used_channels &= ~(1UL << handle->channel);
}
free(handle);
@ -206,13 +209,13 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c
return false;
}
uint8_t group = (channel / 8);
uint8_t group = (channel / SOC_LEDC_CHANNEL_NUM);
uint8_t timer = 0;
bool channel_used = ledc_handle.used_channels & (1UL << channel);
if (channel_used) {
log_i("Channel %u is already set up, given frequency and resolution will be ignored", channel);
if (ledc_set_pin(pin, group, channel % 8) != ESP_OK) {
if (ledc_set_pin(pin, group, channel % SOC_LEDC_CHANNEL_NUM) != ESP_OK) {
log_e("Attaching pin to already used channel failed!");
return false;
}
@ -220,7 +223,7 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c
// Find a timer with matching frequency and resolution, or a free timer
if (!find_matching_timer(group, freq, resolution, &timer)) {
if (!find_free_timer(group, &timer)) {
log_e("No free timers available for speed mode %u", group);
log_w("No free timers available for speed mode %u", group);
return false;
}
@ -239,12 +242,12 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c
}
}
uint32_t duty = ledc_get_duty(group, (channel % 8));
uint32_t duty = ledc_get_duty(group, (channel % SOC_LEDC_CHANNEL_NUM));
ledc_channel_config_t ledc_channel;
memset((void *)&ledc_channel, 0, sizeof(ledc_channel_config_t));
ledc_channel.speed_mode = group;
ledc_channel.channel = (channel % 8);
ledc_channel.channel = (channel % SOC_LEDC_CHANNEL_NUM);
ledc_channel.timer_sel = timer;
ledc_channel.intr_type = LEDC_INTR_DISABLE;
ledc_channel.gpio_num = pin;
@ -274,7 +277,7 @@ bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t c
ledc_handle.used_channels |= 1UL << channel;
}
if (!perimanSetPinBus(pin, ESP32_BUS_TYPE_LEDC, (void *)handle, group, channel)) {
if (!perimanSetPinBus(pin, ESP32_BUS_TYPE_LEDC, (void *)handle, channel, timer)) {
ledcDetachBus((void *)handle);
return false;
}
@ -291,14 +294,40 @@ bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution) {
}
uint8_t channel = __builtin_ctz(free_channel); // Convert the free_channel bit to channel number
return ledcAttachChannel(pin, freq, resolution, channel);
// Try the first available channel
if (ledcAttachChannel(pin, freq, resolution, channel)) {
return true;
}
#ifdef SOC_LEDC_SUPPORT_HS_MODE
// If first attempt failed and HS mode is supported, try to find a free channel in group 1
if ((channel / SOC_LEDC_CHANNEL_NUM) == 0) { // First attempt was in group 0
log_d("LEDC: Group 0 channel %u failed, trying to find a free channel in group 1", channel);
// Find free channels specifically in group 1
uint32_t group1_mask = ((1UL << SOC_LEDC_CHANNEL_NUM) - 1) << SOC_LEDC_CHANNEL_NUM;
int group1_free_channel = (~ledc_handle.used_channels) & group1_mask;
if (group1_free_channel != 0) {
uint8_t group1_channel = __builtin_ctz(group1_free_channel);
if (ledcAttachChannel(pin, freq, resolution, group1_channel)) {
return true;
}
}
}
#endif
log_e(
"No free timers available for freq=%u, resolution=%u. To attach a new channel, use the same frequency and resolution as an already attached channel to "
"share its timer.",
freq, resolution
);
return false;
}
bool ledcWrite(uint8_t pin, uint32_t duty) {
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(pin, ESP32_BUS_TYPE_LEDC);
if (bus != NULL) {
uint8_t group = (bus->channel / 8), channel = (bus->channel % 8);
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM), channel = (bus->channel % SOC_LEDC_CHANNEL_NUM);
//Fixing if all bits in resolution is set = LEDC FULL ON
uint32_t max_duty = (1 << bus->channel_resolution) - 1;
@ -307,8 +336,14 @@ bool ledcWrite(uint8_t pin, uint32_t duty) {
duty = max_duty + 1;
}
ledc_set_duty(group, channel, duty);
ledc_update_duty(group, channel);
if (ledc_set_duty(group, channel, duty) != ESP_OK) {
log_e("ledc_set_duty failed");
return false;
}
if (ledc_update_duty(group, channel) != ESP_OK) {
log_e("ledc_update_duty failed");
return false;
}
return true;
}
@ -321,7 +356,11 @@ bool ledcWriteChannel(uint8_t channel, uint32_t duty) {
log_e("Channel %u is not available (maximum %u) or not used!", channel, LEDC_CHANNELS);
return false;
}
uint8_t group = (channel / 8), timer = ((channel / 2) % 4);
uint8_t group = (channel / SOC_LEDC_CHANNEL_NUM);
ledc_timer_t timer;
// Get the actual timer being used by this channel
ledc_ll_get_channel_timer(LEDC_LL_GET_HW(), group, (channel % SOC_LEDC_CHANNEL_NUM), &timer);
//Fixing if all bits in resolution is set = LEDC FULL ON
uint32_t resolution = 0;
@ -333,8 +372,14 @@ bool ledcWriteChannel(uint8_t channel, uint32_t duty) {
duty = max_duty + 1;
}
ledc_set_duty(group, channel, duty);
ledc_update_duty(group, channel);
if (ledc_set_duty(group, channel, duty) != ESP_OK) {
log_e("ledc_set_duty failed");
return false;
}
if (ledc_update_duty(group, channel) != ESP_OK) {
log_e("ledc_update_duty failed");
return false;
}
return true;
}
@ -343,7 +388,7 @@ uint32_t ledcRead(uint8_t pin) {
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(pin, ESP32_BUS_TYPE_LEDC);
if (bus != NULL) {
uint8_t group = (bus->channel / 8), channel = (bus->channel % 8);
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM), channel = (bus->channel % SOC_LEDC_CHANNEL_NUM);
return ledc_get_duty(group, channel);
}
return 0;
@ -355,8 +400,8 @@ uint32_t ledcReadFreq(uint8_t pin) {
if (!ledcRead(pin)) {
return 0;
}
uint8_t group = (bus->channel / 8), timer = ((bus->channel / 2) % 4);
return ledc_get_freq(group, timer);
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM);
return ledc_get_freq(group, bus->timer_num);
}
return 0;
}
@ -370,12 +415,12 @@ uint32_t ledcWriteTone(uint8_t pin, uint32_t freq) {
return 0;
}
uint8_t group = (bus->channel / 8), timer = ((bus->channel / 2) % 4);
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM);
ledc_timer_config_t ledc_timer;
memset((void *)&ledc_timer, 0, sizeof(ledc_timer_config_t));
ledc_timer.speed_mode = group;
ledc_timer.timer_num = timer;
ledc_timer.timer_num = bus->timer_num;
ledc_timer.duty_resolution = 10;
ledc_timer.freq_hz = freq;
ledc_timer.clk_cfg = clock_source;
@ -386,7 +431,7 @@ uint32_t ledcWriteTone(uint8_t pin, uint32_t freq) {
}
bus->channel_resolution = 10;
uint32_t res_freq = ledc_get_freq(group, timer);
uint32_t res_freq = ledc_get_freq(group, bus->timer_num);
ledcWrite(pin, 0x1FF);
return res_freq;
}
@ -427,12 +472,12 @@ uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution) {
log_e("LEDC pin %u - resolution is zero or it is too big (maximum %u)", pin, LEDC_MAX_BIT_WIDTH);
return 0;
}
uint8_t group = (bus->channel / 8), timer = ((bus->channel / 2) % 4);
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM);
ledc_timer_config_t ledc_timer;
memset((void *)&ledc_timer, 0, sizeof(ledc_timer_config_t));
ledc_timer.speed_mode = group;
ledc_timer.timer_num = timer;
ledc_timer.timer_num = bus->timer_num;
ledc_timer.duty_resolution = resolution;
ledc_timer.freq_hz = freq;
ledc_timer.clk_cfg = clock_source;
@ -442,7 +487,7 @@ uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution) {
return 0;
}
bus->channel_resolution = resolution;
return ledc_get_freq(group, timer);
return ledc_get_freq(group, bus->timer_num);
}
return 0;
}
@ -453,12 +498,14 @@ bool ledcOutputInvert(uint8_t pin, bool out_invert) {
gpio_set_level(pin, out_invert);
#ifdef CONFIG_IDF_TARGET_ESP32P4
esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT_PAD_OUT0_IDX + ((bus->channel) % 8), out_invert, 0);
esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT_PAD_OUT0_IDX + ((bus->channel) % SOC_LEDC_CHANNEL_NUM), out_invert, 0);
#else
#ifdef SOC_LEDC_SUPPORT_HS_MODE
esp_rom_gpio_connect_out_signal(pin, ((bus->channel / 8 == 0) ? LEDC_HS_SIG_OUT0_IDX : LEDC_LS_SIG_OUT0_IDX) + ((bus->channel) % 8), out_invert, 0);
esp_rom_gpio_connect_out_signal(
pin, ((bus->channel / SOC_LEDC_CHANNEL_NUM == 0) ? LEDC_HS_SIG_OUT0_IDX : LEDC_LS_SIG_OUT0_IDX) + ((bus->channel) % SOC_LEDC_CHANNEL_NUM), out_invert, 0
);
#else
esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT0_IDX + ((bus->channel) % 8), out_invert, 0);
esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT0_IDX + ((bus->channel) % SOC_LEDC_CHANNEL_NUM), out_invert, 0);
#endif
#endif // ifdef CONFIG_IDF_TARGET_ESP32P4
return true;
@ -505,7 +552,7 @@ static bool ledcFadeConfig(uint8_t pin, uint32_t start_duty, uint32_t target_dut
}
#endif
#endif
uint8_t group = (bus->channel / 8), channel = (bus->channel % 8);
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM), channel = (bus->channel % SOC_LEDC_CHANNEL_NUM);
// Initialize fade service.
if (!fade_initialized) {
@ -562,6 +609,161 @@ bool ledcFadeWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_
return ledcFadeConfig(pin, start_duty, target_duty, max_fade_time_ms, userFunc, arg);
}
#ifdef SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED
// Default gamma factor for gamma correction (common value for LEDs)
static float ledcGammaFactor = 2.8;
// Gamma correction LUT support
static const float *ledcGammaLUT = NULL;
static uint16_t ledcGammaLUTSize = 0;
// Global variable to store current resolution for gamma callback
static uint8_t ledcGammaResolution = 13;
bool ledcSetGammaTable(const float *gamma_table, uint16_t size) {
if (gamma_table == NULL || size == 0) {
log_e("Invalid gamma table or size");
return false;
}
ledcGammaLUT = gamma_table;
ledcGammaLUTSize = size;
log_i("Custom gamma LUT set with %u entries", size);
return true;
}
void ledcClearGammaTable(void) {
ledcGammaLUT = NULL;
ledcGammaLUTSize = 0;
log_i("Gamma LUT cleared, using mathematical calculation");
}
void ledcSetGammaFactor(float factor) {
ledcGammaFactor = factor;
}
// Gamma correction calculator function
static uint32_t ledcGammaCorrection(uint32_t duty) {
if (duty == 0) {
return 0;
}
uint32_t max_duty = (1U << ledcGammaResolution) - 1;
if (duty >= (1U << ledcGammaResolution)) {
return max_duty;
}
// Use LUT if provided, otherwise use mathematical calculation
if (ledcGammaLUT != NULL && ledcGammaLUTSize > 0) {
// LUT-based gamma correction
uint32_t lut_index = (duty * (ledcGammaLUTSize - 1)) / max_duty;
if (lut_index >= ledcGammaLUTSize) {
lut_index = ledcGammaLUTSize - 1;
}
float corrected_normalized = ledcGammaLUT[lut_index];
return (uint32_t)(corrected_normalized * max_duty);
} else {
// Mathematical gamma correction
double normalized = (double)duty / (1U << ledcGammaResolution);
double corrected = pow(normalized, ledcGammaFactor);
return (uint32_t)(corrected * (1U << ledcGammaResolution));
}
}
static bool ledcFadeGammaConfig(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg) {
ledc_channel_handle_t *bus = (ledc_channel_handle_t *)perimanGetPinBus(pin, ESP32_BUS_TYPE_LEDC);
if (bus != NULL) {
#ifndef SOC_LEDC_SUPPORT_FADE_STOP
#if !CONFIG_DISABLE_HAL_LOCKS
if (bus->lock == NULL) {
bus->lock = xSemaphoreCreateBinary();
if (bus->lock == NULL) {
log_e("xSemaphoreCreateBinary failed");
return false;
}
xSemaphoreGive(bus->lock);
}
//acquire lock
if (xSemaphoreTake(bus->lock, 0) != pdTRUE) {
log_e("LEDC Fade is still running on pin %u! SoC does not support stopping fade.", pin);
return false;
}
#endif
#endif
uint8_t group = (bus->channel / SOC_LEDC_CHANNEL_NUM), channel = (bus->channel % SOC_LEDC_CHANNEL_NUM);
// Initialize fade service.
if (!fade_initialized) {
ledc_fade_func_install(0);
fade_initialized = true;
}
bus->fn = (voidFuncPtr)userFunc;
bus->arg = arg;
ledc_cbs_t callbacks = {.fade_cb = ledcFnWrapper};
ledc_cb_register(group, channel, &callbacks, (void *)bus);
// Prepare gamma curve fade parameters
ledc_fade_param_config_t fade_params[SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX];
uint32_t actual_fade_ranges = 0;
// Use a moderate number of linear segments for smooth gamma curve
const uint32_t linear_fade_segments = 12;
// Set the global resolution for gamma correction
ledcGammaResolution = bus->channel_resolution;
// Fill multi-fade parameter list using ESP-IDF API
esp_err_t err = ledc_fill_multi_fade_param_list(
group, channel, start_duty, target_duty, linear_fade_segments, max_fade_time_ms, ledcGammaCorrection, SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX, fade_params,
&actual_fade_ranges
);
if (err != ESP_OK) {
log_e("ledc_fill_multi_fade_param_list failed: %s", esp_err_to_name(err));
return false;
}
// Apply the gamma-corrected start duty
uint32_t gamma_start_duty = ledcGammaCorrection(start_duty);
// Set multi-fade parameters
err = ledc_set_multi_fade(group, channel, gamma_start_duty, fade_params, actual_fade_ranges);
if (err != ESP_OK) {
log_e("ledc_set_multi_fade failed: %s", esp_err_to_name(err));
return false;
}
// Start the gamma curve fade
err = ledc_fade_start(group, channel, LEDC_FADE_NO_WAIT);
if (err != ESP_OK) {
log_e("ledc_fade_start failed: %s", esp_err_to_name(err));
return false;
}
log_d("Gamma curve fade started on pin %u: %u -> %u over %dms", pin, start_duty, target_duty, max_fade_time_ms);
} else {
log_e("Pin %u is not attached to LEDC. Call ledcAttach first!", pin);
return false;
}
return true;
}
bool ledcFadeGamma(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms) {
return ledcFadeGammaConfig(pin, start_duty, target_duty, max_fade_time_ms, NULL, NULL);
}
bool ledcFadeGammaWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, voidFuncPtr userFunc) {
return ledcFadeGammaConfig(pin, start_duty, target_duty, max_fade_time_ms, (voidFuncPtrArg)userFunc, NULL);
}
bool ledcFadeGammaWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg) {
return ledcFadeGammaConfig(pin, start_duty, target_duty, max_fade_time_ms, userFunc, arg);
}
#endif /* SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED */
static uint8_t analog_resolution = 8;
static int analog_frequency = 1000;
void analogWrite(uint8_t pin, int value) {

View file

@ -232,6 +232,85 @@ bool ledcFadeWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_dut
*/
bool ledcFadeWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg);
//Gamma Curve Fade functions - only available on supported chips
#ifdef SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED
/**
* @brief Set a custom gamma correction lookup table for gamma curve fading.
* The LUT should contain normalized values (0.0 to 1.0) representing
* the gamma-corrected brightness curve.
*
* @param gamma_table Pointer to array of float values (0.0 to 1.0)
* @param size Number of entries in the lookup table
*
* @return true if gamma table was successfully set, false otherwise.
*
* @note The LUT array must remain valid for as long as gamma fading is used.
* Larger tables provide smoother transitions but use more memory.
*/
bool ledcSetGammaTable(const float *gamma_table, uint16_t size);
/**
* @brief Clear the current gamma correction lookup table.
* After calling this, gamma correction will use mathematical
* calculation with the default gamma factor (2.8).
*/
void ledcClearGammaTable(void);
/**
* @brief Set the gamma factor for gamma correction.
*
* @param factor Gamma factor to use for gamma correction.
*/
void ledcSetGammaFactor(float factor);
/**
* @brief Setup and start a gamma curve fade on a given LEDC pin.
* Gamma correction makes LED brightness changes appear more gradual to human eyes.
*
* @param pin GPIO pin
* @param start_duty initial duty cycle of the fade
* @param target_duty target duty cycle of the fade
* @param max_fade_time_ms maximum fade time in milliseconds
*
* @return true if gamma fade was successfully set and started, false otherwise.
*
* @note This function is only available on ESP32 variants that support gamma curve fading.
*/
bool ledcFadeGamma(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms);
/**
* @brief Setup and start a gamma curve fade on a given LEDC pin with a callback function.
*
* @param pin GPIO pin
* @param start_duty initial duty cycle of the fade
* @param target_duty target duty cycle of the fade
* @param max_fade_time_ms maximum fade time in milliseconds
* @param userFunc callback function to be called after fade is finished
*
* @return true if gamma fade was successfully set and started, false otherwise.
*
* @note This function is only available on ESP32 variants that support gamma curve fading.
*/
bool ledcFadeGammaWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void));
/**
* @brief Setup and start a gamma curve fade on a given LEDC pin with a callback function and argument.
*
* @param pin GPIO pin
* @param start_duty initial duty cycle of the fade
* @param target_duty target duty cycle of the fade
* @param max_fade_time_ms maximum fade time in milliseconds
* @param userFunc callback function to be called after fade is finished
* @param arg argument to be passed to the callback function
*
* @return true if gamma fade was successfully set and started, false otherwise.
*
* @note This function is only available on ESP32 variants that support gamma curve fading.
*/
bool ledcFadeGammaWithInterruptArg(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void *), void *arg);
#endif // SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED
#ifdef __cplusplus
}
#endif

View file

@ -38,8 +38,8 @@ extern "C" {
#define HSPI 2 //SPI 2 bus normally mapped to pins 12 - 15, but can be matrixed to any pins
#define VSPI 3 //SPI 3 bus normally attached to pins 5, 18, 19 and 23, but can be matrixed to any pins
#else
#define FSPI 0
#define HSPI 1
#define FSPI 0 // ESP32C2, C3, C6, H2, S3, P4 - SPI 2 bus
#define HSPI 1 // ESP32S3, P4 - SPI 3 bus
#endif
// This defines are not representing the real Divider of the ESP32

View file

@ -9,28 +9,28 @@ Welcome to the compatibility guide for library developers aiming to support mult
Code Adaptations
----------------
To ensure compatibility with both versions of the ESP32 Arduino core, developers should utilize conditional compilation directives in their code. Below is an example of how to conditionally include code based on the ESP32 Arduino core version::
To ensure compatibility with both versions of the ESP32 Arduino core, developers should utilize conditional compilation directives in their code. Below is an example of how to conditionally include code based on the ESP32 Arduino core version:
.. code-block:: cpp
.. code-block:: cpp
#ifdef ESP_ARDUINO_VERSION_MAJOR
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
// Code for version 3.x
#else
// Code for version 2.x
#endif
#else
// Code for version 1.x
#endif
#ifdef ESP_ARDUINO_VERSION_MAJOR
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
// Code for version 3.x
#else
// Code for version 2.x
#endif
#else
// Code for version 1.x
#endif
Version Print
-------------
To easily print the ESP32 Arduino core version at runtime, developers can use the `ESP_ARDUINO_VERSION_STR` macro. Below is an example of how to print the ESP32 Arduino core version::
To easily print the ESP32 Arduino core version at runtime, developers can use the `ESP_ARDUINO_VERSION_STR` macro. Below is an example of how to print the ESP32 Arduino core version:
.. code-block:: cpp
.. code-block:: cpp
Serial.printf(" ESP32 Arduino core version: %s\n", ESP_ARDUINO_VERSION_STR);
Serial.printf(" ESP32 Arduino core version: %s\n", ESP_ARDUINO_VERSION_STR);
API Differences
---------------

View file

@ -0,0 +1,111 @@
/* LEDC Gamma Curve Fade Arduino Example
This example demonstrates gamma curve fading on ESP32 variants that support it.
Gamma correction makes LED brightness changes appear more gradual and natural
to human eyes compared to linear fading.
Two methods are supported:
1. Using a pre-computed Gamma Look-Up Table (LUT) for better performance
2. Using mathematical gamma correction with a gamma factor
Supported chips: ESP32-C6, ESP32-C5, ESP32-H2, ESP32-P4 and future chips with Gamma Fade support
Created by Jan Procházka (https://github.com/P-R-O-C-H-Y/)
*/
// use 12 bit precision for LEDC timer
#define LEDC_TIMER_12_BIT 12
// use 5000 Hz as a LEDC base frequency
#define LEDC_BASE_FREQ 5000
// define starting duty, target duty and maximum fade time
#define LEDC_START_DUTY (0)
#define LEDC_TARGET_DUTY (4095)
#define LEDC_FADE_TIME (2000)
// gamma factor for mathematical calculation
#define LEDC_GAMMA_FACTOR (2.6)
// use gamma LUT for better performance instead of mathematical calculation (gamma factor)
#define USE_GAMMA_LUT 1
// fade LED pins
const uint8_t ledPinR = 4;
const uint8_t ledPinG = 5;
const uint8_t ledPinB = 6;
uint8_t fade_ended = 0; // status of LED gamma fade
bool fade_in = true;
#ifdef USE_GAMMA_LUT
// Custom Gamma LUT demonstration with 101 steps (Brightness 0 - 100% gamma correction look up table (gamma = 2.6))
// Y = B ^ 2.6 - Pre-computed LUT to save runtime computation
static const float ledcGammaLUT[101] = {
0.000000, 0.000006, 0.000038, 0.000110, 0.000232, 0.000414, 0.000666, 0.000994, 0.001406, 0.001910, 0.002512, 0.003218, 0.004035, 0.004969, 0.006025,
0.007208, 0.008525, 0.009981, 0.011580, 0.013328, 0.015229, 0.017289, 0.019512, 0.021902, 0.024465, 0.027205, 0.030125, 0.033231, 0.036527, 0.040016,
0.043703, 0.047593, 0.051688, 0.055993, 0.060513, 0.065249, 0.070208, 0.075392, 0.080805, 0.086451, 0.092333, 0.098455, 0.104821, 0.111434, 0.118298,
0.125416, 0.132792, 0.140428, 0.148329, 0.156498, 0.164938, 0.173653, 0.182645, 0.191919, 0.201476, 0.211321, 0.221457, 0.231886, 0.242612, 0.253639,
0.264968, 0.276603, 0.288548, 0.300805, 0.313378, 0.326268, 0.339480, 0.353016, 0.366879, 0.381073, 0.395599, 0.410461, 0.425662, 0.441204, 0.457091,
0.473325, 0.489909, 0.506846, 0.524138, 0.541789, 0.559801, 0.578177, 0.596920, 0.616032, 0.635515, 0.655374, 0.675610, 0.696226, 0.717224, 0.738608,
0.760380, 0.782542, 0.805097, 0.828048, 0.851398, 0.875148, 0.899301, 0.923861, 0.948829, 0.974208, 1.000000,
};
#endif
void ARDUINO_ISR_ATTR LED_FADE_ISR() {
fade_ended += 1;
}
void setup() {
// Initialize serial communication at 115200 bits per second:
Serial.begin(115200);
// Setup timer with given frequency, resolution and attach it to a led pin with auto-selected channel
ledcAttach(ledPinR, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
ledcAttach(ledPinG, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
ledcAttach(ledPinB, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
#if USE_GAMMA_LUT // Use default gamma LUT for better performance
ledcSetGammaTable(ledcGammaLUT, 101);
#else // Use mathematical gamma correction (default, more flexible)
ledcSetGammaFactor(LEDC_GAMMA_FACTOR); // This is optional to set custom gamma factor (default is 2.8)
#endif
// Setup and start gamma curve fade on led (duty from 0 to 4095)
ledcFadeGamma(ledPinR, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME);
ledcFadeGamma(ledPinG, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME);
ledcFadeGamma(ledPinB, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME);
Serial.println("LED Gamma Fade on started.");
// Wait for fade to end
delay(LEDC_FADE_TIME);
// Setup and start gamma curve fade off led and use ISR (duty from 4095 to 0)
ledcFadeGammaWithInterrupt(ledPinR, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
ledcFadeGammaWithInterrupt(ledPinG, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
ledcFadeGammaWithInterrupt(ledPinB, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
Serial.println("LED Gamma Fade off started.");
}
void loop() {
// Check if fade_ended flag was set to true in ISR
if (fade_ended == 3) {
Serial.println("LED gamma fade ended");
fade_ended = 0;
// Check what gamma fade should be started next
if (fade_in) {
ledcFadeGammaWithInterrupt(ledPinR, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
ledcFadeGammaWithInterrupt(ledPinG, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
ledcFadeGammaWithInterrupt(ledPinB, LEDC_START_DUTY, LEDC_TARGET_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
Serial.println("LED Gamma Fade in started.");
fade_in = false;
} else {
ledcFadeGammaWithInterrupt(ledPinR, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
ledcFadeGammaWithInterrupt(ledPinG, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
ledcFadeGammaWithInterrupt(ledPinB, LEDC_TARGET_DUTY, LEDC_START_DUTY, LEDC_FADE_TIME, LED_FADE_ISR);
Serial.println("LED Gamma Fade out started.");
fade_in = true;
}
}
}

View file

@ -0,0 +1,5 @@
{
"requires": [
"CONFIG_SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED=y"
]
}

View file

@ -1,9 +1,177 @@
| Supported Targets | ESP32-C6 | ESP32-H2 |
| ----------------- | -------- | -------- |
# ESP32 Arduino OpenThreadCLI
# General View
The `OpenThreadCLI` class is an Arduino API for interacting with the OpenThread Command Line Interface (CLI). It allows you to manage and configure the Thread stack using a command-line interface.
This Arduino OpenThread Library allows using ESP OpenThread implementation using CLI and/or Native OpenThread API.
The Library implements 3 C++ Classes:
- `OThread` Class for Native OpenThread API
- `OThreadCLI` Class for CLI OpenThread API
- `DataSet` Class for OpenThread dataset manipulation using Native `OThread` Class
# ESP32 Arduino OpenThread Native
The `OThread` class provides methods for managing the OpenThread instance and controlling the Thread network. It allows you to initialize, start, stop, and manage the Thread network using native OpenThread APIs.
## Class Definition
```cpp
class OpenThread {
public:
static bool otStarted; // Indicates whether the OpenThread stack is running.
// Get the current Thread device role (e.g., Leader, Router, Child, etc.).
static ot_device_role_t otGetDeviceRole();
// Get the current Thread device role as a string.
static const char *otGetStringDeviceRole();
// Print network information (e.g., network name, channel, PAN ID) to the specified stream.
static void otPrintNetworkInformation(Stream &output);
OpenThread();
~OpenThread();
// Returns true if the OpenThread stack is running.
operator bool() const;
// Initialize the OpenThread stack.
static void begin(bool OThreadAutoStart = true);
// Deinitialize the OpenThread stack.
static void end();
// Start the Thread network.
void start();
// Stop the Thread network.
void stop();
// Bring up the Thread network interface (equivalent to "ifconfig up").
void networkInterfaceUp();
// Bring down the Thread network interface (equivalent to "ifconfig down").
void networkInterfaceDown();
// Commit a dataset to the OpenThread instance.
void commitDataSet(const DataSet &dataset);
private:
static otInstance *mInstance; // Pointer to the OpenThread instance.
DataSet mCurrentDataSet; // Current dataset being used by the OpenThread instance.
};
extern OpenThread OThread;
```
## Class Overview
The `OThread` class provides a simple and intuitive interface for managing the OpenThread stack and Thread network. It abstracts the complexity of the OpenThread APIs and provides Arduino-style methods for common operations.
## Public Methods
### Initialization and Deinitialization
- `begin(bool OThreadAutoStart = true)`: Initializes the OpenThread stack. If `OThreadAutoStart` is `true`, the Thread network will start automatically using NVS data.
- `end()`: Deinitializes the OpenThread stack and releases resources.
### Thread Network Control
- `start()`: Starts the Thread network. This is equivalent to the CLI command "thread start".
- `stop()`: Stops the Thread network. This is equivalent to the CLI command "thread stop".
### Network Interface Control
- `networkInterfaceUp()`: Brings up the Thread network interface. This is equivalent to the CLI command "ifconfig up".
- `networkInterfaceDown()`: Brings down the Thread network interface. This is equivalent to the CLI command "ifconfig down".
### Dataset Management
- `commitDataSet(const DataSet &dataset)`: Commits a dataset to the OpenThread instance. This is used to configure the Thread network with specific parameters (e.g., network name, channel, PAN ID).
### Network Information
- `otGetDeviceRole()`: Returns the current Thread device role as an `ot_device_role_t` enum (e.g., `OT_ROLE_LEADER`, `OT_ROLE_ROUTER`).
- `otGetStringDeviceRole()`: Returns the current Thread device role as a string (e.g., "Leader", "Router").
- `otPrintNetworkInformation(Stream &output)`: Prints the current network information (e.g., network name, channel, PAN ID) to the specified stream.
## Key Features
- **Initialization and Cleanup**: Easily initialize and deinitialize the OpenThread stack.
- **Network Control**: Start and stop the Thread network with simple method calls.
- **Dataset Management**: Configure the Thread network using the `DataSet` class and commit it to the OpenThread instance.
- **Network Information**: Retrieve and print the current network information and device role.
## Notes
- The `OThread` class is designed to simplify the use of OpenThread APIs in Arduino sketches.
- It works seamlessly with the DataSet class for managing Thread network configurations.
- Ensure that the OpenThread stack is initialized (`OThread.begin()`) before calling other methods.
This documentation provides a comprehensive overview of the `OThread` class, its methods, and example usage. It is designed to help developers quickly integrate OpenThread functionality into their Arduino projects.
# DataSet Class
The `DataSet` class provides a structured way to manage and configure Thread network datasets using native OpenThread APIs. It allows you to set and retrieve network parameters such as the network name, channel, PAN ID, and more. The `DataSet` class works seamlessly with the `OThread` class to apply these configurations to the OpenThread instance.
## Class Definition
```cpp
class DataSet {
public:
DataSet();
void clear();
void initNew();
const otOperationalDataset &getDataset() const;
// Setters
void setNetworkName(const char *name);
void setExtendedPanId(const uint8_t *extPanId);
void setNetworkKey(const uint8_t *key);
void setChannel(uint8_t channel);
void setPanId(uint16_t panId);
// Getters
const char *getNetworkName() const;
const uint8_t *getExtendedPanId() const;
const uint8_t *getNetworkKey() const;
uint8_t getChannel() const;
uint16_t getPanId() const;
// Apply the dataset to the OpenThread instance
void apply(otInstance *instance);
private:
otOperationalDataset mDataset; // Internal representation of the dataset
};
```
## Class Overview
The DataSet` class simplifies the management of Thread network datasets by providing intuitive methods for setting, retrieving, and applying network parameters. It abstracts the complexity of the OpenThread dataset APIs and provides Arduino-style methods for common operations.
## Public Methods
### Initialization
- `DataSet()`: Constructor that initializes an empty dataset.
- `void clear()`: Clears the dataset, resetting all fields to their default values.
- `void initNew()`: Initializes a new dataset with default values (equivalent to the CLI command dataset init new).
### Setters
- `void setNetworkName(const char *name)`: Sets the network name.
- `void setExtendedPanId(const uint8_t *extPanId)`: Sets the extended PAN ID.
- `void setNetworkKey(const uint8_t *key)`: Sets the network key.
- `void setChannel(uint8_t channel)`: Sets the channel.
- `void setPanId(uint16_t panId)`: Sets the PAN ID.
### Getters
- `const char *getNetworkName() const`: Retrieves the network name.
- `const uint8_t *getExtendedPanId() const`: Retrieves the extended PAN ID.
- `const uint8_t *getNetworkKey() const`: Retrieves the network key.
- `uint8_t getChannel() const`: Retrieves the channel.
- `uint16_t getPanId() const`: Retrieves the PAN ID.
### Dataset Application
- `void apply(otInstance *instance)`: Applies the dataset to the specified OpenThread instance.
## Key Features
- **Dataset Initialization**: Easily initialize a new dataset with default values using initNew().
- **Custom Configuration**: Set custom network parameters such as the network name, channel, and PAN ID using setter methods.
- **Dataset Application**: Apply the configured dataset to the OpenThread instance using apply().
** Notes
- The `DataSet` class is designed to work seamlessly with the `OThread` class for managing Thread network configurations.
- Ensure that the OpenThread stack is initialized (`OThread.begin()`) before applying a dataset.
- The initNew()`` method provides default values for the dataset, which can be customized using the setter methods.
This documentation provides a comprehensive overview of the `DataSet` class, its methods, and example usage. It is designed to help developers easily manage Thread network configurations in their Arduino projects.
# OpenThreadCLI Class
The `OpenThreadCLI` class is an Arduino API for interacting with the OpenThread Command Line Interface (CLI). It allows you to send commands to the OpenThread stack and receive responses. This class is designed to simplify the use of OpenThread CLI commands in Arduino sketches.
There is one main class called `OpenThreadCLI` and a global object used to operate OpenThread CLI, called `OThreadCLI`.\
Some [helper functions](helper_functions.md) were made available for working with the OpenThread CLI environment.
@ -20,7 +188,7 @@ Below are the details of the class:
class OpenThreadCLI : public Stream {
private:
static size_t setBuffer(QueueHandle_t &queue, size_t len);
bool otStarted = false;
static bool otCLIStarted = false;
public:
OpenThreadCLI();
@ -59,14 +227,35 @@ extern OpenThreadCLI OThreadCLI;
- You can customize the console behavior by adjusting parameters such as echoback and buffer sizes.
## Public Methods
### Initialization and Deinitialization
- `begin()`: Initializes the OpenThread stack (optional auto-start).
- `end()`: Deinitializes the OpenThread stack and releases resources.
### Console Management
- `startConsole(Stream& otStream, bool echoback = true, const char* prompt = "ot> ")`: Starts the OpenThread console with the specified stream, echoback option, and prompt.
- `stopConsole()`: Stops the OpenThread console.
- `setPrompt(char* prompt)`: Changes the console prompt (set to NULL for an empty prompt).
- `setEchoBack(bool echoback)`: Changes the console echoback option.
- `setStream(Stream& otStream)`: Changes the console Stream object.
- `onReceive(OnReceiveCb_t func)`: Sets a callback function to handle complete lines of output from the OT CLI.
- `begin(bool OThreadAutoStart = true)`: Initializes the OpenThread stack (optional auto-start).
- `end()`: Deinitializes the OpenThread stack.
### Buffer Management
- `setTxBufferSize(size_t tx_queue_len)`: Sets the transmit buffer size (default is 256 bytes).
- `setRxBufferSize(size_t rx_queue_len)`: Sets the receive buffer size (default is 1024 bytes).
- `write(uint8_t)`, `available()`, `read()`, `peek()`, `flush()`: Standard Stream methods implementation for OpenThread CLI object.
### Stream Methods
- `write(uint8_t)`: Writes a byte to the CLI.
- `available()`: Returns the number of bytes available to read.
- `read()`: Reads a byte from the CLI.
- `peek()`: Returns the next byte without removing it from the buffer.
- `flush()`: Flushes the CLI buffer.
## Key Features
- **Arduino Stream Compatibility**: Inherits from the Stream class, making it compatible with Arduino's standard I/O functions.
- **Customizable Console**: Allows customization of the CLI prompt, echoback behavior, and buffer sizes.
- **Callback Support**: Provides a callback mechanism to handle CLI responses asynchronously.
- **Seamless Integration**: Designed to work seamlessly with the OThread and DataSet classes
## Notes
- The `OThreadCLI` class is designed to simplify the use of OpenThread CLI commands in Arduino sketches.
- It works seamlessly with the `OThread` and `DataSet` classes for managing Thread networks.
- Ensure that the OpenThread stack is initialized (`OThreadCLI.begin()`) before starting the CLI console.
This documentation provides a comprehensive overview of the `OThreadCLI` class, its methods, and example usage. It is designed to help developers easily integrate OpenThread CLI functionality into their Arduino projects.

View file

@ -75,18 +75,18 @@ bool otDeviceSetup(const char **otSetupCmds, uint8_t nCmds1, const char **otCoap
Serial.println("OpenThread started.\r\nWaiting for activating correct Device Role.");
// wait for the expected Device Role to start
uint8_t tries = 24; // 24 x 2.5 sec = 1 min
while (tries && otGetDeviceRole() != expectedRole) {
while (tries && OThread.otGetDeviceRole() != expectedRole) {
Serial.print(".");
delay(2500);
tries--;
}
Serial.println();
if (!tries) {
log_e("Sorry, Device Role failed by timeout! Current Role: %s.", otGetStringDeviceRole());
log_e("Sorry, Device Role failed by timeout! Current Role: %s.", OThread.otGetStringDeviceRole());
rgbLedWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
return false;
}
Serial.printf("Device is %s.\r\n", otGetStringDeviceRole());
Serial.printf("Device is %s.\r\n", OThread.otGetStringDeviceRole());
for (i = 0; i < nCmds2; i++) {
if (!otExecCommand(otCoapCmds[i * 2], otCoapCmds[i * 2 + 1])) {
break;
@ -151,7 +151,8 @@ void setup() {
Serial.begin(115200);
// LED starts RED, indicating not connected to Thread network.
rgbLedWrite(RGB_BUILTIN, 64, 0, 0);
OThreadCLI.begin(false); // No AutoStart is necessary
OThread.begin(false); // No AutoStart is necessary
OThreadCLI.begin();
OThreadCLI.setTimeout(250); // waits 250ms for the OpenThread CLI response
setupNode();
// LED goes Green when all is ready and Red when failed.

View file

@ -69,18 +69,18 @@ bool otDeviceSetup(
Serial.println("OpenThread started.\r\nWaiting for activating correct Device Role.");
// wait for the expected Device Role to start
uint8_t tries = 24; // 24 x 2.5 sec = 1 min
while (tries && otGetDeviceRole() != expectedRole1 && otGetDeviceRole() != expectedRole2) {
while (tries && OThread.otGetDeviceRole() != expectedRole1 && OThread.otGetDeviceRole() != expectedRole2) {
Serial.print(".");
delay(2500);
tries--;
}
Serial.println();
if (!tries) {
log_e("Sorry, Device Role failed by timeout! Current Role: %s.", otGetStringDeviceRole());
log_e("Sorry, Device Role failed by timeout! Current Role: %s.", OThread.otGetStringDeviceRole());
rgbLedWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
return false;
}
Serial.printf("Device is %s.\r\n", otGetStringDeviceRole());
Serial.printf("Device is %s.\r\n", OThread.otGetStringDeviceRole());
for (i = 0; i < nCmds2; i++) {
if (!otExecCommand(otCoapCmds[i * 2], otCoapCmds[i * 2 + 1])) {
break;
@ -176,7 +176,8 @@ void setup() {
Serial.begin(115200);
// LED starts RED, indicating not connected to Thread network.
rgbLedWrite(RGB_BUILTIN, 64, 0, 0);
OThreadCLI.begin(false); // No AutoStart is necessary
OThread.begin(false); // No AutoStart is necessary
OThreadCLI.begin();
OThreadCLI.setTimeout(250); // waits 250ms for the OpenThread CLI response
setupNode();
// LED goes and keeps Blue when all is ready and Red when failed.

View file

@ -1,4 +1,4 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
// 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.
@ -26,7 +26,8 @@
void setup() {
Serial.begin(115200);
OThreadCLI.begin(false); // No AutoStart - fresh start
OThread.begin(false); // No AutoStart - fresh start
OThreadCLI.begin();
Serial.println("OpenThread CLI started - type 'help' for a list of commands.");
OThreadCLI.startConsole(Serial);
}

View file

@ -1,4 +1,4 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
// 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.
@ -35,12 +35,13 @@
void setup() {
Serial.begin(115200);
OThreadCLI.begin(); // AutoStart using Thread default settings
otPrintNetworkInformation(Serial); // Print Current Thread Network Information
OThread.begin(); // AutoStart using Thread default settings
OThreadCLI.begin();
OThread.otPrintNetworkInformation(Serial); // Print Current Thread Network Information
}
void loop() {
Serial.print("Thread Node State: ");
Serial.println(otGetStringDeviceRole());
Serial.println(OThread.otGetStringDeviceRole());
delay(5000);
}

View file

@ -1,4 +1,4 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
// 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.
@ -22,7 +22,8 @@ bool otStatus = true;
void setup() {
Serial.begin(115200);
OThreadCLI.begin(false); // No AutoStart - fresh start
OThread.begin(false); // No AutoStart - fresh start
OThreadCLI.begin();
Serial.println("Setting up OpenThread Node as Router/Child");
Serial.println("Make sure the Leader Node is already running");
@ -39,7 +40,7 @@ void setup() {
}
// wait for the node to enter in the router state
uint32_t timeout = millis() + 90000; // waits 90 seconds to
while (otGetDeviceRole() != OT_ROLE_CHILD && otGetDeviceRole() != OT_ROLE_ROUTER) {
while (OThread.otGetDeviceRole() != OT_ROLE_CHILD && OThread.otGetDeviceRole() != OT_ROLE_ROUTER) {
Serial.print(".");
if (millis() > timeout) {
Serial.println("\r\n\t===> Timeout! Failed.");
@ -70,7 +71,7 @@ void loop() {
if (otStatus) {
Serial.println("Thread NetworkInformation: ");
Serial.println("---------------------------");
otPrintNetworkInformation(Serial);
OThread.otPrintNetworkInformation(Serial);
Serial.println("---------------------------");
} else {
Serial.println("Some OpenThread operation has failed...");

View file

@ -1,4 +1,4 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
// 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.
@ -35,7 +35,8 @@ otInstance *aInstance = NULL;
void setup() {
Serial.begin(115200);
OThreadCLI.begin(false); // No AutoStart - fresh start
OThread.begin(false); // No AutoStart - fresh start
OThreadCLI.begin();
Serial.println();
Serial.println("Setting up OpenThread Node as Leader");
aInstance = esp_openthread_get_instance();
@ -51,11 +52,11 @@ void setup() {
void loop() {
Serial.println("=============================================");
Serial.print("Thread Node State: ");
Serial.println(otGetStringDeviceRole());
Serial.println(OThread.otGetStringDeviceRole());
// Native OpenThread API calls:
// wait until the node become Child or Router
if (otGetDeviceRole() == OT_ROLE_LEADER) {
if (OThread.otGetDeviceRole() == OT_ROLE_LEADER) {
// Network Name
const char *networkName = otThreadGetNetworkName(aInstance);
Serial.printf("Network Name: %s\r\n", networkName);

View file

@ -1,4 +1,4 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
// 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.
@ -34,7 +34,8 @@ otInstance *aInstance = NULL;
void setup() {
Serial.begin(115200);
OThreadCLI.begin(false); // No AutoStart - fresh start
OThread.begin(false); // No AutoStart - fresh start
OThreadCLI.begin();
Serial.println();
Serial.println("Setting up OpenThread Node as Router/Child");
Serial.println("Make sure the Leader Node is already running");
@ -51,11 +52,11 @@ void setup() {
void loop() {
Serial.println("=============================================");
Serial.print("Thread Node State: ");
Serial.println(otGetStringDeviceRole());
Serial.println(OThread.otGetStringDeviceRole());
// Native OpenThread API calls:
// wait until the node become Child or Router
if (otGetDeviceRole() == OT_ROLE_CHILD || otGetDeviceRole() == OT_ROLE_ROUTER) {
if (OThread.otGetDeviceRole() == OT_ROLE_CHILD || OThread.otGetDeviceRole() == OT_ROLE_ROUTER) {
// Network Name
const char *networkName = otThreadGetNetworkName(aInstance);
Serial.printf("Network Name: %s\r\n", networkName);

View file

@ -1,4 +1,4 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
// 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.
@ -27,7 +27,8 @@
void setup() {
Serial.begin(115200);
OThreadCLI.begin(true); // For scanning, AutoStart must be active, any setup
OThread.begin(true); // For scanning, AutoStart must be active, any setup
OThreadCLI.begin();
OThreadCLI.setTimeout(100); // Set a timeout for the CLI response
Serial.println();
Serial.println("This sketch will continuously scan the Thread Local Network and all devices IEEE 802.15.4 compatible");
@ -41,7 +42,7 @@ void loop() {
Serial.println("Scan Failed...");
}
delay(5000);
if (otGetDeviceRole() < OT_ROLE_CHILD) {
if (OThread.otGetDeviceRole() < OT_ROLE_CHILD) {
Serial.println();
Serial.println("This device has not started Thread yet, bypassing Discovery Scan");
return;

View file

@ -39,7 +39,8 @@ void otReceivedLine() {
void setup() {
Serial.begin(115200);
OThreadCLI.begin(); // AutoStart
OThread.begin(); // AutoStart
OThreadCLI.begin();
OThreadCLI.onReceive(otReceivedLine);
}

View file

@ -0,0 +1,34 @@
#include "OThread.h"
OpenThread threadLeaderNode;
DataSet dataset;
void setup() {
Serial.begin(115200);
// Start OpenThread Stack - false for not using NVS dataset information
threadLeaderNode.begin(false);
// Create a new Thread Network Dataset for a Leader Node
dataset.initNew();
// Configure the dataset
dataset.setNetworkName("ESP_OpenThread");
uint8_t extPanId[OT_EXT_PAN_ID_SIZE] = {0xDE, 0xAD, 0x00, 0xBE, 0xEF, 0x00, 0xCA, 0xFE};
dataset.setExtendedPanId(extPanId);
uint8_t networkKey[OT_NETWORK_KEY_SIZE] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
dataset.setNetworkKey(networkKey);
dataset.setChannel(15);
dataset.setPanId(0x1234);
// Apply the dataset and start the network
threadLeaderNode.commitDataSet(dataset);
threadLeaderNode.networkInterfaceUp();
threadLeaderNode.start();
}
void loop() {
// Print network information every 5 seconds
Serial.println("==============================================");
threadLeaderNode.otPrintNetworkInformation(Serial);
delay(5000);
}

View file

@ -0,0 +1,6 @@
{
"requires": [
"CONFIG_OPENTHREAD_ENABLED=y",
"CONFIG_SOC_IEEE802154_SUPPORTED=y"
]
}

View file

@ -0,0 +1,29 @@
#include "OThread.h"
OpenThread threadChildNode;
DataSet dataset;
void setup() {
Serial.begin(115200);
// Start OpenThread Stack - false for not using NVS dataset information
threadChildNode.begin(false);
// clear dataset
dataset.clear();
// Configure the dataset with the same Network Key of the Leader Node
uint8_t networkKey[OT_NETWORK_KEY_SIZE] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
dataset.setNetworkKey(networkKey);
// Apply the dataset and start the network
threadChildNode.commitDataSet(dataset);
threadChildNode.networkInterfaceUp();
threadChildNode.start();
}
void loop() {
// Print network information every 5 seconds
Serial.println("==============================================");
threadChildNode.otPrintNetworkInformation(Serial);
delay(5000);
}

View file

@ -0,0 +1,6 @@
{
"requires": [
"CONFIG_OPENTHREAD_ENABLED=y",
"CONFIG_SOC_IEEE802154_SUPPORTED=y"
]
}

View file

@ -7,7 +7,10 @@
#######################################
OThreadCLI KEYWORD1
OThread KEYWORD1
OpenThreadCLI KEYWORD1
OpenThread KEYWORD1
DataSet KEYWORD1
ot_cmd_return_t KEYWORD1
ot_device_role_t KEYWORD1
@ -35,6 +38,27 @@ otGetRespCmd KEYWORD2
otExecCommand KEYWORD2
otPrintRespCLI KEYWORD2
otPrintNetworkInformation KEYWORD2
clear KEYWORD2
initNew KEYWORD2
getDataset KEYWORD2
setNetworkName KEYWORD2
getNetworkName KEYWORD2
setExtendedPanId KEYWORD2
getExtendedPanId KEYWORD2
setNetworkKey KEYWORD2
getNetworkKey KEYWORD2
setChannel KEYWORD2
getChannel KEYWORD2
setPanId KEYWORD2
getPanId KEYWORD2
apply KEYWORD2
otStarted KEYWORD2
otCLIStarted KEYWORD2
start KEYWORD2
stop KEYWORD2
networkInterfaceUp KEYWORD2
networkInterfaceDown KEYWORD2
commitDataSet KEYWORD2
#######################################
# Constants (LITERAL1)
@ -45,3 +69,5 @@ OT_ROLE_DETACHED LITERAL1
OT_ROLE_CHILD LITERAL1
OT_ROLE_ROUTER LITERAL1
OT_ROLE_LEADER LITERAL1
OT_EXT_PAN_ID_SIZE LITERAL1
OT_NETWORK_KEY_SIZE LITERAL1

View file

@ -0,0 +1,366 @@
#include "OThread.h"
#if SOC_IEEE802154_SUPPORTED
#if CONFIG_OPENTHREAD_ENABLED
#include "esp_err.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_netif_types.h"
#include "esp_vfs_eventfd.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "esp_netif_net_stack.h"
#include "esp_openthread_netif_glue.h"
#include "lwip/netif.h"
static esp_openthread_platform_config_t ot_native_config;
static esp_netif_t *openthread_netif = NULL;
const char *otRoleString[] = {
"Disabled", ///< The Thread stack is disabled.
"Detached", ///< Not currently participating in a Thread network/partition.
"Child", ///< The Thread Child role.
"Router", ///< The Thread Router role.
"Leader", ///< The Thread Leader role.
"Unknown", ///< Unknown role, not initialized or not started.
};
static TaskHandle_t s_ot_task = NULL;
#if CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM
static struct netif *ot_lwip_netif = NULL;
#endif
#if CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM
extern "C" int lwip_hook_ip6_input(struct pbuf *p, struct netif *inp) {
if (ot_lwip_netif && ot_lwip_netif == inp) {
return 0;
}
if (ip6_addr_isany_val(inp->ip6_addr[0].u_addr.ip6)) {
// We don't have an LL address -> eat this packet here, so it won't get accepted on input netif
pbuf_free(p);
return 1;
}
return 0;
}
#endif
static void ot_task_worker(void *aContext) {
esp_vfs_eventfd_config_t eventfd_config = {
.max_fds = 3,
};
bool err = false;
if (ESP_OK != esp_event_loop_create_default()) {
log_e("Failed to create OpentThread event loop");
err = true;
}
if (!err && ESP_OK != esp_netif_init()) {
log_e("Failed to initialize OpentThread netif");
err = true;
}
if (!err && ESP_OK != esp_vfs_eventfd_register(&eventfd_config)) {
log_e("Failed to register OpentThread eventfd");
err = true;
}
// Initialize the OpenThread stack
if (!err && ESP_OK != esp_openthread_init(&ot_native_config)) {
log_e("Failed to initialize OpenThread stack");
err = true;
}
if (!err) {
// Initialize the esp_netif bindings
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_OPENTHREAD();
openthread_netif = esp_netif_new(&cfg);
#if CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM
// Get LwIP Netif
if (openthread_netif != NULL) {
ot_lwip_netif = (struct netif *)esp_netif_get_netif_impl(openthread_netif);
if (ot_lwip_netif == NULL) {
log_e("Failed to get OpenThread LwIP netif");
}
}
#endif
}
if (!err && openthread_netif == NULL) {
log_e("Failed to create OpenThread esp_netif");
err = true;
}
if (!err && ESP_OK != esp_netif_attach(openthread_netif, esp_openthread_netif_glue_init(&ot_native_config))) {
log_e("Failed to attach OpenThread esp_netif");
err = true;
}
if (!err && ESP_OK != esp_netif_set_default_netif(openthread_netif)) {
log_e("Failed to set default OpenThread esp_netif");
err = true;
}
if (!err) {
// only returns in case there is an OpenThread Stack failure...
esp_openthread_launch_mainloop();
}
// Clean up
esp_openthread_netif_glue_deinit();
esp_netif_destroy(openthread_netif);
esp_vfs_eventfd_unregister();
vTaskDelete(NULL);
}
// DataSet Implementation
DataSet::DataSet() {
memset(&mDataset, 0, sizeof(mDataset));
}
void DataSet::clear() {
memset(&mDataset, 0, sizeof(mDataset));
}
void DataSet::initNew() {
otInstance *mInstance = esp_openthread_get_instance();
if (!mInstance) {
log_e("OpenThread not started. Please begin() it before initializing a new dataset.");
return;
}
clear();
otDatasetCreateNewNetwork(mInstance, &mDataset);
}
const otOperationalDataset &DataSet::getDataset() const {
return mDataset;
}
void DataSet::setNetworkName(const char *name) {
strncpy(mDataset.mNetworkName.m8, name, sizeof(mDataset.mNetworkName.m8));
mDataset.mComponents.mIsNetworkNamePresent = true;
}
void DataSet::setExtendedPanId(const uint8_t *extPanId) {
memcpy(mDataset.mExtendedPanId.m8, extPanId, OT_EXT_PAN_ID_SIZE);
mDataset.mComponents.mIsExtendedPanIdPresent = true;
}
void DataSet::setNetworkKey(const uint8_t *key) {
memcpy(mDataset.mNetworkKey.m8, key, OT_NETWORK_KEY_SIZE);
mDataset.mComponents.mIsNetworkKeyPresent = true;
}
void DataSet::setChannel(uint8_t channel) {
mDataset.mChannel = channel;
mDataset.mComponents.mIsChannelPresent = true;
}
void DataSet::setPanId(uint16_t panId) {
mDataset.mPanId = panId;
mDataset.mComponents.mIsPanIdPresent = true;
}
const char *DataSet::getNetworkName() const {
return mDataset.mNetworkName.m8;
}
const uint8_t *DataSet::getExtendedPanId() const {
return mDataset.mExtendedPanId.m8;
}
const uint8_t *DataSet::getNetworkKey() const {
return mDataset.mNetworkKey.m8;
}
uint8_t DataSet::getChannel() const {
return mDataset.mChannel;
}
uint16_t DataSet::getPanId() const {
return mDataset.mPanId;
}
void DataSet::apply(otInstance *instance) {
otDatasetSetActive(instance, &mDataset);
}
// OpenThread Implementation
bool OpenThread::otStarted = false;
otInstance *OpenThread::mInstance = nullptr;
OpenThread::OpenThread() {}
OpenThread::~OpenThread() {
end();
}
OpenThread::operator bool() const {
return otStarted;
}
void OpenThread::begin(bool OThreadAutoStart) {
if (otStarted) {
log_w("OpenThread already started");
return;
}
memset(&ot_native_config, 0, sizeof(esp_openthread_platform_config_t));
ot_native_config.radio_config.radio_mode = RADIO_MODE_NATIVE;
ot_native_config.host_config.host_connection_mode = HOST_CONNECTION_MODE_NONE;
ot_native_config.port_config.storage_partition_name = "nvs";
ot_native_config.port_config.netif_queue_size = 10;
ot_native_config.port_config.task_queue_size = 10;
// Initialize OpenThread stack
xTaskCreate(ot_task_worker, "ot_main_loop", 10240, NULL, 20, &s_ot_task);
if (s_ot_task == NULL) {
log_e("Error: Failed to create OpenThread task");
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;
otError error = otDatasetGetActiveTlvs(esp_openthread_get_instance(), &dataset);
// error = OT_ERROR_FAILED; // teste para forçar NULL dataset
if (error != OT_ERROR_NONE) {
log_i("Failed to get active NVS dataset from OpenThread");
} else {
log_i("Got active NVS dataset from OpenThread");
}
esp_err_t err = esp_openthread_auto_start((error == OT_ERROR_NONE) ? &dataset : NULL);
if (err != ESP_OK) {
log_i("Failed to AUTO start OpenThread");
} else {
log_i("AUTO start OpenThread done");
}
}
otStarted = true;
}
void OpenThread::end() {
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
esp_netif_destroy(openthread_netif);
esp_vfs_eventfd_unregister();
}
otStarted = false;
}
void OpenThread::start() {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return;
}
otThreadSetEnabled(mInstance, true);
log_d("Thread network started");
}
void OpenThread::stop() {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return;
}
otThreadSetEnabled(mInstance, false);
log_d("Thread network stopped");
}
void OpenThread::networkInterfaceUp() {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return;
}
// Enable the Thread interface (equivalent to CLI Command "ifconfig up")
otError error = otIp6SetEnabled(mInstance, true);
if (error != OT_ERROR_NONE) {
log_e("Error: Failed to enable Thread interface (error code: %d)\n", error);
}
log_d("OpenThread Network Interface is up");
}
void OpenThread::networkInterfaceDown() {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return;
}
// Disable the Thread interface (equivalent to CLI Command "ifconfig down")
otError error = otIp6SetEnabled(mInstance, false);
if (error != OT_ERROR_NONE) {
log_e("Error: Failed to disable Thread interface (error code: %d)\n", error);
}
log_d("OpenThread Network Interface is down");
}
void OpenThread::commitDataSet(const DataSet &dataset) {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return;
}
// Commit the dataset as the active dataset
otError error = otDatasetSetActive(mInstance, &(dataset.getDataset()));
if (error != OT_ERROR_NONE) {
log_e("Error: Failed to commit dataset (error code: %d)\n", error);
return;
}
log_d("Dataset committed successfully");
}
ot_device_role_t OpenThread::otGetDeviceRole() {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return OT_ROLE_DISABLED;
}
return (ot_device_role_t)otThreadGetDeviceRole(mInstance);
}
const char *OpenThread::otGetStringDeviceRole() {
return otRoleString[otGetDeviceRole()];
}
void OpenThread::otPrintNetworkInformation(Stream &output) {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return;
}
output.printf("Role: %s", otGetStringDeviceRole());
output.println();
output.printf("RLOC16: 0x%04x", otThreadGetRloc16(mInstance)); // RLOC16
output.println();
output.printf("Network Name: %s", otThreadGetNetworkName(mInstance));
output.println();
output.printf("Channel: %d", otLinkGetChannel(mInstance));
output.println();
output.printf("PAN ID: 0x%04x", otLinkGetPanId(mInstance));
output.println();
const otExtendedPanId *extPanId = otThreadGetExtendedPanId(mInstance);
output.print("Extended PAN ID: ");
for (int i = 0; i < OT_EXT_PAN_ID_SIZE; i++) {
output.printf("%02x", extPanId->m8[i]);
}
output.println();
otNetworkKey networkKey;
otThreadGetNetworkKey(mInstance, &networkKey);
output.print("Network Key: ");
for (int i = 0; i < OT_NETWORK_KEY_SIZE; i++) {
output.printf("%02x", networkKey.m8[i]);
}
output.println();
}
OpenThread OThread;
#endif /* CONFIG_OPENTHREAD_ENABLED */
#endif /* SOC_IEEE802154_SUPPORTED */

View file

@ -0,0 +1,107 @@
// Copyright 2024 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.
#pragma once
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if SOC_IEEE802154_SUPPORTED
#if CONFIG_OPENTHREAD_ENABLED
#include <openthread/thread.h>
#include <openthread/link.h>
#include <openthread/netdata.h>
#include <openthread/ip6.h>
#include <openthread/dataset_ftd.h>
#include <esp_openthread.h>
#include <Arduino.h>
typedef enum {
OT_ROLE_DISABLED = 0, ///< The Thread stack is disabled.
OT_ROLE_DETACHED = 1, ///< Not currently participating in a Thread network/partition.
OT_ROLE_CHILD = 2, ///< The Thread Child role.
OT_ROLE_ROUTER = 3, ///< The Thread Router role.
OT_ROLE_LEADER = 4, ///< The Thread Leader role.
} ot_device_role_t;
extern const char *otRoleString[];
class DataSet {
public:
DataSet();
void clear();
void initNew();
const otOperationalDataset &getDataset() const;
// Setters
void setNetworkName(const char *name);
void setExtendedPanId(const uint8_t *extPanId);
void setNetworkKey(const uint8_t *key);
void setChannel(uint8_t channel);
void setPanId(uint16_t panId);
// Getters
const char *getNetworkName() const;
const uint8_t *getExtendedPanId() const;
const uint8_t *getNetworkKey() const;
uint8_t getChannel() const;
uint16_t getPanId() const;
// Apply the dataset to the OpenThread instance
void apply(otInstance *instance);
private:
otOperationalDataset mDataset;
};
class OpenThread {
public:
static bool otStarted;
static ot_device_role_t otGetDeviceRole();
static const char *otGetStringDeviceRole();
static void otPrintNetworkInformation(Stream &output);
OpenThread();
~OpenThread();
// returns true if OpenThread Stack is running
operator bool() const;
// Initialize OpenThread
static void begin(bool OThreadAutoStart = true);
// Initialize OpenThread
static void end();
// Start the Thread network
void start();
// Stop the Thread network
void stop();
// Start Thread Network Interface
void networkInterfaceUp();
// Stop Thread Network Interface
void networkInterfaceDown();
// Set the dataset
void commitDataSet(const DataSet &dataset);
private:
static otInstance *mInstance;
DataSet mCurrentDataSet;
};
extern OpenThread OThread;
#endif /* CONFIG_OPENTHREAD_ENABLED */
#endif /* SOC_IEEE802154_SUPPORTED */

View file

@ -33,18 +33,12 @@
#include "esp_netif_net_stack.h"
#include "lwip/netif.h"
bool OpenThreadCLI::otCLIStarted = false;
static TaskHandle_t s_cli_task = NULL;
static TaskHandle_t s_console_cli_task = NULL;
static QueueHandle_t rx_queue = NULL;
static QueueHandle_t tx_queue = NULL;
static esp_openthread_platform_config_t ot_native_config;
static TaskHandle_t s_ot_task = NULL;
static esp_netif_t *openthread_netif = NULL;
#if CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM
static struct netif *ot_lwip_netif = NULL;
#endif
#define OT_CLI_MAX_LINE_LENGTH 512
typedef struct {
@ -53,21 +47,7 @@ typedef struct {
String prompt;
OnReceiveCb_t responseCallBack;
} ot_cli_console_t;
static ot_cli_console_t otConsole = {NULL, false, (const char *)NULL, NULL};
#if CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM
extern "C" int lwip_hook_ip6_input(struct pbuf *p, struct netif *inp) {
if (ot_lwip_netif && ot_lwip_netif == inp) {
return 0;
}
if (ip6_addr_isany_val(inp->ip6_addr[0].u_addr.ip6)) {
// We don't have an LL address -> eat this packet here, so it won't get accepted on input netif
pbuf_free(p);
return 1;
}
return 0;
}
#endif
static ot_cli_console_t otConsole = {nullptr, false, (const char *)nullptr, nullptr};
// process the CLI commands sent to the OpenThread stack
static void ot_cli_loop(void *context) {
@ -131,7 +111,7 @@ static int ot_cli_output_callback(void *context, const char *format, va_list arg
}
// if there is a user callback function in place, it shall have the priority
// to process/consume the Stream data received from OpenThread CLI, which is available in its RX Buffer
if (otConsole.responseCallBack != NULL) {
if (otConsole.responseCallBack != nullptr) {
otConsole.responseCallBack();
}
}
@ -205,7 +185,7 @@ void OpenThreadCLI::setEchoBack(bool echoback) {
}
void OpenThreadCLI::setPrompt(char *prompt) {
otConsole.prompt = prompt; // NULL will make the prompt not visible
otConsole.prompt = prompt; // nullptr can make the prompt not visible
}
void OpenThreadCLI::setStream(Stream &otStream) {
@ -213,12 +193,12 @@ void OpenThreadCLI::setStream(Stream &otStream) {
}
void OpenThreadCLI::onReceive(OnReceiveCb_t func) {
otConsole.responseCallBack = func; // NULL will set it off
otConsole.responseCallBack = func; // nullptr will set it off
}
// Stream object shall be already started and configured before calling this function
void OpenThreadCLI::startConsole(Stream &otStream, bool echoback, const char *prompt) {
if (!otStarted) {
if (!otCLIStarted) {
log_e("OpenThread CLI has not started. Please begin() it before starting the console.");
return;
}
@ -226,7 +206,7 @@ void OpenThreadCLI::startConsole(Stream &otStream, bool echoback, const char *pr
if (s_console_cli_task == NULL) {
otConsole.cliStream = &otStream;
otConsole.echoback = echoback;
otConsole.prompt = prompt; // NULL will invalidate the String
otConsole.prompt = prompt; // nullptr will invalidate the String
// it will run in the same priority (1) as the Arduino setup()/loop() task
xTaskCreate(ot_cli_console_worker, "ot_cli_console", 4096, &otConsole, 1, &s_console_cli_task);
} else {
@ -242,12 +222,6 @@ void OpenThreadCLI::stopConsole() {
}
OpenThreadCLI::OpenThreadCLI() {
memset(&ot_native_config, 0, sizeof(esp_openthread_platform_config_t));
ot_native_config.radio_config.radio_mode = RADIO_MODE_NATIVE;
ot_native_config.host_config.host_connection_mode = HOST_CONNECTION_MODE_NONE;
ot_native_config.port_config.storage_partition_name = "nvs";
ot_native_config.port_config.netif_queue_size = 10;
ot_native_config.port_config.task_queue_size = 10;
//sTxString = "";
}
@ -256,79 +230,19 @@ OpenThreadCLI::~OpenThreadCLI() {
}
OpenThreadCLI::operator bool() const {
return otStarted;
return otCLIStarted;
}
static void ot_task_worker(void *aContext) {
esp_vfs_eventfd_config_t eventfd_config = {
.max_fds = 3,
};
bool err = false;
if (ESP_OK != esp_event_loop_create_default()) {
log_e("Failed to create OpentThread event loop");
err = true;
}
if (!err && ESP_OK != esp_netif_init()) {
log_e("Failed to initialize OpentThread netif");
err = true;
}
if (!err && ESP_OK != esp_vfs_eventfd_register(&eventfd_config)) {
log_e("Failed to register OpentThread eventfd");
err = true;
}
// Initialize the OpenThread stack
if (!err && ESP_OK != esp_openthread_init(&ot_native_config)) {
log_e("Failed to initialize OpenThread stack");
err = true;
}
if (!err) {
// Initialize the OpenThread cli
otCliInit(esp_openthread_get_instance(), ot_cli_output_callback, NULL);
// Initialize the esp_netif bindings
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_OPENTHREAD();
openthread_netif = esp_netif_new(&cfg);
#if CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM
// Get LwIP Netif
if (openthread_netif != NULL) {
ot_lwip_netif = (struct netif *)esp_netif_get_netif_impl(openthread_netif);
if (ot_lwip_netif == NULL) {
log_e("Failed to get OpenThread LwIP netif");
}
}
#endif
}
if (!err && openthread_netif == NULL) {
log_e("Failed to create OpenThread esp_netif");
err = true;
}
if (!err && ESP_OK != esp_netif_attach(openthread_netif, esp_openthread_netif_glue_init(&ot_native_config))) {
log_e("Failed to attach OpenThread esp_netif");
err = true;
}
if (!err && ESP_OK != esp_netif_set_default_netif(openthread_netif)) {
log_e("Failed to set default OpenThread esp_netif");
err = true;
}
if (!err) {
// only returns in case there is an OpenThread Stack failure...
esp_openthread_launch_mainloop();
}
// Clean up
esp_openthread_netif_glue_deinit();
esp_netif_destroy(openthread_netif);
esp_vfs_eventfd_unregister();
vTaskDelete(NULL);
}
void OpenThreadCLI::begin(bool OThreadAutoStart) {
if (otStarted) {
void OpenThreadCLI::begin() {
if (otCLIStarted) {
log_w("OpenThread CLI already started. Please end() it before starting again.");
return;
}
xTaskCreate(ot_task_worker, "ot_main_loop", 10240, NULL, 20, &s_ot_task);
if (!OpenThread::otStarted) {
log_w("OpenThread not started. Please begin() it before starting CLI.");
return;
}
//RX Buffer default has 1024 bytes if not preset
if (rx_queue == NULL) {
@ -342,55 +256,29 @@ void OpenThreadCLI::begin(bool OThreadAutoStart) {
log_e("HW CDC RX Buffer error");
}
}
xTaskCreate(ot_cli_loop, "ot_cli", 4096, xTaskGetCurrentTaskHandle(), 2, &s_cli_task);
// starts Thread with default dataset from NVS or from IDF default settings
if (OThreadAutoStart) {
otOperationalDatasetTlvs dataset;
otError error = otDatasetGetActiveTlvs(esp_openthread_get_instance(), &dataset);
// error = OT_ERROR_FAILED; // teste para forçar NULL dataset
if (error != OT_ERROR_NONE) {
log_i("Failed to get active NVS dataset from OpenThread");
} else {
log_i("Got active NVS dataset from OpenThread");
}
esp_err_t err = esp_openthread_auto_start((error == OT_ERROR_NONE) ? &dataset : NULL);
if (err != ESP_OK) {
log_i("Failed to AUTO start OpenThread");
} else {
log_i("AUTO start OpenThread done");
}
}
otStarted = true;
xTaskCreate(ot_cli_loop, "ot_cli", 4096, xTaskGetCurrentTaskHandle(), 2, &s_cli_task);
// Initialize the OpenThread cli
otCliInit(esp_openthread_get_instance(), ot_cli_output_callback, NULL);
otCLIStarted = true;
return;
}
void OpenThreadCLI::end() {
if (!otStarted) {
if (!otCLIStarted) {
log_w("OpenThread CLI already stopped. Please begin() it before stopping again.");
return;
}
if (s_ot_task != NULL) {
vTaskDelete(s_ot_task);
// Clean up
esp_openthread_deinit();
esp_openthread_netif_glue_deinit();
#if CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM
ot_lwip_netif = NULL;
#endif
esp_netif_destroy(openthread_netif);
esp_vfs_eventfd_unregister();
}
if (s_cli_task != NULL) {
vTaskDelete(s_cli_task);
s_cli_task = NULL;
}
if (s_console_cli_task != NULL) {
vTaskDelete(s_console_cli_task);
}
stopConsole();
esp_event_loop_delete_default();
setRxBufferSize(0);
setTxBufferSize(0);
otStarted = false;
otCLIStarted = false;
}
size_t OpenThreadCLI::write(uint8_t c) {

View file

@ -21,7 +21,6 @@
#include "esp_openthread.h"
#include "esp_openthread_cli.h"
#include "esp_openthread_lock.h"
#include "esp_openthread_netif_glue.h"
#include "esp_openthread_types.h"
#include "openthread/cli.h"
@ -31,13 +30,14 @@
#include "openthread/dataset_ftd.h"
#include "Arduino.h"
#include "OThread.h"
typedef std::function<void(void)> OnReceiveCb_t;
class OpenThreadCLI : public Stream {
private:
static size_t setBuffer(QueueHandle_t &queue, size_t len);
bool otStarted = false;
static bool otCLIStarted;
public:
OpenThreadCLI();
@ -53,7 +53,7 @@ public:
void setStream(Stream &otStream); // changes the console Stream object
void onReceive(OnReceiveCb_t func); // called on a complete line of output from OT CLI, as OT Response
void begin(bool OThreadAutoStart = true);
void begin();
void end();
// default size is 256 bytes

View file

@ -19,26 +19,6 @@
#include "OThreadCLI_Util.h"
#include <StreamString.h>
static const char *otRoleString[] = {
"Disabled", ///< The Thread stack is disabled.
"Detached", ///< Not currently participating in a Thread network/partition.
"Child", ///< The Thread Child role.
"Router", ///< The Thread Router role.
"Leader", ///< The Thread Leader role.
};
ot_device_role_t otGetDeviceRole() {
if (!OThreadCLI) {
return OT_ROLE_DISABLED;
}
otInstance *instance = esp_openthread_get_instance();
return (ot_device_role_t)otThreadGetDeviceRole(instance);
}
const char *otGetStringDeviceRole() {
return otRoleString[otGetDeviceRole()];
}
bool otGetRespCmd(const char *cmd, char *resp, uint32_t respTimeout) {
if (!OThreadCLI) {
return false;
@ -174,7 +154,7 @@ bool otPrintRespCLI(const char *cmd, Stream &output, uint32_t respTimeout) {
return true;
}
void otPrintNetworkInformation(Stream &output) {
void otCLIPrintNetworkInformation(Stream &output) {
if (!OThreadCLI) {
return;
}

View file

@ -18,25 +18,14 @@
#if SOC_IEEE802154_SUPPORTED
#if CONFIG_OPENTHREAD_ENABLED
typedef enum {
OT_ROLE_DISABLED = 0, ///< The Thread stack is disabled.
OT_ROLE_DETACHED = 1, ///< Not currently participating in a Thread network/partition.
OT_ROLE_CHILD = 2, ///< The Thread Child role.
OT_ROLE_ROUTER = 3, ///< The Thread Router role.
OT_ROLE_LEADER = 4, ///< The Thread Leader role.
} ot_device_role_t;
typedef struct {
int errorCode;
String errorMessage;
} ot_cmd_return_t;
ot_device_role_t otGetDeviceRole();
const char *otGetStringDeviceRole();
bool otGetRespCmd(const char *cmd, char *resp = NULL, uint32_t respTimeout = 5000);
bool otExecCommand(const char *cmd, const char *arg, ot_cmd_return_t *returnCode = NULL);
bool otPrintRespCLI(const char *cmd, Stream &output, uint32_t respTimeout);
void otPrintNetworkInformation(Stream &output);
#endif /* CONFIG_OPENTHREAD_ENABLED */
#endif /* SOC_IEEE802154_SUPPORTED */

View file

@ -63,9 +63,9 @@ SPIClass::~SPIClass() {
#endif
}
void SPIClass::begin(int8_t sck, int8_t miso, int8_t mosi, int8_t ss) {
bool SPIClass::begin(int8_t sck, int8_t miso, int8_t mosi, int8_t ss) {
if (_spi) {
return;
return true;
}
if (!_div) {
@ -74,7 +74,7 @@ void SPIClass::begin(int8_t sck, int8_t miso, int8_t mosi, int8_t ss) {
_spi = spiStartBus(_spi_num, _div, SPI_MODE0, SPI_MSBFIRST);
if (!_spi) {
return;
return false;
}
if (sck == -1 && miso == -1 && mosi == -1 && ss == -1) {
@ -110,10 +110,11 @@ void SPIClass::begin(int8_t sck, int8_t miso, int8_t mosi, int8_t ss) {
if (_mosi >= 0 && !spiAttachMOSI(_spi, _mosi)) {
goto err;
}
return;
return true;
err:
log_e("Attaching pins to SPI failed.");
return false;
}
void SPIClass::end() {

View file

@ -61,7 +61,7 @@ private:
public:
SPIClass(uint8_t spi_bus = HSPI);
~SPIClass();
void begin(int8_t sck = -1, int8_t miso = -1, int8_t mosi = -1, int8_t ss = -1);
bool begin(int8_t sck = -1, int8_t miso = -1, int8_t mosi = -1, int8_t ss = -1);
void end();
void setHwCs(bool use);

View file

@ -99,11 +99,11 @@ def generate_bootloader_image(bootloader_elf):
"--chip",
build_mcu,
"elf2image",
"--flash_mode",
"--flash-mode",
"${__get_board_flash_mode(__env__)}",
"--flash_freq",
"--flash-freq",
"${__get_board_f_image(__env__)}",
"--flash_size",
"--flash-size",
board_config.get("upload.flash_size", "4MB"),
"-o",
"$TARGET",

View file

@ -22,6 +22,13 @@ static const uint8_t MOSI = 6;
static const uint8_t MISO = 5;
static const uint8_t SCK = 4;
static const uint8_t D5 = 5;
static const uint8_t D6 = 6;
static const uint8_t D7 = 7;
static const uint8_t D8 = 8;
static const uint8_t D9 = 9;
static const uint8_t D10 = 10;
static const uint8_t A0 = 0;
static const uint8_t A1 = 1;
static const uint8_t A2 = 2;

View file

@ -4,7 +4,7 @@
#include <stdint.h>
#define USB_VID 0x2886
#define USB_PID 0x0056
#define USB_PID 0x0063
static const uint8_t LED_BUILTIN = 21;
#define BUILTIN_LED LED_BUILTIN // backward compatibility