├── .gitignore ├── Color.cpp ├── Color.h ├── Config.h ├── Interval.cpp ├── Interval.h ├── LedPiper.cpp ├── LedPiper.h ├── LedStrip.cpp ├── LedStrip.h ├── MovingPeak.cpp ├── MovingPeak.h ├── Namespace.h ├── Pattern.cpp ├── Pattern.h ├── PatternList.cpp ├── PatternList.h ├── README.markdown ├── RandomMarquee.cpp ├── RandomMarquee.h ├── StateList.cpp ├── StateList.h ├── examples ├── frompy │ ├── Manifest.py │ ├── frompy.ino │ ├── lowlevel.py │ └── patterns.py ├── marquee │ └── marquee.ino └── neopixel │ └── neopixel.ino ├── ledcontroller.h └── python └── ledcontroller ├── Manifest.py ├── __init__.py ├── buffer.py ├── color.py ├── patterns ├── Manifest.py ├── __init__.py ├── interpolated_marquee.py ├── obscuring_color.py ├── pattern.py ├── pattern_list.py └── pulser.py ├── sending_buffer.py ├── sending_pattern_list.py ├── sequences.py ├── serialization.py └── turtle_buffer.py /.gitignore: -------------------------------------------------------------------------------- 1 | RGBSTRIP.pdf 2 | WS2801.pdf 3 | *.pyc 4 | *.swp 5 | /html 6 | -------------------------------------------------------------------------------- /Color.cpp: -------------------------------------------------------------------------------- 1 | #include "Color.h" 2 | 3 | #include "Arduino.h" 4 | 5 | #define BITS_PER_CHANNEL 8 6 | #define CHANNEL_MAX 0xFF 7 | 8 | LED_CONTROLLER_NAMESPACE_USING 9 | 10 | Color::Color() { 11 | clear(); 12 | } 13 | 14 | Color::Color(unsigned long combinedValue) { 15 | setCombinedValue(combinedValue); 16 | } 17 | 18 | Color::Color(byte r, byte g, byte b) { 19 | setChannelValues(r, g, b); 20 | } 21 | 22 | void Color::setCombinedValue(unsigned long combinedValue) { 23 | // This necessarily depends on specific implementation details. 24 | color[0] = combinedValue >> (2*BITS_PER_CHANNEL); 25 | color[1] = (combinedValue & 0x00FF00) >> (BITS_PER_CHANNEL); 26 | color[2] = combinedValue & 0x0000FF; 27 | } 28 | 29 | void Color::setChannelValues(byte r, byte g, byte b) { 30 | color[0] = r; 31 | color[1] = g; 32 | color[2] = b; 33 | } 34 | 35 | void Color::clear() { 36 | memset(color, 0, sizeof(color)); 37 | } 38 | 39 | void Color::setRandom() { 40 | for(int i = 0; i < CHANNELS_PER_COLOR; i++) { 41 | // random value in [0, CHANNEL_MAX] 42 | color[i] = random(CHANNEL_MAX); 43 | } 44 | } 45 | 46 | void Color::add(const Color& other) { 47 | for(int i = 0; i < CHANNELS_PER_COLOR; i++) { 48 | unsigned int sum = color[i] + other.color[i]; 49 | sum = min(CHANNEL_MAX, sum); 50 | color[i] = (byte)sum; 51 | } 52 | } 53 | 54 | Color Color::scaled(float f) { 55 | Color scaledColor = Color(); 56 | for(int i = 0; i < CHANNELS_PER_COLOR; i++) { 57 | float s = f * float(color[i]); 58 | s = constrain(s, 0.0, float(0xFF)); 59 | scaledColor.color[i] = byte(s); 60 | } 61 | return scaledColor; 62 | } 63 | 64 | void Color::send(int dataPin, int clockPin) { 65 | for(int i = 0; i < CHANNELS_PER_COLOR; i++) { 66 | sendColorByte(dataPin, clockPin, color[i]); 67 | } 68 | } 69 | 70 | void Color::sendColorByte(int dataPin, int clockPin, byte c) { 71 | for(int bitNum = BITS_PER_CHANNEL-1; bitNum >= 0; bitNum--) { 72 | digitalWrite(clockPin, LOW); 73 | 74 | byte mask = 1 << bitNum; 75 | digitalWrite(dataPin, c & mask ? HIGH : LOW); 76 | 77 | // Maximum input clock frequency for the WS2801 is 25MHz, 78 | // so no delay is required with a 16MHz Arduino Uno. 79 | digitalWrite(clockPin, HIGH); 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /Color.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Namespace.h" 4 | 5 | // This is assumed to be 3 by various Color implementation details. 6 | #define CHANNELS_PER_COLOR 3 7 | 8 | #ifndef byte 9 | typedef unsigned char byte; 10 | #endif 11 | 12 | LED_CONTROLLER_NAMESPACE_ENTER 13 | 14 | /** 15 | * Provide convenience methods around colors for an LED strip. 16 | */ 17 | class Color { 18 | private: 19 | /** 20 | * This is an RGB triple, each channel most significant bit 21 | * first (which is the default for bytes, but also necessary 22 | * for the {@link send()} implementation. 23 | */ 24 | byte color[CHANNELS_PER_COLOR]; 25 | 26 | /** @see {@link send()} */ 27 | static void sendColorByte(int dataPin, int clockPin, byte c); 28 | 29 | public: 30 | /** 31 | * Create a new Color, defaulting to black: (0, 0, 0). 32 | */ 33 | Color(); 34 | 35 | /** 36 | * Create a new Color with the given combined-value color. 37 | * @param combinedValue a color specified as a 24-bit number, 38 | * for example 0xFF0066 (equivalent to rgb(255, 0, 102)) 39 | */ 40 | Color(unsigned long combinedValue); 41 | 42 | /** 43 | * Create a new Color with the given (r, g, b) values, each 44 | * channel given as a byte in [0x00, 0xFF]. 45 | */ 46 | Color(byte r, byte g, byte b); 47 | 48 | void setCombinedValue(unsigned long combinedValue); 49 | void setChannelValues(byte r, byte g, byte b); 50 | 51 | /** Reset the Color to black. */ 52 | void clear(); 53 | 54 | /** 55 | * Set the Color to a random value. This sets each channel 56 | * to a random number in its linear brightness range. 57 | */ 58 | void setRandom(); 59 | 60 | /** 61 | * Add another Color to this one. Clamp overflow per-channel. 62 | */ 63 | void add(const Color& other); 64 | 65 | /** 66 | * Get a scaled version of this color. 67 | * @param f fraction of this color's brightness (linearly, 68 | * and uniformly across channels) 69 | */ 70 | Color scaled(float f); 71 | 72 | /** 73 | * Send the Color's data on the given pins. These pins should 74 | * previously have been set as digital output pins. 75 | * 76 | * When clockPin is set high, the WS2801 IC stores the current 77 | * dataPin value as grayscale data for the current color 78 | * component. When clockPin is low, the IC moves on to the next 79 | * bit and it is safe to change the dataPin value. 80 | * 81 | * @param dataPin "serial gray scale data input" (SDI) on the 82 | * WS2801 IC: to receive color value data 83 | * @param clockPin "data clock input" (CKI) on the WS2801 IC: 84 | * the clock pin associated with data input 85 | */ 86 | void send(int dataPin, int clockPin); 87 | }; 88 | 89 | LED_CONTROLLER_NAMESPACE_EXIT 90 | -------------------------------------------------------------------------------- /Config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // These values are intended to be used globally, and are shared with Python. 4 | 5 | // Specify the number of LEDs on the strip. 6 | #define STRIP_LENGTH 60 7 | 8 | // If defined, use half-precision colors (4 bits per channel). 9 | //#define HALF_PRECISION 10 | 11 | // the string used (by default) as the DataReciver key for color data 12 | #define DATA_RECEIVER_COLOR_KEY "COLORS" 13 | -------------------------------------------------------------------------------- /Interval.cpp: -------------------------------------------------------------------------------- 1 | #include "Interval.h" 2 | 3 | #include "Arduino.h" 4 | 5 | #define MILLIS_MAX ~((unsigned long)0) 6 | 7 | LED_CONTROLLER_NAMESPACE_USING 8 | 9 | Interval::Interval(unsigned long intervalMillis) { 10 | this->intervalMillis = intervalMillis; 11 | lastExpiredMillis = millis(); 12 | expired = false; 13 | } 14 | 15 | bool Interval::update() { 16 | unsigned long currentMillis = millis(); 17 | unsigned long elapsedMillis; 18 | if (currentMillis >= lastExpiredMillis) { 19 | elapsedMillis = currentMillis - lastExpiredMillis; 20 | } else { 21 | elapsedMillis = (MILLIS_MAX - lastExpiredMillis) 22 | + currentMillis; 23 | } 24 | if (elapsedMillis >= intervalMillis) { 25 | lastExpiredMillis = currentMillis 26 | - (elapsedMillis % intervalMillis); 27 | expired = true; 28 | } 29 | return expired; 30 | } 31 | 32 | bool Interval::isExpired() { 33 | return expired; 34 | } 35 | 36 | void Interval::clearExpired() { 37 | expired = false; 38 | } 39 | 40 | void Interval::setInterval(int newIntervalMillis) { 41 | intervalMillis = newIntervalMillis; 42 | } 43 | 44 | int Interval::getInterval() { 45 | return intervalMillis; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /Interval.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Namespace.h" 4 | 5 | LED_CONTROLLER_NAMESPACE_ENTER 6 | 7 | /** 8 | * Track time intervals, including handling clock wrapping. 9 | * Example usage: 10 | * Interval i = Interval(500); // timer for 0.5s 11 | * ... 12 | * void loop() { 13 | * // Call update() often to check the clock. 14 | * if (i.update()) { // return value is isExpired() 15 | * // Do something twice a second. 16 | * ... 17 | * i.clearExpired(); 18 | * // Now isExpired() will not return true until another 19 | * // 500ms from when the Interval last expired. 20 | * } 21 | * } 22 | */ 23 | class Interval { 24 | private: 25 | unsigned long intervalMillis; 26 | unsigned long lastExpiredMillis; 27 | bool expired; 28 | 29 | public: 30 | /** 31 | * Create a new Interval which (when update() is called) will 32 | * set its expired status to true every intervalMillis ms. 33 | */ 34 | Interval(unsigned long intervalMillis); 35 | 36 | /** 37 | * Check the clock, and set the expired status to true if 38 | * at least the interval has elapsed since last expiration. 39 | * @return whether the interval is (still) expired 40 | */ 41 | bool update(); 42 | 43 | bool isExpired(); 44 | 45 | /** 46 | * Clear the expired status (until a call to update() sets it). 47 | */ 48 | void clearExpired(); 49 | 50 | /** 51 | * Set a new interval in milliseconds. This has no effect on 52 | * the expired status, and the new interval is measured from 53 | * the last expiration time (not the time of method call). 54 | */ 55 | void setInterval(int newIntervalMillis); 56 | 57 | int getInterval(); 58 | }; 59 | 60 | LED_CONTROLLER_NAMESPACE_EXIT 61 | -------------------------------------------------------------------------------- /LedPiper.cpp: -------------------------------------------------------------------------------- 1 | #include "LedPiper.h" 2 | 3 | LED_CONTROLLER_NAMESPACE_USING 4 | 5 | LedPiper::LedPiper(int dataPin, int clockPin) : 6 | ledStrip(dataPin, clockPin) 7 | { } 8 | 9 | void LedPiper::setup() { 10 | ledStrip.setup(); 11 | ledStrip.clear(); 12 | ledStrip.send(); 13 | } 14 | 15 | void LedPiper::setColorsAndSend(size_t size, const char* colorBytes) { 16 | unpackColorBytes(size, colorBytes, ledStrip.getColors()); 17 | ledStrip.send(); 18 | } 19 | 20 | #ifdef HALF_PRECISION 21 | void LedPiper::unpackColorBytes(size_t size, const char* colorBytes, 22 | Color* colors) 23 | { 24 | int byteIndex = 0; 25 | bool upper = false; 26 | for(int colorIndex = 0; colorIndex < STRIP_LENGTH && byteIndex < size; 27 | colorIndex++) 28 | { 29 | byte channelBuffer[3]; 30 | for(int channel = 0; channel < 3 && byteIndex < size; 31 | channel++) 32 | { 33 | if (upper) { 34 | channelBuffer[channel] = 0x10 * 35 | ((colorBytes[byteIndex] & 0xF0) >> 4); 36 | upper = false; 37 | byteIndex++; 38 | } else { 39 | channelBuffer[channel] = 0x10 * 40 | (colorBytes[byteIndex] & 0x0F); 41 | upper = true; 42 | } 43 | } 44 | colors[colorIndex].setChannelValues( 45 | channelBuffer[0], channelBuffer[1], channelBuffer[2]); 46 | } 47 | } 48 | #else 49 | void LedPiper::unpackColorBytes(size_t size, const char* colorBytes, 50 | Color* colors) 51 | { 52 | for(int c = 0, v = 0; c < STRIP_LENGTH && v + 2 < size; c++, v += 3) { 53 | colors[c].setChannelValues( 54 | colorBytes[v], colorBytes[v + 1], colorBytes[v + 2]); 55 | } 56 | } 57 | #endif 58 | 59 | const char* LedPiper::KEY = DATA_RECEIVER_COLOR_KEY; 60 | -------------------------------------------------------------------------------- /LedPiper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Namespace.h" 4 | #include "Config.h" 5 | #include "Color.h" 6 | #include "LedStrip.h" 7 | 8 | #include "Arduino.h" 9 | 10 | LED_CONTROLLER_NAMESPACE_ENTER 11 | 12 | /** 13 | * Manage transferral of color data from Serial (via DataReceiver) to LEDs 14 | * (via LedStrip). 15 | * 16 | * See the examples/ for demo usage. 17 | */ 18 | class LedPiper { 19 | private: 20 | LedStrip ledStrip; 21 | 22 | static void unpackColorBytes( 23 | size_t size, const char* colorBytes, Color* colors); 24 | public: 25 | /** 26 | * Initialize the internal LedStrip. 27 | */ 28 | LedPiper(int dataPin, int clockPin); 29 | 30 | /** 31 | * Call the internal LedStrip's setup; clear it and display 32 | * the blank (all-black) state. 33 | */ 34 | void setup(); 35 | 36 | /** 37 | * Read the byte array (from serial) and set the Color values 38 | * of the LedStrip accordingly, then update the LEDs. 39 | */ 40 | void setColorsAndSend(size_t size, const char* colorBytes); 41 | 42 | /** 43 | * The expected data key which will be used for color data 44 | * sent over Serial. (To be used with DataReceiver::addKey.) 45 | */ 46 | static const char* KEY; 47 | }; 48 | 49 | LED_CONTROLLER_NAMESPACE_EXIT 50 | 51 | -------------------------------------------------------------------------------- /LedStrip.cpp: -------------------------------------------------------------------------------- 1 | #include "LedStrip.h" 2 | 3 | #include "Arduino.h" 4 | 5 | LED_CONTROLLER_NAMESPACE_USING 6 | 7 | LedStrip::LedStrip(int dataPin, int clockPin, bool reverse) 8 | : dataPin(dataPin), clockPin(clockPin), reverse(reverse) { } 9 | 10 | void LedStrip::setup() { 11 | pinMode(dataPin, OUTPUT); 12 | pinMode(clockPin, OUTPUT); 13 | } 14 | 15 | void LedStrip::clear() { 16 | for(int i = 0; i < STRIP_LENGTH; i++) { 17 | colors[i].clear(); 18 | } 19 | } 20 | 21 | void LedStrip::send() { 22 | if (reverse) { 23 | for(int i = STRIP_LENGTH-1; i >= 0; i--) { 24 | colors[i].send(dataPin, clockPin); 25 | } 26 | } else { 27 | for(int i = 0; i < STRIP_LENGTH; i++) { 28 | colors[i].send(dataPin, clockPin); 29 | } 30 | } 31 | 32 | // Pull clock low to put strip into reset/post mode. 33 | digitalWrite(clockPin, LOW); 34 | delayMicroseconds(500); // Wait for 500us to go into reset. 35 | } 36 | 37 | Color* LedStrip::getColors() { 38 | return colors; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /LedStrip.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Namespace.h" 4 | #include "Config.h" 5 | #include "Color.h" 6 | 7 | LED_CONTROLLER_NAMESPACE_ENTER 8 | 9 | /** 10 | * Manages the colors of an addressable LED strip. 11 | * 12 | * The size of the strip is defined by STRIP_LENGTH. 13 | */ 14 | class LedStrip { 15 | private: 16 | Color colors[STRIP_LENGTH]; 17 | const int dataPin; 18 | const int clockPin; 19 | const bool reverse; 20 | 21 | public: 22 | LedStrip(int dataPin, int clockPin, bool reverse=false); 23 | 24 | /** Set the pin modes. */ 25 | void setup(); 26 | 27 | /** Clear all the Colors. */ 28 | void clear(); 29 | 30 | /** 31 | * Send all the Colors. 32 | * 33 | * This has all the Colors in the internal array send their 34 | * bits, and then pauses with clockPin low for an additional 35 | * 500 microseconds, causing the WS2081 ICs to switch from 36 | * passing values along to showing colors. 37 | */ 38 | void send(); 39 | 40 | /** 41 | * Get the internal color array, so others can adjust it. 42 | * The array is of size STRIP_LENGTH. 43 | */ 44 | Color* getColors(); 45 | }; 46 | 47 | LED_CONTROLLER_NAMESPACE_EXIT 48 | -------------------------------------------------------------------------------- /MovingPeak.cpp: -------------------------------------------------------------------------------- 1 | #include "MovingPeak.h" 2 | #include "Config.h" 3 | 4 | #include "Arduino.h" 5 | 6 | #define DEFAULT_INTERVAL 10 7 | #define FALLOFF 0.2 8 | #define BOUNCE_LIMIT_DEFAULT 4 9 | #define WINDOW 3 10 | 11 | LED_CONTROLLER_NAMESPACE_USING 12 | 13 | MovingPeak::MovingPeak(const Color& color) : 14 | moveAndDecayInterval(DEFAULT_INTERVAL), 15 | bounces(0) 16 | { 17 | baseColor = color; 18 | setIntensity(1.0); 19 | restart(); 20 | } 21 | 22 | void MovingPeak::setIntensity(float intensity) { 23 | this->intensity = constrain(intensity, 0.0, 1.0); 24 | } 25 | 26 | void MovingPeak::setPosition(int position) { 27 | this->position = constrain(position, 0, STRIP_LENGTH-1); 28 | } 29 | 30 | void MovingPeak::setIncrement(int increment) { 31 | this->increment = increment > 0 ? 1 : -1; 32 | } 33 | 34 | void MovingPeak::restart() { 35 | moveAndDecayInterval.setInterval(DEFAULT_INTERVAL); 36 | position = 0; 37 | increment = 1; 38 | } 39 | 40 | bool MovingPeak::update() { 41 | moveAndDecayInterval.update(); 42 | if (moveAndDecayInterval.isExpired()) { 43 | moveAndDecayInterval.clearExpired(); 44 | advance(); 45 | return true; 46 | } else { 47 | return false; 48 | } 49 | } 50 | 51 | void MovingPeak::advance() { 52 | position += increment; 53 | boolean bounced = false; 54 | if (position >= STRIP_LENGTH) { 55 | bounced = true; 56 | increment = -1; 57 | } else if (position <= 0) { 58 | bounced = true; 59 | increment = 1; 60 | } 61 | if (bounced) { 62 | bounces++; 63 | if (bounces >= BOUNCE_LIMIT_DEFAULT) { 64 | expire(); 65 | } 66 | moveAndDecayInterval.setInterval( 67 | moveAndDecayInterval.getInterval()*2); 68 | intensity *= FALLOFF; 69 | } 70 | } 71 | 72 | void MovingPeak::apply(Color* stripColors) { 73 | if (isExpired()) { 74 | return; 75 | } 76 | for(int i = max(0, position-WINDOW); 77 | i < min(STRIP_LENGTH, position+WINDOW+1); i++) 78 | { 79 | float localIntensity = intensity * 80 | pow(FALLOFF, abs(i - position)); 81 | stripColors[i].add(baseColor.scaled(localIntensity)); 82 | } 83 | } 84 | 85 | -------------------------------------------------------------------------------- /MovingPeak.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Namespace.h" 4 | #include "Color.h" 5 | #include "Interval.h" 6 | #include "Pattern.h" 7 | 8 | LED_CONTROLLER_NAMESPACE_ENTER 9 | 10 | /** 11 | * Represent a spot of bright color with sharp falloff on either side 12 | * which moves back and forth across the LED strip. 13 | * 14 | * The spot bounces at the strip's ends, and at each bounce reduces in 15 | * both velocity and intensity. 16 | */ 17 | class MovingPeak : public Pattern { 18 | private: 19 | Color baseColor; 20 | float intensity; 21 | Interval moveAndDecayInterval; 22 | int position; 23 | int increment; 24 | int bounces; 25 | 26 | void advance(); 27 | public: 28 | /** 29 | * Create a new peak which, at its most intense, is the given 30 | * color. It starts at the 0 end of the strip, moving quickly. 31 | */ 32 | MovingPeak(const Color& color); 33 | 34 | /** 35 | * Set the base intensity (from which the peak's sides will 36 | * further fall from). 37 | */ 38 | void setIntensity(float intensity); 39 | 40 | /** 41 | * Set the peak's current position. 42 | * Clamped to [0, STRIP_LENGTH). 43 | */ 44 | void setPosition(int position); 45 | 46 | /** 47 | * Set the peak's increment (direction of travel). 48 | * Calmped to -1 or 1. 49 | */ 50 | void setIncrement(int increment); 51 | 52 | /** 53 | * Reset the peak's intensity, velocity, and position. 54 | */ 55 | void restart(); 56 | 57 | /** 58 | * Update the peak's position/intensity/velocity, depending on 59 | * elapsed time. 60 | * @return whether it changed. 61 | */ 62 | bool update(); 63 | 64 | /** 65 | * Add this peak's colors to the strip, for output. 66 | */ 67 | void apply(Color* stripColors); 68 | }; 69 | 70 | LED_CONTROLLER_NAMESPACE_EXIT 71 | 72 | -------------------------------------------------------------------------------- /Namespace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Define the LED Controller library namespace and related macros. 5 | */ 6 | 7 | #define LED_CONTROLLER_VERSION v0_1 8 | 9 | #define LED_CONTROLLER_NAMESPACE LedController 10 | 11 | #define LED_CONTROLLER_NAMESPACE_ENTER namespace LED_CONTROLLER_NAMESPACE { \ 12 | namespace LED_CONTROLLER_VERSION { 13 | 14 | #define LED_CONTROLLER_NAMESPACE_EXIT } \ 15 | using namespace LED_CONTROLLER_VERSION; \ 16 | } 17 | 18 | #define LED_CONTROLLER_NAMESPACE_USING using namespace LED_CONTROLLER_NAMESPACE; 19 | 20 | -------------------------------------------------------------------------------- /Pattern.cpp: -------------------------------------------------------------------------------- 1 | #include "Pattern.h" 2 | 3 | LED_CONTROLLER_NAMESPACE_USING 4 | 5 | Pattern::Pattern() : expired(false) { } 6 | 7 | bool Pattern::update() { return false; } 8 | 9 | void Pattern::apply(Color* stripColors) {} 10 | 11 | bool Pattern::isExpired() { return expired; } 12 | 13 | void Pattern::expire() { expired = true; } 14 | 15 | -------------------------------------------------------------------------------- /Pattern.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Namespace.h" 4 | #include "Color.h" 5 | 6 | LED_CONTROLLER_NAMESPACE_ENTER 7 | 8 | /** 9 | * Define an interface for color pattern to be displayed on an LED strip. 10 | */ 11 | class Pattern { 12 | private: 13 | bool expired; 14 | public: 15 | Pattern(); 16 | 17 | /** 18 | * Determine the new color pattern to display, based on state, 19 | * typically to include elapsed time (for animated patterns). 20 | * (Noop default implementation. See 21 | * http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1167672075 22 | * for pure-virtual discussion.) 23 | * @return whether the pattern changed 24 | */ 25 | virtual bool update(); 26 | 27 | /** 28 | * Render this pattern. 29 | * (Noop default implementation.) 30 | * @param stripColors an array of STRIP_LENGTH Colors to be 31 | * adjusted (added to or set) to show this Pattern 32 | */ 33 | virtual void apply(Color* stripColors); 34 | 35 | /** 36 | * @return true if this pattern has stopped contributing to the 37 | * displayed color (such as for a Pattern that fades out) 38 | */ 39 | bool isExpired(); 40 | 41 | /** 42 | * Make isExpired return true. Since expiration may result in 43 | * garbage collection, it is not reversable. 44 | */ 45 | void expire(); 46 | }; 47 | 48 | LED_CONTROLLER_NAMESPACE_EXIT 49 | -------------------------------------------------------------------------------- /PatternList.cpp: -------------------------------------------------------------------------------- 1 | #include "PatternList.h" 2 | 3 | #include "Arduino.h" 4 | 5 | LED_CONTROLLER_NAMESPACE_USING 6 | 7 | PatternList::PatternList(Pattern* pattern) : next(NULL) { 8 | this->pattern = pattern; 9 | } 10 | 11 | PatternList::PatternList() : pattern(NULL), next(NULL) {} 12 | 13 | void PatternList::insert(Pattern* pattern) { 14 | PatternList* list = new PatternList(pattern); 15 | if (list == NULL) { 16 | Serial.println("!l"); 17 | return; 18 | } 19 | insert(list); 20 | } 21 | 22 | void PatternList::insert(PatternList* next) { 23 | if (next->next != NULL) { 24 | delete next->next; 25 | } 26 | PatternList* oldNext = this->next; 27 | this->next = next; 28 | this->next->next = oldNext; 29 | } 30 | 31 | void PatternList::removeNext() { 32 | if (next == NULL) { 33 | return; 34 | } 35 | 36 | PatternList* oldNext = next; 37 | next = oldNext->next; 38 | oldNext->next = NULL; 39 | delete oldNext; 40 | } 41 | 42 | bool PatternList::update() { 43 | bool updated = false; 44 | if (pattern != NULL) { 45 | updated |= pattern->update(); 46 | } 47 | if (next != NULL) { 48 | updated |= next->update(); 49 | if (next->pattern != NULL && next->pattern->isExpired()) { 50 | removeNext(); 51 | } 52 | } 53 | return updated; 54 | } 55 | 56 | void PatternList::apply(Color* stripColors) { 57 | if (pattern != NULL) { 58 | pattern->apply(stripColors); 59 | } 60 | if (next != NULL) { 61 | next->apply(stripColors); 62 | } 63 | } 64 | 65 | PatternList::~PatternList() { 66 | delete next; 67 | delete pattern; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /PatternList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Namespace.h" 4 | #include "Color.h" 5 | #include "Pattern.h" 6 | 7 | LED_CONTROLLER_NAMESPACE_ENTER 8 | 9 | /** 10 | * Provide a linked list to hold, update, and apply Patterns. 11 | * 12 | * The list also takes care of removing expired patterns. 13 | */ 14 | class PatternList { 15 | private: 16 | Pattern* pattern; 17 | PatternList* next; 18 | 19 | /** 20 | * Remove and delete the 'next'. Take over 21 | * any subsequent PatternLists. 22 | */ 23 | void removeNext(); 24 | public: 25 | /** 26 | * Create a new unconnected list node. The node holds and 27 | * takes ownership of the given pattern. 28 | */ 29 | PatternList(Pattern* pattern); 30 | 31 | /** 32 | * Create a new list node with no Pattern (to use as a root). 33 | */ 34 | PatternList(); 35 | 36 | /** 37 | * Create a new PatternList to hold the given Pattern, 38 | * and insert it (taking ownership). 39 | */ 40 | void insert(Pattern* pattern); 41 | 42 | /** 43 | * Insert the given node after this one. (This is preferable to 44 | * append because it is constant time / stack space.) 45 | * 46 | * If the given node has any subsequent nodes, they are 47 | * removed and deleted. 48 | * 49 | * Takes ownership. 50 | */ 51 | void insert(PatternList* next); 52 | 53 | /** 54 | * Update this node's Pattern, and recurr. 55 | * 56 | * After the 'next' updates (if present), if its Pattern 57 | * isExpired, remove and delete the 'next' (and consequently 58 | * its pattern). 59 | * 60 | * @return whether this or any subsequent pattern updated. 61 | */ 62 | bool update(); 63 | 64 | /** 65 | * Call apply on this node's Pattern, and recurr on 'next'. 66 | */ 67 | void apply(Color* stripColors); 68 | 69 | /** 70 | * Delete any 'next' nodes, as well as any Pattern. 71 | */ 72 | ~PatternList(); 73 | }; 74 | 75 | LED_CONTROLLER_NAMESPACE_EXIT 76 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | LED Controller 2 | ============== 3 | 4 | Arduino and Python libraries to control addressable RGB LED strips. (Arduino side, targeting the [WS2812 from SparkFun](https://www.sparkfun.com/products/12027) and the [WS2801 from SparkFun (retired)](https://www.sparkfun.com/products/retired/10312)). Its primary aim is to provide a high-level interface, so the program writer can easily express things like 'I want a blue dot bouncing back and forth' or 'I want to add an orange cast to the whole strip'. 5 | 6 | Example 7 | ------- 8 | 9 | The library has a Python component which can be used to generate color data which is then sent to the C++ side on the Arduino (using [DataReceiver](https://github.com/markfickett/DataReceiver)); it can also be used entirely in C++. The examples directory contains demos for both approaches. 10 | 11 | #include 12 | Color orange(0xFF6600); 13 | RandomMarquee marquee; // manages an array of colors 14 | LedStrip ledStrip(PIN_DATA, PIN_CLOCK); 15 | void setup() { 16 | ledStrip.setup(); // set pin modes 17 | } 18 | void loop() { 19 | // marquee has a timeout, only changes after some ms 20 | if (marquee.update()) { 21 | // clear for the next 'frame' 22 | ledStrip.clear(); 23 | // always put a dim orange dot on the 5th LED 24 | ledStrip.getColors()[4].add(orange.scaled(0.5)); 25 | // add marquee's colors to the output buffer 26 | marquee.apply(ledStrip.getColors()); 27 | // push to the LED strip 28 | ledStrip.send(); 29 | } 30 | } 31 | 32 | Limitations 33 | ----------- 34 | 35 | Sub-pixel rendering on the Arduino in C++ (simulating points of color that lie between LEDs) seem to require too much computation for reasonably fast animation. Also, no optimization has been done for color computation (addition, lerping) or transmitting colors to the strip. However, color updates can be sent from Python at around 50 per second for a 32-LED strip, or 20Hz for 120 LEDs. 36 | 37 | See Also 38 | -------- 39 | 40 | * [fastspi](http://code.google.com/p/fastspi/) explicitly targets performance, as well as supporting multiple LED controller chipsets. 41 | * [Adafruit_NeoPixel](https://github.com/adafruit/Adafruit_NeoPixel) provides a simple interface on various microcontrollers for various RGB LED controllers. 42 | * Using the library: a [continuous-integration build status light](http://www.markfickett.com/stuff/artPage.php?id=377), housing an LED strip in a plexiglass and paper enclosure. 43 | 44 | -------------------------------------------------------------------------------- /RandomMarquee.cpp: -------------------------------------------------------------------------------- 1 | #include "RandomMarquee.h" 2 | 3 | #include "Arduino.h" 4 | 5 | #define DEFAULT_MOVE_INTERVAL 250 6 | #define DEFAULT_BRIGHT_INTERVAL 5 7 | #define DEFAULT_SCALE_BRIGHT 0.02 8 | #define DEFAULT_SCALE_DIM 0.01 9 | 10 | LED_CONTROLLER_NAMESPACE_USING 11 | 12 | RandomMarquee::RandomMarquee() : addColorInterval(DEFAULT_MOVE_INTERVAL), 13 | startIndex(0), brightInterval(DEFAULT_BRIGHT_INTERVAL), 14 | scaleBright(DEFAULT_SCALE_BRIGHT), scaleDim(DEFAULT_SCALE_DIM) 15 | { 16 | setStartColors(); 17 | } 18 | 19 | RandomMarquee::RandomMarquee(int brightInterval, 20 | float scaleBright, float scaleDim) : 21 | addColorInterval(DEFAULT_MOVE_INTERVAL), 22 | startIndex(0), brightInterval(brightInterval), 23 | scaleBright(scaleBright), scaleDim(scaleDim) 24 | { 25 | setStartColors(); 26 | } 27 | 28 | void RandomMarquee::setStartColors() { 29 | colors[0] = Color(0xFF0000); // bright Red 30 | colors[1] = Color(0x00FF00); // bright Green 31 | colors[2] = Color(0x0000FF); // bright Blue 32 | colors[3] = Color(0x010000); // faint red 33 | colors[4] = Color(0x800000); // 1/2 red (0x80 = 128 out of 256) 34 | } 35 | 36 | bool RandomMarquee::update() { 37 | if (addColorInterval.update()) { 38 | addColorInterval.clearExpired(); 39 | advance(); 40 | colors[startIndex].setRandom(); 41 | // Dim the whole strip, make every fifth color brighter. 42 | float scaleAmount = startIndex % brightInterval == 0 ? 43 | scaleBright : scaleDim; 44 | colors[startIndex] = colors[startIndex].scaled(scaleAmount); 45 | return true; 46 | } else { 47 | return false; 48 | } 49 | } 50 | 51 | void RandomMarquee::advance() { 52 | startIndex = startIndex - 1; 53 | if (startIndex < 0) { 54 | startIndex = STRIP_LENGTH - 1; 55 | } 56 | } 57 | 58 | void RandomMarquee::setInterval(int interval) { 59 | addColorInterval.setInterval(interval); 60 | } 61 | 62 | void RandomMarquee::apply(Color* stripColors) { 63 | int i = 0; 64 | for(int sourceIndex = startIndex; sourceIndex < STRIP_LENGTH; 65 | i++, sourceIndex++) 66 | { 67 | stripColors[i].add(colors[sourceIndex]); 68 | } 69 | for(int sourceIndex = 0; i < STRIP_LENGTH; i++, sourceIndex++) { 70 | stripColors[i].add(colors[sourceIndex]); 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /RandomMarquee.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Namespace.h" 4 | #include "Config.h" 5 | #include "Color.h" 6 | #include "Interval.h" 7 | #include "Pattern.h" 8 | 9 | LED_CONTROLLER_NAMESPACE_ENTER 10 | 11 | /** 12 | * A sequence of random Colors which marches along an LED strip. 13 | */ 14 | class RandomMarquee : public Pattern { 15 | private: 16 | Color colors[STRIP_LENGTH]; 17 | Interval addColorInterval; 18 | int startIndex; 19 | const int brightInterval; 20 | const float scaleBright, scaleDim; 21 | 22 | void advance(); 23 | 24 | /** 25 | * Put some known colors at the beginning of the marquee. 26 | */ 27 | void setStartColors(); 28 | public: 29 | RandomMarquee(); 30 | 31 | /** 32 | * @param brightInterval Make every nth color brighter. 33 | * @param scaleBright scale factor by which to adjust every 34 | * nth (bright) color 35 | * @param scaleDim scale factor by which to adjust all but the 36 | * nth (the dim) colors 37 | */ 38 | RandomMarquee(int brightInterval, 39 | float scaleBright, float scaleDim); 40 | 41 | /** 42 | * Every interval, shift all the Colors one along the strip 43 | * and add a new random Color at the beginning. 44 | * 45 | * @return whether the marquee changed (moved) 46 | */ 47 | bool update(); 48 | void apply(Color* stripColors); 49 | 50 | /** 51 | * Set the millisecond interval between moves. 52 | */ 53 | void setInterval(int interval); 54 | }; 55 | 56 | LED_CONTROLLER_NAMESPACE_EXIT 57 | -------------------------------------------------------------------------------- /StateList.cpp: -------------------------------------------------------------------------------- 1 | #include "StateList.h" 2 | 3 | #include "Arduino.h" 4 | 5 | LED_CONTROLLER_NAMESPACE_USING 6 | 7 | #define BROKEN_BLINK_INTERVAL 1000 8 | 9 | Color StateList::colorPassed(0x00FF00); 10 | Color StateList::colorFailed(0xFF0000); 11 | 12 | StateList::StateList() : numKnownStates(0), blinkOn(false), colorsChanged(true), 13 | brokenBlinkInterval(BROKEN_BLINK_INTERVAL), 14 | colorBroken(), colorBrokenDim(), 15 | colorPassedDim(colorPassed), colorFailedDim(colorFailed), 16 | historyScale(1.0) 17 | {} 18 | 19 | void StateList::parseStates(const char* stateString) { 20 | colorsChanged = true; 21 | numKnownStates = 0; 22 | int i = 0; 23 | while(stateString[i] != '\0') { 24 | if (numKnownStates >= STRIP_LENGTH) { 25 | Serial.print("StateList ran out of space, trauncating: " 26 | "more than "); 27 | Serial.print(STRIP_LENGTH); 28 | Serial.print(" states (characters) in \""); 29 | Serial.print(stateString); 30 | Serial.println("\"."); 31 | return; 32 | } 33 | bool isFirst = i == 0; 34 | switch(stateString[i]) { 35 | case STATE_CHAR_PASSED: 36 | stateColors[numKnownStates] = isFirst ? 37 | &colorPassed : &colorPassedDim; 38 | break; 39 | case STATE_CHAR_FAILED: 40 | stateColors[numKnownStates] = isFirst ? 41 | &colorFailed : &colorFailedDim; 42 | break; 43 | case STATE_CHAR_BROKEN: 44 | stateColors[numKnownStates] = isFirst ? 45 | &colorBroken : &colorBrokenDim; 46 | break; 47 | default: 48 | Serial.print("Invalid state (character) \""); 49 | Serial.print(stateString[i]); 50 | Serial.print("\" at index "); 51 | Serial.print(i); 52 | Serial.print(" of state string \""); 53 | Serial.print(stateString); 54 | Serial.println("\", trauncating."); 55 | return; 56 | } 57 | i++; 58 | numKnownStates++; 59 | } 60 | } 61 | 62 | void StateList::setHistoryScale(float scale) { 63 | if (scale != historyScale) { 64 | colorsChanged = true; 65 | historyScale = scale; 66 | colorPassedDim = colorPassed.scaled(historyScale); 67 | colorFailedDim = colorFailed.scaled(historyScale); 68 | } 69 | } 70 | 71 | bool StateList::update() { 72 | bool updated = colorsChanged; 73 | colorsChanged = false; 74 | if (brokenBlinkInterval.update()) { 75 | brokenBlinkInterval.clearExpired(); 76 | updated = true; 77 | blinkOn = !blinkOn; 78 | colorBroken.setCombinedValue(blinkOn ? 0xFF0000 : 0x440000); 79 | colorBrokenDim = colorBroken.scaled(historyScale); 80 | } 81 | return updated; 82 | } 83 | 84 | void StateList::apply(Color* stripColors) { 85 | for(int i = 0; i < numKnownStates && i < STRIP_LENGTH; i++) { 86 | stripColors[i].add(*stateColors[i]); 87 | } 88 | } 89 | 90 | -------------------------------------------------------------------------------- /StateList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Namespace.h" 4 | #include "Config.h" 5 | #include "Color.h" 6 | #include "Interval.h" 7 | #include "Pattern.h" 8 | 9 | LED_CONTROLLER_NAMESPACE_ENTER 10 | 11 | #define STATE_CHAR_PASSED 'p' 12 | #define STATE_CHAR_FAILED 'f' 13 | #define STATE_CHAR_BROKEN 'b' 14 | 15 | /** 16 | * A Pattern which represents a list of states. Visually, it is a sequence of 17 | * colors, drawing from a small set of colors (green, black, blinking) which 18 | * represents a set of states (passing, failed, broken) for tests. 19 | */ 20 | class StateList : public Pattern { 21 | public: 22 | typedef enum { 23 | PASSED, 24 | FAILED, 25 | BROKEN 26 | } State; 27 | private: 28 | int numKnownStates; 29 | bool blinkOn; 30 | Interval brokenBlinkInterval; 31 | bool colorsChanged; 32 | Color* stateColors[STRIP_LENGTH]; 33 | float historyScale; 34 | 35 | static Color colorPassed; 36 | Color colorPassedDim; 37 | static Color colorFailed; 38 | Color colorFailedDim; 39 | Color colorBroken; 40 | Color colorBrokenDim; 41 | public: 42 | StateList(); 43 | 44 | void parseStates(const char* stateString); 45 | 46 | /** 47 | * Set the factor by which to scale all but the first (most 48 | * recent) state's colors. (Expected: dim all but the current 49 | * state.) 50 | */ 51 | void setHistoryScale(float scale); 52 | 53 | bool update(); 54 | void apply(Color* stripColors); 55 | }; 56 | 57 | LED_CONTROLLER_NAMESPACE_EXIT 58 | 59 | -------------------------------------------------------------------------------- /examples/frompy/Manifest.py: -------------------------------------------------------------------------------- 1 | # Set up paths to include the ledcontroller Python library. 2 | import os, sys 3 | led_controller_lib_path = os.path.abspath( 4 | os.path.join(os.path.dirname(__file__), '..', '..', 'python')) 5 | sys.path.append(led_controller_lib_path) 6 | 7 | import ledcontroller 8 | 9 | -------------------------------------------------------------------------------- /examples/frompy/frompy.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * Demonstrate using DataReceiver to read color data from Serial, and using 3 | * ColorPiper to transfer (pipe) that color data to the LED strip. 4 | * 5 | * See the *.py in this directory, which generate and send colors. 6 | */ 7 | 8 | 9 | #include 10 | #include 11 | 12 | #define PIN_LED_DATA 2 // red wire 13 | #define PIN_LED_CLOCK 3 // green wire 14 | 15 | DataReceiver<1> dataReceiver; 16 | LedController::LedPiper ledPiper(PIN_LED_DATA, PIN_LED_CLOCK); 17 | 18 | void setColorsAndSend(size_t size, const char* colorBytes) { 19 | ledPiper.setColorsAndSend(size, colorBytes); 20 | } 21 | 22 | void setup() { 23 | dataReceiver.setup(); 24 | ledPiper.setup(); 25 | dataReceiver.addKey(LedController::LedPiper::KEY, &setColorsAndSend); 26 | dataReceiver.sendReady(); 27 | } 28 | 29 | void loop() { 30 | dataReceiver.readAndUpdate(); 31 | } 32 | 33 | -------------------------------------------------------------------------------- /examples/frompy/lowlevel.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demonstrate generating colors in Python and using data_sender to transfer them 3 | over Serial to the Arduino. (Optionally simulate entirely in Python.) 4 | 5 | Low-level version, using fewer layers of abstraction (and convenience). 6 | """ 7 | 8 | # Set how many times to update the pattern (insert one Color) and redisplay 9 | # on the LED strip; use None to continue forever. 10 | TRIALS = 100 11 | 12 | # Optionally use a dummy serial device and draw to the screen. (Drawing to the 13 | # screen does not prevent sending to the Arduino, but does slow things down.) 14 | DUMMY_SERIAL = False 15 | DRAW = False 16 | 17 | # Change this to match your Serial device. The correct value will be in the 18 | # Arduino app's Tools > Serial Device menu. See directions for picking your 19 | # serial device under "Uploading" at http://arduino.cc/en/Guide/Environment . 20 | SERIAL_DEVICE = '/dev/tty.usbmodemfa141' 21 | 22 | from Manifest import ledcontroller 23 | 24 | from ledcontroller.Manifest import sys, time, random, math, data_sender 25 | from ledcontroller.Manifest import SendingBuffer, sequences 26 | from ledcontroller.Manifest import TurtleBuffer 27 | 28 | if __name__ == '__main__': 29 | dt = 0.0 30 | 31 | # Use a predefined color sequence; either random colors, or a hue 32 | # gradient. These are Python generators, yielding Color objects. 33 | #color_sequence = sequences.GenerateRandom(limit=TRIALS, 34 | # brightInterval=5) 35 | color_sequence = sequences.GenerateHueGradient(limit=TRIALS) 36 | 37 | if DUMMY_SERIAL: 38 | sender = data_sender.DummySender(SERIAL_DEVICE, silent=True) 39 | else: 40 | sender = data_sender.Sender(SERIAL_DEVICE) 41 | # Open the serial connection. 42 | with sender: 43 | 44 | # SendingBuffer has a list of Color objects and encapsulates 45 | # requisite logic for generating bytes and sending. 46 | # For simulating, TurtleBuffer subclasses SendingBuffer and 47 | # draws to the screen using Turtle Graphics as well. 48 | if DRAW: 49 | sending_color_buffer = TurtleBuffer(sender=sender) 50 | else: 51 | sending_color_buffer = SendingBuffer(sender=sender) 52 | 53 | # Put some known colors at the beginning. 54 | for c in sequences.GetSentinels(): 55 | sending_color_buffer.InsertAndPop(c) 56 | 57 | for c in color_sequence: 58 | t = time.time() 59 | 60 | # Insert the next color into one end of the strip (and 61 | # pop the oldest color from the other end). 62 | sending_color_buffer.InsertAndPop(c) 63 | 64 | # Send the updated colors to the Arduino. 65 | sending_color_buffer.Send() 66 | sys.stdout.write('.') 67 | sys.stdout.flush() 68 | 69 | sender.ReadAndPrint() 70 | 71 | dt += time.time() - t 72 | 73 | print 'Elapsed per %d updates: %.2fs' % (TRIALS, dt) 74 | print 'Updates per second: %.2f' % (TRIALS / dt) 75 | 76 | -------------------------------------------------------------------------------- /examples/frompy/patterns.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demonstrate generating colors in Python and using DataSender to transfer them 3 | over Serial to the Arduino. (Optionally simulate entirely in Python.) 4 | 5 | Use abstracting classes to manage lists of Patterns, and writing. 6 | """ 7 | 8 | # Configuration options. 9 | # Do not write to serial (to the Arduino). 10 | DUMMY_SERIAL = False 11 | # Draw using turtle graphics (on screen) as well as to LEDs. Note that this 12 | # slows things down significantly. 13 | DRAW = False 14 | SERIAL_DEVICE = '/dev/tty.usbmodemfa141' 15 | 16 | import traceback 17 | 18 | from Manifest import ledcontroller, sys 19 | 20 | from ledcontroller.Manifest import time, data_sender 21 | from ledcontroller.Manifest import SendingPatternList, Color, \ 22 | TurtleBuffer, sequences 23 | from ledcontroller.patterns.Manifest import InterpolatedMarquee, Pulser 24 | 25 | if __name__ == '__main__': 26 | # A SendingPatternList holds the list of Patterns (which create the 27 | # colors), and manages writing them to the Arduino. 28 | if not DRAW: 29 | # Create a SendingBuffer with defaults. 30 | color_sender = SendingPatternList() 31 | else: 32 | color_sender = SendingPatternList(sending_buffer=TurtleBuffer()) 33 | 34 | # Add some Patterns. 35 | #color_sender.Append(InterpolatedMarquee( 36 | # sequences.GenerateRandom(bright_interval=6), 37 | # speed=15)) 38 | color_sender.Append(InterpolatedMarquee( 39 | (c.Scaled(0.1) for c in 40 | sequences.GenerateHueGradient(repeat_interval=50)), 41 | speed=5.0)) 42 | color_sender.Append(Pulser( 43 | color=Color(rgb=(0, 0, 1)), 44 | reverse=True, 45 | add_delay=3.0)) 46 | 47 | # Open the serial device (connection to the Arduino). 48 | if DUMMY_SERIAL: 49 | serial_sender = data_sender.DummySender(SERIAL_DEVICE, silent=True) 50 | else: 51 | serial_sender = data_sender.Sender(SERIAL_DEVICE) 52 | 53 | with serial_sender: 54 | color_sender.SetSender(serial_sender) 55 | 56 | t = time.time() 57 | actual_trials = 0 58 | exc_count = 0 59 | 60 | # Continue updating until all the Patterns expire. 61 | # If this does not happen, wait for control-C. 62 | print 'Type ^C (hold control, press c) to stop.' 63 | try: 64 | while True: 65 | actual_trials += 1 66 | try: 67 | color_sender.UpdateAndSend() # Uses serial_sender to send the colors. 68 | serial_sender.ReadAndPrint() # Reads any responses from the Arduino. 69 | except data_sender.TimeoutError as e: 70 | print 'Timeout waiting for acknowledgement during read/write.' 71 | exc_count += 1 72 | if exc_count >= 50: 73 | print 'Giving up after too many timeouts.' 74 | raise 75 | if actual_trials % 100 == 0: 76 | sys.stdout.write('.') 77 | sys.stdout.flush() 78 | except KeyboardInterrupt: 79 | traceback.print_exc() 80 | print 'Got ^C, exiting.' 81 | 82 | dt = time.time() - t 83 | 84 | print 'Elapsed per %d updates: %.2fs' % (actual_trials, dt) 85 | print 'Updates per second: %.2f' % (actual_trials / dt) 86 | 87 | -------------------------------------------------------------------------------- /examples/marquee/marquee.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * Demo the addressable LED strip from SparkFun (based on the WS2801 IC). 3 | * 4 | * This is heavily based on code by Nathan Seidle of SparkFun Electronics, 5 | * released in 2011 in the public domain (that is, under the Beerware license). 6 | * The original example [in version history or at 7 | * http://www.sparkfun.com/datasheets/Components/LED/LED_Strip_Example.pde ] 8 | * contains more technical details, which here are noted at relevant points in 9 | * the code. 10 | * 11 | * This example code displays bright red, green, and blue, then trickles 12 | * random colors down the LED strip. 13 | * 14 | * The electrical connections for the strip are: 15 | * Power, 5V and Ground (a red/black pair). The listed requirement is 1.8A, 16 | * but USB power seems to be sufficient. 17 | * and data (connect to the end with the arrow pointing into the strip), one of: 18 | * 4-pin data: 19 | * Blue = 5V 20 | * Red = SDI (Serial Data Input) 21 | * Green = CKI (Clock Input) 22 | * Black = GND 23 | * 5-pin data: 24 | * 2-pin Red+Black = 5V/GND 25 | * Green = CKI 26 | * Red = SDI 27 | */ 28 | 29 | #include 30 | 31 | using LedController::Color; 32 | using LedController::LedStrip; 33 | using LedController::RandomMarquee; 34 | 35 | #define PIN_SDI 2 // Red data wire (not the red 5V wire!) 36 | #define PIN_CKI 3 // Green wire 37 | #define PIN_STATUS_LED 13 // On board LED 38 | 39 | RandomMarquee marquee = RandomMarquee(); 40 | LedStrip ledStrip = LedStrip(PIN_SDI, PIN_CKI); 41 | 42 | void setup() { 43 | pinMode(PIN_STATUS_LED, OUTPUT); 44 | ledStrip.setup(); 45 | 46 | randomSeed(analogRead(0)); 47 | 48 | ledStrip.clear(); 49 | marquee.update(); 50 | marquee.apply(ledStrip.getColors()); 51 | ledStrip.send(); 52 | 53 | delay(2000); 54 | } 55 | 56 | void loop() { 57 | if (marquee.update()) { 58 | ledStrip.clear(); 59 | marquee.apply(ledStrip.getColors()); 60 | ledStrip.send(); 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /examples/neopixel/neopixel.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * Demonstrate using DataReceiver to read color data from Serial, and using 3 | * Adafruit_NeoPixel to transfer that color data to a WS2812 LED strip. 4 | * 5 | * To be used with ../frompy/*.py which generate and send colors. 6 | */ 7 | 8 | 9 | #include 10 | #include 11 | 12 | // https://github.com/adafruit/Adafruit_NeoPixel for 13 | // learn.sparkfun.com/tutorials/ws2812-breakout-hookup-guide 14 | #include 15 | 16 | #define PIN_LED_DATA 6 // green wire 17 | 18 | DataReceiver<1> dataReceiver; 19 | Adafruit_NeoPixel strip = Adafruit_NeoPixel( 20 | STRIP_LENGTH, PIN_LED_DATA, NEO_GRB + NEO_KHZ800); 21 | 22 | 23 | /** 24 | * Copies byte values sent over serial to NeoPixel. 25 | */ 26 | void setColorsAndSend(size_t size, const char* colorBytes) { 27 | for (int p = 0; p < strip.numPixels() && (p * 3) + 2 < size; p++) { 28 | int i = p * 3; 29 | strip.setPixelColor(p, colorBytes[i], colorBytes[i + 1], colorBytes[i + 2]); 30 | } 31 | strip.show(); 32 | } 33 | 34 | 35 | void setup() { 36 | dataReceiver.setup(); 37 | strip.begin(); 38 | strip.show(); 39 | dataReceiver.addKey(DATA_RECEIVER_COLOR_KEY, &setColorsAndSend); 40 | dataReceiver.sendReady(); 41 | } 42 | 43 | 44 | void loop() { 45 | dataReceiver.readAndUpdate(); 46 | } 47 | -------------------------------------------------------------------------------- /ledcontroller.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Include all the files! This simplifies usage as an Arduino library. 5 | */ 6 | 7 | #include "Namespace.h" 8 | 9 | #include "Config.h" 10 | #include "Interval.h" 11 | #include "Color.h" 12 | #include "LedStrip.h" 13 | 14 | #include "LedPiper.h" 15 | 16 | #include "Pattern.h" 17 | #include "PatternList.h" 18 | #include "MovingPeak.h" 19 | #include "RandomMarquee.h" 20 | #include "StateList.h" 21 | 22 | -------------------------------------------------------------------------------- /python/ledcontroller/Manifest.py: -------------------------------------------------------------------------------- 1 | """Centralize imports for the LED Controller library.""" 2 | 3 | import sys, os, time 4 | import random, math 5 | 6 | SHARED_FILE_NAME = os.path.join('..', '..', 'Config.h') # relative to this file 7 | 8 | # Adjust path and import data_sender. 9 | DATA_RECEIVER_URL = 'https://github.com/markfickett/DataReceiver' 10 | data_receiver_path = os.path.abspath( 11 | os.path.join(os.path.dirname(__file__), 12 | '..', '..', '..', 'DataReceiver')) 13 | sys.path.append(data_receiver_path) 14 | try: 15 | import data_sender 16 | except ImportError, e: 17 | raise ImportError('%s: expected in %s, available from %s' 18 | % (e.message, data_receiver_path, DATA_RECEIVER_URL)) 19 | 20 | # Read file shared with Arduino-side and get STRIP_LENGTH, HALF_PRECISION. 21 | STRIP_LENGTH_NAME = 'STRIP_LENGTH' 22 | HALF_PRECISION_NAME = 'HALF_PRECISION' 23 | DATA_RECEIVER_COLOR_KEY_NAME = 'DATA_RECEIVER_COLOR_KEY' 24 | shared_file_path = os.path.join(os.path.dirname(__file__), SHARED_FILE_NAME) 25 | with open(shared_file_path) as shared_file: 26 | shared_values = data_sender.GetSharedValues(shared_file, 27 | type_conversion_map={STRIP_LENGTH_NAME: int}) 28 | STRIP_LENGTH = shared_values[STRIP_LENGTH_NAME] 29 | HALF_PRECISION = shared_values.get(HALF_PRECISION_NAME, False) 30 | DATA_RECEIVER_COLOR_KEY = shared_values[DATA_RECEIVER_COLOR_KEY_NAME].strip('"') 31 | 32 | from color import Color 33 | import serialization 34 | from buffer import Buffer 35 | from sending_buffer import SendingBuffer 36 | import sequences 37 | import patterns 38 | from sending_pattern_list import SendingPatternList 39 | 40 | try: 41 | import turtle 42 | from turtle_buffer import TurtleBuffer 43 | except ImportError, e: 44 | print 'LED Controller: Turtle Graphics unavailable for local LED simulation.' 45 | 46 | -------------------------------------------------------------------------------- /python/ledcontroller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markfickett/LED-Controller/851953395efe030058f1018069c63c9fec93c97a/python/ledcontroller/__init__.py -------------------------------------------------------------------------------- /python/ledcontroller/buffer.py: -------------------------------------------------------------------------------- 1 | from Manifest import Color, STRIP_LENGTH 2 | 3 | class Buffer: 4 | """Encapsulation of a list of Colors.""" 5 | def __init__(self, size=STRIP_LENGTH): 6 | """ 7 | Initializes a list of default (black) Colors. 8 | Args: 9 | size Optionally specifies a size for the buffer. By default, it is the 10 | same size as the LED strip (from the STRIP_LENGTH shared constant). 11 | """ 12 | self._colors = [] 13 | for i in xrange(size): 14 | self._colors.append(Color()) 15 | 16 | def GetSize(self): 17 | return len(self._colors) 18 | 19 | def GetColors(self): 20 | """Returns the internal list of Colors, for modification.""" 21 | return self._colors 22 | 23 | def Clear(self): 24 | """Resets all the colors to blank (black).""" 25 | for c in self._colors: 26 | c.Clear() 27 | 28 | def SetFromBuffer(self, buffer): 29 | """Sets the contents of this buffer to match another buffer (deep copy).""" 30 | for local_color, other_color in zip( 31 | self._colors, buffer.GetColors()): 32 | local_color.set(other_color) 33 | 34 | def AddBuffer(self, buffer): 35 | """Adds another buffer's colors to this'. 36 | 37 | Zips from index 0, and does not affect or use colors past the end of the 38 | shorter buffer. 39 | """ 40 | for local_color, other_color in zip( 41 | self._colors, buffer.GetColors()): 42 | local_color.add(other_color) 43 | 44 | def InsertAndPop(self, color): 45 | """ 46 | Inserts the given Color into the beginning (index 0) of the color 47 | list, and pop a Color from the end (maintaining size). 48 | """ 49 | self._colors.insert(0, color) 50 | return self._colors.pop() 51 | 52 | -------------------------------------------------------------------------------- /python/ledcontroller/color.py: -------------------------------------------------------------------------------- 1 | from Manifest import random 2 | 3 | class Color: 4 | BYTE_MAX = 0xFF 5 | def __init__(self, rgb=None): 6 | """Creates a Color. If no color data is given, the Color will be black. 7 | 8 | Args: 9 | rgb if provided, passed as arguments to SetRgb. 10 | """ 11 | if rgb: 12 | self.SetRgb(*rgb) 13 | else: 14 | self.Clear() 15 | 16 | def Clear(self): 17 | """Resets to the default black color.""" 18 | self.SetRgb(0, 0, 0) 19 | 20 | def SetRgb(self, r, g, b): 21 | """Sets from [0.0, 1.0] RGB values. (Not clamped.)""" 22 | self.__r = float(r) 23 | self.__g = float(g) 24 | self.__b = float(b) 25 | 26 | def Set(self, color): 27 | """Sets this Color to match another.""" 28 | self.SetRgb(*color.GetRgb()) 29 | 30 | def Clamp(self): 31 | """ 32 | Modifies this color so that its component values all are within 33 | expected ranges: RGB in [0.0, 1.0]. 34 | """ 35 | self.__r, self.__g, self.__b = [ 36 | min(1.0, max(0.0, c)) for c in self.GetRgb()] 37 | 38 | def Add(self, c, clamp=True): 39 | """Adds another color to this one (piecewise RGB). 40 | 41 | Args: 42 | clamp whether to clamp the Color after the addition 43 | """ 44 | self.__r += c.__r 45 | self.__g += c.__g 46 | self.__b += c.__b 47 | if clamp: 48 | self.Clamp() 49 | 50 | def Scaled(self, f, clamp=True): 51 | """ 52 | Args: 53 | clamp whether to camp the resultant Color before return 54 | 55 | Returns: 56 | a new Color that is this Color with RGB channels 57 | multiplied by f 58 | """ 59 | c = Color(rgb=[c*f for c in self.GetRgb()]) 60 | if clamp: 61 | c.Clamp() 62 | return c 63 | 64 | def GetRgb(self): 65 | return [self.__r, self.__g, self.__b] 66 | 67 | def GetRgbBytes(self): 68 | """Gets [0x00, 0xFF] RGB values.""" 69 | return [int(min(1, max(0, v)) * self.BYTE_MAX) 70 | for v in self.GetRgb()] 71 | 72 | @staticmethod 73 | def Lerp(x, a, b): 74 | """Returns the linear interpolation between Colors a and b. 75 | 76 | x=0 is a and x=1 is b. 77 | """ 78 | lerped = a.Scaled(1.0-x) 79 | lerped.add(b.Scaled(x)) 80 | return lerped 81 | 82 | @classmethod 83 | def CreateRandom(cls): 84 | return cls(rgb=( 85 | random.random(), 86 | random.random(), 87 | random.random())) 88 | 89 | -------------------------------------------------------------------------------- /python/ledcontroller/patterns/Manifest.py: -------------------------------------------------------------------------------- 1 | from pattern import Pattern 2 | from pattern_list import PatternList 3 | from interpolated_marquee import InterpolatedMarquee 4 | from pulser import Pulser 5 | from obscuring_color import ObscuringColor 6 | -------------------------------------------------------------------------------- /python/ledcontroller/patterns/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markfickett/LED-Controller/851953395efe030058f1018069c63c9fec93c97a/python/ledcontroller/patterns/__init__.py -------------------------------------------------------------------------------- /python/ledcontroller/patterns/interpolated_marquee.py: -------------------------------------------------------------------------------- 1 | from ledcontroller.Manifest import Buffer, time, STRIP_LENGTH, Color, patterns 2 | from Manifest import Pattern 3 | 4 | __all__ = [ 5 | 'InterpolatedMarquee', 6 | ] 7 | 8 | class InterpolatedMarquee(Pattern, Buffer): 9 | """ 10 | Manage timed scrolling of a color sequence, and simulate sub-integer 11 | locations for that movement (sub-pixel smoothing, in effect). 12 | 13 | Smoothness of animation is dependant on frequency of calls to Apply. 14 | 15 | TODO(markfickett) Apparent smoothness also depends on linearity of 16 | response of the LEDs; that is, how smoothly the perceived luminance 17 | varies with numeric color value. Nonlinear interpolation may improve 18 | perceived animation. 19 | """ 20 | def __init__(self, color_sequence, speed=1.0): 21 | """ 22 | Args: 23 | color_sequence an iterable of Color objects, to be scrolled. When 24 | the color_sequence runs out, the marquee will stop animating. 25 | speed the number of positions (LEDs) to advance in one second. 26 | For example, with a speed of 5, the first color from 27 | color_sequence will move (smoothly) from the start of the LED 28 | strip to the 5th LED. 29 | """ 30 | if speed <= 0.0: 31 | raise ValueError('Illegal speed %s must be > 0.' % speed) 32 | Pattern.__init__(self) 33 | Buffer.__init__(self, size=STRIP_LENGTH + 2) 34 | self.__seq = iter(color_sequence) 35 | self.__speed = speed 36 | self.__offset = 1.0 37 | self.__t = None 38 | 39 | def IsChanged(self): 40 | return self.__seq is not None 41 | 42 | def SetSpeed(self, speed): 43 | self.__UpdateColorsAndOffset() 44 | self.__speed = float(speed) 45 | 46 | def __UpdateColorsAndOffset(self): 47 | if self.__seq is None: 48 | return 49 | if self.__t is None: 50 | self.__t = time.time() 51 | t = time.time() 52 | self.__offset += (t - self.__t) * self.__speed 53 | self.__t = t 54 | while self.__offset >= 1.0: 55 | try: 56 | c = self.__seq.next() 57 | except StopIteration: 58 | self.__seq = None 59 | break 60 | self.InsertAndPop(c) 61 | self.__offset -= 1.0 62 | 63 | def Apply(self, color_buffer): 64 | Pattern.Apply(self, color_buffer) 65 | self.__UpdateColorsAndOffset() 66 | colors = color_buffer.GetColors() 67 | f = self.__offset 68 | for i in xrange(min(len(colors), len(self._colors)-1)): 69 | colors[i].Add(self._colors[i].Scaled(f)) 70 | colors[i].Add(self._colors[i+1].Scaled(1.0-f)) 71 | 72 | -------------------------------------------------------------------------------- /python/ledcontroller/patterns/obscuring_color.py: -------------------------------------------------------------------------------- 1 | from ledcontroller.Manifest import Color 2 | from Manifest import Pattern 3 | 4 | class ObscuringColor(Pattern): 5 | def __init__(self, color, opacity=0.0): 6 | Pattern.__init__(self) 7 | self.__color = color 8 | self.SetOpacity(opacity) 9 | 10 | def SetOpacity(self, opacity): 11 | self._SetChanged() 12 | self.__opacity = opacity 13 | 14 | def Apply(self, color_buffer): 15 | Pattern.Apply(self, color_buffer) 16 | for c in color_buffer.GetColors(): 17 | c.set(Color.Lerp(self.__opacity, c, self.__color)) 18 | 19 | -------------------------------------------------------------------------------- /python/ledcontroller/patterns/pattern.py: -------------------------------------------------------------------------------- 1 | class Pattern: 2 | """ 3 | Base class for (animating) color patterns. 4 | """ 5 | def __init__(self): 6 | self.__changed = True 7 | self.__expired = False 8 | 9 | def Apply(self, color_buffer): 10 | """Typically, adds the colors from the pattern to the given buffer. 11 | 12 | More generally, modifies the given color buffer according to this 13 | pattern's current state. 14 | """ 15 | self._ClearChanged() 16 | 17 | def IsChanged(self): 18 | """ 19 | Returns: 20 | whether this Pattern has changed since last rendered. Specifically, 21 | whether a re-render is necessary on account of this Pattern 22 | """ 23 | return self.__changed 24 | 25 | def _SetChanged(self): 26 | self.__changed = True 27 | 28 | def _ClearChanged(self): 29 | self.__changed = False 30 | 31 | def IsExpired(self): 32 | """Returns True if calls to apply will never again have an effect.""" 33 | return self.__expired 34 | 35 | def Expire(self): 36 | self.__expired = True 37 | 38 | -------------------------------------------------------------------------------- /python/ledcontroller/patterns/pattern_list.py: -------------------------------------------------------------------------------- 1 | from Manifest import Pattern 2 | 3 | class PatternList(Pattern): 4 | """ 5 | Encapsulate management for multiple Patterns: check for changes, apply, 6 | and discard expired patterns. 7 | """ 8 | def __init__(self): 9 | Pattern.__init__(self) 10 | self.__patterns = [] 11 | 12 | def Append(self, pattern): 13 | """Adds the given pattern to the list. 14 | 15 | Render order matches order of addition. Patterns are automatically removed 16 | once expired. 17 | """ 18 | self.__patterns.append(pattern) 19 | 20 | def Remove(self, pattern, strict=True): 21 | """Removes the given pattern from the list. 22 | 23 | Args: 24 | strict whether to match the default Python behavior, or (if False) to 25 | silently do nothing if the pattern is not already in the list 26 | """ 27 | if strict or (pattern in self.__patterns): 28 | self.__patterns.remove(pattern) 29 | 30 | def Apply(self, colorBuffer): 31 | for p in self.__patterns: 32 | p.Apply(colorBuffer) 33 | 34 | def IsChanged(self): 35 | """Returns whether any pattern in the list has changed. 36 | 37 | Also does garbage collection, removing expired patterns. 38 | """ 39 | expired = [] 40 | changed = False 41 | for p in self.__patterns: 42 | if p.IsExpired(): 43 | expired.append(p) 44 | changed = True 45 | else: 46 | changed |= p.IsChanged() 47 | for p in expired: 48 | self.__patterns.remove(p) 49 | return changed 50 | 51 | -------------------------------------------------------------------------------- /python/ledcontroller/patterns/pulser.py: -------------------------------------------------------------------------------- 1 | from Manifest import Pattern 2 | from ledcontroller.Manifest import Buffer, Color, time 3 | 4 | class Pulser(Pattern): 5 | """Send pulses of a color along the LED strip.""" 6 | def __init__(self, add_delay=1.0, speed=14.0, 7 | color=Color(rgb=(1,1,1)), width=6.0, reverse=False): 8 | """ 9 | Args: 10 | color the color of the center (maximally intense part) of a pulse 11 | width the maximum number of LEDs affected by a pulse (diameter) 12 | add_delay the number of seconds between new pulses 13 | speed the number of LEDs down the strip a pulse travels / second 14 | reverse If True, pulses travel in the opposite direction; by 15 | default, the start from index 0. 16 | """ 17 | Pattern.__init__(self) 18 | if add_delay <= 0: 19 | raise ValueError('add_delay value %s is not > 0.' 20 | % add_delay) 21 | if speed == 0: 22 | raise ValueError('speed value %s is 0.') 23 | if width <= 0: 24 | raise ValueError('width value %s is not > 0.' 25 | % width) 26 | self.__add_delay = float(add_delay) 27 | self.__speed = float(speed) 28 | self.__interval = self.__speed * self.__add_delay 29 | self.__radius = width/2.0 30 | self.__reverse = reverse 31 | self.__color = color 32 | 33 | self.__t0 = None 34 | 35 | def IsChanged(self): 36 | return True 37 | 38 | def __GeneratePositions(self, max_index): 39 | t = time.time() 40 | if self.__t0 is None: 41 | self.__t0 = t 42 | dt = t - self.__t0 43 | max_pos = max_index + self.__radius 44 | repeats = int(dt / self.__add_delay) 45 | dt -= repeats*self.__add_delay 46 | i = 0 47 | p = -self.__radius + self.__speed*dt 48 | while i <= repeats and p <= max_pos: 49 | yield p 50 | p += self.__interval 51 | i += 1 52 | 53 | def Apply(self, color_buffer): 54 | """Recalculates pulse positions for the current time and add pulses.""" 55 | Pattern.Apply(self, color_buffer) 56 | colors = color_buffer.GetColors() 57 | n = len(colors) 58 | for raw_center in self.__GeneratePositions(n-1): 59 | center = raw_center 60 | if self.__reverse: 61 | center = (n - 1) - center 62 | min_index = int(1 + center - self.__radius) 63 | max_index = int(center + self.__radius) 64 | for i in xrange(min_index, max_index + 1): 65 | if i < 0 or i >= n: 66 | continue 67 | dx = abs(center - i) 68 | f = min(1.0, dx / self.__radius) 69 | colors[i] = colors[i].Scaled(f) 70 | colors[i].Add(self.__color.Scaled(1.0 - f)) 71 | 72 | -------------------------------------------------------------------------------- /python/ledcontroller/sending_buffer.py: -------------------------------------------------------------------------------- 1 | from Manifest import Buffer 2 | from Manifest import data_sender, serialization, time, DATA_RECEIVER_COLOR_KEY 3 | 4 | class SendingBuffer(Buffer): 5 | """ 6 | A Color buffer which also encapsulates logic for sending the Color 7 | data as a byte array (string) to the Arduino over Serial. 8 | """ 9 | def __init__(self, sender=None, **kwargs): 10 | """ 11 | Args: 12 | sender The Serial output stream to be written to, 13 | which may be set/replaced at any point using SetSerial. 14 | """ 15 | Buffer.__init__(self, **kwargs) 16 | self.__sender = sender 17 | 18 | def SetSender(self, sender): 19 | self.__sender = sender 20 | 21 | def Send(self, reverse=False): 22 | """Sends the current contents of the color buffer. 23 | 24 | Uses the current data_sender.Sender, which manages structure and 25 | synchronization. 26 | 27 | Args: 28 | reversed if True, sends Colors in reverse order 29 | """ 30 | if not self.__sender: 31 | raise RuntimeError('Call setSender (or provide in' 32 | ' constructor) before sending.') 33 | colors = self.GetColors() 34 | if reverse: 35 | colors = reversed(colors) 36 | sender_kwargs = { 37 | DATA_RECEIVER_COLOR_KEY: serialization.ToBytes(colors), 38 | } 39 | self.__sender.Send(**sender_kwargs) 40 | 41 | -------------------------------------------------------------------------------- /python/ledcontroller/sending_pattern_list.py: -------------------------------------------------------------------------------- 1 | from Manifest import SendingBuffer 2 | from patterns.Manifest import PatternList 3 | 4 | class SendingPatternList(PatternList): 5 | """ 6 | A PatternList which also has a SendingBuffer, simplifying managing a 7 | strip which is to display only a set of Python-generated patterns. 8 | """ 9 | def __init__(self, sending_buffer=None, reverse=False): 10 | PatternList.__init__(self) 11 | if sending_buffer: 12 | self.__sending_buffer = sending_buffer 13 | else: 14 | self.__sending_buffer = SendingBuffer() 15 | self.__reverse = reverse 16 | 17 | def SetSender(self, sender): 18 | """Sets the data_sender.Sender object used by the SendingBuffer.""" 19 | self.__sending_buffer.SetSender(sender) 20 | 21 | def UpdateAndSend(self): 22 | """Lazily clears the sending buffer, applies all patterns to it, and sends. 23 | 24 | Returns: 25 | whether an update (and send) was necessary 26 | """ 27 | if self.IsChanged(): 28 | self.__sending_buffer.Clear() 29 | self.Apply(self.__sending_buffer) 30 | self.__sending_buffer.Send( 31 | reverse=self.__reverse) 32 | return True 33 | else: 34 | return False 35 | 36 | -------------------------------------------------------------------------------- /python/ledcontroller/sequences.py: -------------------------------------------------------------------------------- 1 | """ 2 | Functions to generate sequences of Colors in useful or pretty patterns. 3 | """ 4 | 5 | __all__ = [ 6 | 'GetSentienls', 7 | 8 | 'GenerateRandom', 9 | 'GenerateHueGradient', 10 | ] 11 | 12 | from Manifest import Color, STRIP_LENGTH, math 13 | 14 | DEFAULT_DIM = 0.1 15 | def GetSentinels(): 16 | """Gets a list of known colors, for a familiar test pattern. 17 | 18 | The colors are: 19 | cyan 20 | yellow 21 | magenta 22 | dim red 23 | """ 24 | return [ 25 | Color(rgb=(0.0, 1.0, 1.0)), 26 | Color(rgb=(1.0, 1.0, 0.0)), 27 | Color(rgb=(1.0, 0.0, 1.0)), 28 | Color(rgb=(DEFAULT_DIM, 0.0, 0.0)), 29 | ] 30 | 31 | def GenerateRandom(limit=None, 32 | bright_interval=None, 33 | scale_bright=1.0, 34 | scale_dim=DEFAULT_DIM): 35 | """Generates a sequence of random Colors. 36 | 37 | Args: 38 | limit the number of random Colors to generate; or None (default), 39 | to continue forever 40 | bright_interval If given, every bright_interval colors will be 41 | scaled by scale_bright, and the rest scaled by scale_dim. 42 | """ 43 | n = 0 44 | while (limit is None) or n < limit: 45 | c = Color.CreateRandom() 46 | if bright_interval is not None: 47 | if n % bright_interval == 0: 48 | c = c.Scaled(scale_bright) 49 | else: 50 | c = c.Scaled(scale_dim) 51 | n += 1 52 | yield c 53 | 54 | HUE_CHANNEL_OFFSETS = (math.pi/2.0, 0.0, -math.pi/2.0) 55 | def GenerateHueGradient(repeat_interval=STRIP_LENGTH, limit=None): 56 | """Generates a gradient through hues. 57 | 58 | Args: 59 | repeat_interval how many Colors before cycling back to the start, 60 | defaulting to the STRIP_LENGTH shared constant 61 | limit the number of Colors to generate; or None (default), to 62 | continue forever 63 | """ 64 | n = 0 65 | t = 0.0 66 | step = 2.0 * math.pi / repeat_interval 67 | while (limit is None) or n < limit: 68 | c = Color(rgb=[ 69 | (0.5*(1.0 + math.sin(t + x))) 70 | for x in HUE_CHANNEL_OFFSETS 71 | ]) 72 | t += step 73 | n += 1 74 | yield c 75 | 76 | -------------------------------------------------------------------------------- /python/ledcontroller/serialization.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pack Colors into strings (byte arrays) for transfer to the Arduino. 3 | """ 4 | 5 | __all__ = [ 6 | 'ToBytes', 7 | ] 8 | 9 | from Manifest import HALF_PRECISION 10 | from Manifest import math 11 | 12 | def ToBytes(colors): 13 | """Converts the given list of Colors to a string (byte array). 14 | 15 | The byte array is suitable for sending to the Arduino. This uses 16 | Color.GetRgbBytes, and then packs those bytes into a string either directly 17 | or (if HALF_PRECISION is True) alternately into the upper and lower 4 bits of 18 | each byte. 19 | 20 | Returns: 21 | a string with color values packed into it 22 | """ 23 | if HALF_PRECISION: 24 | return ToBytesHalf(colors) 25 | else: 26 | return ToBytesFull(colors) 27 | 28 | 29 | def ToBytesFull(colors): 30 | return ''.join( 31 | [''.join( 32 | [chr(c) for c in color.GetRgbBytes()] 33 | ) for color in colors] 34 | ) 35 | 36 | def ToBytesHalf(colors): 37 | upper = False 38 | byte_index = 0 39 | color_bytes = [0xFF,]*(3*int(math.ceil(len(colors)/2.0))) 40 | for color in colors: 41 | for channel in color.GetRgbBytes(): 42 | if upper: 43 | color_bytes[byte_index] |= channel/0x10 << 4 44 | upper = False 45 | byte_index += 1 46 | else: 47 | color_bytes[byte_index] = channel/0x10 48 | upper = True 49 | return ''.join([chr(c) for c in color_bytes]) 50 | 51 | -------------------------------------------------------------------------------- /python/ledcontroller/turtle_buffer.py: -------------------------------------------------------------------------------- 1 | from Manifest import SendingBuffer, Color, turtle 2 | 3 | class TurtleBuffer(SendingBuffer): 4 | __SCALE = 10 5 | __DOT_SIZE = 8 6 | """ 7 | Simulates writing to the Arduino and LED strip using Turtle Graphics 8 | on the local machine. 9 | """ 10 | def __init__(self, **kwargs): 11 | """Initializes the Turtle Graphics canvas.""" 12 | SendingBuffer.__init__(self, **kwargs) 13 | turtle.colormode(1) 14 | turtle.screensize(canvwidth=self.__SCALE*(self.GetSize()+2), 15 | canvheight=self.__SCALE*2) 16 | turtle.setup(height=self.__SCALE*2 + 100) 17 | turtle.setworldcoordinates(-self.__SCALE, -self.__SCALE, 18 | self.__SCALE*(self.GetSize()+1), self.__SCALE) 19 | turtle.bgcolor(0.1, 0.1, 0.1) 20 | turtle.penup() # no connecting line 21 | turtle.hideturtle() # no turtle on screen 22 | turtle.tracer(0, 0) # no animation 23 | turtle.setundobuffer(None) # no undo buffer 24 | 25 | def Send(self, reverse=False): 26 | """Draws the current colors in Turtle Graphics. Also sends to Serial.""" 27 | colors = self.GetColors() 28 | n = len(colors) 29 | if reverse: 30 | colors = reversed(colors) 31 | 32 | ave_color = Color() 33 | 34 | # Clear last time's drawings (dots). 35 | turtle.clear() 36 | # Draw the LEDs. 37 | for c in colors: 38 | turtle.dot(self.__DOT_SIZE, *c.GetRgb()) 39 | turtle.fd(self.__SCALE) 40 | ave_color.Add(c, clamp=False) 41 | # Update the background. (Causes an update.) 42 | turtle.bgcolor(*ave_color.Scaled(0.4 * 1.0/n).GetRgb()) 43 | # Retrace. 44 | turtle.right(180) 45 | turtle.fd(self.__SCALE*n) 46 | turtle.left(180) 47 | 48 | SendingBuffer.Send(self, reverse=reverse) 49 | 50 | --------------------------------------------------------------------------------