├── .gitignore ├── README.md ├── lib ├── easing │ ├── easing.c │ └── easing.h └── readme.txt ├── platformio.ini └── src ├── LEDswarm.cpp ├── LEDswarm.h └── headers ├── Jellyfishv2.h ├── LEDsticks.h ├── atommatrix.h ├── esp32thing.h └── strombluete.h /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | .DS_Store 7 | lib/FX 8 | .clang_complete 9 | .gcc-flags.json 10 | .travis.yml 11 | .vscode 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LEDswarm 2 | ESP8266/ESP32 based mesh to drive and synchronize a swarm of mobile LEDs 3 | 4 | __VIDEO: https://www.youtube.com/watch?v=Uttsf_RPKiI__ 5 | 6 | In the video the nodes are using the awesome M5Stack Atom Matrix ESP32 dev kit: 7 | https://m5stack.com/collections/m5-atom/products/atom-matrix-esp32-development-kit 8 | 9 | Using these awesome libraries: 10 | * painlessMesh: https://gitlab.com/painlessMesh 11 | * FastLED: https://github.com/FastLED/FastLED 12 | * TaskScheduler: https://github.com/arkhipenko/TaskScheduler 13 | * ArduinoTapTempo: https://github.com/dxinteractive/ArduinoTapTempo 14 | 15 | *Please note the official ArduinoTapTempo has a bug that crashes ESP32* I've updated platformio.ini with a fork that has a fix 16 | 17 | ## Features 18 | * No central node needed 19 | * Automatic joining of wifi mesh 20 | * Mesh leader election 21 | * Automatic synchronisation of pattern, pattern timing and BPM (for bpm based patterns). 22 | * Each node will work standalone when out of range of the rest (it will become it's own leader) 23 | * On the Atom Matrix 5x5 rgb display the heart animation will pulse slowly (with a blue center) when *alone* (out of range) and will pulse quickly when *together in a mesh!* 24 | 25 | Exact synchronisation of patterns and timing is done by FastLED using the clock from the mesh instead of an internal timer: 26 | 27 | uint32_t get_millisecond_timer() { 28 | return mesh.getNodeTime()/1000 ; 29 | } 30 | 31 | ## Usage 32 | * Make a new src/headers/.h file with your preferences (see other files for examples) 33 | * Edit platformio.ini with your board details and point to the header file (see other examples) 34 | * Compile & upload 35 | 36 | ## TODO (Wish) list 37 | * Multi-node sequential patterns: nodes handoff patterns to each other in sequence 38 | * Control via an app or other interface 39 | * UI/UX: button to control BPM/brightness - or long/short press controls 40 | -------------------------------------------------------------------------------- /lib/easing/easing.c: -------------------------------------------------------------------------------- 1 | // 2 | // easing.c 3 | // 4 | // Copyright (c) 2011, Auerhaus Development, LLC 5 | // 6 | // This program is free software. It comes without any warranty, to 7 | // the extent permitted by applicable law. You can redistribute it 8 | // and/or modify it under the terms of the Do What The Fuck You Want 9 | // To Public License, Version 2, as published by Sam Hocevar. See 10 | // http://sam.zoy.org/wtfpl/COPYING for more details. 11 | // 12 | 13 | #include 14 | #include "easing.h" 15 | 16 | // Modeled after the line y = x 17 | AHFloat LinearInterpolation(AHFloat p) 18 | { 19 | return p; 20 | } 21 | 22 | // Modeled after the parabola y = x^2 23 | AHFloat QuadraticEaseIn(AHFloat p) 24 | { 25 | return p * p; 26 | } 27 | 28 | // Modeled after the parabola y = -x^2 + 2x 29 | AHFloat QuadraticEaseOut(AHFloat p) 30 | { 31 | return -(p * (p - 2)); 32 | } 33 | 34 | // Modeled after the piecewise quadratic 35 | // y = (1/2)((2x)^2) ; [0, 0.5) 36 | // y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1] 37 | AHFloat QuadraticEaseInOut(AHFloat p) 38 | { 39 | if(p < 0.5) 40 | { 41 | return 2 * p * p; 42 | } 43 | else 44 | { 45 | return (-2 * p * p) + (4 * p) - 1; 46 | } 47 | } 48 | 49 | // Modeled after the cubic y = x^3 50 | AHFloat CubicEaseIn(AHFloat p) 51 | { 52 | return p * p * p; 53 | } 54 | 55 | // Modeled after the cubic y = (x - 1)^3 + 1 56 | AHFloat CubicEaseOut(AHFloat p) 57 | { 58 | AHFloat f = (p - 1); 59 | return f * f * f + 1; 60 | } 61 | 62 | // Modeled after the piecewise cubic 63 | // y = (1/2)((2x)^3) ; [0, 0.5) 64 | // y = (1/2)((2x-2)^3 + 2) ; [0.5, 1] 65 | AHFloat CubicEaseInOut(AHFloat p) 66 | { 67 | if(p < 0.5) 68 | { 69 | return 4 * p * p * p; 70 | } 71 | else 72 | { 73 | AHFloat f = ((2 * p) - 2); 74 | return 0.5 * f * f * f + 1; 75 | } 76 | } 77 | 78 | // Modeled after the quartic x^4 79 | AHFloat QuarticEaseIn(AHFloat p) 80 | { 81 | return p * p * p * p; 82 | } 83 | 84 | // Modeled after the quartic y = 1 - (x - 1)^4 85 | AHFloat QuarticEaseOut(AHFloat p) 86 | { 87 | AHFloat f = (p - 1); 88 | return f * f * f * (1 - p) + 1; 89 | } 90 | 91 | // Modeled after the piecewise quartic 92 | // y = (1/2)((2x)^4) ; [0, 0.5) 93 | // y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1] 94 | AHFloat QuarticEaseInOut(AHFloat p) 95 | { 96 | if(p < 0.5) 97 | { 98 | return 8 * p * p * p * p; 99 | } 100 | else 101 | { 102 | AHFloat f = (p - 1); 103 | return -8 * f * f * f * f + 1; 104 | } 105 | } 106 | 107 | // Modeled after the quintic y = x^5 108 | AHFloat QuinticEaseIn(AHFloat p) 109 | { 110 | return p * p * p * p * p; 111 | } 112 | 113 | // Modeled after the quintic y = (x - 1)^5 + 1 114 | AHFloat QuinticEaseOut(AHFloat p) 115 | { 116 | AHFloat f = (p - 1); 117 | return f * f * f * f * f + 1; 118 | } 119 | 120 | // Modeled after the piecewise quintic 121 | // y = (1/2)((2x)^5) ; [0, 0.5) 122 | // y = (1/2)((2x-2)^5 + 2) ; [0.5, 1] 123 | AHFloat QuinticEaseInOut(AHFloat p) 124 | { 125 | if(p < 0.5) 126 | { 127 | return 16 * p * p * p * p * p; 128 | } 129 | else 130 | { 131 | AHFloat f = ((2 * p) - 2); 132 | return 0.5 * f * f * f * f * f + 1; 133 | } 134 | } 135 | 136 | // Modeled after quarter-cycle of sine wave 137 | AHFloat SineEaseIn(AHFloat p) 138 | { 139 | return sin((p - 1) * M_PI_2) + 1; 140 | } 141 | 142 | // Modeled after quarter-cycle of sine wave (different phase) 143 | AHFloat SineEaseOut(AHFloat p) 144 | { 145 | return sin(p * M_PI_2); 146 | } 147 | 148 | // Modeled after half sine wave 149 | AHFloat SineEaseInOut(AHFloat p) 150 | { 151 | return 0.5 * (1 - cos(p * M_PI)); 152 | } 153 | 154 | // Modeled after shifted quadrant IV of unit circle 155 | AHFloat CircularEaseIn(AHFloat p) 156 | { 157 | return 1 - sqrt(1 - (p * p)); 158 | } 159 | 160 | // Modeled after shifted quadrant II of unit circle 161 | AHFloat CircularEaseOut(AHFloat p) 162 | { 163 | return sqrt((2 - p) * p); 164 | } 165 | 166 | // Modeled after the piecewise circular function 167 | // y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5) 168 | // y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1] 169 | AHFloat CircularEaseInOut(AHFloat p) 170 | { 171 | if(p < 0.5) 172 | { 173 | return 0.5 * (1 - sqrt(1 - 4 * (p * p))); 174 | } 175 | else 176 | { 177 | return 0.5 * (sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1); 178 | } 179 | } 180 | 181 | // Modeled after the exponential function y = 2^(10(x - 1)) 182 | AHFloat ExponentialEaseIn(AHFloat p) 183 | { 184 | return (p == 0.0) ? p : pow(2, 10 * (p - 1)); 185 | } 186 | 187 | // Modeled after the exponential function y = -2^(-10x) + 1 188 | AHFloat ExponentialEaseOut(AHFloat p) 189 | { 190 | return (p == 1.0) ? p : 1 - pow(2, -10 * p); 191 | } 192 | 193 | // Modeled after the piecewise exponential 194 | // y = (1/2)2^(10(2x - 1)) ; [0,0.5) 195 | // y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1] 196 | AHFloat ExponentialEaseInOut(AHFloat p) 197 | { 198 | if(p == 0.0 || p == 1.0) return p; 199 | 200 | if(p < 0.5) 201 | { 202 | return 0.5 * pow(2, (20 * p) - 10); 203 | } 204 | else 205 | { 206 | return -0.5 * pow(2, (-20 * p) + 10) + 1; 207 | } 208 | } 209 | 210 | // Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1)) 211 | AHFloat ElasticEaseIn(AHFloat p) 212 | { 213 | return sin(13 * M_PI_2 * p) * pow(2, 10 * (p - 1)); 214 | } 215 | 216 | // Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1 217 | AHFloat ElasticEaseOut(AHFloat p) 218 | { 219 | return sin(-13 * M_PI_2 * (p + 1)) * pow(2, -10 * p) + 1; 220 | } 221 | 222 | // Modeled after the piecewise exponentially-damped sine wave: 223 | // y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5) 224 | // y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1] 225 | AHFloat ElasticEaseInOut(AHFloat p) 226 | { 227 | if(p < 0.5) 228 | { 229 | return 0.5 * sin(13 * M_PI_2 * (2 * p)) * pow(2, 10 * ((2 * p) - 1)); 230 | } 231 | else 232 | { 233 | return 0.5 * (sin(-13 * M_PI_2 * ((2 * p - 1) + 1)) * pow(2, -10 * (2 * p - 1)) + 2); 234 | } 235 | } 236 | 237 | // Modeled after the overshooting cubic y = x^3-x*sin(x*pi) 238 | AHFloat BackEaseIn(AHFloat p) 239 | { 240 | return p * p * p - p * sin(p * M_PI); 241 | } 242 | 243 | // Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi)) 244 | AHFloat BackEaseOut(AHFloat p) 245 | { 246 | AHFloat f = (1 - p); 247 | return 1 - (f * f * f - f * sin(f * M_PI)); 248 | } 249 | 250 | // Modeled after the piecewise overshooting cubic function: 251 | // y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5) 252 | // y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1] 253 | AHFloat BackEaseInOut(AHFloat p) 254 | { 255 | if(p < 0.5) 256 | { 257 | AHFloat f = 2 * p; 258 | return 0.5 * (f * f * f - f * sin(f * M_PI)); 259 | } 260 | else 261 | { 262 | AHFloat f = (1 - (2*p - 1)); 263 | return 0.5 * (1 - (f * f * f - f * sin(f * M_PI))) + 0.5; 264 | } 265 | } 266 | 267 | AHFloat BounceEaseIn(AHFloat p) 268 | { 269 | return 1 - BounceEaseOut(1 - p); 270 | } 271 | 272 | AHFloat BounceEaseOut(AHFloat p) 273 | { 274 | if(p < 4/11.0) 275 | { 276 | return (121 * p * p)/16.0; 277 | } 278 | else if(p < 8/11.0) 279 | { 280 | return (363/40.0 * p * p) - (99/10.0 * p) + 17/5.0; 281 | } 282 | else if(p < 9/10.0) 283 | { 284 | return (4356/361.0 * p * p) - (35442/1805.0 * p) + 16061/1805.0; 285 | } 286 | else 287 | { 288 | return (54/5.0 * p * p) - (513/25.0 * p) + 268/25.0; 289 | } 290 | } 291 | 292 | AHFloat BounceEaseInOut(AHFloat p) 293 | { 294 | if(p < 0.5) 295 | { 296 | return 0.5 * BounceEaseIn(p*2); 297 | } 298 | else 299 | { 300 | return 0.5 * BounceEaseOut(p * 2 - 1) + 0.5; 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /lib/easing/easing.h: -------------------------------------------------------------------------------- 1 | // 2 | // easing.h 3 | // 4 | // Copyright (c) 2011, Auerhaus Development, LLC 5 | // 6 | // This program is free software. It comes without any warranty, to 7 | // the extent permitted by applicable law. You can redistribute it 8 | // and/or modify it under the terms of the Do What The Fuck You Want 9 | // To Public License, Version 2, as published by Sam Hocevar. See 10 | // http://sam.zoy.org/wtfpl/COPYING for more details. 11 | // 12 | 13 | #ifndef AH_EASING_H 14 | #define AH_EASING_H 15 | 16 | #if defined(__LP64__) && !defined(AH_EASING_USE_DBL_PRECIS) 17 | #define AH_EASING_USE_DBL_PRECIS 18 | #endif 19 | 20 | #ifdef AH_EASING_USE_DBL_PRECIS 21 | #define AH_FLOAT_TYPE double 22 | #else 23 | #define AH_FLOAT_TYPE float 24 | #endif 25 | typedef AH_FLOAT_TYPE AHFloat; 26 | 27 | #if defined __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | typedef AHFloat (*AHEasingFunction)(AHFloat); 32 | 33 | // Linear interpolation (no easing) 34 | AHFloat LinearInterpolation(AHFloat p); 35 | 36 | // Quadratic easing; p^2 37 | AHFloat QuadraticEaseIn(AHFloat p); 38 | AHFloat QuadraticEaseOut(AHFloat p); 39 | AHFloat QuadraticEaseInOut(AHFloat p); 40 | 41 | // Cubic easing; p^3 42 | AHFloat CubicEaseIn(AHFloat p); 43 | AHFloat CubicEaseOut(AHFloat p); 44 | AHFloat CubicEaseInOut(AHFloat p); 45 | 46 | // Quartic easing; p^4 47 | AHFloat QuarticEaseIn(AHFloat p); 48 | AHFloat QuarticEaseOut(AHFloat p); 49 | AHFloat QuarticEaseInOut(AHFloat p); 50 | 51 | // Quintic easing; p^5 52 | AHFloat QuinticEaseIn(AHFloat p); 53 | AHFloat QuinticEaseOut(AHFloat p); 54 | AHFloat QuinticEaseInOut(AHFloat p); 55 | 56 | // Sine wave easing; sin(p * PI/2) 57 | AHFloat SineEaseIn(AHFloat p); 58 | AHFloat SineEaseOut(AHFloat p); 59 | AHFloat SineEaseInOut(AHFloat p); 60 | 61 | // Circular easing; sqrt(1 - p^2) 62 | AHFloat CircularEaseIn(AHFloat p); 63 | AHFloat CircularEaseOut(AHFloat p); 64 | AHFloat CircularEaseInOut(AHFloat p); 65 | 66 | // Exponential easing, base 2 67 | AHFloat ExponentialEaseIn(AHFloat p); 68 | AHFloat ExponentialEaseOut(AHFloat p); 69 | AHFloat ExponentialEaseInOut(AHFloat p); 70 | 71 | // Exponentially-damped sine wave easing 72 | AHFloat ElasticEaseIn(AHFloat p); 73 | AHFloat ElasticEaseOut(AHFloat p); 74 | AHFloat ElasticEaseInOut(AHFloat p); 75 | 76 | // Overshooting cubic easing; 77 | AHFloat BackEaseIn(AHFloat p); 78 | AHFloat BackEaseOut(AHFloat p); 79 | AHFloat BackEaseInOut(AHFloat p); 80 | 81 | // Exponentially-decaying bounce easing 82 | AHFloat BounceEaseIn(AHFloat p); 83 | AHFloat BounceEaseOut(AHFloat p); 84 | AHFloat BounceEaseInOut(AHFloat p); 85 | 86 | #ifdef __cplusplus 87 | } 88 | #endif 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /lib/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for the project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link to executable file. 4 | 5 | The source code of each library should be placed in separate directory, like 6 | "lib/private_lib/[here are source files]". 7 | 8 | For example, see how can be organized `Foo` and `Bar` libraries: 9 | 10 | |--lib 11 | | |--Bar 12 | | | |--docs 13 | | | |--examples 14 | | | |--src 15 | | | |- Bar.c 16 | | | |- Bar.h 17 | | |--Foo 18 | | | |- Foo.c 19 | | | |- Foo.h 20 | | |- readme.txt --> THIS FILE 21 | |- platformio.ini 22 | |--src 23 | |- main.c 24 | 25 | Then in `src/main.c` you should use: 26 | 27 | #include 28 | #include 29 | 30 | // rest H/C/CPP code 31 | 32 | PlatformIO will find your libraries automatically, configure preprocessor's 33 | include paths and build them. 34 | 35 | More information about PlatformIO Library Dependency Finder 36 | - http://docs.platformio.org/page/librarymanager/ldf.html 37 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | ; default_envs = esp32devkit_strombdluete 13 | default_envs = m5stack_atommatrix 14 | 15 | ; Using a fork of ArduinoTapTempo that doesn't crash on ESP32 when using setBpm() 16 | [common_env_data] 17 | lib_deps = 18 | https://github.com/costyn/FX.git 19 | https://gitlab.com/painlessMesh/painlessMesh.git 20 | https://github.com/Andymann/ArduinoTapTempo.git#fix_crash_on_esp32 21 | FastLED 22 | JC_Button 23 | arduinoUnity 24 | AsyncTCP 25 | 26 | [env:m5stack_ledsticks] 27 | platform = espressif32 28 | board = pico32 29 | framework = arduino 30 | monitor_speed = 115200 31 | upload_speed = 1500000 32 | lib_deps = 33 | ${common_env_data.lib_deps} 34 | build_flags = -Isrc/headers -include LEDsticks.h -Wall -std=c++14 35 | 36 | [env:m5stack_atommatrix] 37 | platform = espressif32 38 | board = pico32 39 | framework = arduino 40 | monitor_speed = 115200 41 | ; monitor_port = /dev/cu.usbserial-5552CE1293 42 | upload_port = /dev/cu.usbserial-* 43 | upload_speed = 1500000 44 | lib_deps = 45 | ${common_env_data.lib_deps} 46 | build_flags = -Isrc/headers -include atommatrix.h -Wall -std=c++14 47 | 48 | [env:esp32devkit_strombluete] 49 | platform = espressif32 50 | board = esp32dev 51 | framework = arduino 52 | monitor_speed = 115200 53 | upload_speed = 1500000 54 | build_flags = -Isrc/headers -include strombluete.h 55 | lib_deps = 56 | ${common_env_data.lib_deps} -------------------------------------------------------------------------------- /src/LEDswarm.cpp: -------------------------------------------------------------------------------- 1 | // TODO: convert Serial.printfs and println's to DEBUG statements we can turn on/off 2 | 3 | #include 4 | #include "LEDswarm.h" 5 | 6 | // ************************************************************************ 7 | // SETUP 8 | // ************************************************************************ 9 | 10 | void setup() 11 | { 12 | Serial.begin(115200); 13 | delay(1000); // Startup delay; let things settle down 14 | Serial.println("after Serial begin"); 15 | 16 | // mesh.setDebugMsgTypes(ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE); // all types on; WARNING, buggy!! 17 | // mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION | DEBUG); // set before init() so that you can see startup messages 18 | mesh.setDebugMsgTypes(ERROR | STARTUP | SYNC); // set before init() so that you can see startup messages 19 | // mesh.setDebugMsgTypes( ERROR | STARTUP ); // set before init() so that you can see startup messages 20 | mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT); 21 | Serial.println("after mesh init"); 22 | mesh.onReceive(&receivedCallback); 23 | mesh.onNewConnection(&newConnectionCallback); 24 | mesh.onChangedConnections(&changedConnectionCallback); 25 | mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback); 26 | // mesh.onNodeDelayReceived(&delayReceivedCallback); 27 | 28 | Serial.println("after mesh"); 29 | 30 | #ifdef APA_102 31 | // FastLED.addLeds(leds, NUM_LEDS).setCorrection( TypicalLEDStrip ); 32 | #else 33 | FastLED.addLeds(_localLeds, LEDS_PER_NODE); 34 | #endif 35 | 36 | #ifdef ATOMMATRIX 37 | FastLED.addLeds(matrix_leds, ATOM_NUM_LED); 38 | #endif 39 | 40 | fx.setTempo(DEFAULT_BPM); 41 | 42 | Serial.println("after initled"); 43 | 44 | FastLED.setBrightness(_maxBright); 45 | Serial.println("after setBrightness"); 46 | 47 | userScheduler.addTask(taskSendMessage); 48 | userScheduler.addTask(taskCheckButtonPress); 49 | userScheduler.addTask(taskCurrentPatternRun); 50 | taskCheckButtonPress.enable(); 51 | Serial.println("after taskCheckButtonPress enable"); 52 | 53 | #ifdef AUTOADVANCE 54 | userScheduler.addTask(taskSelectNextPattern); 55 | taskSelectNextPattern.enable(); 56 | #endif 57 | 58 | bpmButton.begin(); 59 | nextPatternButton.begin(); 60 | 61 | // Be warned: Causes crash on main branch of ArduinoTapTempo 62 | tapTempo.setBPM(DEFAULT_BPM); 63 | 64 | Serial.print("Starting up... my Node ID is: "); 65 | Serial.println(mesh.getNodeId()); 66 | checkLeadership(); 67 | 68 | taskCurrentPatternRun.enable(); 69 | } // end setup() 70 | 71 | // ************************************************************************ 72 | // LOOP 73 | // ************************************************************************ 74 | 75 | void loop() 76 | { 77 | mesh.update(); 78 | } 79 | 80 | // ************************************************************************ 81 | // MESH FUNCTIONS 82 | // ************************************************************************ 83 | 84 | // Better to have static and keep the memory allocated or not?? 85 | void sendMessage() 86 | { 87 | if (!tapTempo.isChainActive() || (currentPattern != nextPattern)) 88 | { 89 | JsonDocument outgoingJsonMessage; 90 | 91 | currentPattern = nextPattern; // update our own running pattern 92 | currentBPM = tapTempo.getBPM(); // update our BPM with (possibly new) BPM 93 | newBPMSet = false; // reset the flag 94 | 95 | outgoingJsonMessage["currentBPM"] = currentBPM; 96 | outgoingJsonMessage["currentPattern"] = currentPattern; 97 | 98 | String outgoingJsonString; 99 | serializeJson(outgoingJsonMessage, outgoingJsonString); 100 | mesh.sendBroadcast(outgoingJsonString); 101 | 102 | Serial.printf("%s (%u) %u: Sent broadcast message: ", role.c_str(), _nodePos, mesh.getNodeTime()); 103 | Serial.println(outgoingJsonString); 104 | // Serial.printf("Brightness: %i\n", _maxBright); 105 | } 106 | else 107 | { 108 | Serial.printf("%s %u: No msg to send.\tBPM: %u\tPattern: %u\n", role.c_str(), mesh.getNodeTime(), currentBPM, currentPattern); 109 | } 110 | } // end sendMessage() 111 | 112 | void checkLeadership() 113 | { 114 | uint32_t leaderNodeId = UINT32_MAX; 115 | nodes = mesh.getNodeList(true); 116 | _numNodes = mesh.getNodeList().size(); 117 | 118 | nodes.sort(); 119 | 120 | // SimpleList::iterator nodeIterator = nodes.begin(); // It's a std::list 121 | 122 | // Print out a list of nodes and check which has the lowest Node ID: 123 | Serial.printf("Node list: "); 124 | 125 | // https://www.techiedelight.com/convert-list-to-array-cpp/ 126 | int k = 0; 127 | for (int const &i : nodes) 128 | { 129 | if (i == mesh.getNodeId()) 130 | { 131 | _nodePos = k; 132 | } 133 | if (i < leaderNodeId) 134 | { 135 | leaderNodeId = i; 136 | } 137 | Serial.printf("%u ", i); 138 | k++; 139 | } 140 | Serial.printf("\n"); 141 | 142 | _meshNumLeds = _numNodes * LEDS_PER_NODE; 143 | fx.setMeshNumLeds(_meshNumLeds); 144 | fx.setAlone(alone()); // How lonely are we? 145 | 146 | if (mesh.getNodeId() == leaderNodeId) 147 | { 148 | role = LEADER; 149 | taskSendMessage.enableIfNot(); 150 | taskCheckButtonPress.enableIfNot(); 151 | taskSelectNextPattern.enableIfNot(); 152 | } 153 | else 154 | { 155 | role = FOLLOWER; 156 | taskSendMessage.disable(); // Only LEADER sends broadcast 157 | taskCheckButtonPress.disable(); // Followers can't set BPM 158 | taskSelectNextPattern.disable(); // Followers wait for instructions from the LEADER 159 | } 160 | Serial.printf("%s %u: checkLeadership(); I am %s\n", role.c_str(), mesh.getNodeTime(), role.c_str()); 161 | 162 | Serial.printf("\n%s %u: %d nodes. Mesh Leds: %d. NodePos: %u root: %d\n", role.c_str(), mesh.getNodeTime(), _numNodes, _meshNumLeds, _nodePos, mesh.isRoot()); 163 | 164 | } // end checkLeadership() 165 | 166 | boolean alone() 167 | { 168 | return mesh.getNodeList().size() <= 1; 169 | } 170 | 171 | void receivedCallback(uint32_t from, String &msg) 172 | { 173 | // Serial.printf("Received msg from %u: %s\n", from, msg.c_str()); 174 | if (role == FOLLOWER) 175 | { 176 | JsonDocument root; 177 | deserializeJson(root, msg); 178 | 179 | if (root["currentBPM"]) 180 | { 181 | currentBPM = root["currentBPM"].as(); 182 | nextPattern = root["currentPattern"].as(); 183 | Serial.printf("%s (%u) %u: \tBPM: %u\t Pattern: %u\n", role.c_str(), _nodePos, mesh.getNodeTime(), currentBPM, currentPattern); 184 | Serial.printf(" (%d nodes). Mesh LED count: %d\n", _numNodes, _meshNumLeds); 185 | Serial.printf("%s %u: Tasks Enabled: \t taskSendMessage: %d\t taskCheckButtonPress: %d\t taskSelectNextPattern: %d\n", role.c_str(), 186 | mesh.getNodeTime(), taskSendMessage.isEnabled(), taskCheckButtonPress.isEnabled(), taskSelectNextPattern.isEnabled()); 187 | tapTempo.setBPM(currentBPM); 188 | } 189 | 190 | // if( root["runOnce"] ) { 191 | // executeOneCycle = true ; 192 | // } 193 | } 194 | else if (role == LEADER) 195 | { 196 | JsonDocument root; 197 | deserializeJson(root, msg); 198 | 199 | uint32_t patternRunTime = root["patternRunTime"].as(); 200 | Serial.printf("%s %u (follower end time): \tBPM: %u\t Pattern: %u\tRunTime: %u\n", role.c_str(), mesh.getNodeTime(), currentBPM, currentPattern, patternRunTime); 201 | // taskRunPatternOnNode.forceNextIteration(); // Send message to next Follower 202 | } 203 | // Serial.printf("Copying %d leds from position %d\n", LEDS_PER_NODE, _nodePos*LEDS_PER_NODE ); 204 | Serial.println(); // whitespace for easier reading 205 | } // end receivedCallback() 206 | 207 | void newConnectionCallback(uint32_t nodeId) 208 | { 209 | Serial.printf("%s %u: New Connection from nodeId = %u\n", role.c_str(), mesh.getNodeTime(), nodeId); 210 | checkLeadership(); 211 | } 212 | 213 | void changedConnectionCallback() 214 | { 215 | Serial.printf("%s %u: Changed connections %s\n", role.c_str(), mesh.getNodeTime(), mesh.subConnectionJson().c_str()); 216 | checkLeadership(); 217 | } 218 | 219 | void nodeTimeAdjustedCallback(int32_t offset) 220 | { 221 | Serial.printf("%s: Adjusted time %u. Offset = %d\n", role.c_str(), mesh.getNodeTime(), offset); 222 | } 223 | 224 | void delayReceivedCallback(uint32_t from, int32_t delay) 225 | { 226 | Serial.printf("Delay to node %u is %d us\n", from, delay); 227 | } 228 | 229 | // ************************************************************************ 230 | // USER INTERFACE 231 | // ************************************************************************ 232 | 233 | void checkButtonPress() 234 | { 235 | // bpmButton.read(); 236 | nextPatternButton.read(); 237 | 238 | // if( bpmButton.wasPressed() ) { 239 | // tapTempo.update(true); // update ArduinoTapTempo 240 | // Serial.printf("%s %u: Button TAP. BPM: ", role.c_str(), mesh.getNodeTime() ); 241 | // Serial.println(tapTempo.getBPM() ); 242 | // newBPMSet = true ; 243 | // } else { 244 | // tapTempo.update(false); 245 | // } 246 | 247 | if (nextPatternButton.wasPressed()) 248 | { 249 | selectNextPattern(); 250 | } 251 | 252 | // if( nextPatternButton.pressedFor(500)) { 253 | // cycleBrightness(); 254 | // } 255 | } 256 | 257 | // ************************************************************************ 258 | // LED PATTERN FUNCTIONS 259 | // ************************************************************************ 260 | 261 | // This is where the magic happens! 262 | // @Override This function is called by FastLED inside lib8tion.h. Requests it to use mesg.getNodeTime instead of internal millis() timer. 263 | // Makes every pattern on each node synced!! So AWESOME! 264 | uint32_t get_millisecond_timer() 265 | { 266 | return mesh.getNodeTime() / 1000; 267 | } 268 | 269 | void currentPatternRun() 270 | { 271 | // Serial.print("."); 272 | 273 | // default 274 | // taskCurrentPatternRun.setInterval( CURRENTPATTERN_SELECT_DEFAULT_INTERVAL ) ; 275 | 276 | #ifdef ATOMMATRIX 277 | // Serial.println("Heartbeat!"); 278 | fx.heartbeat(); 279 | #endif 280 | 281 | // fx.spin(); 282 | taskCurrentPatternRun.setInterval(10 * TASK_MILLISECOND); 283 | 284 | // if( currentPattern != nextPattern ) { 285 | // // Serial.printf("%s %u: \t currentPattern: %i\t nextPattern: %i\n", role.c_str(), mesh.getNodeTime(), currentPattern, nextPattern ); 286 | // // fx.firstPatternIteration = true ; // FIXME/TODO move to Mesh 287 | // currentPattern = nextPattern; 288 | // // Serial.printf("%s %u: \t currentPattern: %i\t nextPattern: %i\n", role.c_str(), mesh.getNodeTime(), currentPattern, nextPattern ); 289 | // } 290 | 291 | if (strcmp(routines[currentPattern], "p_rb_stripe") == 0) 292 | { 293 | fx.setCurrentPalette(RainbowStripeColors_p); 294 | fx.FillLEDsFromPaletteColors(); 295 | 296 | #ifdef RT_P_RB 297 | } 298 | else if (strcmp(routines[currentPattern], "p_rb") == 0) 299 | { 300 | fx.setCurrentPalette(RainbowColors_p); 301 | fx.FillLEDsFromPaletteColors(1); 302 | #endif 303 | 304 | #ifdef RT_P_OCEAN 305 | } 306 | else if (strcmp(routines[currentPattern], "p_ocean") == 0) 307 | { 308 | fx.setCurrentPalette(OceanColors_p); 309 | fx.FillLEDsFromPaletteColors(); 310 | #endif 311 | 312 | #ifdef RT_P_HEAT 313 | } 314 | else if (strcmp(routines[currentPattern], "p_heat") == 0) 315 | { 316 | fx.setCurrentPalette(HeatColors_p); 317 | fx.FillLEDsFromPaletteColors(); 318 | #endif 319 | 320 | #ifdef RT_P_LAVA 321 | } 322 | else if (strcmp(routines[currentPattern], "p_lava") == 0) 323 | { 324 | fx.setCurrentPalette(LavaColors_p); 325 | fx.FillLEDsFromPaletteColors(); 326 | #endif 327 | 328 | #ifdef RT_P_PARTY 329 | } 330 | else if (strcmp(routines[currentPattern], "p_party") == 0) 331 | { 332 | fx.setCurrentPalette(PartyColors_p); 333 | fx.FillLEDsFromPaletteColors(); 334 | #endif 335 | 336 | #ifdef RT_P_CLOUD 337 | } 338 | else if (strcmp(routines[currentPattern], "p_cloud") == 0) 339 | { 340 | fx.setCurrentPalette(CloudColors_p); 341 | fx.FillLEDsFromPaletteColors(6); 342 | #endif 343 | 344 | #ifdef RT_P_FOREST 345 | } 346 | else if (strcmp(routines[currentPattern], "p_forest") == 0) 347 | { 348 | fx.setCurrentPalette(ForestColors_p); 349 | fx.FillLEDsFromPaletteColors(); 350 | #endif 351 | 352 | #ifdef RT_TWIRL1 353 | } 354 | else if (strcmp(routines[currentPattern], "twirl1") == 0) 355 | { 356 | fx.twirlers(1, false); 357 | taskCurrentPatternRun.setInterval(1 * TASK_MILLISECOND); 358 | #endif 359 | 360 | #ifdef RT_TWIRL2 361 | } 362 | else if (strcmp(routines[currentPattern], "twirl2") == 0) 363 | { 364 | fx.twirlers(2, false); 365 | taskCurrentPatternRun.setInterval(1 * TASK_MILLISECOND); 366 | #endif 367 | 368 | #ifdef RT_TWIRL4 369 | } 370 | else if (strcmp(routines[currentPattern], "twirl4") == 0) 371 | { 372 | fx.twirlers(4, false); 373 | taskCurrentPatternRun.setInterval(1 * TASK_MILLISECOND); 374 | #endif 375 | 376 | #ifdef RT_TWIRL6 377 | } 378 | else if (strcmp(routines[currentPattern], "twirl6") == 0) 379 | { 380 | fx.twirlers(6, false); 381 | taskCurrentPatternRun.setInterval(1 * TASK_MILLISECOND); 382 | #endif 383 | 384 | #ifdef RT_TWIRL2_O 385 | } 386 | else if (strcmp(routines[currentPattern], "twirl2o") == 0) 387 | { 388 | fx.twirlers(2, true); 389 | taskCurrentPatternRun.setInterval(1 * TASK_MILLISECOND); 390 | #endif 391 | 392 | #ifdef RT_TWIRL4_O 393 | } 394 | else if (strcmp(routines[currentPattern], "twirl4o") == 0) 395 | { 396 | fx.twirlers(4, true); 397 | taskCurrentPatternRun.setInterval(1 * TASK_MILLISECOND); 398 | #endif 399 | 400 | #ifdef RT_TWIRL6_O 401 | } 402 | else if (strcmp(routines[currentPattern], "twirl6o") == 0) 403 | { 404 | fx.twirlers(6, true); 405 | taskCurrentPatternRun.setInterval(1 * TASK_MILLISECOND); 406 | #endif 407 | 408 | #ifdef RT_FADE_GLITTER 409 | } 410 | else if (strcmp(routines[currentPattern], "fglitter") == 0) 411 | { 412 | fx.fadeGlitter(); 413 | #ifdef USING_MPU 414 | taskCurrentPatternRun.setInterval(map(constrain(activityLevel(), 0, 2500), 0, 2500, 40, 2) * TASK_RES_MULTIPLIER); 415 | #else 416 | taskCurrentPatternRun.setInterval(20 * TASK_RES_MULTIPLIER); 417 | #endif 418 | #endif 419 | 420 | #ifdef RT_DISCO_GLITTER 421 | } 422 | else if (strcmp(routines[currentPattern], "dglitter") == 0) 423 | { 424 | fx.discoGlitter(); 425 | #ifdef USING_MPU 426 | taskCurrentPatternRun.setInterval(map(constrain(activityLevel(), 0, 2500), 0, 2500, 40, 2) * TASK_RES_MULTIPLIER); 427 | #else 428 | taskCurrentPatternRun.setInterval(10 * TASK_RES_MULTIPLIER); 429 | #endif 430 | #endif 431 | 432 | #ifdef RT_GLED 433 | // Gravity LED 434 | } 435 | else if (strcmp(routines[currentPattern], "gled") == 0) 436 | { 437 | fx.gLed(); 438 | taskCurrentPatternRun.setInterval(5 * TASK_MILLISECOND); 439 | #endif 440 | 441 | #ifdef RT_BLACK 442 | } 443 | else if (strcmp(routines[currentPattern], "black") == 0) 444 | { 445 | fill_solid(leds, _meshNumLeds, CRGB::Black); 446 | // FastLED.show(); 447 | taskCurrentPatternRun.setInterval(500 * TASK_MILLISECOND); // long because nothing is going on anyways. 448 | #endif 449 | 450 | #ifdef RT_RACERS 451 | } 452 | else if (strcmp(routines[currentPattern], "racers") == 0) 453 | { 454 | fx.racingLeds(); 455 | taskCurrentPatternRun.setInterval(8 * TASK_MILLISECOND); 456 | #endif 457 | 458 | #ifdef RT_WAVE 459 | } 460 | else if (strcmp(routines[currentPattern], "wave") == 0) 461 | { 462 | fx.waveYourArms(); 463 | taskCurrentPatternRun.setInterval(15 * TASK_MILLISECOND); 464 | #endif 465 | 466 | #ifdef RT_SHAKE_IT 467 | } 468 | else if (strcmp(routines[currentPattern], "shakeit") == 0) 469 | { 470 | fx.shakeIt(); 471 | taskCurrentPatternRun.setInterval(8 * TASK_MILLISECOND); 472 | #endif 473 | 474 | #ifdef RT_STROBE1 475 | } 476 | else if (strcmp(routines[currentPattern], "strobe1") == 0) 477 | { 478 | fx.strobe1(); 479 | taskCurrentPatternRun.setInterval(1 * TASK_MILLISECOND); 480 | #endif 481 | 482 | #ifdef RT_STROBE2 483 | } 484 | else if (strcmp(routines[currentPattern], "strobe2") == 0) 485 | { 486 | fx.strobe2(); 487 | taskCurrentPatternRun.setInterval(10 * TASK_MILLISECOND); 488 | #endif 489 | 490 | #ifdef RT_HEARTBEAT 491 | } 492 | else if (strcmp(routines[currentPattern], "heartbeat") == 0) 493 | { 494 | fx.heartbeat(); 495 | taskCurrentPatternRun.setInterval(10 * TASK_MILLISECOND); 496 | #endif 497 | 498 | #ifdef RT_VUMETER 499 | } 500 | else if (strcmp(routines[currentPattern], "vumeter") == 0) 501 | { 502 | vuMeter(); 503 | taskCurrentPatternRun.setInterval(8 * TASK_MILLISECOND); 504 | #endif 505 | 506 | #ifdef RT_FASTLOOP 507 | } 508 | else if (strcmp(routines[currentPattern], "fastloop") == 0) 509 | { 510 | taskCurrentPatternRun.setInterval(10 * TASK_MILLISECOND); 511 | fx.fastLoop(false); 512 | #endif 513 | 514 | #ifdef RT_FASTLOOP2 515 | } 516 | else if (strcmp(routines[currentPattern], "fastloop2") == 0) 517 | { 518 | fx.fastLoop(true); 519 | taskCurrentPatternRun.setInterval(10 * TASK_MILLISECOND); 520 | #endif 521 | 522 | #ifdef RT_PENDULUM 523 | } 524 | else if (strcmp(routines[currentPattern], "pendulum") == 0) 525 | { 526 | fx.pendulum(); 527 | // taskCurrentPatternRun.setInterval( 1500 ) ; // needs a fast refresh rate - optimal in microseconds 528 | taskCurrentPatternRun.setInterval(2); // needs a fast refresh rate 529 | #endif 530 | 531 | #ifdef RT_BOUNCEBLEND 532 | } 533 | else if (strcmp(routines[currentPattern], "bounceblend") == 0) 534 | { 535 | fx.bounceBlend(); 536 | taskCurrentPatternRun.setInterval(10 * TASK_MILLISECOND); 537 | #endif 538 | 539 | #ifdef RT_JUGGLE_PAL 540 | } 541 | else if (strcmp(routines[currentPattern], "jugglepal") == 0) 542 | { 543 | jugglePal(); 544 | // taskCurrentPatternRun.setInterval( 850 ) ; // optimal refresh in microseconds 545 | taskCurrentPatternRun.setInterval(1); // fast refresh rate needed to not skip any LEDs 546 | #endif 547 | 548 | #ifdef RT_NOISE_LAVA 549 | } 550 | else if (strcmp(routines[currentPattern], "noise_lava") == 0) 551 | { 552 | fx.setCurrentPalette(LavaColors_p); 553 | fx.fillnoise8(beatsin8(fx.getTempo(), 1, 25), 30, 1); // pallette, speed, scale, loop 554 | taskCurrentPatternRun.setInterval(10 * TASK_MILLISECOND); 555 | #endif 556 | 557 | #ifdef RT_NOISE_PARTY 558 | } 559 | else if (strcmp(routines[currentPattern], "noise_party") == 0) 560 | { 561 | fx.setCurrentPalette(PartyColors_p); 562 | fx.fillnoise8(beatsin8(fx.getTempo(), 1, 25), 30, 1); 563 | // taskCurrentPatternRun.setInterval( beatsin16( tapTempo.getBPM(), 2000, 50000) ) ; 564 | taskCurrentPatternRun.setInterval(10 * TASK_MILLISECOND); 565 | #endif 566 | 567 | #ifdef RT_QUAD_STROBE 568 | } 569 | else if (strcmp(routines[currentPattern], "quadstrobe") == 0) 570 | { 571 | fx.quadStrobe(); 572 | taskCurrentPatternRun.setInterval((60000 / (tapTempo.getBPM() * 4)) * TASK_MILLISECOND); 573 | #endif 574 | 575 | #ifdef RT_PULSE_3 576 | } 577 | else if (strcmp(routines[currentPattern], "pulse3") == 0) 578 | { 579 | fx.pulse3(); 580 | taskCurrentPatternRun.setInterval(10 * TASK_MILLISECOND); 581 | #endif 582 | 583 | #ifdef RT_PULSE_5 584 | } 585 | else if (strcmp(routines[currentPattern], "pulse5") == 0) 586 | { 587 | fx.pulse5(3, false); 588 | taskCurrentPatternRun.setInterval(10 * TASK_MILLISECOND); 589 | #endif 590 | 591 | #ifdef RT_THREE_SIN_PAL 592 | } 593 | else if (strcmp(routines[currentPattern], "tsp") == 0) 594 | { 595 | fx.threeSinPal(); 596 | taskCurrentPatternRun.setInterval(10 * TASK_MILLISECOND); 597 | #endif 598 | 599 | #ifdef RT_FIRE_STRIPE 600 | } 601 | else if (strcmp(routines[currentPattern], "firestripe") == 0) 602 | { 603 | fx.fireStripe(); 604 | taskCurrentPatternRun.setInterval(10 * TASK_MILLISECOND); 605 | #endif 606 | 607 | #ifdef RT_CYLON 608 | } 609 | else if (strcmp(routines[currentPattern], "cylon") == 0) 610 | { 611 | fx.cylon(); 612 | taskCurrentPatternRun.setInterval(1 * TASK_MILLISECOND); 613 | #endif 614 | } 615 | 616 | // COPY 617 | memcpy(&_localLeds, &_meshleds[_nodePos * LEDS_PER_NODE], sizeof(CRGB) * LEDS_PER_NODE); 618 | 619 | // if( role == LEADER) { 620 | // _localLeds[_nodePos] = CRGB::Purple; 621 | // } else { 622 | // _localLeds[_nodePos] = CRGB::Green; 623 | // } 624 | FastLED.show(); 625 | } 626 | 627 | void selectNextPattern() 628 | { 629 | nextPattern = currentPattern + 1; 630 | // Serial.printf("%s (%u) %u: after upping nextPattern: %i, NUMROUTINES = %i\n", role.c_str(), _nodePos, mesh.getNodeTime(), nextPattern, NUMROUTINES); 631 | 632 | if (nextPattern >= NUMROUTINES) 633 | { 634 | nextPattern = 0; 635 | } 636 | 637 | if (role == LEADER) 638 | { 639 | taskSendMessage.forceNextIteration(); // Schedule next iteration immediately, for sending a new pattern msg to Follower 640 | } 641 | } 642 | 643 | void showNum(CRGB *matrixLeds, int numLeds, int n) 644 | { 645 | 646 | static const bool _ = false; 647 | static const bool X = true; 648 | static const bool digits[] = { 649 | X, X, X, _, X, _, X, X, X, X, X, X, X, _, X, X, X, X, _, X, X, X, X, X, X, X, X, X, X, X, 650 | X, _, X, _, X, _, _, _, X, _, _, X, X, _, X, X, _, _, X, _, _, _, _, X, X, _, X, X, _, X, 651 | X, _, X, _, X, _, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, _, X, _, X, X, X, X, X, X, 652 | X, _, X, _, X, _, X, _, _, _, _, X, _, _, X, _, _, X, X, _, X, _, X, _, X, _, X, _, _, X, 653 | X, X, X, _, X, _, X, X, X, X, X, X, _, _, X, X, X, X, X, X, X, _, X, _, X, X, X, X, X, _}; 654 | 655 | int first = n / 10; 656 | int second = n % 10; 657 | CRGB color = CHSV(beatsin8(5), 255, 255); 658 | 659 | for (int i = 0; i < numLeds; i++) 660 | matrixLeds[i] = CRGB::Black; 661 | 662 | for (int y = 0; y < 5; y++) 663 | { 664 | if (first) 665 | matrixLeds[y * 5] = color; // digit 1 or nothing 666 | 667 | for (int x = 0; x < 3; x++) 668 | { 669 | if (digits[y * 30 + second * 3 + x]) 670 | matrixLeds[y * 5 + first + 1 + x] = color; 671 | } 672 | } 673 | } 674 | -------------------------------------------------------------------------------- /src/LEDswarm.h: -------------------------------------------------------------------------------- 1 | #ifndef LEDSWARM_H 2 | #define LEDSWARM_H 3 | 4 | // ************************************************************************ 5 | // DEFINES FOR LIBRARIES 6 | // ************************************************************************ 7 | 8 | // #define FASTLED_ALLOW_INTERRUPTS 1 // Allow interrupts, to prevent wifi weirdness ; https://github.com/FastLED/FastLED/wiki/Interrupt-problems 9 | // #define INTERRUPT_THRESHOLD 1 // also see https://github.com/FastLED/FastLED/issues/367 10 | #define USE_GET_MILLISECOND_TIMER // Define our own millis() source for FastLED beat functions: see get_millisecond_timer() 11 | #define TASK_RES_MULTIPLIER 1 12 | 13 | // ************************************************************************ 14 | // INCLUDES 15 | // ************************************************************************ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #define LEDSWARM_DEBUG 24 | 25 | #ifdef LEDSWARM_DEBUG 26 | #define DEBUG_PRINT(x) Serial.print(x) 27 | #define DEBUG_PRINTDEC(x) Serial.print(x, DEC) 28 | #define DEBUG_PRINTLN(x) Serial.println(x) 29 | #else 30 | #define DEBUG_PRINT(x) 31 | #define DEBUG_PRINTDEC(x) 32 | #define DEBUG_PRINTLN(x) 33 | #endif 34 | 35 | #define MESH_PREFIX "LEDforge" 36 | #define MESH_PASSWORD "somethingSneaky" 37 | #define MESH_PORT 5555 38 | 39 | #if defined(NEO_PIXEL) 40 | #define CHIPSET WS2812B 41 | #define COLOR_ORDER GRB 42 | #endif 43 | 44 | #define LEADER "LEADER" 45 | #define FOLLOWER "FOLLOWER" 46 | 47 | #if defined(APA_102) || defined(APA_102_SLOW) 48 | #include 49 | #define CHIPSET APA102 50 | #define COLOR_ORDER BGR 51 | #endif 52 | 53 | // ************************************************************************ 54 | // GLOBAL VARIABLES 55 | // ************************************************************************ 56 | 57 | uint8_t _numNodes = 1; // default 58 | uint8_t _nodePos = 0; // default 59 | uint16_t _meshNumLeds = LEDS_PER_NODE; // at boot we are alone 60 | 61 | CRGB _localLeds[LEDS_PER_NODE + 1]; 62 | CRGB _meshleds[MAX_MESH_LEDS + 1]; 63 | 64 | uint8_t _maxBright = DEFAULT_BRIGHTNESS; 65 | uint8_t currentPattern = DEFAULT_PATTERN; // Which mode do we start with 66 | uint8_t nextPattern = currentPattern; 67 | // uint8_t currentBrightness = maxBright ; 68 | 69 | #ifdef ATOMMATRIX 70 | CRGB matrix_leds[ATOM_NUM_LED]; 71 | #endif 72 | 73 | #ifdef ATOMMATRIX 74 | FX fx(_localLeds, matrix_leds, LEDS_PER_NODE); 75 | #else 76 | FX fx(_meshleds, LEDS_PER_NODE); // at boot, mesh is just 1 node 77 | #endif 78 | 79 | painlessMesh mesh; 80 | // https://painlessmesh.gitlab.io/painlessMesh/configuration_8hpp.html#a21459eda80c40da63432b0b89793f46d 81 | SimpleList nodes; // std::list; 82 | String role = LEADER; // default start out as leader unless told otherwise 83 | uint32_t activeFollower; 84 | 85 | // Scheduler 86 | Scheduler userScheduler; // to control your personal task 87 | 88 | // BPM variables 89 | ArduinoTapTempo tapTempo; 90 | bool newBPMSet = true; // flag for when new BPM is set by button 91 | uint32_t currentBPM = DEFAULT_BPM; // default BPM of ArduinoTapTempo 92 | 93 | Button bpmButton(BPM_BUTTON_PIN); 94 | // Button bpmButton(BPM_BUTTON_PIN, 50, true, true); 95 | Button nextPatternButton(BUTTON_PIN); 96 | 97 | // ************************************************************************ 98 | // C++ FUNCTION PROTOTYPEs 99 | // ************************************************************************ 100 | 101 | void checkButtonPress(); 102 | void sendMessage(); 103 | void currentPatternRun(); 104 | void selectNextPattern(); 105 | 106 | uint32_t get_millisecond_timer(); 107 | 108 | void checkLeadership(); 109 | boolean alone(); 110 | void receivedCallback(uint32_t from, String &msg); 111 | void newConnectionCallback(uint32_t nodeId); 112 | void changedConnectionCallback(); 113 | void nodeTimeAdjustedCallback(int32_t offset); 114 | void delayReceivedCallback(uint32_t from, int32_t delay); 115 | 116 | // ************************************************************************ 117 | // TASK VARIABLES (need function prototypes) 118 | // ************************************************************************ 119 | 120 | // Task variables 121 | #define TASK_CHECK_BUTTON_PRESS_INTERVAL 10 // in milliseconds 122 | #define CURRENTPATTERN_SELECT_DEFAULT_INTERVAL 5 // default scheduling time for currentPatternSELECT, in milliseconds 123 | Task taskCheckButtonPress(TASK_CHECK_BUTTON_PRESS_INTERVAL, TASK_FOREVER, &checkButtonPress); 124 | Task taskCurrentPatternRun(CURRENTPATTERN_SELECT_DEFAULT_INTERVAL, TASK_FOREVER, ¤tPatternRun); 125 | Task taskSendMessage(TASK_SECOND * 5, TASK_FOREVER, &sendMessage); // check every 5 second if we have a new BPM / pattern to send 126 | Task taskSelectNextPattern(TASK_SECOND *AUTO_ADVANCE_DELAY, TASK_FOREVER, &selectNextPattern); // switch to next pattern every AUTO_ADVANCE_DELAY seconds 127 | // Task taskRunPatternOnNode( TASK_IMMEDIATE, TASK_ONCE, &runPatternOnNode ); 128 | 129 | // TODO: rewrite this garbage 130 | // Routine Palette Rainbow is always included - a safe routine 131 | const char *routines[] = { 132 | "p_rb_stripe", 133 | #ifdef RT_P_RB 134 | "p_rb", 135 | #endif 136 | #ifdef RT_P_OCEAN 137 | "p_ocean", 138 | #endif 139 | #ifdef RT_P_HEAT 140 | "p_heat", 141 | #endif 142 | #ifdef RT_P_LAVA 143 | "p_lava", 144 | #endif 145 | #ifdef RT_P_PARTY 146 | "p_party", 147 | #endif 148 | #ifdef RT_TWIRL1 149 | "twirl1", 150 | #endif 151 | #ifdef RT_TWIRL2 152 | "twirl2", 153 | #endif 154 | #ifdef RT_TWIRL4 155 | "twirl4", 156 | #endif 157 | #ifdef RT_TWIRL6 158 | "twirl6", 159 | #endif 160 | #ifdef RT_TWIRL2_O 161 | "twirl2o", 162 | #endif 163 | #ifdef RT_TWIRL4_O 164 | "twirl4o", 165 | #endif 166 | #ifdef RT_TWIRL6_O 167 | "twirl6o", 168 | #endif 169 | #ifdef RT_FADE_GLITTER 170 | "fglitter", 171 | #endif 172 | #ifdef RT_DISCO_GLITTER 173 | "dglitter", 174 | #endif 175 | #ifdef RT_RACERS 176 | "racers", 177 | #endif 178 | #ifdef RT_WAVE 179 | "wave", 180 | #endif 181 | #ifdef RT_SHAKE_IT 182 | "shakeit", 183 | #endif 184 | #ifdef RT_STROBE1 185 | "strobe1", 186 | #endif 187 | #ifdef RT_STROBE2 188 | "strobe2", 189 | #endif 190 | #ifdef RT_GLED 191 | "gled", 192 | #endif 193 | #ifdef RT_HEARTBEAT 194 | "heartbeat", 195 | #endif 196 | #ifdef RT_FASTLOOP 197 | "fastloop", 198 | #endif 199 | #ifdef RT_FASTLOOP2 200 | "fastloop2", 201 | #endif 202 | #ifdef RT_PENDULUM 203 | "pendulum", 204 | #endif 205 | #ifdef RT_VUMETER 206 | "vumeter", 207 | #endif 208 | #ifdef RT_NOISE_LAVA 209 | "noise_lava", 210 | #endif 211 | #ifdef RT_NOISE_PARTY 212 | "noise_party", 213 | #endif 214 | #ifdef RT_BOUNCEBLEND 215 | "bounceblend", 216 | #endif 217 | #ifdef RT_JUGGLE_PAL 218 | "jugglepal", 219 | #endif 220 | #ifdef RT_QUAD_STROBE 221 | "quadstrobe", 222 | #endif 223 | #ifdef RT_PULSE_3 224 | "pulse3", 225 | #endif 226 | #ifdef RT_PULSE_5 227 | "pulse5", 228 | #endif 229 | #ifdef RT_THREE_SIN_PAL 230 | "tsp", 231 | #endif 232 | #ifdef RT_CYLON 233 | "cylon", 234 | #endif 235 | #ifdef RT_FIRE_STRIPE 236 | "firestripe", 237 | #endif 238 | #ifdef RT_BLACK 239 | "black", 240 | #endif 241 | 242 | }; 243 | 244 | // array size 245 | #define NUMROUTINES (sizeof(routines) / sizeof(char *)) // array size 246 | // const int numRoutines = (sizeof(routines)/sizeof(char *)); 247 | 248 | #endif 249 | -------------------------------------------------------------------------------- /src/headers/Jellyfishv2.h: -------------------------------------------------------------------------------- 1 | // ---- LED stuff ---- 2 | // For the huge umbrella Jellyfish project 3 | #define NEO_PIXEL 4 | 5 | #define NEO_PIXEL 6 | #define LED_PIN 21 // which pin your Neopixels are connected to 7 | #define NUM_LEDS 205 8 | #define DEFAULT_BRIGHTNESS 100 9 | // #define MAX_BRIGHTNESS 150 // not used yet 10 | 11 | // ---- Buttons ---- 12 | #define BUTTON_PIN 39 13 | #define BUTTON_PIN 12 14 | 15 | // ---- Misc ---- 16 | #define DEFAULT_BPM 60 17 | 18 | // ---- Patterns ---- 19 | #define RT_P_RB_STRIPE 20 | #define RT_P_OCEAN 21 | #define RT_P_HEAT 22 | #define RT_P_LAVA 23 | #define RT_P_PARTY 24 | #define RT_P_FOREST 25 | // #define RT_TWIRL1 26 | // #define RT_TWIRL2 27 | // #define RT_TWIRL4 28 | // #define RT_TWIRL6 29 | // #define RT_TWIRL2_O 30 | // #define RT_TWIRL4_O 31 | // #define RT_TWIRL6_O 32 | // #define RT_FADE_GLITTER 33 | // #define RT_DISCO_GLITTER 34 | // #define RT_HEARTBEAT 35 | #define RT_FASTLOOP 36 | #define RT_FASTLOOP2 37 | // #define RT_PENDULUM 38 | // #define RT_BOUNCEBLEND 39 | #define RT_JUGGLE_PAL 40 | #define RT_NOISE_LAVA 41 | #define RT_NOISE_PARTY 42 | // #define RT_PULSE_5_1 43 | // #define RT_PULSE_5_2 44 | // #define RT_PULSE_5_3 45 | // #define RT_THREE_SIN_PAL 46 | // #define RT_COLOR_GLOW 47 | -------------------------------------------------------------------------------- /src/headers/LEDsticks.h: -------------------------------------------------------------------------------- 1 | // #define ATOMMATRIX 2 | 3 | // ---- LED stuff ---- 4 | #define NEO_PIXEL 5 | #define LEDS_PER_NODE 25 6 | #define LED_PIN_1 26 7 | 8 | #define MAX_NODES 10 9 | #define MAX_MESH_LEDS MAX_NODES *LEDS_PER_NODE 10 | 11 | // 80 is more than enough for night time 12 | #define DEFAULT_BRIGHTNESS 20 13 | #define MAX_BRIGHTNESS 20 // not used yet 14 | 15 | // #define ATOM_LEDPIN 27 16 | // Atom Matrix display/final brightness should not be more than 20! 17 | // MAX_BRIGHTNESS/255 = x/MAX_BRIGHTNESS 18 | // #define ATOM_MAX_BRIGHTNESS 170 // not used yet 19 | // #define ATOM_NUM_LED 25 20 | 21 | // ---- Buttons ---- 22 | #define BUTTON_PIN 39 23 | #define BPM_BUTTON_PIN 25 24 | 25 | // ---- Misc ---- 26 | #define DEFAULT_BPM 130 27 | #define DEFAULT_PATTERN 1 28 | // Should the pattern auto advance? And how long to display each pattern? 29 | // #define AUTOADVANCE 30 | #define AUTO_ADVANCE_DELAY 30 31 | 32 | // ---- Which Patterns To Include? ---- 33 | #define RT_P_RB_STRIPE 34 | // #define RT_P_OCEAN 35 | // #define RT_P_HEAT 36 | // #define RT_P_LAVA 37 | // #define RT_P_PARTY 38 | // #define RT_P_FOREST 39 | // #define RT_TWIRL1 40 | // #define RT_TWIRL2 41 | // #define RT_TWIRL4 42 | // #define RT_TWIRL6 43 | // #define RT_TWIRL2_O 44 | // #define RT_TWIRL4_O 45 | // #define RT_TWIRL6_O 46 | // #define RT_FADE_GLITTER 47 | // #define RT_DISCO_GLITTER 48 | // #define RT_FASTLOOP 49 | // #define RT_FASTLOOP2 50 | // #define RT_PENDULUM 51 | // #define RT_BOUNCEBLEND 52 | // #define RT_JUGGLE_PAL 53 | // #define RT_NOISE_LAVA 54 | // #define RT_NOISE_PARTY 55 | // #define RT_PULSE_5_1 56 | // #define RT_PULSE_5_2 57 | // #define RT_PULSE_5_3 58 | // #define RT_THREE_SIN_PAL 59 | // #define RT_COLOR_GLOW 60 | -------------------------------------------------------------------------------- /src/headers/atommatrix.h: -------------------------------------------------------------------------------- 1 | #define ATOMMATRIX 2 | 3 | // ---- LED stuff ---- 4 | #define NEO_PIXEL 5 | #define LEDS_PER_NODE 25 6 | #define ATOM_LEDPIN 27 7 | // #define ATOM_NUM_LED 25 8 | #define LED_PIN_1 26 9 | 10 | #define MAX_NODES 5 11 | #define MAX_MESH_LEDS MAX_NODES *LEDS_PER_NODE 12 | 13 | // 80 is more than enough for night time 14 | #define DEFAULT_BRIGHTNESS 80 15 | // #define MAX_BRIGHTNESS 100 // not used yet 16 | 17 | // Atom Matrix display/final brightness should not be more than 20! 18 | // MAX_BRIGHTNESS/255 = x/MAX_BRIGHTNESS 19 | // #define ATOM_MAX_BRIGHTNESS 100 // not used yet 20 | #define ATOM_NUM_LED 25 21 | 22 | // ---- Buttons ---- 23 | #define BUTTON_PIN 39 24 | #define BPM_BUTTON_PIN 32 25 | 26 | // ---- Misc ---- 27 | #define DEFAULT_BPM 130 28 | #define DEFAULT_PATTERN 1 29 | // Should the pattern auto advance? And how long to display each pattern? 30 | #define AUTOADVANCE 31 | #define AUTO_ADVANCE_DELAY 30 32 | 33 | // ---- Which Patterns To Include? ---- 34 | #define RT_P_RB_STRIPE 35 | #define RT_P_OCEAN 36 | #define RT_P_HEAT 37 | #define RT_P_LAVA 38 | #define RT_P_PARTY 39 | #define RT_P_FOREST 40 | // #define RT_TWIRL1 41 | // #define RT_TWIRL2 42 | // #define RT_TWIRL4 43 | // #define RT_TWIRL6 44 | // #define RT_TWIRL2_O 45 | // #define RT_TWIRL4_O 46 | // #define RT_TWIRL6_O 47 | // #define RT_FADE_GLITTER 48 | // #define RT_DISCO_GLITTER 49 | // #define RT_FASTLOOP 50 | // #define RT_FASTLOOP2 51 | // #define RT_PENDULUM 52 | // #define RT_BOUNCEBLEND 53 | // #define RT_JUGGLE_PAL 54 | // #define RT_NOISE_LAVA 55 | // #define RT_NOISE_PARTY 56 | // #define RT_PULSE_5_1 57 | // #define RT_PULSE_5_2 58 | // #define RT_PULSE_5_3 59 | // #define RT_THREE_SIN_PAL 60 | // #define RT_COLOR_GLOW 61 | -------------------------------------------------------------------------------- /src/headers/esp32thing.h: -------------------------------------------------------------------------------- 1 | // ---- LED stuff ---- 2 | // External Test Strip on an Sparkfun ESP32 Thing: 3 | #define NEO_PIXEL 4 | 5 | #define NUM_LEDS 25 6 | #define LED_PIN_1 25 7 | #define NUM_LEDS_PER_STRIP 25 8 | #define DEFAULT_BRIGHTNESS 200 9 | // #define MAX_BRIGHTNESS 200 // not used yet 10 | 11 | // ---- Buttons ---- 12 | #define BUTTON_PIN 39 13 | 14 | // ---- Misc ---- 15 | #define DEFAULT_BPM 60 16 | 17 | // ---- Patterns ---- 18 | #define RT_P_RB_STRIPE 19 | #define RT_P_OCEAN 20 | #define RT_P_HEAT 21 | #define RT_P_LAVA 22 | #define RT_P_PARTY 23 | #define RT_P_FOREST 24 | // #define RT_TWIRL1 25 | // #define RT_TWIRL2 26 | // #define RT_TWIRL4 27 | // #define RT_TWIRL6 28 | // #define RT_TWIRL2_O 29 | // #define RT_TWIRL4_O 30 | // #define RT_TWIRL6_O 31 | // #define RT_FADE_GLITTER 32 | // #define RT_DISCO_GLITTER 33 | // #define RT_HEARTBEAT 34 | #define RT_FASTLOOP 35 | #define RT_FASTLOOP2 36 | // #define RT_PENDULUM 37 | // #define RT_BOUNCEBLEND 38 | #define RT_JUGGLE_PAL 39 | #define RT_NOISE_LAVA 40 | #define RT_NOISE_PARTY 41 | // #define RT_PULSE_5_1 42 | // #define RT_PULSE_5_2 43 | // #define RT_PULSE_5_3 44 | // #define RT_THREE_SIN_PAL 45 | // #define RT_COLOR_GLOW 46 | -------------------------------------------------------------------------------- /src/headers/strombluete.h: -------------------------------------------------------------------------------- 1 | // #define ATOMMATRIX 2 | 3 | // ---- LED stuff ---- 4 | #define NEO_PIXEL 5 | #define LEDS_PER_NODE 8 6 | #define NUM_LEDS_PER_STRIP LEDS_PER_NODE 7 | #define LED_PIN_1 18 8 | 9 | #define MAX_NODES 5 10 | #define MAX_MESH_LEDS MAX_NODES *LEDS_PER_NODE 11 | 12 | // There are only 8 leds 13 | #define DEFAULT_BRIGHTNESS 254 14 | // #define MAX_BRIGHTNESS 254 // not used yet 15 | 16 | // ---- Buttons ---- 17 | #define BUTTON_PIN 23 18 | #define BPM_BUTTON_PIN 25 19 | 20 | // ---- Misc ---- 21 | #define DEFAULT_BPM 130 22 | #define DEFAULT_PATTERN 1 23 | // Should the pattern auto advance? And how long to display each pattern? 24 | #define AUTOADVANCE 25 | #define AUTO_ADVANCE_DELAY 30 26 | 27 | // ---- Which Patterns To Include? ---- 28 | // #define RT_P_OCEAN 29 | #define RT_P_HEAT 30 | #define RT_P_LAVA 31 | #define RT_P_PARTY 32 | #define RT_P_FOREST 33 | // #define RT_TWIRL1 34 | // #define RT_TWIRL2 35 | // #define RT_TWIRL4 36 | // #define RT_TWIRL6 37 | // #define RT_TWIRL2_O 38 | // #define RT_TWIRL4_O 39 | // #define RT_TWIRL6_O 40 | // #define RT_FADE_GLITTER 41 | // #define RT_DISCO_GLITTER 42 | // #define RT_FASTLOOP 43 | // #define RT_FASTLOOP2 44 | // #define RT_PENDULUM 45 | // #define RT_BOUNCEBLEND 46 | // #define RT_JUGGLE_PAL 47 | // #define RT_NOISE_LAVA 48 | // #define RT_NOISE_PARTY 49 | // #define RT_PULSE_5_1 50 | // #define RT_PULSE_5_2 51 | // #define RT_PULSE_5_3 52 | // #define RT_THREE_SIN_PAL 53 | // #define RT_COLOR_GLOW 54 | --------------------------------------------------------------------------------