From 872bcb5563d6e23f7228e8f762c5832fbb6449a6 Mon Sep 17 00:00:00 2001 From: mcuw <112967559+mcuw@users.noreply.github.com> Date: Tue, 23 Apr 2024 02:11:18 +0200 Subject: [PATCH 01/14] add RGB example --- README.md | 82 +--------------- doc/nanoESP32C6.pdf | Bin 0 -> 247001 bytes examples/rgb/main.cpp | 42 +++++++++ platformio.ini | 214 ++++++++---------------------------------- src/main.cpp | 30 ++++-- 5 files changed, 109 insertions(+), 259 deletions(-) create mode 100644 doc/nanoESP32C6.pdf create mode 100644 examples/rgb/main.cpp diff --git a/README.md b/README.md index b77605e..6e4f429 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,15 @@ -# ESP32 project template +# ESP32-C6 examples ## Description -This is a project template to create microcontroller apps with automatized firmware builds for [esp32](https://www.espressif.com/en/products/socs/esp32), [esp32s2](https://www.espressif.com/en/products/socs/esp32-s2) and [esp32s3](https://www.espressif.com/en/products/socs/esp32-s3) microcontroller boards. It uses for that [GitHub Actions](https://github.com/features/actions) and [platformio](https://platformio.org/). Use this repository as a template for your own esp32 projects. - -## Requirements - -- [GitHub Actions](https://github.com/features/actions) -- [platformio](https://platformio.org/) -- [python](https://www.python.org/) - -## GitHub Actions - Workflow - -The release build happens in the `build & release` workflow: [build_release.yml](.github/workflows/build_release.yml). -It creates a release, after creation of a new git tag (named it like `v1.0.0`). - -If you want to test the build on all merge w/o creating a tag then the `build` workflow is what you looking for: [build.yml](.github/workflows/build.yml) - -## PlatformIO - -[PlatformIO](https://platformio.org/) is a tool to create microcontroller apps for arduino platforms and compatibles (esp32). You can install the [Visual Studio Code extension](https://platformio.org/install/ide?install=vscode) in the [Visual Studio Code](https://code.visualstudio.com/) IDE. - -## Python - -There is a tiny python script needed to customize the firmware filenames within platformio, see documentation: https://docs.platformio.org/en/stable/scripting/examples/custom_program_name.html - -The [extra_script.py](extra_script.py) script gets the platformio env (e.g. lolin32) and the git-tag for the firmware filename. -This is required to publish several firmware names in the github artifacts of a release. - -## Get Started - - - -1. Login to github - -2. Click on `Use this template` to create a new git repository -3. Implement your application in the [src/main.cpp](src/main.cpp) -4. Comment your new change in the [CHANGELOG.md](CHANGELOG.md) file -5. Push your changes - -```sh -git add . -``` - -```sh -git commit -am "my app" -``` - -```sh -git push -u origin main -``` - -5. Create a new tag to trigger a release, e.g. for v1.0.0 - -```sh -git tag v1.0.0 -``` - -```sh -git push origin v1.0.0 -``` - -6. You can find your firmwares under `Releases` after the CI build finished - -## CHANGELOG - -You can write your changes in the [CHANGELOG.md](CHANGELOG.md) before you create a release. It will be shown under the release page. - -## Example Release - -see [Releases](https://github.com/mcuw/esp-ghbuild-template/releases) on the right sidemenu. - -## Customize your project - -You can reduce and adapt your required boards in the [platformio.ini](platformio.ini). - -Update the [CHANGELOG.md](CHANGELOG.md) file before you are creating a new release. By creating a new git tag you trigger a new release which generate for you the firmwares. +Uncomment `src_dir=` in the `platform.ini` file. ## Supported boards -- ESP32 (buy with affiliate link: [LILYGO T-Beam](https://s.click.aliexpress.com/e/_DBzslDV) with LoRA, lolin32, lolin D32 pro) -- ESP32 S2 -- ESP32 S3 (buy with affiliate link: [LILYGO T-Display S3](https://s.click.aliexpress.com/e/_DBmOMkn), [LILYGO T-Display-S3 AMOLED](https://s.click.aliexpress.com/e/_DmboYpZ), [LILYGO T-Display-S3 Touch](https://s.click.aliexpress.com/e/_DCBgPlV), [LILYGO T-Display S3 Long](https://s.click.aliexpress.com/e/_Dl6UVMx)) - ESP32 C6 with WiFi 6 and BT-5 LE (buy with affiliate link: [UICPAL nanoESP32-C6](https://s.click.aliexpress.com/e/_DdZ83IB) with up to 16MB flash, [ESP32-C6](https://s.click.aliexpress.com/e/_DeLjVMb) with 4MB flash and W2812 RGB LED) +![datasheet - nanoESP32-C6](doc/nanoESP32C6.pdf) + ## Disclaimer Contribution and help ... if you find an issue or wants to contribute then please do not hesitate to create a merge request or an issue. diff --git a/doc/nanoESP32C6.pdf b/doc/nanoESP32C6.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6b3ca9d84a3eafb47af578a1dd02b8ddcdb14ba9 GIT binary patch literal 247001 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 literal 0 HcmV?d00001 diff --git a/examples/rgb/main.cpp b/examples/rgb/main.cpp new file mode 100644 index 0000000..417a2cd --- /dev/null +++ b/examples/rgb/main.cpp @@ -0,0 +1,42 @@ +#include +#include + +// see https://github.com/wuxx/nanoESP32-C6 +// datasheet https://github.com/wuxx/nanoESP32-C6/blob/master/hardware/nanoESP32C6.pdf + +#define DATA_PIN 8 +#define NUMPIXELS 1 +#define BRIGHTNESS 255 + +Adafruit_NeoPixel pixels(NUMPIXELS, DATA_PIN, NEO_GRB + NEO_KHZ800); + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; + + pixels.begin(); + pixels.show(); + pixels.setBrightness(BRIGHTNESS); +} + +void loop() +{ + pixels.clear(); + + Serial.println("Rot"); + pixels.setPixelColor(0, pixels.Color(255, 0, 0)); + pixels.show(); // Send the updated pixel colors to the hardware. + delay(1000); + + Serial.println("Grün"); + pixels.setPixelColor(0, pixels.Color(0, 255, 0)); + pixels.show(); + delay(1000); + + Serial.println("Blau"); + pixels.setPixelColor(0, pixels.Color(0, 0, 255)); + pixels.show(); + delay(1000); +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index bc7f086..1838fa5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,131 +1,62 @@ -; https://docs.platformio.org/en/latest/platforms/espressif32.html +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html [platformio] boards_dir = ./boards +; examples +; src_dir = ./examples/rgb [env] -build_flags = - ; to use e.g. as part of the firmware name - '-DPIOENV="${PIOENV}"' - - ; coding_standards - -Wno-unused-variable - -Wno-unused-but-set-variable - -Wunreachable-code - - ; debug level 5=VERBOSE https://docs.platformio.org/en/latest/platforms/espressif32.html#debug-level - ; -DCORE_DEBUG_LEVEL=5 +build_flags = + '-DPIOENV="${PIOENV}"' + + -Wno-unused-variable + -Wno-unused-but-set-variable + -Wunreachable-code extra_scripts = pre:extra_script.py framework = arduino -lib_deps = - ; place global libs here or under extra.lib_deps_external (see below) +lib_deps = monitor_speed = 115200 platform = espressif32 upload_speed = 921600 -; CUSTOM options -; You need manually inject these options into a section -; using ${extra.} (see below) [extra] -; https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/external-ram.html -build_flags_psram = - -DBOARD_HAS_PSRAM - -mfix-esp32-psram-cache-issue ; https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/external-ram.html#esp32-rev-v1 +build_flags_psram = + -DBOARD_HAS_PSRAM + -mfix-esp32-psram-cache-issue lib_deps_builtin = - SPI - Wire + SPI + Wire lib_deps_external = - ; extend libs for all platforms here + adafruit/Adafruit NeoPixel @ ^1.12.0 [esp32] -build_flags = - ${env.build_flags} -lib_deps = - ${env.lib_deps} - ${extra.lib_deps_builtin} - ${extra.lib_deps_external} - ; extend libs only for esp32 based modules here +build_flags = + ${env.build_flags} +lib_deps = + ${env.lib_deps} + ${extra.lib_deps_builtin} + ${extra.lib_deps_external} [esp32c6] -build_flags = - ${env.build_flags} -lib_deps = - ${env.lib_deps} - ${extra.lib_deps_builtin} - ${extra.lib_deps_external} - ; extend libs only for esp32c6 based modules here -platform = https://github.com/platformio/platform-espressif32.git -platform_packages = - platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git - platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-libs.git#idf-release/v5.1 - -[esp32s2] -build_flags = - ${env.build_flags} -lib_deps = - ${env.lib_deps} - ${extra.lib_deps_builtin} - ${extra.lib_deps_external} - ; extend libs only for esp32s2 based modules here - -[esp32s3] -build_flags = - ${env.build_flags} -lib_deps = - ${env.lib_deps} - ${extra.lib_deps_builtin} - ${extra.lib_deps_external} - ; extend libs only for esp32s3 based modules here - - -; ********* -; * ESP32 * -; ********* - -; pio board config: https://docs.platformio.org/en/latest/platforms/espressif32.html -[env:esp32dev] -extends = esp32 -board = esp32dev -build_flags = - ${esp32.build_flags} -lib_deps = - ${esp32.lib_deps} - -; pio board config: https://docs.platformio.org/en/latest/boards/espressif32/lolin32.html -[env:lolin32] -extends = esp32 -board = lolin32 -build_flags = - ${esp32.build_flags} -lib_deps = - ${esp32.lib_deps} - -; Wemos https://www.wemos.cc/en/latest/d32/d32_pro.html -; pio board config: https://docs.platformio.org/en/latest/boards/espressif32/lolin_d32_pro.html -[env:lolin-d32-pro] -extends = esp32 -board = lolin_d32_pro -build_flags = - ${esp32.build_flags} - ${extra.build_flags_psram} ; 4MB PSRAM +build_flags = + ${env.build_flags} lib_deps = - ${esp32.lib_deps} + ${env.lib_deps} + ${extra.lib_deps_builtin} + ${extra.lib_deps_external} -; LILYGO https://www.lilygo.cc/products/t-beam-v1-1-esp32-lora-module -[env:ttgo-t-beam] -extends = esp32 -board = ttgo-t-beam -build_flags = - ${esp32.build_flags} - ${extra.build_flags_psram} ; 8MB PSRAM -lib_deps = - ${esp32.lib_deps} - -; ************ -; * ESP32-C6 * -; ************ -; devkitc-1: https://docs.espressif.com/projects/espressif-esp-dev-kits/en/latest/esp32c6/esp32-c6-devkitc-1/index.html -; devkitm-1: https://docs.espressif.com/projects/espressif-esp-dev-kits/en/latest/esp32c6/esp32-c6-devkitm-1/index.html +platform = https://github.com/platformio/platform-espressif32.git +platform_packages = + platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git + platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-libs.git#idf-release/v5.1 [env:esp32-c6-n16] extends = esp32c6 @@ -134,70 +65,3 @@ board = esp32-c6-n16 [env:esp32-c6-n4] extends = esp32c6 board = esp32-c6-n4 - -; ************ -; * ESP32-S2 * -; ************ - -; TODO http://www.lilygo.cn/prod_view.aspx?TypeId=50033&Id=1321 with psram -; repository: https://github.com/Xinyuan-LilyGO/LilyGo-T-Display-S2 -[env:lilygo-t-display-s2] -extends = esp32s2 -board = esp32dev ; what about platformio board support and an own? - -; ************ -; * ESP32-S3 * -; ************ - -; pio board config: https://docs.platformio.org/en/latest/boards/espressif32/esp32s3box.html -[env:esp32s3box] -extends = esp32s3 -board = esp32s3box -build_flags = - ${esp32s3.build_flags} -lib_deps = - ${esp32s3.lib_deps} - -; pio board config: https://docs.platformio.org/en/latest/boards/espressif32/esp32-s3-devkitc-1.html -[env:esp32-s3-devkitc-1] -extends = esp32s3 -board = esp32-s3-devkitc-1 -build_flags = - ${esp32s3.build_flags} - ; PSRAM is optional -lib_deps = - ${esp32s3.lib_deps} - -; LILYGO: https://www.lilygo.cc/products/t-display-s3 -; repository: https://github.com/Xinyuan-LilyGO/T-Display-S3 -; pio board config: https://docs.platformio.org/en/latest/boards/espressif32/lilygo-t-display-s3.html -; Buy and support mcuw (affiliate link): https://s.click.aliexpress.com/e/_DE2TNqz -[env:lilygo-t-display-s3] -board = lilygo-t-display-s3 -build_flags = - ${esp32s3.build_flags} - ${extra.build_flags_psram} ; 8MB PSRAM -lib_deps = - ${esp32s3.lib_deps} - -; LILYGO: https://www.lilygo.cc/products/t-display-s3-long -; repository: https://github.com/Xinyuan-LilyGO/T-Display-S3-Long -; Buy and support mcuw on aliexpress (affiliate link): https://s.click.aliexpress.com/e/_DEv67TX -[env:lilygo-t-display-s3-long] -board = lilygo-t-display-s3-long -board_build.partitions = huge_app.csv -build_flags = - ${esp32s3.build_flags} - ${extra.build_flags_psram} ; 8MB PSRAM -lib_deps = - ${esp32s3.lib_deps} - -; SeeedStudio: https://www.seeedstudio.com/WT32-3-5-Inch-Display-p-5542.html -; Antratek: https://www.antratek.com/wt32-sco1-plus -; repository: https://github.com/wireless-tag-com/lv_port_esp32 -; ESP32-TUX - github: https://github.com/sukesh-ak/ESP32-TUX -[env:wt32-sc01-plus] -extends = env:esp32-s3-devkitc-1 -build_flags = - ${env:esp32-s3-devkitc-1.build_flags} - ${extra.build_flags_psram} ; 2MB PSRAM diff --git a/src/main.cpp b/src/main.cpp index e73b4d2..a9ed843 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,30 @@ #include +#include -void setup() { +// see https://github.com/wuxx/nanoESP32-C6 +// datasheet https://github.com/wuxx/nanoESP32-C6/blob/master/hardware/nanoESP32C6.pdf + +#define DATA_PIN 8 +#define NUMPIXELS 1 +#define BRIGHTNESS 255 + +Adafruit_NeoPixel pixels(NUMPIXELS, DATA_PIN, NEO_GRB + NEO_KHZ800); + +void setup() +{ Serial.begin(115200); - while (!Serial); - + while (!Serial) + ; + + pixels.begin(); + pixels.show(); + pixels.setBrightness(BRIGHTNESS); } -void loop() { - Serial.println("Hello World"); - delay(2000); +void loop() +{ + pixels.clear(); + + // pixels.setPixelColor(0, pixels.Color(255, 0, 0)); + // pixels.show(); // Send the updated pixel colors to the hardware. } \ No newline at end of file From 48ee9cd8ee0f41021b20f146e18374cc2cd07072 Mon Sep 17 00:00:00 2001 From: mcuw <112967559+mcuw@users.noreply.github.com> Date: Tue, 23 Apr 2024 02:15:05 +0200 Subject: [PATCH 02/14] update changelog v1.0.0 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18c1792..61654f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ CHANGELOG -## v0.0.0 +## v1.0.0 -PLEASE WRITE YOUR CHANGES BEFORE YOU CREATE A RELEASE IN THE CHANGELOG.MD FILE! +Add RGB NeoPixel example. \ No newline at end of file From 6e42ca8f069e92cc5152e1beec0232d31e5ce04f Mon Sep 17 00:00:00 2001 From: mcuw <112967559+mcuw@users.noreply.github.com> Date: Tue, 23 Apr 2024 09:51:41 +0200 Subject: [PATCH 03/14] replace adafruit neopixel library with esp32 builtin hal; deactivate serial prints as default and workaround --- .vscode/settings.json | 16 ++++++++++++++++ examples/rgb/main.cpp | 36 ++++++++++-------------------------- include/DebugUtils.h | 24 ++++++++++++++++++++++++ platformio.ini | 5 ++--- src/main.cpp | 22 +++++++--------------- 5 files changed, 59 insertions(+), 44 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 include/DebugUtils.h diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c2139d4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "files.associations": { + "*.json": "json", + "array": "cpp", + "deque": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "string_view": "cpp", + "initializer_list": "cpp", + "span": "cpp", + "*.tcc": "cpp", + "system_error": "cpp" + } +} \ No newline at end of file diff --git a/examples/rgb/main.cpp b/examples/rgb/main.cpp index 417a2cd..9b0c373 100644 --- a/examples/rgb/main.cpp +++ b/examples/rgb/main.cpp @@ -1,42 +1,26 @@ #include -#include +#include -// see https://github.com/wuxx/nanoESP32-C6 -// datasheet https://github.com/wuxx/nanoESP32-C6/blob/master/hardware/nanoESP32C6.pdf - -#define DATA_PIN 8 -#define NUMPIXELS 1 -#define BRIGHTNESS 255 +// pinouts: https://github.com/espressif/arduino-esp32/blob/master/variants/esp32c6/pins_arduino.h -Adafruit_NeoPixel pixels(NUMPIXELS, DATA_PIN, NEO_GRB + NEO_KHZ800); +// see https://github.com/wuxx/nanoESP32-C6 void setup() { - Serial.begin(115200); - while (!Serial) - ; - - pixels.begin(); - pixels.show(); - pixels.setBrightness(BRIGHTNESS); + serialBegin(115200); } void loop() { - pixels.clear(); - - Serial.println("Rot"); - pixels.setPixelColor(0, pixels.Color(255, 0, 0)); - pixels.show(); // Send the updated pixel colors to the hardware. + serialPrintLn("Rot"); + neopixelWrite(PIN_NEOPIXEL, 255, 0, 0); delay(1000); - Serial.println("Grün"); - pixels.setPixelColor(0, pixels.Color(0, 255, 0)); - pixels.show(); + // serialPrintLn("Grün"); + neopixelWrite(PIN_NEOPIXEL, 0, 255, 0); delay(1000); - Serial.println("Blau"); - pixels.setPixelColor(0, pixels.Color(0, 0, 255)); - pixels.show(); + // serialPrintLn("Blau"); + neopixelWrite(PIN_NEOPIXEL, 0, 0, 255); delay(1000); } \ No newline at end of file diff --git a/include/DebugUtils.h b/include/DebugUtils.h new file mode 100644 index 0000000..c653c96 --- /dev/null +++ b/include/DebugUtils.h @@ -0,0 +1,24 @@ +#ifndef DEBUG_UTILS_H + +/** + * Currently facing an issue that + * when using serial prints over the USB Serial + * the ESP32 is not working w/o an open USB Serial terminal/ monitor + * activate serial in platform.ini if needed: + +[env] +build_flags = + '-DSERIAL_DEBUG' +*/ + +#ifdef SERIAL_DEBUG +#define serialBegin(x) Serial.begin(x) +#define serialPrint(x) Serial.print(x) +#define serialPrintLn(x) Serial.println(x) +#else +#define serialBegin(x) +#define serialPrint(x) +#define serialPrintLn(x) +#endif + +#endif diff --git a/platformio.ini b/platformio.ini index 1838fa5..3443d3c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -16,7 +16,7 @@ boards_dir = ./boards [env] build_flags = '-DPIOENV="${PIOENV}"' - + ; '-DSERIAL_DEBUG' -Wno-unused-variable -Wno-unused-but-set-variable -Wunreachable-code @@ -28,14 +28,13 @@ platform = espressif32 upload_speed = 921600 [extra] -build_flags_psram = +build_flags_psram = -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue lib_deps_builtin = SPI Wire lib_deps_external = - adafruit/Adafruit NeoPixel @ ^1.12.0 [esp32] build_flags = diff --git a/src/main.cpp b/src/main.cpp index a9ed843..ea2b190 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,30 +1,22 @@ #include -#include -// see https://github.com/wuxx/nanoESP32-C6 -// datasheet https://github.com/wuxx/nanoESP32-C6/blob/master/hardware/nanoESP32C6.pdf - -#define DATA_PIN 8 -#define NUMPIXELS 1 -#define BRIGHTNESS 255 +// pinouts: https://github.com/espressif/arduino-esp32/blob/master/variants/esp32c6/pins_arduino.h -Adafruit_NeoPixel pixels(NUMPIXELS, DATA_PIN, NEO_GRB + NEO_KHZ800); +// see https://github.com/wuxx/nanoESP32-C6 void setup() { Serial.begin(115200); while (!Serial) ; - - pixels.begin(); - pixels.show(); - pixels.setBrightness(BRIGHTNESS); } void loop() { - pixels.clear(); + // The ESP32-C6 has an attenuator on the ADC, which defaults to 11dB. This causes the resolution to be reduced + analogSetAttenuation(ADC_0db); - // pixels.setPixelColor(0, pixels.Color(255, 0, 0)); - // pixels.show(); // Send the updated pixel colors to the hardware. + // // blink + // neopixelWrite(PIN_NEOPIXEL, 0, 255, 0); + // delay(1000); } \ No newline at end of file From d28d7ea6effb73ea6d07e79dceb9b6431d4dcc63 Mon Sep 17 00:00:00 2001 From: mcuw <112967559+mcuw@users.noreply.github.com> Date: Tue, 23 Apr 2024 09:55:58 +0200 Subject: [PATCH 04/14] use DebugUtils in main app --- examples/rgb/main.cpp | 6 +++--- src/main.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/rgb/main.cpp b/examples/rgb/main.cpp index 9b0c373..1508374 100644 --- a/examples/rgb/main.cpp +++ b/examples/rgb/main.cpp @@ -12,15 +12,15 @@ void setup() void loop() { - serialPrintLn("Rot"); + serialPrintLn("red"); neopixelWrite(PIN_NEOPIXEL, 255, 0, 0); delay(1000); - // serialPrintLn("Grün"); + serialPrintLn("green"); neopixelWrite(PIN_NEOPIXEL, 0, 255, 0); delay(1000); - // serialPrintLn("Blau"); + serialPrintLn("blue"); neopixelWrite(PIN_NEOPIXEL, 0, 0, 255); delay(1000); } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index ea2b190..c5bd80c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ #include +#include // pinouts: https://github.com/espressif/arduino-esp32/blob/master/variants/esp32c6/pins_arduino.h @@ -6,9 +7,7 @@ void setup() { - Serial.begin(115200); - while (!Serial) - ; + serialBegin(115200); } void loop() @@ -17,6 +16,7 @@ void loop() analogSetAttenuation(ADC_0db); // // blink + // serialPrintLn("green"); // neopixelWrite(PIN_NEOPIXEL, 0, 255, 0); // delay(1000); } \ No newline at end of file From 1563e72896436628c485a72129ce6eb9795e2852 Mon Sep 17 00:00:00 2001 From: mcuw <112967559+mcuw@users.noreply.github.com> Date: Fri, 26 Apr 2024 00:10:45 +0200 Subject: [PATCH 05/14] add OTA example --- .gitignore | 1 + examples/ota/credentials.h | 3 + examples/ota/main.cpp | 27 ++++++++ lib/OTA/OTA.cpp | 128 +++++++++++++++++++++++++++++++++++++ lib/OTA/OTA.h | 20 ++++++ platformio.ini | 21 +++++- upload_params_example.ini | 8 +++ 7 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 examples/ota/credentials.h create mode 100644 examples/ota/main.cpp create mode 100644 lib/OTA/OTA.cpp create mode 100644 lib/OTA/OTA.h create mode 100644 upload_params_example.ini diff --git a/.gitignore b/.gitignore index 4de822f..2fda03f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ .vscode/launch.json .vscode/ipch .DS_Store +upload_params.ini diff --git a/examples/ota/credentials.h b/examples/ota/credentials.h new file mode 100644 index 0000000..9797994 --- /dev/null +++ b/examples/ota/credentials.h @@ -0,0 +1,3 @@ +#ifndef AP_PSK +#define AP_PSK "1234" +#endif diff --git a/examples/ota/main.cpp b/examples/ota/main.cpp new file mode 100644 index 0000000..6c61cd2 --- /dev/null +++ b/examples/ota/main.cpp @@ -0,0 +1,27 @@ +#include +#include +#include "credentials.h" + +OTA ota; +String ap_default_psk(AP_PSK); // Default PSK. + +void setup() +{ + // reset RGB-LED + neopixelWrite(PIN_NEOPIXEL, 0, 0, 0); + + ota.begin(ap_default_psk); + // or protected but does not work yet + // ota.begin(ap_default_psk, MY_SSID, PASSPHRASE); +} + +void loop() +{ + ota.handle(); + + // blink example + // neopixelWrite(PIN_NEOPIXEL, 0, 0, 255); + // delay(200); + // neopixelWrite(PIN_NEOPIXEL, 0, 0, 0); + // delay(200); +} \ No newline at end of file diff --git a/lib/OTA/OTA.cpp b/lib/OTA/OTA.cpp new file mode 100644 index 0000000..540a5c6 --- /dev/null +++ b/lib/OTA/OTA.cpp @@ -0,0 +1,128 @@ +#include +#include +#include "./OTA.h" +#include "esp_mac.h" // required - exposes esp_mac_type_t values + +#define HOSTNAME "ESP32-" + +OTA::OTA() +{ +} + +String OTA::getDefaultMacAddress() +{ + String mac = ""; + + unsigned char mac_base[6] = {0}; + + if (esp_efuse_mac_get_default(mac_base) == ESP_OK) + { + char buffer[13]; // 6*2 characters for hex + 1 character for null terminator + sprintf(buffer, "%02X%02X%02X%02X%02X%02X", mac_base[0], mac_base[1], mac_base[2], mac_base[3], mac_base[4], mac_base[5]); + mac = buffer; + } + + return mac; +} + +String OTA::getHostname() +{ + String hostname(HOSTNAME); + hostname += getDefaultMacAddress(); + return hostname; +} + +void OTA::startAP(const String &passphrase) +{ + String ssid(getHostname()); + + // Go into software AP mode. + WiFi.mode(WIFI_AP); + delay(10); + int channel = 1; + int ssid_hidden = 0; + int max_connection = 4; + WiFi.softAP(ssid, passphrase, channel, ssid_hidden, max_connection, false, WIFI_AUTH_WPA3_PSK); + + // Start OTA server. + ArduinoOTA.setHostname(ssid.c_str()); + ArduinoOTA.setPassword(passphrase.c_str()); + ArduinoOTA.begin(); +} + +boolean OTA::startSTA(const char *station_ssid, const char *station_passphrase, const String &passphrase) +{ + if (strcmp(station_ssid, "") == 0 || strcmp(station_passphrase, "") == 0) + { + return false; + } + + String hostname(getHostname()); + + // Check Wifi mode + if (WiFi.getMode() != WIFI_STA) + { + WiFi.mode(WIFI_STA); + delay(10); + } + + // Compare file config with sdk config + if (strcmp(WiFi.SSID().c_str(), station_ssid) == 0 && strcmp(WiFi.psk().c_str(), station_passphrase) == 0) + { + // Begin with sdk config + WiFi.begin(); + } + else + { + // Serial.println(F("WiFi config changed")); + + // Connect to WiFi station + WiFi.begin(station_ssid, station_passphrase); + + // Serial.print(F("New SSID: ")); + // Serial.println(WiFi.SSID()); + } + + // Serial.println(F("Wait for WiFi connection.")); + + // Give ESP 10 seconds to connect to station + unsigned long startTime = millis(); + while (WiFi.status() != WL_CONNECTED && millis() - startTime < 10000) + { + // Serial.print("."); + // Serial.print(WiFi.status()); + delay(500); + } + // Serial.println(); + + // Check connection + if (WiFi.status() != WL_CONNECTED) + { + // Serial.println(F("Failed to connect to WiFi station")); + return false; + } + + // print IP Address + // Serial.print(F("STA IP address: ")); + // Serial.println(WiFi.localIP()); + + // Start OTA server + ArduinoOTA.setHostname(hostname.c_str()); + ArduinoOTA.setPassword(passphrase.c_str()); + ArduinoOTA.begin(); + + return true; +} + +void OTA::begin(const String &password, const char *station_ssid, const char *station_psk) +{ + if (!startSTA(station_ssid, station_psk, password)) + { + startAP(password); + } +} + +void OTA::handle() +{ + ArduinoOTA.handle(); +} diff --git a/lib/OTA/OTA.h b/lib/OTA/OTA.h new file mode 100644 index 0000000..ae3e7ed --- /dev/null +++ b/lib/OTA/OTA.h @@ -0,0 +1,20 @@ +#ifndef OTA_H +#define OTA_H + +class OTA +{ +public: + OTA(); + virtual ~OTA(){}; + + void begin(const String &password, const char *station_ssid = "", const char *station_passphrase = ""); + void handle(); + +private: + void startAP(const String &passphrase); + boolean startSTA(const char *station_ssid, const char *station_passphrase, const String &password); + String getHostname(); + String getDefaultMacAddress(); +}; + +#endif \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 3443d3c..bbae9b7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,8 +10,10 @@ [platformio] boards_dir = ./boards +extra_configs = upload_params.ini ; examples ; src_dir = ./examples/rgb +; src_dir = ./examples/ota [env] build_flags = @@ -51,16 +53,33 @@ lib_deps = ${env.lib_deps} ${extra.lib_deps_builtin} ${extra.lib_deps_external} - platform = https://github.com/platformio/platform-espressif32.git platform_packages = platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-libs.git#idf-release/v5.1 +[esp32c6-ota] +upload_speed = 2000000 +upload_protocol = espota +; change upload_port after connected to your network instead of the ESP AP +upload_port = 192.168.4.1 + [env:esp32-c6-n16] extends = esp32c6 board = esp32-c6-n16 +[env:esp32-c6-n16-ota] +extends = env:esp32-c6-n16 +upload_speed = ${esp32c6-ota.upload_speed} +upload_protocol = ${esp32c6-ota.upload_protocol} +upload_port = ${esp32c6-ota.upload_port} + [env:esp32-c6-n4] extends = esp32c6 board = esp32-c6-n4 + +[env:esp32-c6-n4-ota] +extends = esp32-c6-n4 +upload_speed = ${esp32c6-ota.upload_speed} +upload_protocol = ${esp32c6-ota.upload_protocol} +upload_port = ${esp32c6-ota.upload_port} diff --git a/upload_params_example.ini b/upload_params_example.ini new file mode 100644 index 0000000..505cd1b --- /dev/null +++ b/upload_params_example.ini @@ -0,0 +1,8 @@ +[env:esp32-c6-n16-ota] +upload_flags = + ; replace 1234 with yours the same as AP_PSK in cred.h + --auth=1234 + +[env:esp32-c6-n16-ota] +upload_flags = + --auth=1234 From 991d3e52429df3e4075d5a5c4ab7f4da11774f8e Mon Sep 17 00:00:00 2001 From: mcuw <112967559+mcuw@users.noreply.github.com> Date: Fri, 26 Apr 2024 08:22:15 +0200 Subject: [PATCH 06/14] update ota example and reset dev board feature --- examples/ota/main.cpp | 9 +++++++-- src/main.cpp | 8 ++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/examples/ota/main.cpp b/examples/ota/main.cpp index 6c61cd2..2faae56 100644 --- a/examples/ota/main.cpp +++ b/examples/ota/main.cpp @@ -5,10 +5,15 @@ OTA ota; String ap_default_psk(AP_PSK); // Default PSK. -void setup() +void initDevBoard(uint8_t redValue = 0, uint8_t greenValue = 0, uint8_t blueValue = 0) { // reset RGB-LED - neopixelWrite(PIN_NEOPIXEL, 0, 0, 0); + neopixelWrite(PIN_NEOPIXEL, redValue, greenValue, blueValue); +} + +void setup() +{ + initDevBoard(0, 0, 0); ota.begin(ap_default_psk); // or protected but does not work yet diff --git a/src/main.cpp b/src/main.cpp index c5bd80c..f446251 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,11 @@ // see https://github.com/wuxx/nanoESP32-C6 +void initDevBoard(uint8_t redValue = 0, uint8_t greenValue = 0, uint8_t blueValue = 0) +{ + neopixelWrite(PIN_NEOPIXEL, redValue, greenValue, blueValue); // default off RGB-LED if (0, 0, 0) +} + void setup() { serialBegin(115200); @@ -12,6 +17,9 @@ void setup() void loop() { + // reset RGB-LED, ... + initDevBoard(0, 0, 0); + // The ESP32-C6 has an attenuator on the ADC, which defaults to 11dB. This causes the resolution to be reduced analogSetAttenuation(ADC_0db); From ba935eeea135e52537378ad11105569d543783c1 Mon Sep 17 00:00:00 2001 From: mcuw <112967559+mcuw@users.noreply.github.com> Date: Tue, 30 Apr 2024 01:19:02 +0200 Subject: [PATCH 07/14] add more examples and fix OTA hostname --- examples/mac/main.cpp | 14 ++++++++++++++ examples/ota/credentials.h | 2 +- examples/ota/main.cpp | 5 +++-- examples/rgb/main.cpp | 6 +++--- examples/serial/main.cpp | 12 ++++++++++++ examples/wifi-ap/main.cpp | 21 +++++++++++++++++++++ include/DebugUtils.h | 4 ++-- lib/OTA/OTA.cpp | 36 +++++++++++++----------------------- lib/OTA/OTA.h | 1 - platformio.ini | 7 ++++++- src/main.cpp | 2 +- upload_params_example.ini | 6 +++--- 12 files changed, 79 insertions(+), 37 deletions(-) create mode 100644 examples/mac/main.cpp create mode 100644 examples/serial/main.cpp create mode 100644 examples/wifi-ap/main.cpp diff --git a/examples/mac/main.cpp b/examples/mac/main.cpp new file mode 100644 index 0000000..bb3926c --- /dev/null +++ b/examples/mac/main.cpp @@ -0,0 +1,14 @@ +#include +#include + +void setup() +{ + Serial.begin(115200); +} + +void loop() +{ + WiFi.mode(WIFI_STA); + Serial.println(WiFi.macAddress()); + delay(1000); +} \ No newline at end of file diff --git a/examples/ota/credentials.h b/examples/ota/credentials.h index 9797994..d5b84e8 100644 --- a/examples/ota/credentials.h +++ b/examples/ota/credentials.h @@ -1,3 +1,3 @@ #ifndef AP_PSK -#define AP_PSK "1234" +#define AP_PSK "12345678" #endif diff --git a/examples/ota/main.cpp b/examples/ota/main.cpp index 2faae56..100fc90 100644 --- a/examples/ota/main.cpp +++ b/examples/ota/main.cpp @@ -1,9 +1,9 @@ #include #include #include "credentials.h" +#include OTA ota; -String ap_default_psk(AP_PSK); // Default PSK. void initDevBoard(uint8_t redValue = 0, uint8_t greenValue = 0, uint8_t blueValue = 0) { @@ -13,9 +13,10 @@ void initDevBoard(uint8_t redValue = 0, uint8_t greenValue = 0, uint8_t blueValu void setup() { + serialBegin(115200); initDevBoard(0, 0, 0); - ota.begin(ap_default_psk); + ota.begin(AP_PSK); // or protected but does not work yet // ota.begin(ap_default_psk, MY_SSID, PASSPHRASE); } diff --git a/examples/rgb/main.cpp b/examples/rgb/main.cpp index 1508374..0ceda06 100644 --- a/examples/rgb/main.cpp +++ b/examples/rgb/main.cpp @@ -12,15 +12,15 @@ void setup() void loop() { - serialPrintLn("red"); + serialPrintln("red"); neopixelWrite(PIN_NEOPIXEL, 255, 0, 0); delay(1000); - serialPrintLn("green"); + serialPrintln("green"); neopixelWrite(PIN_NEOPIXEL, 0, 255, 0); delay(1000); - serialPrintLn("blue"); + serialPrintln("blue"); neopixelWrite(PIN_NEOPIXEL, 0, 0, 255); delay(1000); } \ No newline at end of file diff --git a/examples/serial/main.cpp b/examples/serial/main.cpp new file mode 100644 index 0000000..a959396 --- /dev/null +++ b/examples/serial/main.cpp @@ -0,0 +1,12 @@ +#include + +void setup() +{ + Serial.begin(115200); +} + +void loop() +{ + Serial.println("loop"); + delay(2000); +} \ No newline at end of file diff --git a/examples/wifi-ap/main.cpp b/examples/wifi-ap/main.cpp new file mode 100644 index 0000000..4825009 --- /dev/null +++ b/examples/wifi-ap/main.cpp @@ -0,0 +1,21 @@ +#include +#include + +void setup() +{ + // Go into software AP mode. + WiFi.mode(WIFI_AP); + delay(10); + int channel = 1; + int ssid_hidden = 0; + int max_connection = 4; + WiFi.softAP("ESP32-c6", "passphrase", channel, ssid_hidden, max_connection, false, WIFI_AUTH_WPA3_PSK); +} + +void loop() +{ + neopixelWrite(PIN_NEOPIXEL, 255, 0, 0); + delay(200); + neopixelWrite(PIN_NEOPIXEL, 0, 0, 255); + delay(200); +} \ No newline at end of file diff --git a/include/DebugUtils.h b/include/DebugUtils.h index c653c96..76afe0e 100644 --- a/include/DebugUtils.h +++ b/include/DebugUtils.h @@ -14,11 +14,11 @@ build_flags = #ifdef SERIAL_DEBUG #define serialBegin(x) Serial.begin(x) #define serialPrint(x) Serial.print(x) -#define serialPrintLn(x) Serial.println(x) +#define serialPrintln(x) Serial.println(x) #else #define serialBegin(x) #define serialPrint(x) -#define serialPrintLn(x) +#define serialPrintln(x) #endif #endif diff --git a/lib/OTA/OTA.cpp b/lib/OTA/OTA.cpp index 540a5c6..ce7cf93 100644 --- a/lib/OTA/OTA.cpp +++ b/lib/OTA/OTA.cpp @@ -2,6 +2,7 @@ #include #include "./OTA.h" #include "esp_mac.h" // required - exposes esp_mac_type_t values +#include "esp_mac.h" #define HOSTNAME "ESP32-" @@ -9,43 +10,33 @@ OTA::OTA() { } -String OTA::getDefaultMacAddress() -{ - String mac = ""; - - unsigned char mac_base[6] = {0}; - - if (esp_efuse_mac_get_default(mac_base) == ESP_OK) - { - char buffer[13]; // 6*2 characters for hex + 1 character for null terminator - sprintf(buffer, "%02X%02X%02X%02X%02X%02X", mac_base[0], mac_base[1], mac_base[2], mac_base[3], mac_base[4], mac_base[5]); - mac = buffer; - } - - return mac; -} - String OTA::getHostname() { String hostname(HOSTNAME); - hostname += getDefaultMacAddress(); + hostname += WiFi.macAddress(); return hostname; } void OTA::startAP(const String &passphrase) { - String ssid(getHostname()); - // Go into software AP mode. + WiFi.mode(WIFI_STA); + String ssid(getHostname()); // STA-, Serial- and ESPNow-MAC WiFi.mode(WIFI_AP); delay(10); int channel = 1; int ssid_hidden = 0; int max_connection = 4; - WiFi.softAP(ssid, passphrase, channel, ssid_hidden, max_connection, false, WIFI_AUTH_WPA3_PSK); + if (WiFi.softAP(ssid, passphrase, channel, ssid_hidden, max_connection, false, WIFI_AUTH_WPA3_PSK)) + { + // neopixelWrite(PIN_NEOPIXEL, 0, 0, 255); + } + else + { + // neopixelWrite(PIN_NEOPIXEL, 255, 0, 0); + } // Start OTA server. - ArduinoOTA.setHostname(ssid.c_str()); ArduinoOTA.setPassword(passphrase.c_str()); ArduinoOTA.begin(); } @@ -57,14 +48,13 @@ boolean OTA::startSTA(const char *station_ssid, const char *station_passphrase, return false; } - String hostname(getHostname()); - // Check Wifi mode if (WiFi.getMode() != WIFI_STA) { WiFi.mode(WIFI_STA); delay(10); } + String hostname(getHostname()); // Compare file config with sdk config if (strcmp(WiFi.SSID().c_str(), station_ssid) == 0 && strcmp(WiFi.psk().c_str(), station_passphrase) == 0) diff --git a/lib/OTA/OTA.h b/lib/OTA/OTA.h index ae3e7ed..5772373 100644 --- a/lib/OTA/OTA.h +++ b/lib/OTA/OTA.h @@ -14,7 +14,6 @@ class OTA void startAP(const String &passphrase); boolean startSTA(const char *station_ssid, const char *station_passphrase, const String &password); String getHostname(); - String getDefaultMacAddress(); }; #endif \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index bbae9b7..ec5bbec 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,13 +12,17 @@ boards_dir = ./boards extra_configs = upload_params.ini ; examples +; src_dir = ./examples/serial +; src_dir = ./examples/mac ; src_dir = ./examples/rgb +; src_dir = ./examples/wifi-ap ; src_dir = ./examples/ota [env] build_flags = '-DPIOENV="${PIOENV}"' - ; '-DSERIAL_DEBUG' + '-DSERIAL_DEBUG' + -DCORE_DEBUG_LEVEL=5 -Wno-unused-variable -Wno-unused-but-set-variable -Wunreachable-code @@ -26,6 +30,7 @@ extra_scripts = pre:extra_script.py framework = arduino lib_deps = monitor_speed = 115200 +monitor_port = /dev/cu.usbmodem5 platform = espressif32 upload_speed = 921600 diff --git a/src/main.cpp b/src/main.cpp index f446251..ce4f116 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,7 +24,7 @@ void loop() analogSetAttenuation(ADC_0db); // // blink - // serialPrintLn("green"); + // serialPrintln("green"); // neopixelWrite(PIN_NEOPIXEL, 0, 255, 0); // delay(1000); } \ No newline at end of file diff --git a/upload_params_example.ini b/upload_params_example.ini index 505cd1b..f39666d 100644 --- a/upload_params_example.ini +++ b/upload_params_example.ini @@ -1,8 +1,8 @@ [env:esp32-c6-n16-ota] upload_flags = - ; replace 1234 with yours the same as AP_PSK in cred.h - --auth=1234 + ; replace 12345678 with yours the same as AP_PSK in cred.h + --auth=12345678 [env:esp32-c6-n16-ota] upload_flags = - --auth=1234 + --auth=12345678 From 9afd0fbc600f8f474d855daf583ffb77cb989f43 Mon Sep 17 00:00:00 2001 From: mcuw <112967559+mcuw@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:48:26 +0200 Subject: [PATCH 08/14] add ota-telnet example; chore OTA --- .vscode/settings.json | 3 +- examples/ota-manager/credentials.h | 3 ++ examples/ota-telnet/credentials.h | 3 ++ examples/ota-telnet/main.cpp | 53 ++++++++++++++++++++++++++++++ examples/ota/main.cpp | 7 ++-- lib/OTA/OTA.cpp | 21 +++++++----- platformio.ini | 9 +++-- 7 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 examples/ota-manager/credentials.h create mode 100644 examples/ota-telnet/credentials.h create mode 100644 examples/ota-telnet/main.cpp diff --git a/.vscode/settings.json b/.vscode/settings.json index c2139d4..cef319e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,7 @@ "initializer_list": "cpp", "span": "cpp", "*.tcc": "cpp", - "system_error": "cpp" + "system_error": "cpp", + "regex": "cpp" } } \ No newline at end of file diff --git a/examples/ota-manager/credentials.h b/examples/ota-manager/credentials.h new file mode 100644 index 0000000..d5b84e8 --- /dev/null +++ b/examples/ota-manager/credentials.h @@ -0,0 +1,3 @@ +#ifndef AP_PSK +#define AP_PSK "12345678" +#endif diff --git a/examples/ota-telnet/credentials.h b/examples/ota-telnet/credentials.h new file mode 100644 index 0000000..d5b84e8 --- /dev/null +++ b/examples/ota-telnet/credentials.h @@ -0,0 +1,3 @@ +#ifndef AP_PSK +#define AP_PSK "12345678" +#endif diff --git a/examples/ota-telnet/main.cpp b/examples/ota-telnet/main.cpp new file mode 100644 index 0000000..8587e24 --- /dev/null +++ b/examples/ota-telnet/main.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include "credentials.h" + +OTA ota; +ESPTelnetStream telnet; + +void initDevBoard(uint8_t redValue = 0, uint8_t greenValue = 0, uint8_t blueValue = 0) +{ + // reset RGB-LED + neopixelWrite(PIN_NEOPIXEL, redValue, greenValue, blueValue); +} + +void setup() +{ + serialBegin(115200); + initDevBoard(0, 0, 0); + + ota.begin(AP_PSK); + // or use this for an existing WLAN: + // ota.begin(ap_default_psk, MY_SSID, PASSPHRASE); + + telnet.begin(); +} + +void loop() +{ + ota.handle(); + telnet.loop(); + + if (telnet.available() > 0) + { + int message = telnet.read(); + + switch (message) + { + case 'a': + neopixelWrite(PIN_NEOPIXEL, 0, 0, 255); + telnet.println("light"); + break; + + case 'o': + neopixelWrite(PIN_NEOPIXEL, 0, 0, 0); + telnet.println("dark"); + break; + } + } + + telnet.println("loop"); + delay(200); +} \ No newline at end of file diff --git a/examples/ota/main.cpp b/examples/ota/main.cpp index 100fc90..8ba635e 100644 --- a/examples/ota/main.cpp +++ b/examples/ota/main.cpp @@ -1,7 +1,7 @@ #include #include -#include "credentials.h" #include +#include "credentials.h" OTA ota; @@ -17,7 +17,7 @@ void setup() initDevBoard(0, 0, 0); ota.begin(AP_PSK); - // or protected but does not work yet + // or use this for an existing WLAN: // ota.begin(ap_default_psk, MY_SSID, PASSPHRASE); } @@ -28,6 +28,7 @@ void loop() // blink example // neopixelWrite(PIN_NEOPIXEL, 0, 0, 255); // delay(200); + // neopixelWrite(PIN_NEOPIXEL, 0, 0, 0); - // delay(200); + // delay(100); } \ No newline at end of file diff --git a/lib/OTA/OTA.cpp b/lib/OTA/OTA.cpp index ce7cf93..ab5d9a0 100644 --- a/lib/OTA/OTA.cpp +++ b/lib/OTA/OTA.cpp @@ -4,24 +4,29 @@ #include "esp_mac.h" // required - exposes esp_mac_type_t values #include "esp_mac.h" -#define HOSTNAME "ESP32-" +#define HOSTNAME_PREFIX "ESP32-" OTA::OTA() { } +// get MAC of STA-, Serial- and ESPNow-MAC String OTA::getHostname() { - String hostname(HOSTNAME); + String hostname(HOSTNAME_PREFIX); + if (WiFi.getMode() != WIFI_STA) + { + WiFi.mode(WIFI_STA); + delay(10); + } hostname += WiFi.macAddress(); + WiFi.disconnect(true); return hostname; } void OTA::startAP(const String &passphrase) { - // Go into software AP mode. - WiFi.mode(WIFI_STA); - String ssid(getHostname()); // STA-, Serial- and ESPNow-MAC + String ssid(getHostname()); WiFi.mode(WIFI_AP); delay(10); int channel = 1; @@ -48,13 +53,14 @@ boolean OTA::startSTA(const char *station_ssid, const char *station_passphrase, return false; } + String hostname(getHostname()); + // Check Wifi mode if (WiFi.getMode() != WIFI_STA) { WiFi.mode(WIFI_STA); delay(10); } - String hostname(getHostname()); // Compare file config with sdk config if (strcmp(WiFi.SSID().c_str(), station_ssid) == 0 && strcmp(WiFi.psk().c_str(), station_passphrase) == 0) @@ -73,8 +79,6 @@ boolean OTA::startSTA(const char *station_ssid, const char *station_passphrase, // Serial.println(WiFi.SSID()); } - // Serial.println(F("Wait for WiFi connection.")); - // Give ESP 10 seconds to connect to station unsigned long startTime = millis(); while (WiFi.status() != WL_CONNECTED && millis() - startTime < 10000) @@ -97,7 +101,6 @@ boolean OTA::startSTA(const char *station_ssid, const char *station_passphrase, // Serial.println(WiFi.localIP()); // Start OTA server - ArduinoOTA.setHostname(hostname.c_str()); ArduinoOTA.setPassword(passphrase.c_str()); ArduinoOTA.begin(); diff --git a/platformio.ini b/platformio.ini index ec5bbec..fce4d32 100644 --- a/platformio.ini +++ b/platformio.ini @@ -17,6 +17,7 @@ extra_configs = upload_params.ini ; src_dir = ./examples/rgb ; src_dir = ./examples/wifi-ap ; src_dir = ./examples/ota +; src_dir = ./examples/ota-telnet [env] build_flags = @@ -36,12 +37,14 @@ upload_speed = 921600 [extra] build_flags_psram = - -DBOARD_HAS_PSRAM - -mfix-esp32-psram-cache-issue lib_deps_builtin = SPI - Wire + I2C + ; FS lib_deps_external = + lennarthennigs/ESP Telnet @ ^2.2.1 + ; me-no-dev/ESPAsyncTCP @ ^1.2.2 + ; me-no-dev/ESP Async WebServer @ ^1.2.3 [esp32] build_flags = From 6782cbec9e15b6ab0665bd262a53a0a850ff0ab2 Mon Sep 17 00:00:00 2001 From: mcuw <112967559+mcuw@users.noreply.github.com> Date: Thu, 16 May 2024 23:07:36 +0200 Subject: [PATCH 09/14] adapt boards config to use the whole 16 MB flash; add ota-webserver and ota-manager examples --- .gitignore | 4 + .vscode/settings.json | 7 +- boards/esp32-c6-n16.json | 5 +- examples/ota-manager/main.cpp | 169 +++++++++++++++++++++++++++ examples/ota-webserver/credentials.h | 3 + examples/ota-webserver/main.cpp | 124 ++++++++++++++++++++ lib/OTA/OTA.cpp | 19 ++- platformio.ini | 10 +- src/main.cpp | 7 +- 9 files changed, 327 insertions(+), 21 deletions(-) create mode 100644 examples/ota-manager/main.cpp create mode 100644 examples/ota-webserver/credentials.h create mode 100644 examples/ota-webserver/main.cpp diff --git a/.gitignore b/.gitignore index 2fda03f..4b9f1da 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ .vscode/ipch .DS_Store upload_params.ini + +# projectplan and design +*.kanban +*.excalidraw diff --git a/.vscode/settings.json b/.vscode/settings.json index cef319e..9aa2d7f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,6 +12,11 @@ "span": "cpp", "*.tcc": "cpp", "system_error": "cpp", - "regex": "cpp" + "regex": "cpp", + "compare": "cpp", + "functional": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp" } } \ No newline at end of file diff --git a/boards/esp32-c6-n16.json b/boards/esp32-c6-n16.json index 287f5e4..2bf4036 100644 --- a/boards/esp32-c6-n16.json +++ b/boards/esp32-c6-n16.json @@ -7,10 +7,11 @@ "mcu": "esp32c6", "variant": "esp32c6", "extra_flags": [ - "-DARDUINO_ESP32S3_DEV", + "-DARDUINO_ESP32C6_DEV", "-DARDUINO_USB_MODE=1", "-DARDUINO_USB_CDC_ON_BOOT=1" - ] + ], + "partitions": "default_16MB.csv" }, "connectivity": [ "wifi", diff --git a/examples/ota-manager/main.cpp b/examples/ota-manager/main.cpp new file mode 100644 index 0000000..b3362a9 --- /dev/null +++ b/examples/ota-manager/main.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include +#include + +#include "credentials.h" + +#define PREF_KEY "config" +#define PREF_SSID_KEY "ssid" +#define PREF_PASSPHRASE_KEY "passphrase" + +OTA ota; +String ap_default_psk(AP_PSK); // Set your default PSK in credentials.h if you want +ESPTelnetStream telnet; + +WebServer server(80); +Preferences prefs; + +void initDevBoard(uint8_t redValue = 0, uint8_t greenValue = 0, uint8_t blueValue = 0) +{ + // init RGB-LED + neopixelWrite(PIN_NEOPIXEL, redValue, greenValue, blueValue); + + // you can initialize the dev board here ... +} + +void handleRoot() +{ + if (server.hasArg(PREF_SSID_KEY) && server.arg(PREF_SSID_KEY).length() && server.hasArg(PREF_PASSPHRASE_KEY) && server.arg(PREF_PASSPHRASE_KEY).length()) + { + const String ssid(server.arg(PREF_SSID_KEY)); + prefs.begin(PREF_KEY, false); + prefs.putString(PREF_SSID_KEY, ssid); + prefs.putString(PREF_PASSPHRASE_KEY, server.arg(PREF_PASSPHRASE_KEY)); + prefs.end(); + + serialPrint("Credentials saved for: "); + serialPrintln(ssid); + telnet.print("Credentials saved for: "); + telnet.println(ssid); + + char success[150]; + snprintf( + success, 148, + "\ +\ + ESP32-C6 OTA with webserver\ + \ +\ +\ +\ +"); + server.send(200, F("text/html"), success); + + return; + } + + prefs.begin(PREF_KEY, true); + const String ssid(prefs.isKey(PREF_SSID_KEY) ? prefs.getString(PREF_SSID_KEY) : ""); + prefs.end(); + + char temp[740]; + snprintf( + temp, 740, + "\ + \ + ESP32-C6 OTA with webserver\ + \ + \ + \ +

OTA and webserver example

\ +
\ +
\ +
\ + \ +
\ +
\ +
\ + \ +
\ +
\ + \ + \ +
\ +
\ + \ +", + ssid); + server.send(200, F("text/html"), temp); +} + +void handleSaved() +{ + char success[330]; + + snprintf( + success, 324, + "\ +\ + ESP32-C6 OTA with webserver\ + \ +\ +\ +

OTA and webserver example

\ +
Credentials saved. ESP is restarting and will be available under a new IP.
\ +\ +"); + server.send(200, F("text/html"), success); + + delay(2000); + ESP.restart(); +} + +void handleNotFound() +{ + String message("File Not Found\n\n"); + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + + for (uint8_t i = 0; i < server.args(); i++) + { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + + server.send(404, "text/plain", message); +} + +void setup() +{ + serialBegin(115200); + delay(5000); + + initDevBoard(255, 0, 0); + + prefs.begin(PREF_KEY, true); + String ssid(prefs.isKey(PREF_SSID_KEY) ? prefs.getString(PREF_SSID_KEY).c_str() : ""); + String passphrase(prefs.isKey(PREF_PASSPHRASE_KEY) ? prefs.getString(PREF_PASSPHRASE_KEY).c_str() : ""); + prefs.end(); + serialPrint("SSID: "); + serialPrintln(ssid); + + ota.begin(ap_default_psk, ssid.c_str(), passphrase.c_str()); + telnet.begin(); + + server.on("/", handleRoot); + server.on("/saved", handleSaved); + server.onNotFound(handleNotFound); + + server.begin(); +} + +void loop() +{ + ota.handle(); + telnet.loop(); + server.handleClient(); + delay(2); // give cpu time to do things +} diff --git a/examples/ota-webserver/credentials.h b/examples/ota-webserver/credentials.h new file mode 100644 index 0000000..d5b84e8 --- /dev/null +++ b/examples/ota-webserver/credentials.h @@ -0,0 +1,3 @@ +#ifndef AP_PSK +#define AP_PSK "12345678" +#endif diff --git a/examples/ota-webserver/main.cpp b/examples/ota-webserver/main.cpp new file mode 100644 index 0000000..62f98c5 --- /dev/null +++ b/examples/ota-webserver/main.cpp @@ -0,0 +1,124 @@ +#include +#include +#include +#include + +#include "credentials.h" + +OTA ota; +String ap_default_psk(AP_PSK); // Set your default PSK in credentials.h if you want +ESPTelnetStream telnet; + +WebServer server(80); + +const char NEWLINE = '\n'; +const char CARRIAGE_RETURN = '\r'; + +void initDevBoard(uint8_t redValue = 0, uint8_t greenValue = 0, uint8_t blueValue = 0) +{ + // init RGB-LED + neopixelWrite(PIN_NEOPIXEL, redValue, greenValue, blueValue); +} + +void handleRoot() +{ + if (server.hasArg("name")) + { + char success[350]; + const String message(F("\ +\ + ESP32-C6 OTA with webserver\ + \ +\ +\ +
Hello %s
\ +\ +")); + snprintf( + success, message.length(), + message.c_str(), + server.arg("name")); + server.send(200, F("text/html"), success); + + return; + } + + char temp[540]; + snprintf( + temp, 536, + "\ + \ + ESP32-C6 OTA with webserver\ + \ + \ + \ +

OTA and webserver example

\ +
\ +
\ +
\ + \ +
\ +
\ + \ + \ +
\ +
\ + \ +"); + server.send(200, "text/html", temp); +} + +void handleNotFound() +{ + String message = "File Not Found\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + + for (uint8_t i = 0; i < server.args(); i++) + { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + + server.send(404, "text/plain", message); +} + +void setup() +{ + initDevBoard(255, 0, 0); + + const char *MY_SSID = ""; + const char *PASSPHRASE = ""; + // TODO show UI and save in preferences, load on start and put it here + // on UI - posibility to reset credentials to show AP + ota.begin(ap_default_psk, MY_SSID, PASSPHRASE); + telnet.begin(); + + server.on(F("/"), handleRoot); + server.onNotFound(handleNotFound); + + server.begin(); +} + +void loop() +{ + ota.handle(); + telnet.loop(); + server.handleClient(); + delay(2); // give cpu time to do things +} diff --git a/lib/OTA/OTA.cpp b/lib/OTA/OTA.cpp index ab5d9a0..db3cef6 100644 --- a/lib/OTA/OTA.cpp +++ b/lib/OTA/OTA.cpp @@ -1,8 +1,6 @@ #include #include #include "./OTA.h" -#include "esp_mac.h" // required - exposes esp_mac_type_t values -#include "esp_mac.h" #define HOSTNAME_PREFIX "ESP32-" @@ -20,6 +18,7 @@ String OTA::getHostname() delay(10); } hostname += WiFi.macAddress(); + hostname.replace(":", ""); WiFi.disconnect(true); return hostname; } @@ -34,16 +33,14 @@ void OTA::startAP(const String &passphrase) int max_connection = 4; if (WiFi.softAP(ssid, passphrase, channel, ssid_hidden, max_connection, false, WIFI_AUTH_WPA3_PSK)) { - // neopixelWrite(PIN_NEOPIXEL, 0, 0, 255); + // Start OTA server. + ArduinoOTA.setPassword(passphrase.c_str()); + ArduinoOTA.begin(); } else { - // neopixelWrite(PIN_NEOPIXEL, 255, 0, 0); + neopixelWrite(PIN_NEOPIXEL, 255, 0, 0); } - - // Start OTA server. - ArduinoOTA.setPassword(passphrase.c_str()); - ArduinoOTA.begin(); } boolean OTA::startSTA(const char *station_ssid, const char *station_passphrase, const String &passphrase) @@ -54,6 +51,7 @@ boolean OTA::startSTA(const char *station_ssid, const char *station_passphrase, } String hostname(getHostname()); + // Serial.println(hostname); // Check Wifi mode if (WiFi.getMode() != WIFI_STA) @@ -92,15 +90,16 @@ boolean OTA::startSTA(const char *station_ssid, const char *station_passphrase, // Check connection if (WiFi.status() != WL_CONNECTED) { - // Serial.println(F("Failed to connect to WiFi station")); + // Serial.println("Failed to connect to WiFi station"); return false; } // print IP Address - // Serial.print(F("STA IP address: ")); + // Serial.print("STA IP address: "); // Serial.println(WiFi.localIP()); // Start OTA server + ArduinoOTA.setHostname(hostname.c_str()); // open http://esp32-XX.local ArduinoOTA.setPassword(passphrase.c_str()); ArduinoOTA.begin(); diff --git a/platformio.ini b/platformio.ini index fce4d32..4e34ed7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,6 +18,8 @@ extra_configs = upload_params.ini ; src_dir = ./examples/wifi-ap ; src_dir = ./examples/ota ; src_dir = ./examples/ota-telnet +; src_dir = ./examples/ota-webserver +; src_dir = ./examples/ota-manager [env] build_flags = @@ -31,7 +33,8 @@ extra_scripts = pre:extra_script.py framework = arduino lib_deps = monitor_speed = 115200 -monitor_port = /dev/cu.usbmodem5 +; uncomment when using multiple ESPs +; monitor_port = /dev/cu.usbmodem5 platform = espressif32 upload_speed = 921600 @@ -39,12 +42,9 @@ upload_speed = 921600 build_flags_psram = lib_deps_builtin = SPI - I2C - ; FS + Wire lib_deps_external = lennarthennigs/ESP Telnet @ ^2.2.1 - ; me-no-dev/ESPAsyncTCP @ ^1.2.2 - ; me-no-dev/ESP Async WebServer @ ^1.2.3 [esp32] build_flags = diff --git a/src/main.cpp b/src/main.cpp index ce4f116..114a368 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ #include +#include #include // pinouts: https://github.com/espressif/arduino-esp32/blob/master/variants/esp32c6/pins_arduino.h @@ -8,6 +9,9 @@ void initDevBoard(uint8_t redValue = 0, uint8_t greenValue = 0, uint8_t blueValue = 0) { neopixelWrite(PIN_NEOPIXEL, redValue, greenValue, blueValue); // default off RGB-LED if (0, 0, 0) + + // The ESP32-C6 has an attenuator on the ADC, which defaults to 11dB. This causes the resolution to be reduced + analogSetAttenuation(ADC_0db); } void setup() @@ -20,9 +24,6 @@ void loop() // reset RGB-LED, ... initDevBoard(0, 0, 0); - // The ESP32-C6 has an attenuator on the ADC, which defaults to 11dB. This causes the resolution to be reduced - analogSetAttenuation(ADC_0db); - // // blink // serialPrintln("green"); // neopixelWrite(PIN_NEOPIXEL, 0, 255, 0); From 73ef26412a9288f8cd2b7f1e1bb0b90aaf6d33e8 Mon Sep 17 00:00:00 2001 From: mcuw <112967559+mcuw@users.noreply.github.com> Date: Thu, 16 May 2024 23:33:19 +0200 Subject: [PATCH 10/14] chore --- examples/ota-webserver/main.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/examples/ota-webserver/main.cpp b/examples/ota-webserver/main.cpp index 62f98c5..4a8821d 100644 --- a/examples/ota-webserver/main.cpp +++ b/examples/ota-webserver/main.cpp @@ -11,9 +11,6 @@ ESPTelnetStream telnet; WebServer server(80); -const char NEWLINE = '\n'; -const char CARRIAGE_RETURN = '\r'; - void initDevBoard(uint8_t redValue = 0, uint8_t greenValue = 0, uint8_t blueValue = 0) { // init RGB-LED @@ -81,7 +78,7 @@ void handleRoot() void handleNotFound() { - String message = "File Not Found\n\n"; + String message("File Not Found\n\n"); message += "URI: "; message += server.uri(); message += "\nMethod: "; @@ -104,12 +101,13 @@ void setup() const char *MY_SSID = ""; const char *PASSPHRASE = ""; - // TODO show UI and save in preferences, load on start and put it here - // on UI - posibility to reset credentials to show AP + + // how to setup credentials (SSID, PASSPHRASE), + // see ota-manager example ota.begin(ap_default_psk, MY_SSID, PASSPHRASE); telnet.begin(); - server.on(F("/"), handleRoot); + server.on("/", handleRoot); server.onNotFound(handleNotFound); server.begin(); From 90fd78d362b6b76ca7f38aaf119928a6549b55c5 Mon Sep 17 00:00:00 2001 From: mcuw <112967559+mcuw@users.noreply.github.com> Date: Sun, 19 May 2024 01:09:28 +0200 Subject: [PATCH 11/14] add ota-manager-rtos with RTOS tasks --- .vscode/settings.json | 3 +- examples/ota-manager-rtos/credentials.h | 3 + examples/ota-manager-rtos/main.cpp | 257 ++++++++++++++++++++++++ examples/ota-manager/main.cpp | 4 +- include/DebugUtils.h | 2 + lib/OTA/OTA.cpp | 55 ++++- lib/OTA/OTA.h | 2 +- platformio.ini | 5 +- 8 files changed, 322 insertions(+), 9 deletions(-) create mode 100644 examples/ota-manager-rtos/credentials.h create mode 100644 examples/ota-manager-rtos/main.cpp diff --git a/.vscode/settings.json b/.vscode/settings.json index 9aa2d7f..901dd8b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,6 +17,7 @@ "functional": "cpp", "tuple": "cpp", "type_traits": "cpp", - "utility": "cpp" + "utility": "cpp", + "limits": "cpp" } } \ No newline at end of file diff --git a/examples/ota-manager-rtos/credentials.h b/examples/ota-manager-rtos/credentials.h new file mode 100644 index 0000000..d5b84e8 --- /dev/null +++ b/examples/ota-manager-rtos/credentials.h @@ -0,0 +1,3 @@ +#ifndef AP_PSK +#define AP_PSK "12345678" +#endif diff --git a/examples/ota-manager-rtos/main.cpp b/examples/ota-manager-rtos/main.cpp new file mode 100644 index 0000000..51ec389 --- /dev/null +++ b/examples/ota-manager-rtos/main.cpp @@ -0,0 +1,257 @@ +#include +#include +#include +#include +#include +#include + +#include "credentials.h" +#include + +#define PREF_KEY "config" +#define PREF_SSID_KEY "ssid" +#define PREF_PASSPHRASE_KEY "passphrase" + +String ap_default_psk(AP_PSK); // Set your default PSK in credentials.h if you want +ESPTelnetStream telnet; + +WebServer server(80); +Preferences prefs; + +TaskHandle_t OTATask, TelnetTask, UiTask, SleepTask; +OTA ota; + +// sleep modes, see +// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/sleep_modes.html + +void initDevBoard(uint8_t redValue = 0, uint8_t greenValue = 0, uint8_t blueValue = 0) +{ + // init RGB-LED + neopixelWrite(PIN_NEOPIXEL, redValue, greenValue, blueValue); + + // you can initialize the dev board here ... +} + +void handleRoot() +{ + if (server.hasArg(PREF_SSID_KEY) && server.arg(PREF_SSID_KEY).length() && server.hasArg(PREF_PASSPHRASE_KEY) && server.arg(PREF_PASSPHRASE_KEY).length()) + { + const String ssid(server.arg(PREF_SSID_KEY)); + prefs.begin(PREF_KEY, false); + prefs.putString(PREF_SSID_KEY, ssid); + prefs.putString(PREF_PASSPHRASE_KEY, server.arg(PREF_PASSPHRASE_KEY)); + prefs.end(); + + serialPrint("Credentials saved for: "); + serialPrintln(ssid); + telnet.print("Credentials saved for: "); + telnet.println(ssid); + + char success[150]; + snprintf( + success, 148, + "\ +\ + ESP32-C6 OTA with webserver\ + \ +\ +\ +\ +"); + server.send(200, F("text/html"), success); + + return; + } + + prefs.begin(PREF_KEY, true); + const String ssid(prefs.isKey(PREF_SSID_KEY) ? prefs.getString(PREF_SSID_KEY) : ""); + prefs.end(); + + char temp[740]; + snprintf( + temp, 740, + "\ + \ + ESP32-C6 OTA with webserver\ + \ + \ + \ +

OTA and webserver example

\ +
\ +
\ +
\ + \ +
\ +
\ +
\ + \ +
\ +
\ + \ + \ +
\ +
\ + \ +", + ssid); + server.send(200, F("text/html"), temp); +} + +void handleSaved() +{ + char success[330]; + + snprintf( + success, 324, + "\ +\ + ESP32-C6 OTA with webserver\ + \ +\ +\ +

OTA and webserver example

\ +
Credentials saved. ESP is restarting and will be available under a new IP.
\ +\ +"); + server.send(200, F("text/html"), success); + + delay(2000); + ESP.restart(); +} + +void handleNotFound() +{ + String message("File Not Found\n\n"); + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + + for (uint8_t i = 0; i < server.args(); i++) + { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + + server.send(404, "text/plain", message); +} + +void otaTask(void *parameters) +{ + for (;;) + { + ota.handle(); + vTaskDelay(portTICK_PERIOD_MS); + } +} + +void telnetTask(void *parameters) +{ + for (;;) + { + telnet.loop(); + vTaskDelay(portTICK_PERIOD_MS); + } +} + +void uiTask(void *parameters) +{ + for (;;) + { + server.handleClient(); + vTaskDelay(portTICK_PERIOD_MS); + } +} + +void sleepTask(void *parameters) +{ + esp_err_t err = esp_sleep_enable_timer_wakeup(10 * 1000000); + if (err == ESP_ERR_INVALID_ARG) + { + telnet.print("sleep timer wakeup value is too high"); + telnet.println(err); + } + + for (;;) + { + if (ota.isLoading()) + { + vTaskDelay(1000 / portTICK_PERIOD_MS); + continue; + } + // TODO test this: + // esp_err_t err = esp_sleep_enable_wifi_wakeup(); + // telnet.println("enable wifi wakeup..."); + // telnet.println(err); + + // esp_now_deinit();x + neopixelWrite(PIN_NEOPIXEL, 0, 0, 0); + telnet.println("sleep ..."); + telnet.flush(); + serialFlush(); + err = esp_light_sleep_start(); // ESP_OK or ESP_ERR_INVALID_STATE + if (err == ESP_OK) + { + neopixelWrite(PIN_NEOPIXEL, 0, 255, 0); + + // wake up or failed + // err = esp_now_init(); + delay(100); + telnet.println("wake up ..."); + } + else + { + neopixelWrite(PIN_NEOPIXEL, 255, 0, 0); + telnet.println(err); + } + + // give other tasks time + vTaskDelay(10000 / portTICK_PERIOD_MS); + } +} + +void setup() +{ + serialBegin(115200); + + initDevBoard(0, 255, 0); + + prefs.begin(PREF_KEY, true); + String ssid(prefs.getString(PREF_SSID_KEY).c_str()); + String passphrase(prefs.getString(PREF_PASSPHRASE_KEY).c_str()); + prefs.end(); + serialPrint("SSID: "); + serialPrintln(ssid); + + ota.begin(ap_default_psk, ssid.c_str(), passphrase.c_str()); + telnet.begin(); + + server.on("/", handleRoot); + server.on("/saved", handleSaved); + server.onNotFound(handleNotFound); + server.begin(); + + xTaskCreatePinnedToCore(otaTask, "ota", 4096, NULL, 3, &OTATask, 0); + + xTaskCreatePinnedToCore(telnetTask, "debug", 4096, NULL, 2, &TelnetTask, 0); + + xTaskCreatePinnedToCore(uiTask, "ui", 4096, NULL, 2, &UiTask, 0); + + xTaskCreatePinnedToCore(sleepTask, "sleep", 2048, NULL, 1, &SleepTask, 0); +} + +void loop() +{ + // telnet.print("core: "); + // telnet.println(xPortGetCoreID()); + // delay(1000); + + // run only tasks w/o the non priorized loop task + vTaskDelete(NULL); +} diff --git a/examples/ota-manager/main.cpp b/examples/ota-manager/main.cpp index b3362a9..48e0acd 100644 --- a/examples/ota-manager/main.cpp +++ b/examples/ota-manager/main.cpp @@ -144,8 +144,8 @@ void setup() initDevBoard(255, 0, 0); prefs.begin(PREF_KEY, true); - String ssid(prefs.isKey(PREF_SSID_KEY) ? prefs.getString(PREF_SSID_KEY).c_str() : ""); - String passphrase(prefs.isKey(PREF_PASSPHRASE_KEY) ? prefs.getString(PREF_PASSPHRASE_KEY).c_str() : ""); + String ssid(prefs.getString(PREF_SSID_KEY).c_str()); + String passphrase(prefs.getString(PREF_PASSPHRASE_KEY).c_str()); prefs.end(); serialPrint("SSID: "); serialPrintln(ssid); diff --git a/include/DebugUtils.h b/include/DebugUtils.h index 76afe0e..33b42a3 100644 --- a/include/DebugUtils.h +++ b/include/DebugUtils.h @@ -15,10 +15,12 @@ build_flags = #define serialBegin(x) Serial.begin(x) #define serialPrint(x) Serial.print(x) #define serialPrintln(x) Serial.println(x) +#define serialFlush() Serial.flush() #else #define serialBegin(x) #define serialPrint(x) #define serialPrintln(x) +#define serialFlush() #endif #endif diff --git a/lib/OTA/OTA.cpp b/lib/OTA/OTA.cpp index db3cef6..0e35cc6 100644 --- a/lib/OTA/OTA.cpp +++ b/lib/OTA/OTA.cpp @@ -4,6 +4,8 @@ #define HOSTNAME_PREFIX "ESP32-" +bool loading = false; + OTA::OTA() { } @@ -25,15 +27,16 @@ String OTA::getHostname() void OTA::startAP(const String &passphrase) { - String ssid(getHostname()); + String hostname(getHostname()); WiFi.mode(WIFI_AP); delay(10); int channel = 1; int ssid_hidden = 0; int max_connection = 4; - if (WiFi.softAP(ssid, passphrase, channel, ssid_hidden, max_connection, false, WIFI_AUTH_WPA3_PSK)) + if (WiFi.softAP(hostname, passphrase, channel, ssid_hidden, max_connection, false, WIFI_AUTH_WPA3_PSK)) { // Start OTA server. + ArduinoOTA.setHostname(hostname.c_str()); // open http://esp32-XX.local ArduinoOTA.setPassword(passphrase.c_str()); ArduinoOTA.begin(); } @@ -73,7 +76,7 @@ boolean OTA::startSTA(const char *station_ssid, const char *station_passphrase, // Connect to WiFi station WiFi.begin(station_ssid, station_passphrase); - // Serial.print(F("New SSID: ")); + // Serial.print("New SSID: "); // Serial.println(WiFi.SSID()); } @@ -101,6 +104,47 @@ boolean OTA::startSTA(const char *station_ssid, const char *station_passphrase, // Start OTA server ArduinoOTA.setHostname(hostname.c_str()); // open http://esp32-XX.local ArduinoOTA.setPassword(passphrase.c_str()); + + ArduinoOTA + .onStart([]() + { + loading = true; + // Serial.println("Start updating "); + neopixelWrite(PIN_NEOPIXEL, 255, 255, 255); }) + .onEnd([]() + { + loading = false; + // Serial.println("\nEnd"); + }); + // .onProgress([](unsigned int progress, unsigned int total) + // { + // // Serial.printf("Progress: %u%%\r", (progress / (total / 100))); + // }) + // .onError([](ota_error_t error) + // { + // Serial.printf("Error[%u]: ", error); + // // if (error == OTA_AUTH_ERROR) + // // { + // // Serial.println("Auth Failed"); + // // } + // // else if (error == OTA_BEGIN_ERROR) + // // { + // // Serial.println("Begin Failed"); + // // } + // // else if (error == OTA_CONNECT_ERROR) + // // { + // // Serial.println("Connect Failed"); + // // } + // // else if (error == OTA_RECEIVE_ERROR) + // // { + // // Serial.println("Receive Failed"); + // // } + // // else if (error == OTA_END_ERROR) + // // { + // // Serial.println("End Failed"); + // // } + // }); + ArduinoOTA.begin(); return true; @@ -118,3 +162,8 @@ void OTA::handle() { ArduinoOTA.handle(); } + +bool OTA::isLoading() +{ + return loading; +} diff --git a/lib/OTA/OTA.h b/lib/OTA/OTA.h index 5772373..ea2fcdf 100644 --- a/lib/OTA/OTA.h +++ b/lib/OTA/OTA.h @@ -6,9 +6,9 @@ class OTA public: OTA(); virtual ~OTA(){}; - void begin(const String &password, const char *station_ssid = "", const char *station_passphrase = ""); void handle(); + bool isLoading(); private: void startAP(const String &passphrase); diff --git a/platformio.ini b/platformio.ini index 4e34ed7..0bca961 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,7 +19,7 @@ extra_configs = upload_params.ini ; src_dir = ./examples/ota ; src_dir = ./examples/ota-telnet ; src_dir = ./examples/ota-webserver -; src_dir = ./examples/ota-manager +; src_dir = ./examples/ota-manager-rtos [env] build_flags = @@ -70,7 +70,8 @@ platform_packages = upload_speed = 2000000 upload_protocol = espota ; change upload_port after connected to your network instead of the ESP AP -upload_port = 192.168.4.1 +; upload_port = 192.168.4.1 +upload_port = 192.168.50.105 [env:esp32-c6-n16] extends = esp32c6 From 85c2260e7455c7732bc86f8549add7e0b9dd3a96 Mon Sep 17 00:00:00 2001 From: mcuw <112967559+mcuw@users.noreply.github.com> Date: Wed, 9 Apr 2025 00:36:23 +0200 Subject: [PATCH 12/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e4f429..2508fc5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Uncomment `src_dir=` in the `platform.ini` file. ## Supported boards -- ESP32 C6 with WiFi 6 and BT-5 LE (buy with affiliate link: [UICPAL nanoESP32-C6](https://s.click.aliexpress.com/e/_DdZ83IB) with up to 16MB flash, [ESP32-C6](https://s.click.aliexpress.com/e/_DeLjVMb) with 4MB flash and W2812 RGB LED) +- ESP32 C6 with WiFi 6 and BT-5 LE (buy with affiliate link: [NanoESP32-C6](https://s.click.aliexpress.com/e/_ooBtUih) with up to 16MB flash, [ESP32-C6](https://s.click.aliexpress.com/e/_DeLjVMb) with 4MB flash and W2812 RGB LED) ![datasheet - nanoESP32-C6](doc/nanoESP32C6.pdf) From 9efad36c365206615a5fe0b8013d9e8a2c51dd1b Mon Sep 17 00:00:00 2001 From: mcuw <112967559+mcuw@users.noreply.github.com> Date: Wed, 9 Apr 2025 11:32:31 +0200 Subject: [PATCH 13/14] fix: fix build for env:esp32-c6-n4-ota --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 0bca961..558231d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -88,7 +88,7 @@ extends = esp32c6 board = esp32-c6-n4 [env:esp32-c6-n4-ota] -extends = esp32-c6-n4 +extends = env:esp32-c6-n4 upload_speed = ${esp32c6-ota.upload_speed} upload_protocol = ${esp32c6-ota.upload_protocol} upload_port = ${esp32c6-ota.upload_port} From 81f333a3cc768db18048c7b61965456ac4b410ec Mon Sep 17 00:00:00 2001 From: mcuw <112967559+mcuw@users.noreply.github.com> Date: Fri, 13 Jun 2025 08:56:37 +0200 Subject: [PATCH 14/14] doc: add link to new mcuw SDK for LilyGo T-QT C6 dev board --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2508fc5..5d48e70 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,14 @@ Uncomment `src_dir=` in the `platform.ini` file. ## Supported boards -- ESP32 C6 with WiFi 6 and BT-5 LE (buy with affiliate link: [NanoESP32-C6](https://s.click.aliexpress.com/e/_ooBtUih) with up to 16MB flash, [ESP32-C6](https://s.click.aliexpress.com/e/_DeLjVMb) with 4MB flash and W2812 RGB LED) +- [NanoESP32-C6](https://s.click.aliexpress.com/e/_ooBtUih) (affiliate link) with up to 16MB flash [datasheet](doc/nanoESP32C6.pdf) -![datasheet - nanoESP32-C6](doc/nanoESP32C6.pdf) +- [Super Mini ESP32-C6](https://s.click.aliexpress.com/e/_DeLjVMb) (affiliate link) with 4MB flash and W2812 RGB LED + +- [LilyGo T-QT-C6](https://github.com/mcuw/esp32-t-qt-c6-sdk): use Arduino SDK https://github.com/mcuw/esp32-t-qt-c6-sdk ## Disclaimer -Contribution and help ... if you find an issue or wants to contribute then please do not hesitate to create a merge request or an issue. +Contribution and help ... if you find an issue or wants to contribute then please do not hesitate to create a pull request or an issue. We provide our build template as is, and we make no promises or guarantees about this code.