Published

Dependencies:   BLE_API TLC5955 mbed nRF51822

Fork of BLE_LoopbackUART by Bluetooth Low Energy

Revision:
14:73923b07ae4a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sequencer.cpp	Sat Jun 09 23:23:06 2018 +0000
@@ -0,0 +1,913 @@
+/*
+
+Control lighting sequences
+
+
+*/
+
+#include "TLC5955.h"
+#include <ctype.h>
+#include "sequencer.h"
+#include "color.h"
+
+DigitalOut led1(LED1);
+
+// zero-based time, starts at 7:30PM
+volatile uint32_t TheElapsedTime = (1000*60*60*6) + (1000*60*30);    
+
+
+// THE FOLLOWING CHANNELS (ZERO-BASED) ARE BROKEN, AND ARE SKIPPED:
+// 4,6,14, 35-46
+
+ 
+// Each row address indivual panels starting at the front right panel and moving clockwise as viewed from below
+// These are the two middle (internal-use) channels: 12, 34
+
+int PANELS[ROWS][COLUMNS] = {  
+   {0, 1, 2, 3, 5, 7} ,   // Lowest row of panels
+   {8, 9, 10, 11, 13, 15} ,  
+   {16, 17, 18, 19, 20, 21} ,  
+   {22, 23, 24, 25, 26, 27} ,  
+   {28, 29, 31, 32, 33, 34}   // Highest row of panels
+  };   
+  
+//int PANELS[5][6] = {  
+//   {0, 1, 2, 3, 4, 5} ,   // Lowest row of panels
+//   {6, 7, 8, 9, 10, 11} ,  
+//   {12, 13, 14, 15, 16, 17} ,  
+//   {18, 19, 20, 21, 22, 23} ,  
+//   {24, 25, 26, 27, 28, 29}   // Highest row of panels
+//};
+
+int CHANNEL_MAPPING[] = { 8, 12, 9, 13, 14, 10, 15, 11, 7, 3, 6, 2, 1, 5, 0, 4 };
+
+uint16_t debugRedOut[NUMBER_OF_PANELS];
+uint16_t debugGreenOut[NUMBER_OF_PANELS];
+uint16_t debugBlueOut[NUMBER_OF_PANELS];
+
+volatile int channelColorSettings[NUMBER_OF_PANELS];
+volatile int Settings[NUMBER_OF_PANELS];
+volatile int channelColorPointer[2][NUMBER_OF_PANELS];
+volatile int randomBaseTimeOffset[NUMBER_OF_PANELS];
+volatile int randomMovementTimeOffset[NUMBER_OF_PANELS];
+
+volatile uint16_t UserAdjustableMovementInterval = 2000;
+
+extern TLC5955* theChip;
+
+volatile int TheBaseDitherMode;
+volatile int TheBaseEffectType;
+volatile int TheBaseTimeConstant;
+volatile uint32_t TheBaseEffectStartTime;
+const int* TheBaseColorList;
+
+volatile int TheMovementPosition;
+volatile int TheMoveEndPosition;
+volatile int TheMovementNumberOfLevels;
+volatile int NumberOfMovesSoFar;
+
+void (*TheMovementAddressModePtr)(int,const int*,int);
+
+volatile int TheMovementMode;
+volatile int TheDitherMode;
+volatile int TheMovementFill;
+volatile int TheEffectType;
+const int* TheColorList;
+volatile int TheTimeConstant;
+volatile int TheMoveTimeConstant;
+volatile uint32_t TheEffectStartTime;
+volatile int TheMode=1; // lighting effect mode. zero is off.
+volatile bool inMinuteEffect = FALSE;
+volatile bool inHourEffect = FALSE;
+        
+// https://developer.mbed.org/questions/2886/Why-is-the-rand-function-not-the-least-b/
+unsigned int m_z=12434,m_w=33254;
+unsigned int rnd() {
+    m_z = 36969 * (m_z & 65535) + (m_z >>16);
+    m_w = 18000 * (m_w & 65535) + (m_w >>16);
+    return ((m_z <<16) + m_w);
+}
+
+void setMode(int mode) {
+    TheMode = mode;
+}
+
+int getMode() {
+    return TheMode;
+}
+
+
+int movementToEnum(char* input) {  
+    if (!strcmp(input,"v-")) {
+        return VERTICAL_UP;
+    } else if (!strcmp(input,"v+")) {
+        return VERTICAL_DOWN;
+    } else if (!strcmp(input,"h+")) {
+        return HORIZONTAL_FORWARD;
+    } else if (!strcmp(input,"h-")) {
+        return HORIZONTAL_BACKWARDS;
+    } else if (!strcmp(input,"r+")) {
+        return RADIAL_CLOCKWISE;
+    } else if (!strcmp(input,"r-")) {
+        return RADIAL_COUNTERCLOCKWISE;
+    } else if (!strcmp(input,"b+")) {
+        return BARREL_CLOCKWISE;
+    } else if (!strcmp(input,"b-")) {
+        return BARREL_COUNTERCLOCKWISE;
+    } else if (!strcmp(input,"al")) {
+        return ALL;
+    } else if (!strcmp(input,"of")) {
+        return OFF;
+    } else {
+        return -1;
+    }
+}
+
+int ditherToEnum(char* input) {
+  if (!strcmp(input,"f")) {
+        return FIXED;
+    } else if (!strcmp(input,"c")) {
+        return CROSSFADE;
+    } else if (!strcmp(input,"i")) {
+        return FADE_IN;
+    } else if (!strcmp(input,"o")) {
+        return FADE_OUT_AND_IN;
+    } else if (!strcmp(input,"s")) {
+        return FADE_IN_AND_OUT;
+    } else if (!strcmp(input,"p")) {
+        return PULSED_INTENSITY;
+    } else {
+        return -1;    
+    }
+}
+
+int fillToEnum(char* input) {
+  if (!strcmp(input,"l")) {
+        return LINE;
+    } else if (!strcmp(input,"f")) {
+        return FILL;
+    } else {    
+        return -1;
+    }
+}
+
+int effectToEnum(char* input) {
+  if (!strcmp(input,"c")) {
+        return CONSTANT;
+    } else if (!strcmp(input,"r")) {
+        return RANDOM;
+    } else if (!strcmp(input,"s")) {
+        return SEQUENCE;
+    } else if (!strcmp(input,"f")) {
+        return FIXED_RANDOM;
+    } else {
+        return -1;    
+    }
+}
+
+// only compare first two characters. The rest are unused.
+const int* colorListToPointer(char* input) {
+    char shortInput[3] ;
+    shortInput[0] = input[0];
+    shortInput[1] = input[1];
+    shortInput[2] = 0;
+    for (int i=0; i< USER_COLOR_CHOICES_SIZE; i++) {
+        char shortColor[3];
+        shortColor[0] = listOfColorLists[i].colorListName[0];
+        shortColor[1] = listOfColorLists[i].colorListName[1];
+        shortColor[2] = 0;
+        if (!strcmp (shortColor, shortInput)) {
+            return listOfColorLists[i].colorList;
+        }
+    }
+    return NULL;
+}
+
+// The channels on the board are numbered differently than the channels in software, so this corrects for it
+int channelMapping(int channel) {
+    int valueToMap = channel % 16;
+    int channelOffset = channel / 16;
+    return (channelOffset*16) + CHANNEL_MAPPING[valueToMap];
+}
+
+int getColorArraySize(const int* colorArray) {
+    int i=0;
+    while (colorArray[i] != -1) {
+        i++;
+    }
+    return i;
+}
+    
+int pickRandomColorFromArray(const int* colorArray) {
+    int n = getColorArraySize(colorArray);
+    int random = colorArray[rnd() % n];
+    //printf("n=%d, s=%d, r=%d\n\r",n, rnd(), random);
+    return random;
+}
+
+void setArrayColor(int panel, int color, bool isBaseEffect) {
+    channelColorPointer[isBaseEffect][panel] = color;    
+}
+
+int getArrayColor(int panel, bool isBaseEffect) {
+    return channelColorPointer[isBaseEffect][panel];
+}
+
+int pickNextColorFromArray(int panel, const int* colorArray, bool isBaseEffect) {
+    channelColorPointer[isBaseEffect][panel]++;
+    int n = getColorArraySize(colorArray);
+    channelColorPointer[isBaseEffect][panel] = channelColorPointer[isBaseEffect][panel] % n;
+    return colorArray[channelColorPointer[isBaseEffect][panel]];
+}
+
+void setChannelToColorName(int channel, int colorName) {
+    debugRedOut[channel] = color_data[colorName].rgb.r;
+    debugGreenOut[channel] = color_data[colorName].rgb.g;
+    debugBlueOut[channel] = color_data[colorName].rgb.b;
+        
+    channel = channelMapping(channel);
+    theChip->setChannel(channel, 
+    color_data[colorName].rgb.r,
+    color_data[colorName].rgb.g,
+    color_data[colorName].rgb.b);
+} 
+
+// 8 bit r,g,b for 24 bit color
+void setChannelToRGB(int channel, uint16_t r, uint16_t g, uint16_t b) {
+
+    debugRedOut[channel] = r;
+    debugGreenOut[channel] = g;
+    debugBlueOut[channel] = b;
+
+    channel = channelMapping(channel);
+    theChip->setChannel(channel,r,g,b);
+}
+
+void setStartingMovePosition() {
+    switch(TheMovementMode) {
+        case VERTICAL_UP:
+            TheMovementPosition=-1;
+            TheMoveEndPosition=4;
+        break;
+        case VERTICAL_DOWN:
+            TheMovementPosition=5;
+            TheMoveEndPosition=0;
+        break;
+        case HORIZONTAL_FORWARD:
+            TheMovementPosition=-1;
+            TheMoveEndPosition=2;
+        break;
+        case HORIZONTAL_BACKWARDS:
+            TheMovementPosition=3;
+            TheMoveEndPosition=0;
+       break;
+        case RADIAL_CLOCKWISE:
+            TheMovementPosition=-1;
+            TheMoveEndPosition=5;
+
+        break;
+        case RADIAL_COUNTERCLOCKWISE:
+            TheMovementPosition=6;
+            TheMoveEndPosition=0;
+        break;
+        case BARREL_CLOCKWISE:
+            TheMovementPosition=-1;
+            TheMoveEndPosition=9;
+        break;
+        case BARREL_COUNTERCLOCKWISE:
+            TheMovementPosition=10;
+            TheMoveEndPosition=0;
+        break;
+        case ALL:
+        case OFF:
+            TheMovementPosition=-1;
+            TheMoveEndPosition=2;
+        default:
+        break;
+    }
+}
+
+void setPanelTargetColor(int channel, int aColor) {
+    channelColorSettings[channel] = aColor;
+}
+
+int getPanelTargetColor(int channel) {
+    return channelColorSettings[channel];
+}
+
+void setPanelStartingColor(int channel, int aColor) {
+    Settings[channel] = aColor;
+}
+
+int getPanelStartingColor(int channel) {
+    return Settings[channel];
+}
+
+
+const char* getNameFromColorNumber(int color) {
+    return color_data[color].name;
+}
+
+
+void setBaseEffect( int ditherMode, int effectType, const int* colorList, int timeConstant ) {
+    TheBaseDitherMode = ditherMode;
+    TheBaseEffectType = effectType;
+    TheBaseColorList = colorList;
+    TheBaseTimeConstant = timeConstant; // controls internal fades, blinking, etc.
+    TheBaseEffectStartTime = TheElapsedTime;
+    
+    //printf ("New Base Effect. t=%d:%d dither=%d, effect=%d, firstColor=%s, timeConstant=%d\n\r", 
+    //(TheElapsedTime/(1000*60*60))+1, (TheElapsedTime%(1000*60*60))/(60*1000),
+    //ditherMode, effectType, getNameFromColorNumber(colorList[0]), timeConstant);
+    
+    if (TheBaseEffectType == CONSTANT) {
+        // set all panels to a randomly selected color
+        int theColor = pickRandomColorFromArray(colorList);
+        //printf ("Using constant base color=(%d), %s\n\r", theColor, getNameFromColorNumber(theColor));
+        for (int i=0; i< NUMBER_OF_PANELS; i++) {
+            setPanelStartingColor(i, getPanelTargetColor(i));
+            setArrayColor(i, theColor,1);
+        }  
+    } else if (TheBaseEffectType == FIXED_RANDOM) {
+        // set each panel to a randomly selected color that doesn't change for the duration of the effect
+        for (int i=0; i< NUMBER_OF_PANELS; i++) {
+            setPanelStartingColor(i, getPanelTargetColor(i));
+            setArrayColor(i, pickRandomColorFromArray(colorList),1);
+        }
+    }
+    
+    for (int i=0; i< NUMBER_OF_PANELS; i++) {
+        randomBaseTimeOffset[i] = rnd() % TheBaseTimeConstant/2;
+    }
+}
+
+
+void setOverlayEffect( int movementMode, int ditherMode, int movementFill, int effectType, const int* colorList, int timeConstant, int moveTimeConstant ) {
+    TheMovementMode = movementMode;
+    TheDitherMode = ditherMode;
+    TheMovementFill = movementFill;
+    TheEffectType = effectType;
+    TheColorList = colorList;
+    TheTimeConstant = timeConstant;  // controls internal fades, blinking, etc.
+    TheMoveTimeConstant = moveTimeConstant;
+    TheEffectStartTime = TheElapsedTime;
+    NumberOfMovesSoFar = 0;
+    setStartingMovePosition();
+    //printf ("New Movement Effect. move=%d, dither=%d, effect=%d, firstColor=%s, timeConstant=%d\n\r", 
+    //movementMode, ditherMode, effectType, getNameFromColorNumber(colorList[0]), timeConstant);
+    
+    if (TheEffectType == CONSTANT) {
+        // set all panels to a randomly selected color
+        int theColor = pickRandomColorFromArray(colorList);
+        //printf ("Using constant overlay color=(%d), %s\n\r", theColor, getNameFromColorNumber(theColor));
+        for (int i=0; i< NUMBER_OF_PANELS; i++) {
+            setPanelStartingColor(i, getPanelTargetColor(i));
+            setArrayColor(i, theColor,0);
+        }  
+    } else if (TheEffectType == FIXED_RANDOM) {
+        // set each panel to a randomly selected color that doesn't change for the duration of the effect
+        for (int i=0; i< NUMBER_OF_PANELS; i++) {
+            setPanelStartingColor(i, getPanelTargetColor(i));
+            setArrayColor(i, pickRandomColorFromArray(colorList),0);
+        }
+    }    
+    for (int i=0; i< NUMBER_OF_PANELS; i++) {
+        randomMovementTimeOffset[i] = rnd() % TheTimeConstant/2;
+    }
+}
+
+inline void crossFade(int startingR, int startingG, int startingB, int endingR, int endingG, int endingB, uint32_t timeConstant, uint32_t currentTime, uint16_t* r, uint16_t* g, uint16_t* b) {
+    currentTime = currentTime % timeConstant;
+    
+    //printf("crossFrade current=%d, total=%d\n\r", currentTime, timeConstant);
+    
+    //if (currentTime > timeConstant) currentTime = timeConstant;
+    
+    *r =  (uint16_t) ((((uint32_t)startingR *  (uint32_t)(timeConstant - currentTime)) + ( (uint32_t)endingR * (uint32_t) currentTime)) / ((uint32_t) timeConstant));
+    *g =  (uint16_t) ((((uint32_t)startingG *  (uint32_t)(timeConstant - currentTime)) + ( (uint32_t)endingG * (uint32_t) currentTime)) / ((uint32_t) timeConstant));
+    *b =  (uint16_t) ((((uint32_t)startingB *  (uint32_t)(timeConstant - currentTime)) + ( (uint32_t)endingB * (uint32_t) currentTime)) / ((uint32_t) timeConstant));
+
+    //printf ("t=%d, c=%d, start=(%x,%x,%x), ending=(%x,%x,%x), now=%x,%x,%x\n\r", currentTime, timeConstant, startingR, startingG, startingB, endingR, endingG, endingB, *r, *b, *g );
+    //printf("s={%d,%d,%d}, e={%d,%d,%d}, c={%d,%d,%d}\n\r",   startingR, startingG, startingB, endingR, endingG, endingB, *r, *g, *b);
+}
+
+inline void crossFade(int startingColor, int endingColor, uint32_t timeConstant, uint32_t currentTime, uint16_t* r, uint16_t* g, uint16_t* b) {
+    int startingR, startingG, startingB;
+    int endingR, endingG, endingB;
+        
+    startingR = color_data[startingColor].rgb.r;
+    startingG = color_data[startingColor].rgb.g;
+    startingB = color_data[startingColor].rgb.b;
+    
+    endingR = color_data[endingColor].rgb.r;
+    endingG = color_data[endingColor].rgb.g;
+    endingB = color_data[endingColor].rgb.b;
+
+    crossFade(startingR, startingG, startingB, endingR, endingG, endingB, timeConstant, currentTime, r, g, b); 
+}
+
+void manageDither(int totalTimeForEffect, int timeIntoEffect, int startingColor, int endingColor, int panel) {
+    uint16_t r,g,b;
+    
+    switch (TheBaseDitherMode) {
+        case FIXED:
+            setChannelToColorName(panel, endingColor);
+            break;
+        case CROSSFADE:
+            crossFade(startingColor, endingColor, totalTimeForEffect, timeIntoEffect, &r, &g, &b);
+            setChannelToRGB(panel,r,g,b);
+            break;
+        case FADE_IN:
+            crossFade(COLOR_BLACK, endingColor, totalTimeForEffect, timeIntoEffect, &r, &g, &b);
+            setChannelToRGB(panel,r,g,b);    
+            break;
+        case FADE_OUT_AND_IN:
+            if (timeIntoEffect < totalTimeForEffect/2) {
+                crossFade(startingColor, COLOR_BLACK, totalTimeForEffect/2, timeIntoEffect, &r, &g, &b);
+            } else {
+                crossFade(COLOR_BLACK, endingColor, totalTimeForEffect/2, timeIntoEffect - (totalTimeForEffect/2), &r, &g, &b);
+            }
+            setChannelToRGB(panel,r,g,b);    
+            break;
+        case FADE_IN_AND_OUT:
+            if (timeIntoEffect < totalTimeForEffect/2) {
+                crossFade(COLOR_BLACK, endingColor, totalTimeForEffect/2, timeIntoEffect, &r, &g, &b);
+            } else {
+                crossFade(endingColor, COLOR_BLACK, totalTimeForEffect/2, timeIntoEffect - (totalTimeForEffect/2), &r, &g, &b);
+            }
+            setChannelToRGB(panel,r,g,b);    
+            break;
+        case PULSED_INTENSITY:
+            if (timeIntoEffect < totalTimeForEffect/2) {
+                crossFade(  color_data[endingColor].rgb.r/4, color_data[endingColor].rgb.g/4, color_data[endingColor].rgb.b/4, 
+                            color_data[endingColor].rgb.r, color_data[endingColor].rgb.g, color_data[endingColor].rgb.b, 
+                            totalTimeForEffect/2, timeIntoEffect, &r, &g, &b);
+            } else {
+                crossFade(  color_data[endingColor].rgb.r, color_data[endingColor].rgb.g, color_data[endingColor].rgb.b, 
+                            color_data[endingColor].rgb.r/4, color_data[endingColor].rgb.g/4, color_data[endingColor].rgb.b/4, 
+                            totalTimeForEffect/2, timeIntoEffect, &r, &g, &b);
+            }
+            setChannelToRGB(panel,r,g,b);    
+            break;
+        default:
+            break;
+    }
+}
+
+
+int isTimeForNewColor(int timeConstant, int timeSoFar) {
+    if (timeSoFar % timeConstant < CLOCK_GRANULARITY) {
+       //printf("isTime! soFar=%d, c=%d\n\r", timeSoFar, timeConstant);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+
+void manageEffectType(int panel, const int* colorList, int isBaseEffect) {
+    uint32_t startTime, timeConstant;
+    int effectType;
+    
+    if (isBaseEffect) {
+        effectType = TheBaseEffectType;        
+        timeConstant = TheBaseTimeConstant - (effectType == RANDOM) ? randomBaseTimeOffset[panel] : 0;
+        startTime = TheBaseEffectStartTime;
+    } else {
+        effectType = TheEffectType;
+        timeConstant = TheTimeConstant - (effectType == RANDOM) ? randomMovementTimeOffset[panel] : 0;
+        startTime = TheEffectStartTime;
+    }
+
+
+    
+    switch (effectType) {
+    case CONSTANT:
+    case FIXED_RANDOM:
+        // the colors are selected when the effect is first created
+        setPanelStartingColor(panel, getArrayColor(panel, isBaseEffect));
+        setPanelTargetColor(panel, getArrayColor(panel, isBaseEffect));
+        break;
+    case RANDOM:
+        // TODO: Change to a random number that is picked so that the average length will be elapsed time. (probability at elapsedTime is 50% that it will be time)
+        if (isTimeForNewColor(timeConstant, TheElapsedTime-startTime)) {
+            setPanelStartingColor(panel, getPanelTargetColor(panel));
+            setPanelTargetColor(panel, pickRandomColorFromArray(colorList));
+            if (isBaseEffect) {
+                randomBaseTimeOffset[panel] = rnd() % TheBaseTimeConstant / 2;
+            } else {
+                randomMovementTimeOffset[panel] = rnd() % TheTimeConstant / 2;
+            }
+        }
+        break;
+    case SEQUENCE:
+        if (isTimeForNewColor(timeConstant, TheElapsedTime-startTime)) {
+            setPanelStartingColor(panel, getPanelTargetColor(panel));
+            int nextColor = pickNextColorFromArray(panel,colorList,isBaseEffect);
+            setPanelTargetColor(panel, nextColor);    
+        }
+        // Dither from old sequence color to new sequence color
+        break;
+    case RANDOM_STROBE:
+        // fluctuate intensity randomly
+        break;
+    default:
+        break;
+    }
+    manageDither(timeConstant, TheElapsedTime-startTime, getPanelStartingColor(panel), getPanelTargetColor(panel), panel);
+}
+
+// top-to-bottom  - 5 levels
+void addressVertically (int location, const int* colorList, int isBaseEffect) {
+    if (location >= 0 && location < 5) {
+        for (int i = 0; i < 6; i++) {
+            manageEffectType(PANELS[location][i], colorList, isBaseEffect);
+        }
+    }
+}
+
+// front-to-back - 3 levels
+void addressHorizontally (int location, const int* colorList, int isBaseEffect) {
+    if (location >= 0 && location < 3) {
+        for (int i = 0; i < 5; i++) {
+            manageEffectType(PANELS[i][location], colorList, isBaseEffect);
+            manageEffectType(PANELS[i][5-location], colorList, isBaseEffect);
+        }
+    }
+}
+
+void addressHorizontalRadially (int location, const int* colorList, int isBaseEffect) {
+    if (location >= 0 && location < 6) {
+        for (int i = 0; i < 5; i++) {
+            manageEffectType(PANELS[i][location], colorList, isBaseEffect);
+        }
+    }
+}
+
+void addressVerticallyRadially(int location, const int* colorList, int isBaseEffect) {
+    if (location >= 0 && location < 5) {
+        for (int i = 0; i < 3; i++) {
+            manageEffectType(PANELS[location][i], colorList, isBaseEffect);
+        }
+    }
+    if (location >= 5 && location < 10) {
+        for (int i = 3; i < 6; i++) {
+            manageEffectType(PANELS[9-location][i], colorList, isBaseEffect);
+        }
+    }
+}
+
+int getMovementLevels(int aMovementMode) {
+    switch (aMovementMode) {
+            case VERTICAL_UP:
+                return ROWS;
+            case VERTICAL_DOWN:
+                return ROWS;
+            case HORIZONTAL_FORWARD:
+                return 3;
+            case HORIZONTAL_BACKWARDS:
+                return 3;
+            case RADIAL_CLOCKWISE:
+                return 6;
+            case RADIAL_COUNTERCLOCKWISE:
+                return 6;
+            case BARREL_CLOCKWISE:
+                return 10;
+            case BARREL_COUNTERCLOCKWISE:
+                return 10;
+            case ALL:
+                return 1;
+            case OFF:
+                return 1;
+            default:
+                return 1;
+    }
+}
+
+void manageMovement() {
+    // Track which panels get painted with the Base information, and which get the effect information
+    
+    // cycle through moves at TheMoveTimeConstant rate
+    //printf (">> t=%d, c=%d, %d <? %d\n\r",  (TheElapsedTime-TheEffectStartTime), TheMoveTimeConstant, (TheElapsedTime-TheEffectStartTime) % TheMoveTimeConstant, CLOCK_GRANULARITY);
+    
+    if ((TheElapsedTime-TheEffectStartTime) % TheMoveTimeConstant < CLOCK_GRANULARITY) {
+        //printf ("Moving. Mode=%d\n\r", TheMovementMode);
+        switch(TheMovementMode) {
+            case VERTICAL_UP:
+                TheMovementPosition++;
+                TheMovementNumberOfLevels = ROWS;
+                TheMovementAddressModePtr = &addressVertically;
+            break;
+            case VERTICAL_DOWN:
+                TheMovementPosition--;
+                TheMovementNumberOfLevels = ROWS;
+                TheMovementAddressModePtr = &addressVertically;
+            break;
+            case HORIZONTAL_FORWARD:
+                TheMovementPosition++;
+                TheMovementNumberOfLevels = 3;
+                TheMovementAddressModePtr = &addressHorizontally;
+            break;
+            case HORIZONTAL_BACKWARDS:
+                TheMovementPosition--;
+                TheMovementNumberOfLevels = 3;
+                TheMovementAddressModePtr = &addressHorizontally;
+            break;
+            case RADIAL_CLOCKWISE:
+                TheMovementNumberOfLevels = 6;
+                TheMovementPosition++;
+                TheMovementAddressModePtr = &addressHorizontalRadially;
+            break;
+            case RADIAL_COUNTERCLOCKWISE:
+                TheMovementNumberOfLevels = 6;
+                TheMovementPosition--;
+                TheMovementAddressModePtr = &addressHorizontalRadially;
+            break;
+            case BARREL_CLOCKWISE:
+                TheMovementPosition++;
+                TheMovementNumberOfLevels = 10;
+                TheMovementAddressModePtr = &addressVerticallyRadially;
+            break;
+            case BARREL_COUNTERCLOCKWISE:
+                TheMovementPosition--;
+                TheMovementNumberOfLevels = 10;
+                TheMovementAddressModePtr = &addressVerticallyRadially;
+            break;
+            case ALL:
+                TheMovementPosition = 0;
+                TheMovementNumberOfLevels = 3;
+                TheMovementAddressModePtr = &addressHorizontally;
+            break;
+            case OFF:
+                TheMovementPosition = 0;
+                TheMovementNumberOfLevels = 3;
+                TheMovementAddressModePtr = &addressHorizontally;
+            break;
+            default:
+            break;
+        }
+        if (TheMovementMode != OFF) {
+            NumberOfMovesSoFar++;
+            //printf ("Incrementing moves to: %d\n\r", NumberOfMovesSoFar);
+        }
+    }
+    // end the move after it has reached its end
+    if (NumberOfMovesSoFar > TheMovementNumberOfLevels) {
+        //printf ("TURNING MOVE OFF.\n\r");
+        TheMovementMode = OFF;
+        NumberOfMovesSoFar = 0;
+    }
+    
+    if (TheMovementPosition < 0) TheMovementPosition = TheMovementNumberOfLevels-1;
+    TheMovementPosition = TheMovementPosition % TheMovementNumberOfLevels;
+
+
+    //printf("mode=%d, pos=%d, levels=%d, numMoves=%d\n\r",TheMovementMode, TheMovementPosition, TheMovementNumberOfLevels, NumberOfMovesSoFar);
+    
+    // paint all the subeffects at 40Hz (or whatever the refresh rate is)
+    for (int i=0; i < TheMovementNumberOfLevels; i++) {
+        if (TheMovementMode != OFF && (i < TheMovementPosition && TheMovementFill == FILL || (TheMovementMode == ALL))) {
+            TheMovementAddressModePtr (i, TheColorList,  false); 
+        } else if (i == TheMovementPosition && (TheMovementMode != OFF)){
+            TheMovementAddressModePtr (i, TheColorList,  false); 
+        } else {
+            TheMovementAddressModePtr (i, TheBaseColorList,  true); 
+        }
+    }
+    
+    // Latch the current color settings into the hardware
+    theChip->latchData();
+}
+
+const int* getRandomMovementColorList() {
+    return movementColors[rnd() % MOVEMENT_COLOR_LIST_SIZE];
+}
+
+const int* getRandomBaseColorList() {
+    return baseColors[rnd() % BASE_COLOR_LIST_SIZE];
+}
+
+const int* getRandomColorList() {
+    return listOfColorLists[rnd() % USER_COLOR_CHOICES_SIZE].colorList;
+}
+
+void generateBaseEffect(bool isBackgroundYellow) {
+    if (TheMode == USER_BASE || TheMode == USER_MOVEMENT_AND_BASE) {
+        return; // already set by user
+    } else {        
+        int randomDitherMode = rnd() % 6;
+        int randomEffectType = rnd() % 4;
+        const int* randomColorList;
+        if (isBackgroundYellow) {
+            randomColorList = getRandomBaseColorList();    
+        } else {
+            randomColorList = getRandomMovementColorList();    
+        }            
+        int timeConstant = 300 + (rnd() % 8000);
+        setBaseEffect( randomDitherMode, randomEffectType, randomColorList, timeConstant ); 
+    }
+}
+
+void generateMovementEffect(int durationMS) {
+
+    // select a random movement effect to overlay onto the base effect
+    if (TheMode == USER_MOVEMENT || TheMode == USER_MOVEMENT_AND_BASE) {
+        return; // already set by user
+    } else {
+        int randomMovementMode = rnd() % 9;
+        int randomDitherMode = rnd() % 6;
+        int randomMovementFill = rnd() % 3;
+        int randomEffectType = rnd() % 4;
+        const int* randomColorList = getRandomMovementColorList();
+        int moveTimeConstant =  (durationMS*(3+(rnd()%5))/8) / getMovementLevels(randomMovementMode);
+        //int timeConstant =  ((durationMS*7)/8) / getMovementLevels(randomMovementMode);
+        int timeConstant = 200 + (rnd() % (durationMS-200));
+        setOverlayEffect( randomMovementMode, randomDitherMode, randomMovementFill, randomEffectType, randomColorList, timeConstant, moveTimeConstant);
+        //printf ("New Movement Effect. move=%d, dither=%d, effect=%d, firstColor=%s, timeConstant=%d\n\r", 
+    }
+}
+
+// hour is zero based, starting at midnight
+void generateHourlyMovement(int hour, int msIntoEffect) {
+    inHourEffect = TRUE;
+    
+    if (msIntoEffect < 2000) {
+    // turn off for two seconds
+        setChannelToColorName (BEAK, COLOR_BLACK);
+        setChannelToColorName (EYES, COLOR_BLACK);
+        setBaseEffect( CROSSFADE, CONSTANT, Black, 500 ); 
+        setOverlayEffect( ALL, FIXED, LINE, CONSTANT, Black, 2000, 2000);
+    } else if (msIntoEffect < 3000 + (hour*1000)) {
+        if (msIntoEffect%1000 < 500) {
+            //normal
+            setChannelToColorName (BEAK, COLOR_ORANGE_WEB_COLOR);
+            setChannelToColorName (EYES, COLOR_RED);
+            setBaseEffect( FIXED, CONSTANT, OneYellow, 500 ); 
+            setOverlayEffect( OFF, FIXED, LINE, CONSTANT, Black, 500, 500);
+        } else {
+            //weird
+            setChannelToColorName (BEAK, COLOR_HUNTER_GREEN);
+            setChannelToColorName (EYES, COLOR_BLUE);
+            setBaseEffect( FIXED, RANDOM, Electrics, 100 ); 
+            setOverlayEffect( OFF, FIXED, LINE, CONSTANT, Black, 500, 500);
+        }
+    
+    } else if (msIntoEffect < 4000 + (hour*1000)) {
+        // turn off for one seconds
+        setChannelToColorName (BEAK, COLOR_BLACK);
+        setChannelToColorName (EYES, COLOR_BLACK);
+        setChannelToColorName(INTERNAL1, COLOR_RED);
+        setChannelToColorName(INTERNAL2, COLOR_RED);
+        setBaseEffect( CROSSFADE, CONSTANT, Black, 500 ); 
+        setOverlayEffect( ALL, FIXED, LINE, CONSTANT, Black, 2000, 2000);
+    } else {
+        inHourEffect = FALSE;
+        // select a new yellow (normal duck) base effect
+        generateBaseEffect(true);
+        setChannelToColorName (BEAK, COLOR_ORANGE_WEB_COLOR);
+        setChannelToColorName (EYES, COLOR_BLUE);
+        setChannelToColorName(INTERNAL1, COLOR_WHITE);
+        setChannelToColorName(INTERNAL2, COLOR_WHITE);
+    }
+}
+
+void generatePerMinuteMovement(int msIntoEffect) {
+    inMinuteEffect = TRUE;
+    
+    // end after 4 seconds
+    if (msIntoEffect >= 4000) {
+        inMinuteEffect = FALSE;
+        // select a new yellow (normal duck) base effect
+        generateBaseEffect(true);
+        setChannelToColorName (BEAK, COLOR_ORANGE_WEB_COLOR);
+        setChannelToColorName (EYES, COLOR_BLUE);
+        setChannelToColorName(INTERNAL1, COLOR_WHITE);
+        setChannelToColorName(INTERNAL2, COLOR_WHITE);
+    } else {
+        if (msIntoEffect % 500 < CLOCK_GRANULARITY) {
+            // do some number of movement effects in a row, twice a second
+            generateBaseEffect(false);
+            generateMovementEffect(500);
+            setChannelToColorName (BEAK, COLOR_HUNTER_GREEN);
+            setChannelToColorName (EYES, COLOR_RED);
+            setChannelToColorName(INTERNAL1, COLOR_RED);
+            setChannelToColorName(INTERNAL2, COLOR_RED);
+        }    
+    }
+}
+
+
+// This could also be used to beat match, hence the duration
+void generatePerSecondMovement(int durationMS) {
+    // single movement effect
+    // if the movement effect is a fill, it will be restored in the next cycle
+    generateMovementEffect(durationMS);    
+}
+
+
+void manageTimedInteractions() {
+    TheElapsedTime += CLOCK_GRANULARITY; // called every 25mS
+    if (TheElapsedTime % 43200000 < CLOCK_GRANULARITY) {
+        TheElapsedTime = 0; // reset timer at midnight
+    } else if (TheElapsedTime % 3600000 < CLOCK_GRANULARITY || inHourEffect) {
+        // hourly tasks (reset timer)
+        generateHourlyMovement(TheElapsedTime / 3600000, TheElapsedTime % 3600000);
+    } else if (TheElapsedTime % 60000 < CLOCK_GRANULARITY || inMinuteEffect) {
+        // one minute tasks
+        generatePerMinuteMovement(TheElapsedTime % 60000);
+    } else if (TheElapsedTime % UserAdjustableMovementInterval < CLOCK_GRANULARITY) {
+        // one second tasks
+        generatePerSecondMovement(UserAdjustableMovementInterval);
+    }
+    
+    // This manages all the effects
+    manageMovement();
+}
+
+// called prior to effects to initialize everything
+void SequencerConfig() {
+    for (int i=0;i<NUMBER_OF_PANELS;i++) {
+        channelColorPointer[0][i]=0; // default to first color in list
+        channelColorPointer[1][i]=0; // default to first color in list
+        channelColorSettings[i] = COLOR_BLACK;
+        Settings[i] = COLOR_BLACK;
+    }
+    
+    TheMovementPosition = 0;
+    TheMovementNumberOfLevels = 3;
+    TheMovementAddressModePtr = &addressHorizontally;
+
+    // Set a default base effect
+    //setBaseEffect( CROSSFADE, RANDOM, Electrics, 300 ); 
+    //setBaseEffect( CROSSFADE, RANDOM, KulorSunsetCamping, 500 ); 
+    //setBaseEffect( CROSSFADE, RANDOM, TestColors, 2000 ); 
+    setBaseEffect( FIXED, CONSTANT, GoodYellows, 5000 ); 
+    
+    // Set overlay effect to a reasonable neutral setting (OFF)
+    setOverlayEffect(OFF , FIXED , LINE, CONSTANT, All, 1000, 1000 );
+    setChannelToColorName (BEAK, COLOR_ORANGE_WEB_COLOR);
+    setChannelToColorName (EYES, COLOR_BLUE);
+    setChannelToColorName(INTERNAL1, COLOR_WHITE);
+    setChannelToColorName(INTERNAL2, COLOR_WHITE);
+
+}
+    
+void convertStringToLowercase(const char* input, char*output) {
+    for (int i=0; i<strlen(input);i++) {
+        output[i] = tolower(input[i]);
+    }
+    output[strlen(input)] = 0;
+}
+
+int getColorByName(char* colorName) {
+    char lowerCaseInput[64];
+    char lowerCaseSearch[64];
+
+    convertStringToLowercase(colorName, lowerCaseInput);
+    for (int i=0; i< COLOR_NAMES_MAX; i++) {
+        convertStringToLowercase(color_data[i].name, lowerCaseSearch);
+        if (!strcmp(lowerCaseSearch, lowerCaseInput)) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+void debugInteractions() {
+    // Display panel settings using a text output
+    printf ("t=%d\n\r", TheElapsedTime);
+    for (int i=0;i<ROWS;i++) {
+        for (int j=0;j<COLUMNS;j++) {
+            //printf ("-%d", (channelColorSettings[PANELS[i][j]]));
+            //printf ("-%u:%u:%u", debugRedOut[PANELS[i][j]], debugGreenOut[PANELS[i][j]], debugBlueOut[PANELS[i][j]]);
+            printf ("-%s", getNameFromColorNumber(channelColorSettings[PANELS[i][j]]));
+        }
+        printf ("\n\r");
+    }
+    printf("\n\r\n\r");
+}
+
+// called at 40Hz from main's Ticker
+uint16_t timeCount=0;
+uint16_t count=0;
+void Sequencer() {
+    led1 = !led1;
+    
+    if (TheMode == TEST) {
+        return; // mode zero turns the sequencer off. Useful for testing or setting up a static display
+    } else if (TheMode == SUN) {
+        // sun mode
+        for (int i=0; i< NUMBER_OF_PANELS;i++) {
+            setChannelToColorName(i, COLOR_WHITE);
+        }
+        theChip->latchData();
+        return;        
+    }    
+    
+    // mode 1 is the standard mode
+    manageTimedInteractions();
+    if (SEQUENCER_RATE >= 0.25) {
+        // only show debug output if the sequencer is running slow enough
+        debugInteractions();
+    }
+}
\ No newline at end of file