Dan Thyer
Published © MIT

Magical OpenThread Mesh Christmas Lights!

Build an awesome Christmas light show with the timing coordinated over the OpenThread mesh network!

AdvancedFull instructions provided16 hours1,970
Magical OpenThread Mesh Christmas Lights!

Things used in this project

Hardware components

Argon
Particle Argon
Used for the gateway and to synchronize the timing.
×1
Xenon
Particle Xenon
1 used on the 20 foot tree (12 channels), 1 used to control 6 light balls, 1 used to control a second set of 6 light balls
×3
Solid State Relay 8-Channel
1 - Christmas Tree, 1 - Christmas Balls, 1- Second set of Christmas Balls
×3
Solid State Relay - 4 Channels
Used with 8-Channel set for the 12 Channels of the Christmas Tree.
×1
Black pipe - 1/2 inch diameter - 10 foot
Used for the Christmas Tree
×2
Black pipe - 1/2 inch coupler
Used to connect the 10 foot pipes for the Christmas Tree.
×1

Story

Read more

Schematics

Christmas Tree Circuit

The Christmas tree has 12 solid state relays and is controlled with a Particle Xenon. There is a hidden 5V switching power supply in the enclosure underneath the circuit.

Light Feature Circuit

The light feature circuits are controlled with a Particle Xenon and a bank of solid state relays. There is a hidden 5V switching power supply in the enclosure underneath the circuit.

Code

Gateway Code

Arduino
This code is loaded on a Particle Argon gateway node.
int _pinHeartbeat = D7;
int _lastHeartbeatMillis = 0;
int _synchronizeMeshChristmasMillis = 0;
int _mode = 0;
bool _synchronizeMeshChristmasLights = false;

void setup() {
    Mesh.subscribe("christmasTree", christmasTreeMeshHandler);
    Particle.function("changeAllChristmasMode", changeAllChristmasMode);
    
    WiFi.setCredentials("LogicalLiving","^XxS8]%$r75MB=R/");
    WiFi.setCredentials("DanPhone", "!^YaZAnM[-&H`6Dm");
    WiFi.setCredentials("PME_Guest","4-)WL+>qTMTJnZ3r");
    
    pinMode(_pinHeartbeat, OUTPUT);
}

void loop() {
    heartBeat();
    synchronizeMeshChristmasLightsIfReady();
    delay(200);
}

void heartBeat() {
    if(_lastHeartbeatMillis + 500 < millis() ) {
        _lastHeartbeatMillis = millis();
        digitalWrite(_pinHeartbeat, !digitalRead(_pinHeartbeat));
    }
}

int changeAllChristmasMode(String mode) {
    if(mode == "") {
        _mode = _mode + 1;
    } else {
        _mode = mode.toInt();
    }
  
    if(_mode < 0 || _mode > 14){
        _mode = 0;
    }
    
    Mesh.publish("changeChristmasMode", String(_mode));
    //Mesh.publish("changeChristmasMode", "8");
    return _mode;
}

void christmasTreeMeshHandler(const char *event, const char *data){
    if (!_synchronizeMeshChristmasLights) {
        _synchronizeMeshChristmasMillis = millis();
    }
    _synchronizeMeshChristmasLights = true;
    Particle.publish("christmasLights", "ready-to-send-sync", PRIVATE);
}

void synchronizeMeshChristmasLightsIfReady() {
    //Allow for a delay before tries so that micros can sync with the particle cloud and do OS functions
    if (_synchronizeMeshChristmasLights && _synchronizeMeshChristmasMillis + 1000 < millis()) {
        _synchronizeMeshChristmasMillis = millis();
        
        //Keep trying each iteration until successful
        Particle.publish("christmasLights", "synchronize", PRIVATE);
        if (Mesh.publish("christmasLights", "synchronize") == SYSTEM_ERROR_NONE) {
            _synchronizeMeshChristmasLights = false; 
        }  
    }
}

Light Feature Code

Arduino
All of the mesh light feature nodes are running the same code.
int _pinSolidStateRelay1  = D0; 
int _pinSolidStateRelay2  = D1; 
int _pinSolidStateRelay3  = D2;
int _pinSolidStateRelay4  = D3;
int _pinSolidStateRelay5  = D4;
int _pinSolidStateRelay6  = D5;
int _pinSolidStateRelay7  = D6;
int _pinSolidStateRelay8  = D7;
int _pinSolidStateRelay9  = A4;
int _pinSolidStateRelay10 = A3;
int _pinSolidStateRelay11 = A2;
int _pinSolidStateRelay12 = A1;
int _pinHeartbeat = D7;
int _mode = 0;
int _synchronizeLightsMillis = 0;
int _lastHeartbeatMillis = 0;

bool _isChristmasTree = false;
bool _isChristmasBalls1 = false;
bool _isChristmasBalls2 = false;
bool _isRunningChristmasMagicRoutine = false;

void setup() {
    Particle.subscribe("particle/device/name", deviceNameHandler);
    Mesh.subscribe("christmasLights", christmasLightsMeshHandler);
    Mesh.subscribe("changeChristmasMode", changeChristmasModeMeshHandler);
    Particle.function("changeMode", changeModeHandler);
    Particle.publish("particle/device/name");  //Ask Particle cloud for device name
    
    RGB.control (true);
    pinMode(_pinSolidStateRelay1,  OUTPUT);
    pinMode(_pinSolidStateRelay2,  OUTPUT);
    pinMode(_pinSolidStateRelay3,  OUTPUT);
    pinMode(_pinSolidStateRelay4,  OUTPUT);
    pinMode(_pinSolidStateRelay5,  OUTPUT);
    pinMode(_pinSolidStateRelay6,  OUTPUT);
    pinMode(_pinSolidStateRelay7,  OUTPUT);
    pinMode(_pinSolidStateRelay8,  OUTPUT);
    pinMode(_pinSolidStateRelay9,  OUTPUT);
    pinMode(_pinSolidStateRelay10, OUTPUT);
    pinMode(_pinSolidStateRelay11, OUTPUT);
    pinMode(_pinSolidStateRelay12, OUTPUT);
    
    lightStrand(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
}

void loop() {
    switch (_mode) 
    {
        // Runs Christmas magic routine synchronized on all mesh devices
        case 0: 
            showIdentity();
            //showHeartbeat();  //It blinks a channel so only do during debugging
            
            if(_isChristmasTree) {
                synchronizeLights();
            }
            break;
            
        //light a single channel based on the mode
        case 1 ... 12: 
            rgbBlue();
            lightStrand(_mode==1, _mode==2, _mode==3, _mode==4, _mode==5, _mode==6, _mode==7, _mode==8, _mode==9, _mode==10, _mode==11, _mode==12, 300);
            break;
         
        //light all lights
        case 13: 
            rgbBlue();
            lightStrand(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 300);
            break;
            
        //light one at a time on a slow loop to figure out the sequence to put in the trees
        case 14: 
            rgbBlue();
            shiftStrandLeft(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2000, 12);
            break;
            
        // code to be executed if n doesn't match any cases
        default: 
            _mode = 0;
    }
    
}

void showHeartbeat() {
    if(_lastHeartbeatMillis + 500 < millis() ) {
        _lastHeartbeatMillis = millis();
        digitalWrite(_pinHeartbeat, !digitalRead(_pinHeartbeat));
    }
}

void showIdentity() {
    if(_isChristmasTree) {
        rgbGreen();
    }
    else if (_isChristmasBalls1) {
        rgbCyan();
    }
    else if (_isChristmasBalls2) {
        rgbYellow();
    }
    else {
        rgbRed();
    }
}

void synchronizeLights() {
    //Have a delay between mesh message trys
    if (_synchronizeLightsMillis + 1000 < millis()) {
        _synchronizeLightsMillis = millis();
        //Particle.publish("christmasTree", "ready-to-synchronize", PRIVATE);
        if (Mesh.publish("christmasTree", "ready-to-synchronize") != SYSTEM_ERROR_NONE){
            RGB.color(255,0,0); //Red
        }
    }
    delay(200);
}

void christmasLightsMeshHandler(const char *event, const char *data) {
    if (!_isRunningChristmasMagicRoutine){
        _isRunningChristmasMagicRoutine = true;
        christmasMagicRoutine();
        _isRunningChristmasMagicRoutine = false;
    }
}

void changeChristmasModeMeshHandler(const char *event, const char *data) {
    _mode = atoi(data);
}

int changeModeHandler(String mode) {
    if(mode == "") {
        _mode = _mode + 1;
    } else {
        _mode = mode.toInt();
    }
  
    return _mode;
}

void christmasMagicRoutine() {
    //The _mode can be updated at anytime from a Particle function
    if(_mode==0) { shiftStrandLeft(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 12); }
    if(_mode==0) { shiftStrandRight(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 12); }
    if(_mode==0) { fadeAllStrand(); }
    if(_mode==0) { fadeAllStrand(); }
    if(_mode==0) { fadeAllStrand(); }
    if(_mode==0) { fadeAllStrand(); }
    if(_mode==0) { shiftStrandLeft(1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 600, 5); }
    if(_mode==0) { shiftStrandLeft(1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 40, 12*4); }
    if(_mode==0) { lightStrand(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1000); }
    if(_mode==0) { diamond(false); }
    if(_mode==0) { delay(300); }
    if(_mode==0) { diamond(true); }
    if(_mode==0) { shiftStrandLeft(1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 150, 12*4); }
    if(_mode==0) { fadeStrandRotate(); }
    if(_mode==0) { fadeStrandRotate(); }
    if(_mode==0) { fadeStrandRotate(); }
    if(_mode==0) { fadeStrandRotate(); }
    if(_mode==0) { shiftStrandRight(1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 80, 12*6); }
    if(_mode==0) { lightStrand(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1000); }
}

void fadeStrandRotate() {
    fadeStrand(10, 8, 6, 4, 2, 0, 2, 4, 6, 8, 10, 10);
    fadeStrand(8, 6, 4, 2, 0, 2, 4, 6, 8, 10, 10, 10);
    fadeStrand(6, 4, 2, 0, 2, 4, 6, 8, 10, 10, 10, 8);
    fadeStrand(4, 2, 0, 2, 4, 6, 8, 10, 10, 10, 8, 6);
    fadeStrand(2, 0, 2, 4, 6, 8, 10, 10, 10, 8, 6, 4);
    fadeStrand(0, 2, 4, 6, 8, 10, 10, 10, 8, 6, 4, 2);
    fadeStrand(2, 4, 6, 8, 10, 10, 10, 8, 6, 4, 2, 0);
    fadeStrand(4, 6, 8, 10, 10, 10, 8, 6, 4, 2, 0, 2);
    fadeStrand(6, 8, 10, 10, 10, 8, 6, 4, 2, 0, 2, 4);
    fadeStrand(8, 10, 10, 10, 8, 6, 4, 2, 0, 2, 4, 6);
    fadeStrand(10, 10, 10, 8, 6, 4, 2, 0, 2, 4, 6, 8);
    fadeStrand(10, 10, 8, 6, 4, 2, 0, 2, 4, 6, 8, 10);
}

void fadeAllStrand() {
    for (int count = 0; count <= 10; count++)
    {
        fadeStrand(count,count,count,count,count,count,count,count,count,count,count,count);
    }
    for (int count = 10; count >= 0; count--)
    {
        fadeStrand(count,count,count,count,count,count,count,count,count,count,count,count);
    }
}

void diamond(boolean reverse) {
    if (reverse)
    {
        lightStrand(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 200);
        lightStrand(1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 200);
        lightStrand(1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 200);
        lightStrand(1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 200);
        lightStrand(1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 200);
        lightStrand(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 200);
        lightStrand(1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 200);
        lightStrand(1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 200);
        lightStrand(1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 200);
        lightStrand(1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 200);
        lightStrand(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 200);
        lightStrand(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200);
    }
    else
    {
        lightStrand(0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 200);
        lightStrand(0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 200);
        lightStrand(0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 200);
        lightStrand(0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 200);
        lightStrand(0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 200);
        lightStrand(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 200);
        lightStrand(0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 200);
        lightStrand(0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 200);
        lightStrand(0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 200);
        lightStrand(0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 200);
        lightStrand(0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 200);
        lightStrand(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200);
    }
}

void shiftStrandLeft(boolean light1,  boolean light2, boolean light3, boolean light4, boolean light5, 
                     boolean light6,  boolean light7, boolean light8, boolean light9, boolean light10, 
                     boolean light11, boolean light12, int delayMilliSeconds, int loopCount) {
    for (int count = 1; count <= loopCount; count++) 
    { 
        lightStrand(light1,  light2, light3, light4, light5, light6, light7, light8, light9, light10, 
                    light11, light12, delayMilliSeconds);
                    
        boolean lightHold = light1;
        light1  = light2;
        light2  = light3;
        light3  = light4;
        light4  = light5;
        light5  = light6;
        light6  = light7;
        light7  = light8;
        light8  = light9;
        light9  = light10;
        light10 = light11;
        light11 = light12;
        light12 = lightHold;
    }
}

void shiftStrandRight(boolean light1,  boolean light2, boolean light3, boolean light4, boolean light5, 
                      boolean light6,  boolean light7, boolean light8, boolean light9, boolean light10, 
                      boolean light11, boolean light12, int delayMilliSeconds, int loopCount) {
    for (int count = 1; count <= loopCount; count++) 
    { 
        lightStrand(light1, light2, light3, light4, light5, light6, light7, light8, light9, light10, light11, light12, delayMilliSeconds);
        
        boolean lightHold = light12;
        light12 = light11;
        light11 = light10;
        light10 = light9;
        light9  = light8;
        light8  = light7;
        light7  = light6;
        light6  = light5;
        light5  = light4;
        light4  = light3;
        light3  = light2;
        light2  = light1;
        light1  = lightHold;
    }
}

void lightStrand(boolean light1,  boolean light2, boolean light3, boolean light4, boolean light5,
                 boolean light6,  boolean light7, boolean light8, boolean light9, boolean light10, 
                 boolean light11, boolean light12, int delayMilliSeconds) {
    digitalWrite(_pinSolidStateRelay1,  light1);
    digitalWrite(_pinSolidStateRelay2,  light2);
    digitalWrite(_pinSolidStateRelay3,  light3);
    digitalWrite(_pinSolidStateRelay4,  light4);
    digitalWrite(_pinSolidStateRelay5,  light5);
    digitalWrite(_pinSolidStateRelay6,  light6);
    digitalWrite(_pinSolidStateRelay7,  light7);
    digitalWrite(_pinSolidStateRelay8,  light8);
    digitalWrite(_pinSolidStateRelay9,  light9);
    digitalWrite(_pinSolidStateRelay10, light10);
    digitalWrite(_pinSolidStateRelay11, light11);
    digitalWrite(_pinSolidStateRelay12, light12);
    delay(delayMilliSeconds); 
}

void fadeStrand(int light1, int light2, int light3, int light4, int light5, int light6, int light7, 
                int light8, int light9, int light10, int light11, int light12) {
    for (int pulse = 1; pulse <= 10; pulse++) { 
        lightStrand(pulse<=light1,  pulse<=light2, pulse<=light3, pulse<=light4, pulse<=light5, 
                    pulse<=light6,  pulse<=light7, pulse<=light8, pulse<=light9, pulse<=light10, 
                    pulse<=light11, pulse<=light12, 5);
    }
}

void deviceNameHandler(const char *topic, const char *data) {
    char deviceName[32] = "";
    strncpy(deviceName, data, sizeof(deviceName)-1);
    Particle.publish("DeviceNameSet", deviceName, PRIVATE);
    if (strcmp(deviceName,"ChristmasTree")==0) {
        _isChristmasTree=true;
        Particle.publish("WhatIAm", "ChrismasTree", PRIVATE);
    } else if (strcmp(deviceName,"ChristmasBalls1")==0) {
        _isChristmasBalls1=true;
        Particle.publish("WhatIAm", "ChrismasBalls1", PRIVATE);
    } else if (strcmp(deviceName,"ChristmasBalls2")==0) {
        _isChristmasBalls2=true;
        Particle.publish("WhatIAm", "ChrismasBalls2", PRIVATE);
    }
}

void rgbGreen()  { RGB.color(0,255,0); }
void rgbCyan()   { RGB.color(0,255,255); }
void rgbYellow() { RGB.color(128,255,0); }
void rgbBlue()   { RGB.color(0,0,255); }
void rgbRed()    { RGB.color(255,0,0); }
                    

Credits

Dan Thyer

Dan Thyer

3 projects • 10 followers
Husband, father, business owner, software architect, IoT, web/mobile development, microcontrollers- basically a total geek. Microsoft RD/MVP

Comments