Created
July 1, 2019 14:29
-
-
Save l0c0luke/bb7677d09a5cc40e5cc4b22b7d8d239d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "Arduino.h" | |
#include <FastLED.h> | |
#include <WiFi.h> | |
#include "heltec.h" | |
#include "jsbutton.h" | |
#define BAND 915E6 | |
#define STRING_LEN 128 | |
// -- Configuration specific key. The value should be modified if config structure was changed. | |
#define CONFIG_VERSION "v6" | |
// -- When CONFIG_PIN is pulled to ground on startup, the Thing will use the initial | |
// password to buld an AP. (E.g. in case of lost password) | |
#define CONFIG_PIN 1 | |
#define PRG_PIN 0 | |
// Definition for the array of routines to display. | |
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) | |
// -- Status indicator pin. | |
// First it will light up (kept LOW), on Wifi connection it will blink, | |
// when connected to the Wifi it will turn off (kept HIGH). | |
#define STATUS_PIN LED_BUILTIN | |
#define BRIGHTNESS 255 | |
#define TEMPERATURE ClearBlueSky | |
#define LEDS_PIN_1 12 | |
#define LEDS_PIN_2 13 | |
#define NUM_LEDS 140 | |
#define CHIPSET WS2812 | |
#define COLOR_ORDER GRB | |
int currentLedsState = 0; | |
int proposedLedsState = 0; | |
CRGB leds1[NUM_LEDS]; | |
CRGB leds2[NUM_LEDS]; | |
uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current | |
uint8_t gHue = 0; // rotating "base color" used by many of the patterns | |
typedef void (*SimplePatternList[])(); | |
// -- Callback method declarations. | |
void oledUpdate(); | |
void processLoraPayload(String payload); | |
void onLoraReceive(int packetSize); | |
void sendLoraMessage(String outgoing); | |
int pinState = HIGH; | |
String incomingLoraMessage; | |
String outgoingLoraMessage; | |
String loraPayloadToSend; | |
byte loraGroup = 0xBB; | |
boolean inMenu = false; | |
String lightModes[] = { | |
"Rainbow", | |
"Rainbow With Glitter", | |
"Confetti", | |
"Sine Lon", | |
"Juggle", | |
"BPM", | |
"Bouncing Balls", | |
"Lightning", | |
"Moving Gradient", | |
"Police Lights", | |
"Fire", | |
"Off" | |
}; | |
// -- Moving Gradient | |
// User variables | |
CHSV gradStartColor(0,255,255); // Gradient start color. | |
CHSV gradEndColor(161,255,255); // Gradient end color. | |
uint8_t gradStartPos = 0; // Starting position of the gradient. | |
#define gradLength 50 // How many pixels (in total) is the grad from start to end. | |
int8_t gradDelta = 1; // 1 or -1. (Negative value reverses direction.) | |
// If you wanted to move your gradient 32 pixels in 120 seconds, then: | |
// 120sec / 32pixel = 3.75sec | |
// 3.75sec x 1000miliseconds/sec = 3750milliseconds | |
#define gradMoveDelay 10 // How fast to move the gradient (in Milliseconds) | |
CRGB grad[gradLength]; // A place to save the gradient colors. (Don't edit this) | |
// -- Lightning | |
uint8_t frequency = 50; // controls the interval between strikes | |
uint8_t flashes = 8; //the upper limit of flashes per strike | |
unsigned int dimmer = 1; | |
uint8_t ledstart; // Starting location of a flash | |
uint8_t ledlen; | |
// -- End Lightning | |
// Fire | |
CRGBPalette16 gPal; | |
bool gReverseDirection = false; | |
// --- Bouncing Balls --- | |
#define GRAVITY -9.81 // Downward (negative) acceleration of gravity in m/s^2 | |
#define h0 1 // Starting height, in meters, of the ball (strip length) | |
#define NUM_BALLS 4 // Number of bouncing balls you want (recommend < 7, but 20 is fun in its own way) | |
float h[NUM_BALLS] ; // An array of heights | |
float vImpact0 = sqrt( -2 * GRAVITY * h0 ); // Impact velocity of the ball when it hits the ground if "dropped" from the top of the strip | |
float vImpact[NUM_BALLS] ; // As time goes on the impact velocity will change, so make an array to store those values | |
float tCycle[NUM_BALLS] ; // The time since the last time the ball struck the ground | |
int pos[NUM_BALLS] ; // The integer position of the dot on the strip (LED index) | |
long tLast[NUM_BALLS] ; // The clock time of the last ground strike | |
float COR[NUM_BALLS] ; // Coefficient of Restitution (bounce damping) | |
// --- END BOUNCING BALLS --- | |
void setup() | |
{ | |
pinMode(PRG_PIN, INPUT); | |
pinMode(16, OUTPUT); | |
digitalWrite(16, LOW); // set GPIO16 low to reset OLED | |
delay(50); | |
digitalWrite(16, HIGH); | |
Serial.begin(115200); | |
Serial.println(); | |
Serial.println("Starting up..."); | |
Heltec.begin(true /*DisplayEnable Enable*/, true /*Heltec.LoRa Enable*/, true /*Serial Enable*/, true /*PABOOST Enable*/, BAND /*long BAND*/); | |
// LEDs | |
FastLED.addLeds<CHIPSET, LEDS_PIN_1, COLOR_ORDER>(leds1, NUM_LEDS).setCorrection(TypicalLEDStrip); | |
FastLED.addLeds<CHIPSET, LEDS_PIN_2, COLOR_ORDER>(leds2, NUM_LEDS).setCorrection(TypicalLEDStrip); | |
FastLED.setBrightness(BRIGHTNESS); | |
FastLED.setTemperature(TEMPERATURE); | |
fill_solid(leds1, NUM_LEDS, CRGB::Black); | |
fill_solid(leds2, NUM_LEDS, CRGB::Black); | |
FastLED.show(); | |
// setup variables for bouncing ball | |
for (int i = 0 ; i < NUM_BALLS ; i++) { // Initialize variables | |
tLast[i] = millis(); | |
h[i] = h0; | |
pos[i] = 0; // Balls start on the ground | |
vImpact[i] = vImpact0; // And "pop" up at vImpact0 | |
tCycle[i] = 0; | |
COR[i] = 0.90 - float(i)/pow(NUM_BALLS,2); | |
} | |
// setup for moving gradient | |
fill_gradient(grad, gradStartPos, gradStartColor, gradStartPos+gradLength-1, gradEndColor, SHORTEST_HUES); | |
// fire | |
//gPal = CRGBPalette16( CRGB::Black, CRGB::Red, CRGB::Yellow, CRGB::White); | |
gPal = HeatColors_p; | |
Serial.println("LENSiE Saber is ready."); | |
} | |
void onLoraReceive(int packetSize) | |
{ | |
if (packetSize == 0) | |
return; // if there's no packet, return | |
// read packet header bytes: | |
int recipient = LoRa.read(); // recipient address | |
byte incomingLength = LoRa.read(); // incoming msg length | |
String incomingLoraMessage = ""; | |
while (LoRa.available()) | |
{ | |
incomingLoraMessage += (char)LoRa.read(); | |
} | |
if (incomingLength != incomingLoraMessage.length()) | |
{ // check length for error | |
Serial.println("error: message length does not match length"); | |
return; | |
} | |
// if the recipient isn't this device or broadcast, | |
if (recipient != loraGroup) | |
{ | |
Serial.println("This message is not for me."); | |
return; | |
} | |
Serial.println("Processing LoRa: " + incomingLoraMessage); | |
processLoraPayload(incomingLoraMessage); | |
// if message is for this device, or broadcast, print details: | |
//Serial.println("Message length: " + String(incomingLength)); | |
//Serial.println("Message: " + incomingLoraMessage); | |
//Serial.println("RSSI: " + String(LoRa.packetRssi())); | |
//Serial.println("Snr: " + String(LoRa.packetSnr())); | |
} | |
void processLoraPayload(String payload) | |
{ | |
String noun; // who should listen | |
String verb; // command they should do | |
int firstSlash = payload.indexOf("/"); | |
if (firstSlash == -1) | |
{ | |
Serial.println("Invalid LoRa command with: " + payload); | |
} | |
else | |
{ | |
noun = payload.substring(0, firstSlash); | |
int secondSlash = payload.indexOf("/", firstSlash + 1); | |
verb = payload.substring(firstSlash + 1, payload.length()); | |
if (noun == "saber") | |
{ | |
Serial.println("We got a saber command"); | |
currentLedsState = verb.toInt(); | |
proposedLedsState = verb.toInt(); | |
} | |
else | |
{ | |
Serial.println("Invalid LoRa command with: " + payload); | |
} | |
} | |
} | |
void sendLoraMessage(String outgoing) | |
{ | |
Serial.print("LoRa Send: " + outgoing); | |
LoRa.beginPacket(); // start packet | |
LoRa.write(loraGroup); // add sender address | |
LoRa.write(outgoing.length()); // add payload length | |
LoRa.print(outgoing); // add payload | |
LoRa.endPacket(); // finish packet and send it | |
Serial.println(" done"); | |
} | |
void oledUpdate() | |
{ | |
Heltec.display->clear(); | |
Heltec.display->setColor(WHITE); | |
Heltec.display->setFont(ArialMT_Plain_10); | |
Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT); | |
Heltec.display->drawString(0, 0, "Name: " + WiFi.macAddress()); | |
if (WiFi.SSID() != 0) | |
{ | |
Heltec.display->drawString(0, 10, "WiFi: " + WiFi.SSID() + " " + WiFi.RSSI()); | |
Heltec.display->drawString(0, 20, "IP: " + WiFi.localIP().toString()); | |
} | |
else | |
{ | |
Heltec.display->drawString(0, 10, "WiFi: Disconnected"); | |
} | |
if (inMenu == false) { | |
Heltec.display->drawString(0, 30, "* Long press for menu. *"); | |
} else { | |
Heltec.display->drawString(0, 30, "Menu: " + lightModes[proposedLedsState] + "?"); | |
Heltec.display->drawString(0, 50, "(Long press to select.)"); | |
} | |
Heltec.display->display(); | |
} | |
// ************************* | |
// ** LEDEffect Functions ** | |
// ************************* | |
uint8_t V; //brightness for rainbow | |
uint8_t S; //saturation for rainbow | |
boolean toggleS; | |
boolean toggleV; | |
//Amount to tint (desaturate) rainbow. Can use either RGB or HSV format | |
//CRGB tintAmt(128,128,128); | |
CHSV tintAmt(0,0,90); | |
void rainbow() { | |
static uint16_t sPseudotime = 0; | |
static uint16_t sLastMillis = 0; | |
static uint16_t sHue16 = 0; | |
uint8_t sat8 = beatsin88( 87, 220, 250); | |
uint8_t brightdepth = beatsin88( 341, 96, 224); | |
uint16_t brightnessthetainc16 = beatsin88( 203, (25 * 256), (40 * 256)); | |
uint8_t msmultiplier = beatsin88(147, 23, 60); | |
uint16_t hue16 = sHue16;//gHue * 256; | |
uint16_t hueinc16 = beatsin88(113, 1, 3000); | |
uint16_t ms = millis(); | |
uint16_t deltams = ms - sLastMillis ; | |
sLastMillis = ms; | |
sPseudotime += deltams * msmultiplier; | |
sHue16 += deltams * beatsin88( 400, 5,9); | |
uint16_t brightnesstheta16 = sPseudotime; | |
for( uint16_t i = 0 ; i < NUM_LEDS; i++) { | |
hue16 += hueinc16; | |
uint8_t hue8 = hue16 / 256; | |
brightnesstheta16 += brightnessthetainc16; | |
uint16_t b16 = sin16( brightnesstheta16 ) + 32768; | |
uint16_t bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; | |
uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; | |
bri8 += (255 - brightdepth); | |
CRGB newcolor = CHSV( hue8, sat8, bri8); | |
uint16_t pixelnumber = i; | |
pixelnumber = (NUM_LEDS-1) - pixelnumber; | |
nblend( leds1[pixelnumber], newcolor, 64); | |
nblend( leds2[pixelnumber], newcolor, 64); | |
} | |
} // rainbow() | |
void rainbowWithGlitter() { | |
rainbow(); // Built-in FastLED rainbow, plus some random sparkly glitter. | |
addGlitter(80); | |
} // rainbowWithGlitter() | |
void addGlitter(fract8 chanceOfGlitter) { | |
if(random8() < chanceOfGlitter) { | |
leds1[ random16(NUM_LEDS) ] += CRGB::White; | |
leds2[ random16(NUM_LEDS) ] += CRGB::White; | |
} | |
} // addGlitter() | |
void confetti() { // Random colored speckles that blink in and fade smoothly. | |
fadeToBlackBy(leds1, NUM_LEDS, 10); | |
fadeToBlackBy(leds2, NUM_LEDS, 10); | |
int pos = random16(NUM_LEDS); | |
leds1[pos] += CHSV(gHue + random8(64), 200, 255); | |
leds2[pos] += CHSV(gHue + random8(64), 200, 255); | |
} // confetti() | |
void sinelon() { // A colored dot sweeping back and forth, with fading trails. | |
fadeToBlackBy(leds1, NUM_LEDS, 20); | |
fadeToBlackBy(leds2, NUM_LEDS, 20); | |
int pos = beatsin16(13,0,NUM_LEDS-1); | |
leds1[pos] += CHSV(gHue, 255, 192); | |
leds2[pos] += CHSV(gHue, 255, 192); | |
} // sinelon() | |
void bpm() { // Colored stripes pulsing at a defined Beats-Per-Minute. | |
uint8_t BeatsPerMinute = 62; | |
CRGBPalette16 palette = PartyColors_p; | |
uint8_t beat = beatsin8(BeatsPerMinute, 64, 255); | |
for(int i = 0; i < NUM_LEDS; i++) { //9948 | |
leds1[i] = ColorFromPalette(palette, gHue+(i*2), beat-gHue+(i*10)); | |
leds2[i] = ColorFromPalette(palette, gHue+(i*2), beat-gHue+(i*10)); | |
} | |
} // bpm() | |
void juggle() { // Eight colored dots, weaving in and out of sync with each other. | |
fadeToBlackBy(leds1, NUM_LEDS, 20); | |
fadeToBlackBy(leds2, NUM_LEDS, 20); | |
byte dothue = 0; | |
for(int i = 0; i < 8; i++) { | |
leds1[beatsin16(i+7,0,NUM_LEDS-1)] |= CHSV(dothue, 200, 255); | |
leds2[beatsin16(i+7,0,NUM_LEDS-1)] |= CHSV(dothue, 200, 255); | |
dothue += 32; | |
} | |
} | |
void bouncingBalls() { | |
for (int i = 0 ; i < NUM_BALLS ; i++) { | |
tCycle[i] = millis() - tLast[i] ; // Calculate the time since the last time the ball was on the ground | |
// A little kinematics equation calculates positon as a function of time, acceleration (gravity) and intial velocity | |
h[i] = 0.5 * GRAVITY * pow( tCycle[i]/1000 , 2.0 ) + vImpact[i] * tCycle[i]/1000; | |
if ( h[i] < 0 ) { | |
h[i] = 0; // If the ball crossed the threshold of the "ground," put it back on the ground | |
vImpact[i] = COR[i] * vImpact[i] ; // and recalculate its new upward velocity as it's old velocity * COR | |
tLast[i] = millis(); | |
if ( vImpact[i] < 0.01 ) vImpact[i] = vImpact0; // If the ball is barely moving, "pop" it back up at vImpact0 | |
} | |
pos[i] = round( h[i] * (NUM_LEDS - 1) / h0); // Map "h" to a "pos" integer index position on the LED strip | |
} | |
//Choose color of LEDs, then the "pos" LED on | |
for (int i = 0 ; i < NUM_BALLS ; i++) { | |
leds1[pos[i]] = CHSV( uint8_t (i * 40) , 255, 255); | |
leds2[pos[i]] = CHSV( uint8_t (i * 40) , 255, 255); | |
} | |
FastLED.show(); | |
//Then off for the next loop around | |
for (int i = 0 ; i < NUM_BALLS ; i++) { | |
leds1[pos[i]] = CRGB::Black; | |
leds2[pos[i]] = CRGB::Black; | |
} | |
FastLED.show(); | |
} | |
void lightning() { | |
ledstart = random8(NUM_LEDS); // Determine starting location of flash | |
ledlen = random8(NUM_LEDS-ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) | |
for (int flashCounter = 0; flashCounter < random8(3,flashes); flashCounter++) { | |
if(flashCounter == 0) dimmer = 5; // the brightness of the leader is scaled down by a factor of 5 | |
else dimmer = random8(1,3); // return strokes are brighter than the leader | |
fill_solid(leds1+ledstart,ledlen,CHSV(255, 0, 255/dimmer)); | |
fill_solid(leds2+ledstart,ledlen,CHSV(255, 0, 255/dimmer)); | |
FastLED.show(); // Show a section of LED's | |
FastLED.delay(random8(4,10)); // each flash only lasts 4-10 milliseconds | |
fill_solid(leds1+ledstart,ledlen,CHSV(255,0,0)); // Clear the section of LED's | |
fill_solid(leds2+ledstart,ledlen,CHSV(255,0,0)); | |
FastLED.show(); | |
if (flashCounter == 0) delay (150); // longer delay until next flash after the leader | |
FastLED.delay(50+random8(100)); // shorter delay between strokes | |
} // for() | |
FastLED.delay(random8(frequency)*100); // delay between strikes | |
} | |
void movingGradient() { | |
EVERY_N_MILLISECONDS(gradMoveDelay) { | |
uint8_t count = 0; | |
for (uint8_t i = gradStartPos; i < gradStartPos+gradLength; i++) { | |
leds1[i % NUM_LEDS] = grad[count]; | |
leds2[i % NUM_LEDS] = grad[count]; | |
count++; | |
} | |
FastLED.show(); // Display the pixels. | |
FastLED.clear(); // Clear the strip to not leave behind lit pixels as grad moves. | |
gradStartPos = gradStartPos + gradDelta; // Update start position. | |
if ( (gradStartPos > NUM_LEDS-1) || (gradStartPos < 0) ) { // Check if outside NUM_LEDS range | |
gradStartPos = gradStartPos % NUM_LEDS; // Loop around as needed. | |
} | |
} | |
} | |
int BOTTOM_INDEX = 0; | |
int TOP_INDEX = int(NUM_LEDS/2); | |
//-PERISTENT VARS | |
int idex = 0; //-LED INDEX (0 to NUM_LEDS-1 | |
int idx_offset = 0; //-OFFSET INDEX (BOTTOM LED TO ZERO WHEN LOOP IS TURNED/DOESN'T REALLY WORK) | |
int ihue = 0; //-HUE (0-360) | |
int ibright = 0; //-BRIGHTNESS (0-255) | |
int isat = 0; //-SATURATION (0-255) | |
int bouncedirection = 0; //-SWITCH FOR COLOR BOUNCE (0-1) | |
float tcount = 0.0; //-INC VAR FOR SIN LOOPS | |
int lcount = 0; //-ANOTHER COUNTING VAR | |
//-FIND INDEX OF ANTIPODAL OPPOSITE LED | |
int antipodal_index(int i) { | |
//int N2 = int(NUM_LEDS/2); | |
int iN = i + TOP_INDEX; | |
if (i >= TOP_INDEX) {iN = ( i + TOP_INDEX ) % NUM_LEDS; } | |
return iN; | |
} | |
void policeLights() { | |
idex++; | |
if (idex >= NUM_LEDS) {idex = 0;} | |
int idexR = idex; | |
int idexB = antipodal_index(idexR); | |
leds1[idexR] = CHSV( 255, 0, 0); | |
leds1[idexB] = CHSV( 0, 0, 255); | |
leds2[idexR] = CHSV( 255, 0, 0); | |
leds2[idexB] = CHSV( 0, 0, 255); | |
FastLED.delay(40); | |
} | |
#define COOLING 55 | |
// SPARKING: What chance (out of 255) is there that a new spark will be lit? | |
// Higher chance = more roaring fire. Lower chance = more flickery fire. | |
// Default 120, suggested range 50-200. | |
#define SPARKING 120 | |
void Fire2012WithPalette() | |
{ | |
// Array of temperature readings at each simulation cell | |
static byte heat[NUM_LEDS]; | |
// Step 1. Cool down every cell a little | |
for( int i = 0; i < NUM_LEDS; i++) { | |
heat[i] = qsub8( heat[i], random8(0, ((COOLING * 10) / NUM_LEDS) + 2)); | |
} | |
// Step 2. Heat from each cell drifts 'up' and diffuses a little | |
for( int k= NUM_LEDS - 1; k >= 2; k--) { | |
heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3; | |
} | |
// Step 3. Randomly ignite new 'sparks' of heat near the bottom | |
if( random8() < SPARKING ) { | |
int y = random8(7); | |
heat[y] = qadd8( heat[y], random8(160,255) ); | |
} | |
// Step 4. Map from heat cells to LED colors | |
for( int j = 0; j < NUM_LEDS; j++) { | |
// Scale the heat value from 0-255 down to 0-240 | |
// for best results with color palettes. | |
byte colorindex = scale8( heat[j], 240); | |
CRGB color = ColorFromPalette( gPal, colorindex); | |
int pixelnumber; | |
if( gReverseDirection ) { | |
pixelnumber = (NUM_LEDS-1) - j; | |
} else { | |
pixelnumber = j; | |
} | |
leds1[pixelnumber] = color; | |
leds2[pixelnumber] = color; | |
} | |
} | |
void turnOff() { // Eight colored dots, weaving in and out of sync with each other. | |
fadeToBlackBy(leds1, NUM_LEDS, 20); | |
fadeToBlackBy(leds2, NUM_LEDS, 20); | |
} | |
SimplePatternList gPatterns = {rainbow, rainbowWithGlitter, confetti, sinelon, juggle, bpm, bouncingBalls, lightning, movingGradient, policeLights, Fire2012WithPalette, turnOff }; // Don't know why this has to be here. . . | |
void readButton() { // Read the button and increase the mode | |
uint8_t b = checkButton(); | |
if (b == 1) { // Just a click event to advance to next pattern | |
if (inMenu == true) { | |
if (proposedLedsState == ARRAY_SIZE(gPatterns) - 1) { | |
proposedLedsState = 0; | |
} else { | |
proposedLedsState = proposedLedsState + 1; | |
} | |
} | |
} | |
if (b == 2) { // A double-click event to reset to 0 pattern | |
Serial.println("double click"); | |
} | |
if (b == 3) { // A hold event to write current pattern to EEPROM | |
if (inMenu == false) { | |
Serial.println("Going MENU"); | |
inMenu = true; | |
} else { | |
currentLedsState = proposedLedsState; | |
sendLoraMessage("saber/" + String(currentLedsState)); | |
inMenu = false; | |
Serial.println("Leaving MENU with " + String(currentLedsState)); | |
} | |
} | |
} | |
void loop() | |
{ | |
onLoraReceive(LoRa.parsePacket()); | |
oledUpdate(); | |
readButton(); | |
EVERY_N_MILLISECONDS(10) { | |
gPatterns[currentLedsState](); // Call the current pattern function once, updating the 'leds' array | |
} | |
EVERY_N_MILLISECONDS(20) { // slowly cycle the "base color" through the rainbow | |
gHue++; | |
} | |
FastLED.show(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment