Jim Brower
Published © MIT

Quartz Crystal NeoPixel LED Candle for Particle Photon

3D printed "Quartz Crystal" looking nightlight.

BeginnerShowcase (no instructions)3 hours796
Quartz Crystal NeoPixel LED Candle for Particle Photon

Things used in this project

Story

Read more

Custom parts and enclosures

3D files

Base and crystal

Code

DingleLedCandle.ino

C/C++
for Particle Photon
// candle for Adafruit NeoPixel
// 1 pixel version
// by Tim Bartlett, December 2013

/*

Modified for Particle Photon June 2016 by Jim Brower
1: can turn on/off with a Particle.function()
2: automatically turns on at Sunset and off at programmed time
3:Uses "sun_time" webhook for weatherunderground sunrise/sunset times:

{
	"event": "sun_time",
	"url": "http://api.wunderground.com/api/getYourOwnApiKey/astronomy/q/{{my-state}}/{{my-city}}.json",
	"requestType": "POST",
	"headers": null,
	"query": null,
	"responseTemplate": "{{#sun_phase}}{{sunrise.hour}}~{{sunrise.minute}}~{{sunset.hour}}~{{sunset.minute}}~{{#moon_phase}}{{current_time.hour}}~{{current_time.minute}}{{/moon_phase}}~{{/sun_phase}}",
	"responseTopic": "{{SPARK_CORE_ID}}_sun_time",
	"json": null,
	"auth": null,
	"coreid": null,
	"deviceid": null,
	"mydevices": true
}

*/

#include "neopixel.h"
#include "application.h"

#define PIXEL_COUNT 1
#define PIXEL_PIN D2
#define PIXEL_TYPE WS2812B    // USING GRB WS2821B's here

SYSTEM_THREAD(ENABLED);

// color variables: mix RGB (0-255) for desired yellow
int redPx = 255;
int grnHigh = 100; //110-120 for 5v, 135 for 3.3v
int bluePx = 10; //10 for 5v, 15 for 3.3v

// animation time variables, with recommendations
int burnDepth = 10; //10 for 5v, 14 for 3.3v -- how much green dips below grnHigh for normal burn -
int flutterDepth = 25; //25 for 5v, 30 for 3.3v -- maximum dip for flutter
int cycleTime = 120; //120 -- duration of one dip in milliseconds

// pay no attention to that man behind the curtain
int fDelay;
int fRep;
int flickerDepth;
int burnDelay;
int burnLow;
int flickDelay;
int flickLow;
int flutDelay;
int flutLow;

Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

int powerState = -1;
int lastTimerState = -1;
int lastState = -1;

struct TimerTime{
  int theHour;
  int theMinute;
};

TimerTime sunrise = { 6, 15};
TimerTime sunset = {10, 30};
TimerTime onTime = {17, 30};
TimerTime offTime = {23, 59};

const char* cityLocation = "Princeton";  //City for my Photon
const char* stateLocation = "NJ";     // State for my Photon
const int sunsetOffset = -2;

String responseTopic;
char sunriseBuffer[256] = "";
char publishString[125] = "";
char currentTime[6] = "24:00";
void setup()
{
  flickerDepth = (burnDepth + flutterDepth) / 2.4;
  burnLow = grnHigh - burnDepth;
  burnDelay = (cycleTime / 2) / burnDepth;
  flickLow = grnHigh - flickerDepth;
  flickDelay = (cycleTime / 2) / flickerDepth;
  flutLow = grnHigh - flutterDepth;
  flutDelay = ((cycleTime / 2) / flutterDepth);

  strcpy(publishString, "{\"my-city\": \"");
  strcat(publishString, cityLocation);
  strcat(publishString, "\", \"my-state\": \"");
  strcat(publishString, stateLocation);
  strcat(publishString, "\" }");
  responseTopic = System.deviceID();
  Particle.subscribe(responseTopic, webhookHandler, MY_DEVICES);
  Particle.function("CrystalPower", powerFunction);
  Particle.function("SetTimes", setTimes);
  Particle.variable("Power", powerState);
  Particle.variable("currentTime", currentTime);
  Particle.variable("SunriseInfo", sunriseBuffer);
  strip.begin();
  strip.setPixelColor(0, 255, 0, 0);
  strip.show();
  delay(500);
  Particle.publish("sun_time", publishString, 60, PRIVATE);
}

// In loop, call CANDLE STATES, with duration in seconds
// 1. on() = solid yellow
// 2. burn() = candle is burning normally, flickering slightly
// 3. flicker() = candle flickers noticably
// 4. flutter() = the candle needs air!

void loop()
{
  int timerState = (timerEvaluate()? 1 : 0);
  if(timerState != lastTimerState)
  {
    powerState = timerState;
    Particle.publish("pushover", powerState? "Timer set crystal ON" : "Timer set crystal off", 60, PRIVATE);
  }
  lastTimerState = timerState;
  static uint32_t flickMode = 0;
  if(powerState == 1)
  {
    switch (flickMode % 10)
    {
      case 0:
        burn(random(4, 10));
        break;
      case 1:
        flicker(random(4, 10));
        break;
      case 2:
        burn(random(4, 10));
        break;
      case 3:
        flutter(random(4, 10));
        break;
      case 4:
        burn(random(4, 10));
        break;
      case 5:
        on(random(4, 10));
        break;
      case 6:
        burn(random(4, 10));
        break;
      case 7:
        flicker(random(4, 10));
        break;
      case 8:
        on(random(4, 10));
        break;
      case 9:
        flutter(random(4, 10));
        break;
    }
    flickMode++;
  }
  else
  {
    if(lastState != powerState)
    {
      strip.setPixelColor(0, 0, 0, 0);
      strip.show();
    }
  }
  lastState = powerState;
  for (static unsigned long pubTimer = millis(); ( millis() - pubTimer ) > 60 * 60000UL; pubTimer = millis()) // request sun times every hour
  {
    Particle.publish("sun_time", publishString, 60, PRIVATE);
  }
  for (static unsigned long clockUpdate = millis(); ( millis() - clockUpdate ) > 1000UL; clockUpdate = millis()) // update the clock Particle.variable() once a second
  {
    uint8_t hour = Time.hour();
    uint8_t minute = Time.minute();
    char timeBuffer[6] = "";
    sprintf(timeBuffer, "%02d:%02d", hour, minute);
    strcpy(currentTime, timeBuffer);
  }
}

// basic fire function - not called in main loop
void fire(int grnLow) {
  for (int grnPx = grnHigh; grnPx > grnLow; grnPx--) {
    strip.setPixelColor(0, grnPx, redPx, bluePx);
    strip.show();
    delay(fDelay);
  }
  for (int grnPx = grnLow; grnPx < grnHigh; grnPx++) {
    strip.setPixelColor(0, grnPx, redPx, bluePx);
    strip.show();
    delay(fDelay);
  }
}

// fire animation
void on(int f) {
  fRep = f * 1000;
  int grnPx = grnHigh - 5;
  strip.setPixelColor(0, grnPx, redPx, bluePx);
  strip.show();
  delay(fRep);
}

void burn(int f) {
  fRep = f * 8;
  fDelay = burnDelay;
  for (int var = 0; var < fRep; var++) {
    fire(burnLow);
  }
}

void flicker(int f) {
  fRep = f * 8;
  fDelay = burnDelay;
  fire(burnLow);
  fDelay = flickDelay;
  for (int var = 0; var < fRep; var++) {
    fire(flickLow);
  }
  fDelay = burnDelay;
  fire(burnLow);
  fire(burnLow);
  fire(burnLow);
}

void flutter(int f) {
  fRep = f * 8;
  fDelay = burnDelay;
  fire(burnLow);
  fDelay = flickDelay;
  fire(flickLow);
  fDelay = flutDelay;
  for (int var = 0; var < fRep; var++) {
    fire(flutLow);
  }
  fDelay = flickDelay;
  fire(flickLow);
  fire(flickLow);
  fDelay = burnDelay;
  fire(burnLow);
  fire(burnLow);
}

void webhookHandler(const char *event, const char *data)
{
  if (strstr(event, "sun_time"))
  {
    gotSunTime(event, data);
  }
}

void gotSunTime(const char * event, const char * data)
{
  //sunriseBuffer = "";
  strcpy(sunriseBuffer, data);
  sunrise.theHour = atoi(strtok(sunriseBuffer, "\"~"));
  sunrise.theMinute = atoi(strtok(NULL, "~"));
  sunset.theHour = atoi(strtok(NULL, "~"));
  sunset.theMinute = atoi(strtok(NULL, "~"));
  int currentHour = atoi(strtok(NULL, "~"));
  int currentMinute = atoi(strtok(NULL, "~"));
  Time.zone(0);
  Time.zone(utcOffset(Time.hour(), currentHour));
  sprintf(sunriseBuffer, "%s, %s Sunrise: %02d:%02d, Sunset: %02d:%02d, Sunset Offset = %d, Off Time: %02d:%02d, Last Updated: %02d:%02d", cityLocation, stateLocation, sunrise.theHour, sunrise.theMinute, sunset.theHour, sunset.theMinute, sunsetOffset, offTime.theHour, offTime.theMinute, currentHour, currentMinute);
  //Particle.publish("pushover", sunriseBuffer, 60, PRIVATE);
  sunset.theHour += sunsetOffset; // let's come on one hour before sunset. <<<<<<<<<<<<<<<<<<<<<<<< IMPORTANT
}

int utcOffset(int utcHour, int localHour)  // sorry Baker Island, this won't work for you (UTC-12)
{
  if (utcHour == localHour)
  {
    return 0;
  }
  else if (utcHour > localHour)
  {
    if (utcHour - localHour >= 12)
    {
      return 24 - utcHour + localHour;
    }
    else
    {
      return localHour - utcHour;
    }
  }
  else
  {
    if (localHour - utcHour > 12)
    {
      return  localHour - 24 - utcHour;
    }
    else
    {
      return localHour - utcHour;
    }
  }
}

int powerFunction(String command)
{
  int value = command.toInt();
  if (value == 0 || value == 1)
  {
    powerState = value;
    Particle.publish("pushover", powerState? "User set crystal ON" : "User set crystal off", 60, PRIVATE);
    return value;
  }
  else if (value == 2)
  {
    powerState = !powerState;
    Particle.publish("pushover", powerState? "User set crystal ON" : "User set crystal off", 60, PRIVATE);
    return powerState;
  }
  else return -1;
}
time_t tmConvert_t(int YYYY, byte MM, byte DD, byte hh, byte mm, byte ss)
{
  struct tm t;
  t.tm_year = YYYY-1900;
  t.tm_mon = MM - 1;
  t.tm_mday = DD;
  t.tm_hour = hh;
  t.tm_min = mm;
  t.tm_sec = ss;
  t.tm_isdst = 0;
  time_t t_of_day = mktime(&t);
  return t_of_day;
}
//
bool timerEvaluate()  // comparing time here is easier with Unix timestamps...
{
  int on_time = tmConvert_t(Time.year(), Time.month(), Time.day(), sunset.theHour, sunset.theMinute, 0);
  int off_time = tmConvert_t(Time.year(), Time.month(), Time.day(), offTime.theHour, offTime.theMinute, 0);
  int now_time = tmConvert_t(Time.year(), Time.month(), Time.day(), Time.hour(), Time.minute(), Time.second());
  //
  if (on_time < off_time)
  {
    return (now_time > on_time && now_time < off_time);
  }
  else if (off_time < on_time)
  {
    return (now_time > on_time || now_time < off_time);
  }
  else // if both on and off are set to the same time, I'm confused... so let's not lite up at all!
  {
    return false;
  }
}

int setTimes(String command)
{
  int gotTime = 0;
  Serial.println(command);
  char timeString[63] = "";
  command.toCharArray(timeString, sizeof(timeString));
  char* ptr;
  ptr = strstr(timeString,"ON");
  if(ptr)
  {
    ptr += 2;
    double on = atof(ptr);
    onTime.theHour = int(on);
    onTime.theMinute = (on - onTime.theHour) * 60.0;
    gotTime += 1;
  }
  ptr = strstr(timeString,"OFF");
  if(ptr)
  {
    ptr += 3;
    double off = atof(ptr);
    offTime.theHour = int(off);
    offTime.theMinute = (off - offTime.theHour) * 60.0;
    gotTime += 10;
  }
  sprintf(sunriseBuffer, "%s, %s Sunrise: %02d:%02d, Sunset: %02d:%02d, Sunset Offset = %d, Off Time: %02d:%02d, Last Updated: %02d:%02d", cityLocation, stateLocation, sunrise.theHour, sunrise.theMinute, sunset.theHour, sunset.theMinute, sunsetOffset, offTime.theHour, offTime.theMinute, Time.hour(), Time.minute());
  return gotTime;
}

Credits

Jim Brower

Jim Brower

1 project • 3 followers

Comments