Created
July 12, 2018 09:25
-
-
Save JontyMC/198603ce7430f363bf3b28f0b7acef3b 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 <bluefruit.h> | |
#include <Adafruit_NeoPixel.h> | |
#define NUMPIX 54 | |
#define POT_PIN A5 //potentiometer | |
#define MIC_PIN A2 // Microphone is attached to this analog pin | |
#define LED_PIN 30 // NeoPixel LED strand is connected to this pin | |
#define BUTTON_PIN 7 // NeoPixel LED strand is connected to this pin | |
#define DC_OFFSET 0 // DC offset in mic signal - if unusure, leave 0 | |
#define NOISE 30 // Noise/hum/interference in mic signal | |
#define SAMPLES 60 // Length of buffer for dynamic level adjustment | |
#define WSIDE1 0 | |
#define WSIDE2 6 | |
#define WSIDE3 25 | |
#define WSIDE4 35 | |
#define TOP1 8 // Allow dot to go slightly off scale | |
#define TOP2 21 // Allow dot to go slightly off scale` | |
#define TOP3 12 // Allow dot to go slightly off scale` | |
#define TOP4 21 // Allow dot to go slightly off scale` | |
#define PEAK_FALL 60 // Rate of peak falling dot | |
byte | |
peak1 = 0, // Used for falling dot | |
peak2 = 0, // Used for falling dot | |
peak3 = 0, // Used for falling dot | |
peak4 = 0, // Used for falling dot | |
dotCount = 0, // Frame counter for delaying dot-falling speed | |
volCount = 0; // Frame counter for storing past volume data | |
int | |
vol[SAMPLES], // Collection of prior volume samples | |
lvl = 10, // Current "dampened" audio level | |
minLvlAvg = 0, // For dynamic adjustment of graph low & high | |
maxLvlAvg1 = 512, | |
maxLvlAvg2 = 512, | |
maxLvlAvg3 = 512, | |
maxLvlAvg4 = 512, | |
mode = 0, | |
buttonState = 0, | |
potThreshold = 10, | |
potLastValue = 0, | |
brightness = 230, | |
cycleNum = 0, | |
cycleNum2 = 0, | |
colorCycleNum = 0, | |
wait = 50, | |
paletteLength; | |
unsigned long time, | |
temperatureInterval = 1000, | |
temperatureLastRefresh = 0, | |
buttonInterval = 150, | |
buttonLastRefresh = 50, | |
modeLastRefresh = 0; | |
uint32_t palette[10]; | |
int defaultShowNum = 0, defaultShowCycles = 0; | |
bool switchShow = false; | |
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIX, LED_PIN, NEO_RGB + NEO_KHZ400); | |
static uint8_t wagonServiceId[31] = { 0x01,0x01,0x06, 0x1A,0xFF,0x4C,0x00,0x02,0x15,0x71,0x3d,0x00,0x00,0x50,0x3e,0x4c,0x75,0xba,0x94,0x31,0x48,0xf1,0x8d,0x94,0x1e,0x00,0x00,0x00,0x00,0xC5 }; | |
static uint8_t wagonCharacteristic1Id[31] = { 0x02,0x01,0x06, 0x1A,0xFF,0x4C,0x00,0x02,0x15,0x71,0x3d,0x00,0x00,0x50,0x3e,0x4c,0x75,0xba,0x94,0x31,0x48,0xf1,0x8d,0x94,0x1e,0x00,0x00,0x00,0x00,0xC5 }; | |
static uint8_t wagonCharacteristic2Id[31] = { 0x03,0x01,0x06, 0x1A,0xFF,0x4C,0x00,0x02,0x15,0x71,0x3d,0x00,0x00,0x50,0x3e,0x4c,0x75,0xba,0x94,0x31,0x48,0xf1,0x8d,0x94,0x1e,0x00,0x00,0x00,0x00,0xC5 }; | |
static uint8_t wagonCharacteristic3Id[31] = { 0x04,0x01,0x06, 0x1A,0xFF,0x4C,0x00,0x02,0x15,0x71,0x3d,0x00,0x00,0x50,0x3e,0x4c,0x75,0xba,0x94,0x31,0x48,0xf1,0x8d,0x94,0x1e,0x00,0x00,0x00,0x00,0xC5 }; | |
static uint8_t wagonCharacteristic4Id[31] = { 0x05,0x01,0x06, 0x1A,0xFF,0x4C,0x00,0x02,0x15,0x71,0x3d,0x00,0x00,0x50,0x3e,0x4c,0x75,0xba,0x94,0x31,0x48,0xf1,0x8d,0x94,0x1e,0x00,0x00,0x00,0x00,0xC5 }; | |
BLEService wagonService = BLEService(wagonServiceId); | |
BLECharacteristic wagonCharacteristic1 = BLECharacteristic(wagonCharacteristic1Id); | |
BLECharacteristic wagonCharacteristic2 = BLECharacteristic(wagonCharacteristic2Id); | |
BLECharacteristic wagonCharacteristic3 = BLECharacteristic(wagonCharacteristic3Id); | |
BLECharacteristic wagonCharacteristic4 = BLECharacteristic(wagonCharacteristic4Id); | |
void setup() { | |
Serial.begin(115200); | |
Serial.println("Wagon Lights Server"); | |
Serial.println("-----------------------"); | |
pinMode(BUTTON_PIN, INPUT); | |
memset(vol, 0, sizeof(vol)); | |
strip.begin(); | |
changePalette(2); | |
Bluefruit.begin(); | |
Bluefruit.setName("Wagon"); | |
setupWagonService(); | |
startAdv(); | |
} | |
void loop() { | |
time = millis(); | |
if (mode != 2) { | |
strip.setBrightness(brightness); | |
} | |
switch (mode) { | |
default: //also case 0 | |
defaultShow(); | |
break; | |
case 1: | |
soundReactive(); | |
break; | |
case 2: | |
solid(); | |
break; | |
case 3: | |
fade(); | |
break; | |
case 4: | |
rainbow(); | |
break; | |
case 5: | |
rainbowDist(); | |
break; | |
case 6: | |
colorWipe(); | |
break; | |
case 7: | |
theaterChase(); | |
break; | |
case 8: | |
ledsOff(); | |
break; | |
//TODO: Kitt, new kitt, fill up one at a time, bounce back and forth, rotating strips | |
} | |
if(time - temperatureLastRefresh >= temperatureInterval) | |
{ | |
temperatureLastRefresh = time; | |
takeTemperature(); | |
} | |
if (time - buttonLastRefresh >= buttonInterval) { | |
buttonLastRefresh = time; | |
int newButtonState = digitalRead(BUTTON_PIN); | |
if (newButtonState == HIGH && buttonState != HIGH) { | |
Serial.println("b on"); | |
incrementMode(); | |
} | |
buttonState = newButtonState; | |
int reading = analogRead(POT_PIN); | |
if (abs(potLastValue - reading) > potThreshold) { | |
potLastValue = reading; | |
int brightness = map(reading,0,1000,0,255); | |
Serial.println(brightness); | |
strip.setBrightness(brightness); | |
} | |
} | |
} | |
void incrementMode() { | |
mode += 1; | |
if (mode > 6) { | |
mode = 0; | |
} | |
} | |
void takeTemperature() { | |
int reading = analogRead(A0); | |
float voltage = reading * 3.3; | |
voltage /= 1024.0; | |
// print out the voltage | |
Serial.print(voltage); Serial.println(" volts"); | |
// now print out the temperature | |
float temperatureC = (voltage - 0.5) * 100 ; //converting from 10 mv per degree wit 500 mV offset | |
//to degrees ((voltage - 500mV) times 100) | |
Serial.print(temperatureC); Serial.println(" degrees C"); | |
char str[3]; | |
sprintf(str, "%d", (int)temperatureC); | |
wagonCharacteristic1.notify(str); | |
} | |
void ledsOff() { | |
setAll(strip.Color(0, 0, 0)); | |
} | |
void setAll(uint32_t c) { | |
for(uint16_t i=0; i<strip.numPixels(); i++) { | |
strip.setPixelColor(i, c); | |
} | |
strip.show(); | |
} | |
void defaultShow() { | |
if (switchShow == true) { | |
switchShow = false; | |
defaultShowNum++; | |
if (defaultShowNum > 5){ | |
defaultShowNum = 0; | |
} | |
} | |
switch (defaultShowNum) { | |
case 0: | |
solid(); | |
break; | |
case 1: | |
rainbow(); | |
break; | |
case 2: | |
colorWipe(); | |
break; | |
case 3: | |
theaterChase(); | |
break; | |
case 4: | |
rainbowDist(); | |
break; | |
case 5: | |
fade(); | |
break; | |
} | |
} | |
void solid() { | |
defaultShowCycles = 8; | |
if (time - modeLastRefresh >= wait) { | |
modeLastRefresh = time; | |
if (cycleNum > 30) { | |
cycleNum = 0; | |
incrementPalette(); | |
} | |
setAll(palette[colorCycleNum]); | |
cycleNum++; | |
} | |
} | |
void fade() { | |
defaultShowCycles = 4; | |
if (time - modeLastRefresh >= wait/100) { | |
modeLastRefresh = time; | |
if (cycleNum > 1200) { | |
cycleNum = 0; | |
incrementPalette(); | |
} | |
int brightness = 0; | |
if (cycleNum < 500) { | |
brightness = map(cycleNum,0,499,0,150); | |
} else if (cycleNum < 1000) { | |
brightness = map(cycleNum,500,1000,150,0); | |
} | |
strip.setBrightness(brightness); | |
setAll(palette[colorCycleNum]); | |
cycleNum++; | |
} | |
} | |
void colorWipe() { | |
defaultShowCycles = 4; | |
if (time - modeLastRefresh >= wait) { | |
modeLastRefresh = time; | |
if (cycleNum > strip.numPixels()) { | |
cycleNum = 0; | |
incrementPalette(); | |
} | |
strip.setPixelColor(cycleNum, palette[colorCycleNum]); | |
strip.show(); | |
cycleNum++; | |
} | |
} | |
int currentShowCycles = 1; | |
void incrementPalette() { | |
colorCycleNum++; | |
if (colorCycleNum >= paletteLength) { | |
colorCycleNum = 0; | |
currentShowCycles++; | |
if (currentShowCycles >= defaultShowCycles) { | |
switchShow = true; | |
currentShowCycles = 1; | |
} | |
} | |
} | |
void theaterChase() { | |
defaultShowCycles = 2; | |
if (time - modeLastRefresh >= wait) { | |
modeLastRefresh = time; | |
if (cycleNum > 2) { | |
cycleNum = 0; | |
} | |
if (cycleNum2 > 54) { | |
cycleNum2 = 0; | |
incrementPalette(); | |
} | |
for (uint16_t i=0; i < strip.numPixels(); i=i+3) { | |
uint16_t previous = i+cycleNum - 1; | |
if (previous < 0) { | |
previous = strip.numPixels(); | |
} | |
strip.setPixelColor(previous, 0); //turn every third pixel off | |
} | |
for (uint16_t i=0; i < strip.numPixels(); i=i+3) { | |
strip.setPixelColor(i+cycleNum, palette[colorCycleNum]); //turn every third pixel on | |
} | |
strip.show(); | |
cycleNum++; | |
cycleNum2++; | |
} | |
} | |
void rainbow() { | |
defaultShowCycles = 1; | |
if (time - modeLastRefresh >= wait) { | |
modeLastRefresh = time; | |
if (cycleNum > 255) { | |
cycleNum = 0; | |
currentShowCycles++; | |
if (currentShowCycles >= defaultShowCycles) { | |
switchShow = true; | |
currentShowCycles = 1; | |
} | |
} | |
for(uint16_t i=0; i< strip.numPixels(); i++) { | |
strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + cycleNum) & 255)); | |
} | |
strip.show(); | |
cycleNum++; | |
} | |
} | |
void rainbowDist() { | |
defaultShowCycles = 1; | |
if (time - modeLastRefresh >= wait) { | |
modeLastRefresh = time; | |
if (cycleNum > 255) { | |
cycleNum = 0; | |
currentShowCycles++; | |
if (currentShowCycles >= defaultShowCycles) { | |
switchShow = true; | |
currentShowCycles = 1; | |
} | |
} | |
for(uint16_t i=0; i< strip.numPixels(); i++) { | |
strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + cycleNum) & 255)); | |
} | |
strip.show(); | |
cycleNum++; | |
} | |
} | |
void soundReactive() { | |
uint8_t i; | |
uint16_t minLvl, maxLvl1, maxLvl2, maxLvl3, maxLvl4; | |
int n; | |
n = analogRead(MIC_PIN); // Raw reading from mic | |
n = abs(n - 512 - DC_OFFSET); // Center on zero | |
n = (n <= NOISE) ? 0 : (n - NOISE); // Remove noise/hum | |
lvl = ((lvl * 7) + n) >> 3; // "Dampened" reading (else looks twitchy) | |
peak1 = setVolPixels(WSIDE1, WSIDE2-1, peak1, TOP1, maxLvlAvg1); | |
peak2 = setVolPixels(WSIDE2, WSIDE3-1, peak2, TOP2, maxLvlAvg2); | |
peak3 = setVolPixels(WSIDE3, WSIDE4-1, peak3, TOP3, maxLvlAvg3); | |
peak4 = setVolPixels(WSIDE4, NUMPIX-1, peak4, TOP4, maxLvlAvg4); | |
strip.show(); // Update strip | |
// Every few frames, make the peak pixel drop by 1: | |
if(++dotCount >= PEAK_FALL) { //fall rate | |
if(peak1 > 0) peak1--; | |
if(peak2 > 0) peak2--; | |
if(peak3 > 0) peak3--; | |
if(peak4 > 0) peak4--; | |
dotCount = 0; | |
} | |
vol[volCount] = n; // Save sample for dynamic leveling | |
if(++volCount >= SAMPLES) volCount = 0; // Advance/rollover sample counter | |
// Get volume range of prior frames | |
minLvl = maxLvl1 = maxLvl2 = maxLvl3 = maxLvl4 = vol[0]; | |
for(i=1; i<SAMPLES; i++) { | |
if(vol[i] < minLvl) minLvl = vol[i]; | |
else { | |
if(vol[i] > maxLvl1) maxLvl1 = vol[i]; | |
if(vol[i] > maxLvl2) maxLvl2 = vol[i]; | |
if(vol[i] > maxLvl3) maxLvl3 = vol[i]; | |
if(vol[i] > maxLvl4) maxLvl4 = vol[i]; | |
} | |
} | |
// minLvl and maxLvl indicate the volume range over prior frames, used | |
// for vertically scaling the output graph (so it looks interesting | |
// regardless of volume level). If they're too close together though | |
// (e.g. at very low volume levels) the graph becomes super coarse | |
// and 'jumpy'...so keep some minimum distance between them (this | |
// also lets the graph go to zero when no sound is playing): | |
if((maxLvl1 - minLvl) < TOP1) maxLvl1 = minLvl + TOP1; | |
if((maxLvl2 - minLvl) < TOP2) maxLvl2 = minLvl + TOP2; | |
if((maxLvl3 - minLvl) < TOP3) maxLvl3 = minLvl + TOP3; | |
if((maxLvl4 - minLvl) < TOP4) maxLvl4 = minLvl + TOP4; | |
minLvlAvg = (minLvlAvg * 63 + minLvl) >> 6; // Dampen min/max levels | |
maxLvlAvg1 = (maxLvlAvg1 * 63 + maxLvl1) >> 6; // (fake rolling average) | |
maxLvlAvg2 = (maxLvlAvg2 * 63 + maxLvl2) >> 6; // (fake rolling average) | |
maxLvlAvg3 = (maxLvlAvg3 * 63 + maxLvl3) >> 6; // (fake rolling average) | |
maxLvlAvg4 = (maxLvlAvg4 * 63 + maxLvl4) >> 6; // (fake rolling average) | |
} | |
byte setVolPixels(uint8_t first, uint8_t last, byte peak, int top, int maxLvlAvg) { | |
// Calculate bar height based on dynamic min/max levels (fixed point): | |
int height = top * (lvl - minLvlAvg) / (long)(maxLvlAvg - minLvlAvg); | |
if(height < 0L) height = 0; // Clip output | |
else if(height > top) height = top; | |
if(height > peak) peak = height; // Keep 'peak' dot at top | |
// Color pixels based on rainbow gradient | |
uint8_t numPixels = last-first+1; | |
for(uint8_t i=0; i<numPixels; i++) { | |
if(i >= height) strip.setPixelColor(i + first, 0, 0, 0); | |
else strip.setPixelColor(i + first,Wheel(map(i,0,numPixels-1,30,150))); | |
} | |
// Draw peak dot | |
if(peak > 0 && peak <= numPixels-1) strip.setPixelColor(peak+first,Wheel(map(peak,0,19-1,30,150))); | |
return peak; | |
} | |
uint32_t Wheel(byte WheelPos) { | |
if(WheelPos < 85) { | |
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); | |
} else if(WheelPos < 170) { | |
WheelPos -= 85; | |
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3); | |
} else { | |
WheelPos -= 170; | |
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); | |
} | |
} | |
void setupWagonService(void) | |
{ | |
wagonService.begin(); | |
wagonCharacteristic1.setProperties(CHR_PROPS_NOTIFY); | |
wagonCharacteristic1.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); | |
wagonCharacteristic1.setFixedLen(2); | |
wagonCharacteristic1.begin(); | |
wagonCharacteristic2.setProperties(CHR_PROPS_WRITE); | |
wagonCharacteristic2.setPermission(SECMODE_OPEN, SECMODE_OPEN); | |
wagonCharacteristic2.setFixedLen(20); | |
wagonCharacteristic2.setWriteCallback(*write_cb); | |
wagonCharacteristic2.begin(); | |
wagonCharacteristic3.setProperties(CHR_PROPS_WRITE); | |
wagonCharacteristic3.setPermission(SECMODE_OPEN, SECMODE_OPEN); | |
wagonCharacteristic3.setFixedLen(20); | |
wagonCharacteristic3.setWriteCallback(*write_cb2); | |
wagonCharacteristic3.begin(); | |
wagonCharacteristic4.setProperties(CHR_PROPS_WRITE); | |
wagonCharacteristic4.setPermission(SECMODE_OPEN, SECMODE_OPEN); | |
wagonCharacteristic4.setFixedLen(20); | |
wagonCharacteristic4.setWriteCallback(*write_cb3); | |
wagonCharacteristic4.begin(); | |
} | |
union arrayToUint32 { | |
byte array[4]; | |
uint32_t integer; | |
}; | |
void write_cb(BLECharacteristic& chr, uint8_t* data, uint16_t len, uint16_t offset) | |
{ | |
Serial.println("BLE: Change mode"); | |
arrayToUint32 converter = {data[0],data[1],data[2],data[3]}; //Create a converter | |
uint32_t i = converter.integer; | |
Serial.println(i); | |
mode = i; | |
} | |
void changePalette(int num) { | |
uint32_t red = strip.Color(255, 0, 0); | |
uint32_t green = strip.Color(0, 255, 0); | |
uint32_t blue = strip.Color(0, 0, 255); | |
uint32_t yellow = strip.Color(255, 255, 0); | |
uint32_t orange = strip.Color(255, 100, 0); | |
uint32_t pink = strip.Color(255, 105, 180); | |
uint32_t purple = strip.Color(128, 0, 128); | |
uint32_t white = strip.Color(255, 255, 255); | |
switch (num) { | |
default: //also case 0 | |
paletteLength = 7; | |
palette[0] = red; | |
palette[1] = orange; | |
palette[2] = yellow; | |
palette[3] = strip.Color(0, 128, 0); | |
palette[4] = blue; | |
palette[5] = strip.Color(75, 99, 130); | |
palette[6] = strip.Color(189, 49, 255); | |
break; | |
case 1: | |
paletteLength = 3; | |
palette[0] = red; | |
palette[1] = green; | |
palette[2] = blue; | |
break; | |
case 2: | |
paletteLength = 3; | |
palette[0] = red; | |
palette[1] = white; | |
palette[2] = blue; | |
break; | |
case 3: | |
paletteLength = 3; | |
palette[0] = blue; | |
palette[1] = yellow; | |
break; | |
} | |
} | |
void write_cb2(BLECharacteristic& chr, uint8_t* data, uint16_t len, uint16_t offset) | |
{ | |
Serial.println("BLE: Change brightness"); | |
arrayToUint32 converter = {data[0],data[1],data[2],data[3]}; //Create a converter | |
uint32_t i = converter.integer; | |
Serial.println(i); | |
brightness = i; | |
} | |
void write_cb3(BLECharacteristic& chr, uint8_t* data, uint16_t len, uint16_t offset) | |
{ | |
Serial.println("BLE: Change palette"); | |
arrayToUint32 converter = {data[0],data[1],data[2],data[3]}; //Create a converter | |
uint32_t i = converter.integer; | |
Serial.println(i); | |
changePalette(i); | |
} | |
void startAdv(void) | |
{ | |
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); | |
Bluefruit.Advertising.addTxPower(); | |
Bluefruit.Advertising.addService(wagonService); | |
Bluefruit.ScanResponse.addName(); | |
Bluefruit.Advertising.start(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment