Skip to content

Instantly share code, notes, and snippets.

@l0c0luke
Created July 1, 2019 14:29
Show Gist options
  • Save l0c0luke/bb7677d09a5cc40e5cc4b22b7d8d239d to your computer and use it in GitHub Desktop.
Save l0c0luke/bb7677d09a5cc40e5cc4b22b7d8d239d to your computer and use it in GitHub Desktop.
#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