From 7bfee7b9082f91cb43c19efde873093b5f98c36a Mon Sep 17 00:00:00 2001 From: tms Date: Wed, 29 Jul 2026 22:03:01 -0400 Subject: [PATCH] initial commit of working code --- .gitignore | 35 ----- README.md | 1 + include/README | 39 ++++++ include/jellies.h | 45 +++++++ platformio.ini | 30 +++++ src/LEDSwarmer.h | 131 +++++++++++++++++++ src/main.cpp | 321 ++++++++++++++++++++++++++++++++++++++++++++++ test/README | 11 ++ 8 files changed, 578 insertions(+), 35 deletions(-) create mode 100644 include/README create mode 100644 include/jellies.h create mode 100644 platformio.ini create mode 100644 src/LEDSwarmer.h create mode 100644 src/main.cpp create mode 100644 test/README diff --git a/.gitignore b/.gitignore index a8b0d1d..e69de29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,35 +0,0 @@ -# ---> Android -# Gradle files -.gradle/ -build/ - -# Local configuration file (sdk path, etc) -local.properties - -# Log/OS Files -*.log - -# Android Studio generated files and folders -captures/ -.externalNativeBuild/ -.cxx/ -*.apk -output.json - -# IntelliJ -*.iml -.idea/ -misc.xml -deploymentTargetDropDown.xml -render.experimental.xml - -# Keystore files -*.jks -*.keystore - -# Google Services (e.g. APIs or Firebase) -google-services.json - -# Android Profiling -*.hprof - diff --git a/README.md b/README.md index 5e1aaf7..ad81413 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # jellyswarmer +ESP32|PainlessMesh|FastLED diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/include/jellies.h b/include/jellies.h new file mode 100644 index 0000000..cb18fc1 --- /dev/null +++ b/include/jellies.h @@ -0,0 +1,45 @@ +#ifndef JELLIES_H +#define JELLIES_H + +extern const TProgmemPalette16 rainbowPalette_p PROGMEM = +{ + CRGB::Red, + CRGB::OrangeRed, + CRGB::Orange, + CRGB::LightYellow, + CRGB::Yellow, + CRGB::YellowGreen, + CRGB::LawnGreen, + CRGB::Green, + CRGB::Aquamarine, + CRGB::LightBlue, + CRGB::Blue, + CRGB::Cyan, + CRGB::BlueViolet, + CRGB::Violet, + CRGB::DarkViolet, + CRGB::MediumVioletRed +}; + + +// extern const CHSVPalette16 rainbowSpectrumPalette_p = +// { +// CHSV(0, 160, 64), +// CHSV(12, 160, 64), +// CHSV(24, 160, 64), +// CHSV(36, 160, 64), +// CHSV(48, 160, 64), +// CHSV(64, 160, 64), +// CHSV(82, 160, 64), +// CHSV(104, 160, 64), +// CHSV(124, 160, 64), +// CHSV(138, 160, 64), +// CHSV(156, 160, 64), +// CHSV(174, 160, 64), +// CHSV(192, 160, 64), +// CHSV(204, 160, 64), +// CHSV(230, 160, 64), +// CHSV(244, 160, 64) +// }; + +#endif diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..1e323c0 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,30 @@ +; 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] +default_envs = jellypico32 +lib_dir = 'src/' + +[env:jellypico32] +platform = espressif32 +platform_packages = platformio/framework-arduinoespressif32@3.20011.230801 +board = pico32 +framework = arduino +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder +upload_speed = 1500000 +lib_deps = + painlessmesh/painlessMesh @ ^1.5.0 + ArduinoJson + AsyncTCP + https://github.com/arkhipenko/TaskScheduler@3.7.0 + fastled/FastLED@^3.6.0 + https://github.com/hpwit/I2SClocklessLedDriver.git +build_flags = -Iinclude/ -Wall -std=c++14 diff --git a/src/LEDSwarmer.h b/src/LEDSwarmer.h new file mode 100644 index 0000000..1fa6b23 --- /dev/null +++ b/src/LEDSwarmer.h @@ -0,0 +1,131 @@ +#ifndef LEDSWARMER_H +#define LEDSWARMER_H + +// #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns +// #define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass +// #define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only +// #define _TASK_LTS_POINTER // Compile with support for local task storage pointer +// #define _TASK_PRIORITY // Support for layered scheduling priority +// #define _TASK_MICRO_RES // Support for microsecond resolution +// #define _TASK_STD_FUNCTION // Support for std::function (ESP8266 ONLY) +// #define _TASK_DEBUG // Make all methods and variables public for debug purposes +// #define _TASK_INLINE // Make all methods "inline" - needed to support some multi-tab, multi-file implementations +// #define _TASK_TIMEOUT 5 + +// #define FASTLED_ALLOW_INTERRUPTS 0 +// #define FASTLED_INTERRUPT_RETRY_COUNT 0 + +#define FASTLED_ALLOW_INTERRUPTS 1 // Allow interrupts, to prevent wifi weirdness ; https://github.com/FastLED/FastLED/wiki/Interrupt-problems +#define INTERRUPT_THRESHOLD 1 // also see https://github.com/FastLED/FastLED/issues/367 +#define USE_GET_MILLISECOND_TIMER // Define our own millis() source for FastLED beat functions: see get_millisecond_timer() +#define TASK_RES_MULTIPLIER 1 + +#define LED_ANIMATION_FRAME_PERIOD_MS 25 // 40 Hz update rate + +#define NEO_PIXEL +#define CHIPSET WS2812B +#define COLOR_ORDER GRB +#define LED_PIN_1 4 +#define LED_PIN_2 2 +#define LED_PIN_3 14 +#define LED_PIN_4 12 +#define LED_PIN_5 13 +#define LED_PIN_6 15 + +#define LEDS_PER_STRIP 6 +#define STRIPS_PER_NODE 6 +#define LEDS_PER_NODE LEDS_PER_STRIP * STRIPS_PER_NODE + +#define DEFAULT_BRIGHTNESS 64 + +// ************************************************************************ +// INCLUDES +// ************************************************************************ + +#include +#include +#include +#include + +#include "jellies.h" + +#define MESH_PREFIX "tomsJelliesMesh" +#define MESH_PASSWORD "somethingSneaky" +#define MESH_PORT 7465 + +#define TASK_RESTART_CYCLES 40000 + +// leds + mesh +CRGB _localLeds[LEDS_PER_NODE]; +painlessMesh mesh; +I2SClocklessLedDriver _driver; + +int pins[STRIPS_PER_NODE] = {4,2,14,12,13,15} + +// prototype for mesh timer +uint32_t get_millisecond_timer(); + +// MESH function prototypes (callbacks) +void receivedCallback( uint32_t from, String &msg ) ; +void newConnectionCallback(uint32_t nodeId) ; +void changedConnectionCallback() ; +void nodeTimeAdjustedCallback(int32_t offset) ; +void delayReceivedCallback(uint32_t from, int32_t delay) ; + +// Task (User) function prototypes +void currentPatternRun(); +void sendMessage(); +void chooseNextPattern(); +void updateLEDs(); +void updateCurrentAction(); +void darkNow(); +void rainbowDefault(); +void fadeInOut(); +void sparkle(); +void initSparkle(); + +#define LED_STILL_FRAME_PERIOD_MS 1000 +#define LED_SLOW_ANIMATION_FRAME_PERIOD_MS 100 + +#define TOTAL_PRESETS 64 + +// #define _TASK_SLEEP_ON_IDLE_RUN 1 + +#include + +//Scheduler + Tasks +Scheduler userScheduler; // to control your personal task1 +Task taskCurrentPatternRun( + (TASK_MILLISECOND * LED_ANIMATION_FRAME_PERIOD_MS), + TASK_FOREVER, + ¤tPatternRun, + &userScheduler, + false); +Task taskSendMessage( + TASK_SECOND * (random8(4) + 5), + TASK_FOREVER, + &sendMessage, + &userScheduler, + false ); +Task taskEvolveColorPreset( + TASK_SECOND * (random8(8) + 8), + TASK_FOREVER, + &chooseNextPattern, + &userScheduler, + false); + +// State vars +uint16_t _counter = 0; +uint8_t _speed_lo = 2; +uint8_t _speed_hi = 6; +uint8_t _density = 6; +uint8_t _current_color_preset = 45; +uint8_t _next_color_preset = 46; + +uint8_t _ledState[LEDS_PER_NODE*2]; + +const char *_current_action = "noaction"; + +boolean node_connected = false; + +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1d53538 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,321 @@ +#include "LEDSwarmer.h" + +void setup() +{ + + Serial.begin(115200); + delay((100 * random8(20)) + 40000); // Startup delay; let things settle down0 + + // mesh.setDebugMsgTypes( ERROR | c | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on; WARNING, buggy!! + mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION | SYNC | MESH_STATUS ); // set before init() so that you can see startup messages + // mesh.setDebugMsgTypes( ERROR | DEBUG | STARTUP | CONNECTION | SYNC ); // set before init() so that you can see startup messages + // mesh.setDebugMsgTypes( ERROR | STARTUP ); // set before init() so that you can see startup messages + + + mesh.init( + MESH_PREFIX, + MESH_PASSWORD, + &userScheduler, + MESH_PORT); + + mesh.onReceive(&receivedCallback); + mesh.onNewConnection(&newConnectionCallback); + mesh.onChangedConnections(&changedConnectionCallback); + mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback); + mesh.onNodeDelayReceived(&delayReceivedCallback); + + + // FastLED.addLeds(_localLeds, 0 * LEDS_PER_STRIP, LEDS_PER_STRIP); + // FastLED.addLeds(_localLeds, 1 * LEDS_PER_STRIP, LEDS_PER_STRIP); + // FastLED.addLeds(_localLeds, 2 * LEDS_PER_STRIP, LEDS_PER_STRIP); + // FastLED.addLeds(_localLeds, 3 * LEDS_PER_STRIP, LEDS_PER_STRIP); + // FastLED.addLeds(_localLeds, 4 * LEDS_PER_STRIP, LEDS_PER_STRIP); + // FastLED.addLeds(_localLeds, 5 * LEDS_PER_STRIP, LEDS_PER_STRIP); + + _driver.initled((uint8_t*)_localLeds, pins, 6, 6, GBR); + _driver.setBrightness(100); + + userScheduler.addTask(taskCurrentPatternRun); + userScheduler.addTask(taskSendMessage); + userScheduler.addTask(taskEvolveColorPreset); + + taskCurrentPatternRun.enable(); + taskSendMessage.enable(); + taskEvolveColorPreset.enable(); + +} + +void loop() +{ + mesh.update(); +} + +#ifdef USE_GET_MILLISECOND_TIMER +uint32_t get_millisecond_timer() +{ + return mesh.getNodeTime() / TASK_SECOND; +} +#endif + +void initSparkle() { + + darkNow(); + int i = 1 + random8(LEDS_PER_STRIP); // land first somewhere on strip 0 + int c = 0; + + while (i < LEDS_PER_NODE) { + _ledState[c] = i; + _ledState[c+1] = 2 + random8(_speed_lo, _speed_hi); + c += 2; + i += (1 + random8(_density)); + } + +} + + +void darkNow() { + + for (uint8_t i=0; i < LEDS_PER_NODE; i++) + { + _localLeds[i] = CRGB::Black; + } +} + + +void rainbowDefault() { + + for ( uint8_t i = 0; i < LEDS_PER_NODE; i++) { + _localLeds[i] = ColorFromPalette( rainbowPalette_p, (_current_color_preset % 16) * 16, 24, LINEARBLEND ); + } + +} + + +void stripesDefault() { + + uint8_t _idx = beatsin8(20, 0, 5); + // Serial.println(_idx); + + for ( uint8_t i = 0; i < LEDS_PER_NODE; i++) { + if (_idx == (i % 6)) { + _localLeds[i] = ColorFromPalette( rainbowPalette_p, (_current_color_preset % 16) * 16, 24, LINEARBLEND ); + } else { + _localLeds[i] = CRGB::Black; + } + } +} + + +void fadeInOut() { + + uint8_t b = beatsin8(15, 5, 36); + b = ease8InOutQuad(b); + + for ( uint8_t i = 0; i < LEDS_PER_NODE; i++) { + _localLeds[i] = ColorFromPalette( rainbowPalette_p, (_current_color_preset % 16) * 16, b, LINEARBLEND ); + } + +} + + +void sparkle() { + + for (int i=0; i 0) { + + _localLeds[ _ledState[(2*i)] ] = ColorFromPalette( rainbowPalette_p, (_current_color_preset % 16) * 16, beatsin8(_ledState[(2*i)+1], 2, 64)); + + } + } + +} + + + +void updateLEDs() { + + if (strcmp(_current_action, "dark") == 0) { + + darkNow(); + + } else if (strcmp(_current_action, "color_p") == 0) { + + rainbowDefault(); + + } else if (strcmp(_current_action, "stripes_p") == 0) { + + stripesDefault(); + + } else if (strcmp(_current_action, "faders_p") == 0) { + + fadeInOut(); + + } else if (strcmp(_current_action, "sparkle_p") == 0) { + + sparkle(); + + } else { + + darkNow(); + + } + +} + + +void currentPatternRun() { + + if (_counter > TASK_RESTART_CYCLES) + { + Serial.println(_counter); + ESP.restart(); + } else if ((_counter % 1000) == 0) { + Serial.println("-------"); + Serial.println(_counter); + Serial.println(""); + } + + // if (node_connected == true) { + + // updateCurrentAction(); + updateLEDs(); + + _driver.showPixels(); + _counter += 1; + // } +} + + +void chooseNextPattern() { + + if ((random8() < 96) && (node_connected == true)) { + + Serial.println("BING!"); + + if (_next_color_preset != _current_color_preset) { + _current_color_preset = _next_color_preset; + } else { + _current_color_preset = (_current_color_preset + 1) % TOTAL_PRESETS; + } + + if ((_current_color_preset / 16) == 0) { + + _current_action = "color_p"; + taskCurrentPatternRun.disable(); + taskCurrentPatternRun.setInterval( + (TASK_MILLISECOND * LED_STILL_FRAME_PERIOD_MS) + ); + taskCurrentPatternRun.enable(); + + } else if ((_current_color_preset / 16) == 1) { + + _current_action = "stripes_p"; + taskCurrentPatternRun.disable(); + taskCurrentPatternRun.setInterval( + (TASK_MILLISECOND * LED_SLOW_ANIMATION_FRAME_PERIOD_MS) + ); + taskCurrentPatternRun.enable(); + + } else if ((_current_color_preset / 16) == 2) { + + _current_action = "faders_p"; + taskCurrentPatternRun.disable(); + taskCurrentPatternRun.setInterval( + (TASK_MILLISECOND * LED_ANIMATION_FRAME_PERIOD_MS) + ); + taskCurrentPatternRun.enable(); + + } else { + + _current_action = "sparkle_p"; + initSparkle(); + taskCurrentPatternRun.disable(); + taskCurrentPatternRun.setInterval( + (TASK_MILLISECOND * LED_ANIMATION_FRAME_PERIOD_MS) + ); + taskCurrentPatternRun.enable(); + + } + } +} + + +void sendMessage() +{ + + if (node_connected == true) { + + static StaticJsonDocument<256> msg; + + msg["currentAction"] = _current_action; + msg["currentPattern"] = _current_color_preset; + + String str; + serializeJson(msg, str); + mesh.sendBroadcast(str); + Serial.printf("\n%s (%s) %u: Sent broadcast message: %s\n\n", "role.c_str()", "_nodePos", mesh.getNodeTime(), str.c_str()); + } +} + + +// MESH callbacks +void receivedCallback(uint32_t from, String &msg) +{ + if (node_connected == true) { + + Serial.printf("Received msg from %u: %s\n", from, msg.c_str()); + + static StaticJsonDocument<256> root; + deserializeJson(root, msg); + + if( root["currentPattern"] ) + { + _next_color_preset = root["currentPattern"].as() ; + } + } +} + + +void newConnectionCallback(uint32_t nodeId) +{ + Serial.printf("%s %u: New Connection from nodeId = %u\n", "role.c_str()", mesh.getNodeTime(), nodeId); + // checkLeadership() ; +} + + +void changedConnectionCallback() +{ + + Serial.printf("%s %u: Changed connections %s\n", "role.c_str()", mesh.getNodeTime(), mesh.subConnectionJson().c_str()); + + int _numNodes = mesh.getNodeList().size(); + + if (_numNodes > 0) { + + node_connected = true; + + } else { + + node_connected = false; + Serial.println(_counter); + ESP.restart(); + + } +} + + +void nodeTimeAdjustedCallback(int32_t offset) +{ + Serial.printf("%s: Adjusted time %u. Offset = %d\n", "role.c_str()", mesh.getNodeTime(), offset); +} + + +void delayReceivedCallback(uint32_t from, int32_t delay) +{ + Serial.printf("Delay to node %u is %d us\n", from, delay); +} diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html