commit 5c0f6b1061a54108cf1f861f5539830beff7e777 Author: starcalc Date: Wed Jul 5 02:05:34 2017 +0200 Initial commit diff --git a/NeoPatterns.cpp b/NeoPatterns.cpp new file mode 100644 index 0000000..b19f3ca --- /dev/null +++ b/NeoPatterns.cpp @@ -0,0 +1,714 @@ +#include "NeoPatterns.h" + +NeoPatterns::NeoPatterns(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)()) : + Adafruit_NeoPixel(pixels, pin, type) +{ + OnComplete = callback; + //Allocate a zero initialized block of memory big enough to hold "pixels" uint8_t. + pixelR = ( uint8_t* ) calloc( pixels, sizeof( uint8_t ) ); + pixelG = ( uint8_t* ) calloc( pixels, sizeof( uint8_t ) ); + pixelB = ( uint8_t* ) calloc( pixels, sizeof( uint8_t ) ); + pixelR_buffer = ( uint8_t* ) calloc( pixels, sizeof( uint8_t ) ); + pixelG_buffer = ( uint8_t* ) calloc( pixels, sizeof( uint8_t ) ); + pixelB_buffer = ( uint8_t* ) calloc( pixels, sizeof( uint8_t ) ); +} + +void NeoPatterns::Update() { + if ((millis() - lastUpdate) > Interval) // time to update + { + lastUpdate = millis(); + switch (ActivePattern) + { + case RAINBOW_CYCLE: + RainbowCycleUpdate(); + break; + case THEATER_CHASE: + TheaterChaseUpdate(); + break; + case COLOR_WIPE: + ColorWipeUpdate(); + break; + case SCANNER: + ScannerUpdate(); + break; + case FADE: + FadeUpdate(); + break; + case RANDOM_FADE: + RandomFadeUpdate(); + break; + case RANDOM_FADE_SINGLE: + RandomFadeSingleUpdate(); + break; + case SMOOTH: + SmoothUpdate(); + break; + case ICON: + IconUpdate(); + break; + case PLASMA: + PlasmaUpdate(); + break; + case FILL: + break; + case RANDOM: + break; + case NONE: + break; + default: + break; + } + } else { + delay(1); + } +} + +void NeoPatterns::Increment() +{ + if (Direction == FORWARD) + { + Index++; + if (Index >= TotalSteps) + { + Index = 0; + if (OnComplete != NULL) + { + OnComplete(); // call the completion callback + } + } + } + else // Direction == REVERSE + { + --Index; + if (Index <= 0) + { + Index = TotalSteps - 1; + if (OnComplete != NULL) + { + OnComplete(); // call the completion callback + } + } + } +} + +void NeoPatterns::Reverse() { + if (Direction == FORWARD) + { + Direction = REVERSE; + Index = TotalSteps - 1; + } + else + { + Direction = FORWARD; + Index = 0; + } +} + +void NeoPatterns::Stop(uint8_t interval) { + Interval = interval; + ActivePattern = NONE; +} + +void NeoPatterns::None(uint8_t interval) { + Interval = interval; + if (ActivePattern != NONE) { + clear(); + show(); + } + ActivePattern = NONE; +} + +/****************** Effects ******************/ + +void NeoPatterns::RainbowCycle(uint8_t interval, direction dir) { + ActivePattern = RAINBOW_CYCLE; + Interval = interval; + TotalSteps = 255; + Index = 0; + Direction = dir; +} + +void NeoPatterns::RainbowCycleUpdate() +{ + for (int i = 0; i < numPixels(); i++) + { + setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255)); + } + show(); + Increment(); +} + +void NeoPatterns::TheaterChase(uint32_t color1, uint32_t color2, uint8_t interval, direction dir) { + ActivePattern = THEATER_CHASE; + Interval = interval; + TotalSteps = numPixels(); + Color1 = color1; + Color2 = color2; + Index = 0; + Direction = dir; +} +void NeoPatterns::TheaterChaseUpdate() { + for (int i = 0; i < numPixels(); i++) + { + if ((i + Index) % 3 == 0) + { + setPixelColor(i, Color1); + } + else + { + setPixelColor(i, Color2); + } + } + show(); + Increment(); +} + +void NeoPatterns::ColorWipe(uint32_t color, uint8_t interval, direction dir) +{ + ActivePattern = COLOR_WIPE; + Interval = interval; + TotalSteps = numPixels(); + Color1 = color; + Index = 0; + Direction = dir; +} + +// Update the Color Wipe Pattern +void NeoPatterns::ColorWipeUpdate() +{ + setPixelColor(Index, Color1); + show(); + Increment(); +} + +// Initialize for a SCANNNER +void NeoPatterns::Scanner(uint32_t color1, uint8_t interval, bool colorful, bool spiral) +{ + ActivePattern = SCANNER; + Interval = interval; + TotalSteps = (numPixels() - 1) * 2; + Color1 = color1; + Index = 0; + wPos = 0; + this->colorful = colorful; + this->spiral = spiral; +} + +// Update the Scanner Pattern +void NeoPatterns::ScannerUpdate() +{ + if (colorful) { + Color1 = Wheel(wPos); + if (wPos >= 255) { + wPos = 0; + } + else { + wPos++; + } + } + for (int i = 0; i < numPixels(); i++) + { + int finalpos; + if (spiral) { + finalpos = numToSpiralPos(i); + } + else + { + finalpos = i; + } + if (i == Index) // Scan Pixel to the right + { + setPixelColor(finalpos, Color1); + } + else if (i == TotalSteps - Index) // Scan Pixel to the left + { + setPixelColor(finalpos, Color1); + } + else // Fading tail + { + setPixelColor(finalpos, DimColor(getPixelColor(finalpos))); + } + } + show(); + Increment(); + +} + +void NeoPatterns::Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir) +{ + ActivePattern = FADE; + Interval = interval; + TotalSteps = steps; + Color1 = color1; + Color2 = color2; + Index = 0; + Direction = dir; +} + +// Update the Fade Pattern +void NeoPatterns::FadeUpdate() +{ + // Calculate linear interpolation between Color1 and Color2 + // Optimise order of operations to minimize truncation error + uint8_t red = ((Red(Color1) * (TotalSteps - Index)) + (Red(Color2) * Index)) / TotalSteps; + uint8_t green = ((Green(Color1) * (TotalSteps - Index)) + (Green(Color2) * Index)) / TotalSteps; + uint8_t blue = ((Blue(Color1) * (TotalSteps - Index)) + (Blue(Color2) * Index)) / TotalSteps; + + ColorSet(Color(red, green, blue)); + show(); + Increment(); +} + +void NeoPatterns::RandomFade(uint8_t interval ) { + ActivePattern = RANDOM_FADE; + Interval = interval; + TotalSteps = 255; + Index = 0; +} + +void NeoPatterns::RandomFadeUpdate() { + ColorSet(Wheel(Index)); + Increment(); +} + +void NeoPatterns::RandomFadeSingle(uint8_t interval, uint8_t speed) { + ActivePattern = RANDOM_FADE_SINGLE; + Interval = interval; + TotalSteps = 255; + Index = 0; + WheelSpeed = speed; + RandomBuffer(); +} + +void NeoPatterns::RandomFadeSingleUpdate() { + for (int i = 0; i < numPixels(); i++) { + pixelR_buffer[i] += random(0, random(0, WheelSpeed + 1) + 1); //use buffer red channel for color wheel + setPixelColor(i, Wheel(pixelR_buffer[i])); + } + show(); + Increment(); +} + +void NeoPatterns::RandomBuffer() +{ + for (int i = 0; i < numPixels(); i++) { + uint32_t c = Wheel(random(0, 256)); + pixelR_buffer[i] = (uint8_t)(c >> 16); + pixelG_buffer[i] = (uint8_t)(c >> 8); + pixelB_buffer[i] = (uint8_t)c; + } +} + +void NeoPatterns::Random() +{ + None(); // Stop all other effects + ActivePattern = RANDOM; + for (int i = 0; i < numPixels(); i++) { + setPixelColor(i, Wheel(random(0, 256))); + } + show(); +} + +void NeoPatterns::Smooth(uint8_t wheelSpeed, uint8_t smoothing, uint8_t strength, uint8_t interval) { + ActivePattern = SMOOTH; + Interval = interval; + Index = 0; + WheelSpeed = wheelSpeed; + Smoothing = smoothing; + Strength = strength; + movingPoint_x = 3; + movingPoint_y = 3; + // Clear buffer (from previous or different effects) + for (int i = 0; i < numPixels(); i++) { + pixelR_buffer[i] = 0; + pixelG_buffer[i] = 0; + pixelB_buffer[i] = 0; + } +} + +void NeoPatterns::SmoothUpdate() { + uint32_t c = Wheel(wPos); + wPosSlow += WheelSpeed; + wPos = (wPos + (wPosSlow / 10) ) % 255; + wPosSlow = wPosSlow % 16; + + uint8_t r = (uint8_t)(c >> 16); + uint8_t g = (uint8_t)(c >> 8); + uint8_t b = (uint8_t)c; + + movingPoint_x = movingPoint_x + 8 + random(-random(0, 1 + 1), random(0, 1 + 1) + 1); + movingPoint_y = movingPoint_y + 8 + random(-random(0, 1 + 1), random(0, 1 + 1) + 1); + if (movingPoint_x < 8) { + movingPoint_x = 8 - movingPoint_x; + } else if (movingPoint_x >= 16) { + movingPoint_x = 22 - movingPoint_x; + } else { + movingPoint_x -= 8; + } + + if (movingPoint_y < 8) { + movingPoint_y = 8 - movingPoint_y; + } else if (movingPoint_y >= 16) { + movingPoint_y = 22 - movingPoint_y; + } else { + movingPoint_y -= 8; + } + uint8_t startx = movingPoint_x; + uint8_t starty = movingPoint_y; + + for (int i = 0; i < Strength; i++) { + + movingPoint_x = startx + 8 + random(-random(0, 2 + 1), random(0, 2 + 1) + 1); + movingPoint_y = starty + 8 + random(-random(0, 2 + 1), random(0, 2 + 1) + 1); + + if (movingPoint_x < 8) { + movingPoint_x = 8 - movingPoint_x; + } else if (movingPoint_x >= 16) { + movingPoint_x = 22 - movingPoint_x; + } else { + movingPoint_x -= 8; + } + + if (movingPoint_y < 8) { + movingPoint_y = 8 - movingPoint_y; + } else if (movingPoint_y >= 16) { + movingPoint_y = 22 - movingPoint_y; + } else { + movingPoint_y -= 8; + } + + if (pixelR[xyToPos(movingPoint_x, movingPoint_y)] < r) { + pixelR[xyToPos(movingPoint_x, movingPoint_y)]++; + } else if (pixelR[xyToPos(movingPoint_x, movingPoint_y)] > r) { + pixelR[xyToPos(movingPoint_x, movingPoint_y)]--; + } + if (pixelG[xyToPos(movingPoint_x, movingPoint_y)] < g) { + pixelG[xyToPos(movingPoint_x, movingPoint_y)]++; + } else if (pixelG[xyToPos(movingPoint_x, movingPoint_y)] > g) { + pixelG[xyToPos(movingPoint_x, movingPoint_y)]--; + } + if (pixelB[xyToPos(movingPoint_x, movingPoint_y)] < b) { + pixelB[xyToPos(movingPoint_x, movingPoint_y)]++; + } else if (pixelB[xyToPos(movingPoint_x, movingPoint_y)] > b) { + pixelB[xyToPos(movingPoint_x, movingPoint_y)]--; + } + } + + movingPoint_x = startx; + movingPoint_y = starty; + + for (int i = 0; i < numPixels(); i++) { + pixelR_buffer[i] = (Smoothing / 100.0) * pixelR[i] + (1.0 - (Smoothing / 100.0)) * getAverage(pixelR, i, 0, 0); + pixelG_buffer[i] = (Smoothing / 100.0) * pixelG[i] + (1.0 - (Smoothing / 100.0)) * getAverage(pixelG, i, 0, 0); + pixelB_buffer[i] = (Smoothing / 100.0) * pixelB[i] + (1.0 - (Smoothing / 100.0)) * getAverage(pixelB, i, 0, 0); + + } + + for (int i = 0; i < numPixels(); i++) { + pixelR[i] = pixelR_buffer[i]; + pixelG[i] = pixelG_buffer[i]; + pixelB[i] = pixelB_buffer[i]; + setPixelColor(i, pixelR[i], pixelG[i], pixelB[i]); + } + + show(); +} + + +/****************** Icon ******************/ + +void NeoPatterns::Icon(uint8_t fontchar, String iconcolor, uint8_t interval) +{ + // Save last effect, should be called after completion again + SavedPattern = ActivePattern; + SavedInterval = Interval; + SavedTotalSteps = TotalSteps; + SavedIndex = Index; + SavedColor1 = Color1; + SavedDirection = Direction; + SavedPlasmaPhase = PlasmaPhase; + SavedPlasmaPhaseIncrement = PlasmaPhaseIncrement; + SavedPlasmaColorStretch = PlasmaColorStretch; + ActivePattern = ICON; + Interval = interval; + TotalSteps = 80; + Index = 80; + Color1 = parseColor(iconcolor); + FontChar = fontchar; + Direction = REVERSE; +} + +void NeoPatterns::IconUpdate() +{ + for (int i = 0; i < numPixels(); i++) { + uint64_t mask = 1LL << (uint64_t)i; + + if ( (font[FontChar]&mask) == 0) { + setPixelColor(numToPos(i), Color(0, 0, 0)); //bit is 0 at pos i + } else { + float _brightness = 1.0 - ( (TotalSteps - Index) * 1.0 / TotalSteps ); + uint8_t _r = (uint8_t)(Color1 >> 16); + uint8_t _g = (uint8_t)(Color1 >> 8); + uint8_t _b = (uint8_t)Color1; + setPixelColor(numToPos(i), Color(_r * _brightness, _g * _brightness, _b * _brightness)); //bit is 1 at pos i + } + } + show(); + Increment(); +} + +void NeoPatterns::IconComplete() +{ + // Reload last effect + ActivePattern = SavedPattern; + Interval = SavedInterval; + TotalSteps = SavedTotalSteps; + Index = SavedIndex; + Color1 = SavedColor1; + Direction = SavedDirection; + PlasmaPhase = SavedPlasmaPhase; + PlasmaPhaseIncrement = SavedPlasmaPhaseIncrement; + PlasmaColorStretch = SavedPlasmaColorStretch; +} + +// Based upon https://github.com/johncarl81/neopixelplasma +void NeoPatterns::Plasma(float phase, float phaseIncrement, float colorStretch, uint8_t interval) +{ + ActivePattern = PLASMA; + Interval = interval; + PlasmaPhase = phase; + PlasmaPhaseIncrement = phaseIncrement; + PlasmaColorStretch = colorStretch; +} + +void NeoPatterns::PlasmaUpdate() +{ + PlasmaPhase += PlasmaPhaseIncrement; + int edge = (int)sqrt(numPixels()); + // The two points move along Lissajious curves, see: http://en.wikipedia.org/wiki/Lissajous_curve + // The sin() function returns values in the range of -1.0..1.0, so scale these to our desired ranges. + // The phase value is multiplied by various constants; I chose these semi-randomly, to produce a nice motion. + Point p1 = { (sin(PlasmaPhase * 1.000) + 1.0) * (edge / 2), (sin(PlasmaPhase * 1.310) + 1.0) * (edge / 2) }; + Point p2 = { (sin(PlasmaPhase * 1.770) + 1.0) * (edge / 2), (sin(PlasmaPhase * 2.865) + 1.0) * (edge / 2) }; + Point p3 = { (sin(PlasmaPhase * 0.250) + 1.0) * (edge / 2), (sin(PlasmaPhase * 0.750) + 1.0) * (edge / 2)}; + + byte row, col; + + // For each row... + for ( row = 0; row < edge; row++ ) { + float row_f = float(row); // Optimization: Keep a floating point value of the row number, instead of recasting it repeatedly. + + // For each column... + for ( col = 0; col < edge; col++ ) { + float col_f = float(col); // Optimization. + + // Calculate the distance between this LED, and p1. + Point dist1 = { col_f - p1.x, row_f - p1.y }; // The vector from p1 to this LED. + float distance1 = sqrt( dist1.x * dist1.x + dist1.y * dist1.y ); + + // Calculate the distance between this LED, and p2. + Point dist2 = { col_f - p2.x, row_f - p2.y }; // The vector from p2 to this LED. + float distance2 = sqrt( dist2.x * dist2.x + dist2.y * dist2.y ); + + // Calculate the distance between this LED, and p3. + Point dist3 = { col_f - p3.x, row_f - p3.y }; // The vector from p3 to this LED. + float distance3 = sqrt( dist3.x * dist3.x + dist3.y * dist3.y ); + + // Warp the distance with a sin() function. As the distance value increases, the LEDs will get light,dark,light,dark,etc... + // You can use a cos() for slightly different shading, or experiment with other functions. Go crazy! + float color_1 = distance1; // range: 0.0...1.0 + float color_2 = distance2; + float color_3 = distance3; + float color_4 = (sin( distance1 * distance2 * PlasmaColorStretch )) + 2.0 * 0.5; + + // Square the color_f value to weight it towards 0. The image will be darker and have higher contrast. + color_1 *= color_1 * color_4; + color_2 *= color_2 * color_4; + color_3 *= color_3 * color_4; + color_4 *= color_4; + + // Scale the color up to 0..7 . Max brightness is 7. + //strip.setPixelColor(col + (edge * row), strip.Color(color_4, 0, 0) ); + + setPixelColor(xyToPos(row, col), Color(color_1, color_2, color_3)); + } + } + show(); +} + + +/****************** Helper functions ******************/ + +void NeoPatterns::SetColor1(uint32_t color) { + Color1 = color; +} +void NeoPatterns::SetColor2(uint32_t color) { + Color2 = color; +} + +// Calculate 50% dimmed version of a color (used by ScannerUpdate) +uint32_t NeoPatterns::DimColor(uint32_t color) +{ + // Shift R, G and B components one bit to the right + uint32_t dimColor = Color(Red(color) >> 1, Green(color) >> 1, Blue(color) >> 1); + return dimColor; +} + +// Set all pixels to a color (synchronously) +void NeoPatterns::ColorSet(uint32_t color) +{ + for (int i = 0; i < numPixels(); i++) + { + setPixelColor(i, color); + } + show(); +} + +void NeoPatterns::ColorSetParameters(String parameters) +{ + None(); + ActivePattern = FILL; + ColorSet(parseColor(parameters)); +} + +// Returns the Red component of a 32-bit color +uint8_t NeoPatterns::Red(uint32_t color) +{ + return (color >> 16) & 0xFF; +} + +// Returns the Green component of a 32-bit color +uint8_t NeoPatterns::Green(uint32_t color) +{ + return (color >> 8) & 0xFF; +} + +// Returns the Blue component of a 32-bit color +uint8_t NeoPatterns::Blue(uint32_t color) +{ + return color & 0xFF; +} + +// Input a value 0 to 255 to get a color value. +// The colors are a transition r - g - b - back to r. +uint32_t NeoPatterns::Wheel(byte WheelPos) +{ + WheelPos = 255 - WheelPos; + if (WheelPos < 85) + { + return Color(255 - WheelPos * 3, 0, WheelPos * 3); + } + else if (WheelPos < 170) + { + WheelPos -= 85; + return Color(0, WheelPos * 3, 255 - WheelPos * 3); + } + else + { + WheelPos -= 170; + return Color(WheelPos * 3, 255 - WheelPos * 3, 0); + } +} + +// Convert x y pixel position to matrix position +uint8_t NeoPatterns::xyToPos(int x, int y) { + if (y % 2 == 0) { + return (y * 8 + x); + } else { + return (y * 8 + (7 - x)); + } +} + +//convert pixel number to actual 8x8 matrix position +uint8_t NeoPatterns::numToPos(int num) { + int x = num % 8; + int y = num / 8; + return xyToPos(x, y); +} + +// Convert pixel number to actual 8x8 matrix position in a spiral +uint8_t NeoPatterns::numToSpiralPos(int num) { + int edge = (int)sqrt(numPixels()); + int findx = edge - 1; // 7 + int findy = 0; + int stepsize = edge - 1; // initial value (0..7) + int stepnumber = 0; // each "step" should be used twice + int count = -1; + int dir = 1; // direction: 0 = incX, 1=incY, 2=decX, 3=decY + if (num < edge) { + return num; // trivial + } + for (int i = edge; i <= num; i++) + { + count++; + if (count == stepsize) { + count = 0; + // Change direction + dir++; + stepnumber++; + if (stepnumber == 2) { + stepsize -= 1; + stepnumber = 0; + } + if (dir == 4) { + dir = 0; + } + } + switch (dir) { + case 0: + findx++; + break; + case 1: + findy++; + break; + case 2: + findx--; + break; + case 3: + findy--; + break; + } + } + return xyToPos(findx, findy); +} + +uint8_t NeoPatterns::getAverage(uint8_t array[], uint8_t i, int x, int y) +{ + // TODO: This currently works only with 8x8 (64 pixel)! + uint16_t sum = 0; + uint8_t count = 0; + if (i >= 8) { //up + sum += array[i - 8]; + count++; + } + if (i < (64 - 8)) { //down + sum += array[i + 8]; + count++; + } + if (i >= 1) { //left + sum += array[i - 1]; + count++; + } + if (i < (64 - 1)) { //right + sum += array[i + 1]; + count++; + } + return sum / count; +} + +uint32_t NeoPatterns::parseColor(String value) { + if (value.charAt(0) == '#') { //solid fill + String color = value.substring(1); + int number = (int) strtol( &color[0], NULL, 16); + // Split them up into r, g, b values + int r = number >> 16; + int g = number >> 8 & 0xFF; + int b = number & 0xFF; + return Color(r, g, b); + } + return 0; +} + + + + diff --git a/NeoPatterns.h b/NeoPatterns.h new file mode 100644 index 0000000..c818cc5 --- /dev/null +++ b/NeoPatterns.h @@ -0,0 +1,113 @@ +#include +#include "font.h" + +// Pattern types supported: +enum pattern { NONE, RAINBOW_CYCLE, THEATER_CHASE, COLOR_WIPE, SCANNER, FADE, RANDOM_FADE, SMOOTH, ICON, RANDOM_FADE_SINGLE, PLASMA, FILL, RANDOM }; +// Patern directions supported: +enum direction { FORWARD, REVERSE }; + +class NeoPatterns : public Adafruit_NeoPixel +{ + public: + NeoPatterns(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)()); + + void Update(); + void Reverse(); + void None(uint8_t interval = 40); + void Stop(uint8_t interval = 40); + void RainbowCycle(uint8_t interval, direction dir = FORWARD); + void RainbowCycleUpdate(); + void TheaterChase(uint32_t color1, uint32_t color2, uint8_t interval, direction dir = FORWARD); + void TheaterChaseUpdate(); + void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD); + void ColorWipeUpdate(); + void Scanner(uint32_t color1 = 16711680, uint8_t interval = 40, bool colorful = false, bool spiral = false); + void ScannerUpdate(); + void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD); + void FadeUpdate(); + void RandomFade(uint8_t interval = 100); + void RandomFadeUpdate(); + void RandomFadeSingle(uint8_t interval = 100, uint8_t speed = 5); + void RandomFadeSingleUpdate(); + void RandomBuffer(); + void Random(); + void Smooth(uint8_t wheelSpeed = 16, uint8_t smoothing = 80, uint8_t strength = 50, uint8_t interval = 40); + void SmoothUpdate(); + void Icon(uint8_t fontchar, String iconcolor = "#FFFFFF", uint8_t interval = 30); + void IconUpdate(); + void IconComplete(); + void Plasma(float phase = 0, float phaseIncrement = 0.08, float colorStretch = 0.11, uint8_t interval = 60); // 0.08 and 0.11 // 0.03 und 0.3 + void PlasmaUpdate(); + + void SetColor1(uint32_t color); + void SetColor2(uint32_t color); + //Utilities + void ColorSet(uint32_t color); + void ColorSetParameters(String parameters); + uint8_t Red(uint32_t color); + uint8_t Green(uint32_t color); + uint8_t Blue(uint32_t color); + uint32_t Wheel(byte WheelPos); + uint8_t numToSpiralPos(int num); + uint8_t xyToPos(int x, int y); + uint8_t numToPos(int num); + uint8_t getAverage(uint8_t array[], uint8_t i, int x, int y); + uint32_t parseColor(String value); + private: + + // Member Variables: + pattern ActivePattern; // which pattern is running + pattern SavedPattern; + direction Direction; // direction to run the pattern + direction SavedDirection; + + unsigned long Interval; // milliseconds between updates + unsigned long SavedInterval; + unsigned long lastUpdate; // last update of position + + uint32_t Color1, Color2; // What colors are in use + uint32_t SavedColor1; + uint16_t TotalSteps; // total number of steps in the pattern + uint16_t SavedTotalSteps; + uint16_t Index; // current step within the pattern + uint16_t SavedIndex; + uint8_t Every; // Turn every "Every" pixel in Color1/Color2 + + byte wPos; + bool colorful; + bool spiral; + uint8_t wPosSlow; + uint8_t WheelSpeed; + uint8_t Smoothing; + uint8_t Strength; + uint8_t movingPoint_x; + uint8_t movingPoint_y; + uint8_t *pixelR; + uint8_t *pixelG; + uint8_t *pixelB; + uint8_t *pixelR_buffer; + uint8_t *pixelG_buffer; + uint8_t *pixelB_buffer; + + uint8_t FontChar; + + float PlasmaPhase; + float SavedPlasmaPhase; + float PlasmaPhaseIncrement; + float SavedPlasmaPhaseIncrement; + float PlasmaColorStretch; + float SavedPlasmaColorStretch; + + + uint32_t DimColor(uint32_t color); + void Increment(); + void (*OnComplete)(); // Callback on completion of pattern + + // Convenient 2D point structure + struct Point { + float x; + float y; + }; + + +}; diff --git a/esp-pixelbox.ino b/esp-pixelbox.ino new file mode 100644 index 0000000..0502579 --- /dev/null +++ b/esp-pixelbox.ino @@ -0,0 +1,263 @@ +#include +#include +#include +#include "NeoPatterns.h" +#ifdef __AVR__ +#include +#endif + +#define PIN D2 //data pin for ws2812 (pixelprojektor @ ctdo: PIN 2) // Für pixelpad: Pin2 +#define NUMPIXELS 64 + +NeoPatterns strip = NeoPatterns(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800, &StripComplete); + +bool stopAfterCompletion; + +void StripComplete() { + if (stopAfterCompletion) + { + strip.IconComplete(); + } + return; +} + +HomieNode homieNode("pixel", "commands"); + + +bool onSetColor(const HomieRange& range, const String& value) { + if (!range.isRange || range.index < 0 || range.index > 1) { + return false; + } + switch (range.index) { + case 0: + strip.SetColor1(value.toInt()); + break; + case 1: + strip.SetColor2(value.toInt()); + break; + } + homieNode.setProperty("color_" + String(range.index)).send(value); +} + +bool onSetPixel(const HomieRange& range, const String& value) { + if (!range.isRange) { + strip.None(); + strip.ColorSet(value.toInt()); + homieNode.setProperty("pixel").send(value); + return true; + } + if (range.index < 0 || range.index > strip.numPixels() - 1) { + return false; + } + strip.None(); + strip.setPixelColor(range.index, value.toInt()); + strip.show(); + homieNode.setProperty("pixel_" + String(range.index)).send(value); +} + +bool onSetVU(const HomieRange& range, const String& value) { + strip.Stop(); // Do not show any "effects" anymore + strip.clear(); // All pixels to black + String remaining = value; + String current; + int i = 0; + do + { + if (remaining.length() == 1) + { + current = remaining.substring(0,1); + } else { + current = remaining.substring(0,2); + } + int currentvalue = (int) strtol(¤t[0], NULL, 10); + // White peak + strip.setPixelColor(strip.numToPos(56+i-currentvalue*8), 16777215); // White dot + // Shaded bar "below" the peak + if (currentvalue>0) + { + for (int j=currentvalue-1;j>-1;j--) + { + strip.setPixelColor(strip.numToPos(56+i-j*8), 4276545); // Greyscale + } + } + i++; + remaining = remaining.substring(2); + } while (remaining.length()>0); // TODO: Not bigger than strip + strip.show(); + homieNode.setProperty("VU_" + String(range.index)).send(value); +} + +bool onSetBrightness(const HomieRange& range, const String& value) { + long brightness = value.toInt(); + if (brightness < 0 || brightness > 255) { + return false; + } + strip.setBrightness(brightness); + strip.show(); + homieNode.setProperty("brightness").send(value); +} + +bool onSetPixels(const HomieRange& range, const String& value) { + + String remaining = value; + int i = 0; + // Kein Effekt + strip.Stop(); + do { + String current = remaining.substring(0, 7); + Homie.getLogger() << i << ":" << current << endl; + uint32_t currentcolor = strip.parseColor(current); + + strip.setPixelColor(strip.numToPos(i), currentcolor); + i++; + + remaining = remaining.substring(7); + + } while (remaining.length() > 2 && (i < strip.numPixels())); + Homie.getLogger() << " filling rest with black" << endl; + while (i < strip.numPixels()) { + strip.setPixelColor(strip.numToPos(i), strip.Color(0, 0, 0)); + i++; + } + + strip.show(); + + return true; +} + +bool onSetEffect(const HomieRange& range, const String& value) { + stopAfterCompletion = false; + String effect = value; + effect.toLowerCase(); + if (effect == "scanner") { + strip.Scanner(strip.Color(255, 0, 0)); + } + else if (effect == "randomscanner") { + strip.Scanner(strip.Color(255, 0, 0), 40, true); + } + else if (effect == "larsonspiral") { + strip.Scanner(strip.Color(255, 0, 0), 40, true, true); + } + else if (effect == "rainbowcycle") { + strip.RainbowCycle(50); + } + else if (effect == "theaterchase" || effect == "chase") { + strip.TheaterChase(strip.Color(255, 0, 0), strip.Color(0, 0, 255), 100); + } + else if (effect == "fade") { + strip.Fade(strip.Color(255, 0, 0), strip.Color(0, 0, 255), 200, 100); + } + else if (effect == "randomfade") { + strip.RandomFade(); + } + else if (effect == "random") { + strip.Random(); + } + else if (effect == "smooth") { //example: smooth|[wheelspeed]|[smoothing]|[strength] wheelspeed=1-255, smoothing=0-100, strength=1-255 + strip.Smooth(16, 80, 50, 40); + } + else if (effect == "plasma") { + strip.Plasma(); + } + else { + // Test whether command with parameters was sent + int sep = value.indexOf("|"); + String command = value.substring(0, sep); + String parameters = value.substring(sep + 1); + if (command.equals("fill")) { + strip.ColorSetParameters(parameters); + } + else if (command.equals("randomfade")) { + int sepparam = parameters.indexOf("|"); + int p1 = parameters.substring(0, sepparam).toInt(); + if (p1 <= 0) { + p1 = 5; + } + strip.RandomFadeSingle(p1); + } + else { + strip.None(); + } + } + homieNode.setProperty("effect").send(value); +} + +bool onSetIcon(const HomieRange& range, const String& value) { + stopAfterCompletion = true; + String _iconname = value; + if (value[0] == '#') { //color given + strip.Icon(value.substring(7)[0], value.substring(0, 6)); + } + else { + strip.Icon(value[0]); + } + homieNode.setProperty("icon").send(value); +} + +bool onSetClear(const HomieRange& range, const String& value) { + strip.None(); + strip.clear(); + strip.show(); + homieNode.setProperty("clear").send(value); +} + +bool onSetLength(const HomieRange& range, const String& value) { + strip.None(); + strip.clear(); + strip.show(); + int newLength = value.toInt(); + if (newLength > 0) { + strip.updateLength(newLength); + } + homieNode.setProperty("length").send(value); +} + +void loopHandler() { + strip.Update(); +} + +void setup() { + Serial.begin(115200); + + Homie_setFirmware("pixelbox", "1.0.0"); + Homie.setLoopFunction(loopHandler); + + homieNode.advertiseRange("pixel", 0, NUMPIXELS - 1).settable(onSetPixel); + homieNode.advertiseRange("color", 0, 1).settable(onSetColor); + homieNode.advertise("brightness").settable(onSetBrightness); + homieNode.advertise("effect").settable(onSetEffect); + homieNode.advertise("clear").settable(onSetClear); + homieNode.advertise("length").settable(onSetLength); + homieNode.advertise("icon").settable(onSetIcon); + homieNode.advertiseRange("pixels", 0, (NUMPIXELS - 1)*7).settable(onSetPixels); + homieNode.advertiseRange("vu", 0, sqrt(NUMPIXELS)-1).settable(onSetVU); + + Homie.setup(); + + strip.begin(); + strip.clear(); + // strip.setBrightness(64); + strip.setBrightness(255); // HEEELLLLLLL :) + strip.show(); + stopAfterCompletion = false; // Default + // strip.Plasma(); // Default effect + + ArduinoOTA.setHostname("pixelbox"); + ArduinoOTA.onStart([]() { + strip.clear(); + strip.setBrightness(64); + }); + ArduinoOTA.onEnd([]() { + strip.clear(); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + strip.setPixelColor(progress / (total / NUMPIXELS), strip.Color(100, 0, 0)); + strip.show(); + }); + ArduinoOTA.begin(); +} + +void loop() { + Homie.loop(); + ArduinoOTA.handle(); +} diff --git a/font.h b/font.h new file mode 100644 index 0000000..9b2094c --- /dev/null +++ b/font.h @@ -0,0 +1,150 @@ + +/* the values in this array are a 8x8 bitmap font for ascii characters */ +static uint64_t font[128] = { + /************************************************************************ + font.c + Copyright (C) Lisa Milne 2014 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see + ************************************************************************/ + 0x7E7E7E7E7E7E0000, /* NUL */ + 0x7E7E7E7E7E7E0000, /* SOH */ + 0x7E7E7E7E7E7E0000, /* STX */ + 0x7E7E7E7E7E7E0000, /* ETX */ + 0x7E7E7E7E7E7E0000, /* EOT */ + 0x7E7E7E7E7E7E0000, /* ENQ */ + 0x7E7E7E7E7E7E0000, /* ACK */ + 0x7E7E7E7E7E7E0000, /* BEL */ + 0x7E7E7E7E7E7E0000, /* BS */ + 0x0, /* TAB */ + 0x7E7E7E7E7E7E0000, /* LF */ + 0x7E7E7E7E7E7E0000, /* VT */ + 0x7E7E7E7E7E7E0000, /* FF */ + 0x7E7E7E7E7E7E0000, /* CR */ + 0x7E7E7E7E7E7E0000, /* SO */ + 0x7E7E7E7E7E7E0000, /* SI */ + 0x7E7E7E7E7E7E0000, /* DLE */ + 0x7E7E7E7E7E7E0000, /* DC1 */ + 0x7E7E7E7E7E7E0000, /* DC2 */ + 0x7E7E7E7E7E7E0000, /* DC3 */ + 0x7E7E7E7E7E7E0000, /* DC4 */ + 0x7E7E7E7E7E7E0000, /* NAK */ + 0x7E7E7E7E7E7E0000, /* SYN */ + 0x7E7E7E7E7E7E0000, /* ETB */ + 0x7E7E7E7E7E7E0000, /* CAN */ + 0x7E7E7E7E7E7E0000, /* EM */ + 0x7E7E7E7E7E7E0000, /* SUB */ + 0x7E7E7E7E7E7E0000, /* ESC */ + 0x7E7E7E7E7E7E0000, /* FS */ + 0x7E7E7E7E7E7E0000, /* GS */ + 0x7E7E7E7E7E7E0000, /* RS */ + 0x7E7E7E7E7E7E0000, /* US */ + 0x0, /* (space) */ + 0x808080800080000, /* ! */ + 0x2828000000000000, /* " */ + 0x287C287C280000, /* # */ + 0x81E281C0A3C0800, /* $ */ + 0x6094681629060000, /* % */ + 0x1C20201926190000, /* & */ + 0x808000000000000, /* ' */ + 0x810202010080000, /* ( */ + 0x1008040408100000, /* ) */ + 0x2A1C3E1C2A000000, /* * */ + 0x8083E08080000, /* + */ + 0x81000, /* , */ + 0x3C00000000, /* - */ + 0x80000, /* . */ + 0x204081020400000, /* / */ + 0x1824424224180000, /* 0 */ + 0x8180808081C0000, /* 1 */ + 0x3C420418207E0000, /* 2 */ + 0x3C420418423C0000, /* 3 */ + 0x81828487C080000, /* 4 */ + 0x7E407C02423C0000, /* 5 */ + 0x3C407C42423C0000, /* 6 */ + 0x7E04081020400000, /* 7 */ + 0x3C423C42423C0000, /* 8 */ + 0x3C42423E023C0000, /* 9 */ + 0x80000080000, /* : */ + 0x80000081000, /* ; */ + 0x6186018060000, /* < */ + 0x7E007E000000, /* = */ + 0x60180618600000, /* > */ + 0x3844041800100000, /* ? */ + 0x3C449C945C201C, /* @ */ + 0x1818243C42420000, /* A */ + 0x7844784444780000, /* B */ + 0x3844808044380000, /* C */ + 0x7844444444780000, /* D */ + 0x7C407840407C0000, /* E */ + 0x7C40784040400000, /* F */ + 0x3844809C44380000, /* G */ + 0x42427E4242420000, /* H */ + 0x3E080808083E0000, /* I */ + 0x1C04040444380000, /* J */ + 0x4448507048440000, /* K */ + 0x40404040407E0000, /* L */ + 0x4163554941410000, /* M */ + 0x4262524A46420000, /* N */ + 0x1C222222221C0000, /* O */ + 0x7844784040400000, /* P */ + 0x1C222222221C0200, /* Q */ + 0x7844785048440000, /* R */ + 0x1C22100C221C0000, /* S */ + 0x7F08080808080000, /* T */ + 0x42424242423C0000, /* U */ + 0x8142422424180000, /* V */ + 0x4141495563410000, /* W */ + 0x4224181824420000, /* X */ + 0x4122140808080000, /* Y */ + 0x7E040810207E0000, /* Z */ + 0x3820202020380000, /* [ */ + 0x4020100804020000, /* */ + 0x3808080808380000, /* ] */ + 0x1028000000000000, /* ^ */ + 0x7E0000, /* _ */ + 0x1008000000000000, /* ` */ + 0x3C023E463A0000, /* a */ + 0x40407C42625C0000, /* b */ + 0x1C20201C0000, /* c */ + 0x2023E42463A0000, /* d */ + 0x3C427E403C0000, /* e */ + 0x18103810100000, /* f */ + 0x344C44340438, /* g */ + 0x2020382424240000, /* h */ + 0x800080808080000, /* i */ + 0x800180808080870, /* j */ + 0x20202428302C0000, /* k */ + 0x1010101010180000, /* l */ + 0x665A42420000, /* m */ + 0x2E3222220000, /* n */ + 0x3C42423C0000, /* o */ + 0x5C62427C4040, /* p */ + 0x3A46423E0202, /* q */ + 0x2C3220200000, /* r */ + 0x1C201804380000, /* s */ + 0x103C1010180000, /* t */ + 0x2222261A0000, /* u */ + 0x424224180000, /* v */ + 0x81815A660000, /* w */ + 0x422418660000, /* x */ + 0x422214081060, /* y */ + 0x3C08103C0000, /* z */ + 0x1C103030101C0000, /* { */ + 0x808080808080800, /* | */ + 0x38080C0C08380000, /* } */ + 0x324C000000, /* ~ */ + 0x7E7E7E7E7E7E0000 /* DEL */ +}; +