Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
Jim Brower
Published © MIT

phoneyTV for Spark

Internet enabled, presence-simulating home security device that simulates light emanating from a television.

Full instructions provided1,092
phoneyTV for Spark

Things used in this project

Hardware components

Spark Core
Particle Spark Core
The awesome heart of the project...
×1
10mm Blue Ultra Bright 0.5watt LEDs
×4
10mm White Ultra Bright 0.5watt LEDs
×4
10mm Green Ultra Bright 0.5watt LEDs
×2
10mm Red Ultra Bright 0.5watt LEDs
×2

Story

Read more

Code

PhoneyTV_Spark.ino

C/C++
Type in a caption
/*
 * PhoneyTV_Spark V1.0
 * 8-FEB-2014
 * by Jim Brower  >> BulldogLowell@gmail.com
 *
 * PhoneyTV_Spark illuminates 6 sets of LED's in a random fashion as to mimic the
 * ambient light emanating from a television.  It is intended to make an empty
 * home (or an empty section of a home) appear to be occupied by someone watching
 * TV.  As an alternative to a real television, this uses a tiny fraction of the
 * electrical energy.
 *
 * You can adjust the length of the blink interval and its "twitchyness" by
 * modifying the random number generators, if you prefer more/less 'motion' in
 * in your unit.
 *
 * Takes advantage of available PWM using the wht/blu LEDs to allow
 * fluctuations in the intensity of the light, enhancing the PhoneyTV's
 * realistic ambient light effect.
 *
 * Originally Created 12-APR-2014
 * http://forum.mysensors.org/topic/85/phoneytv-for-vera-is-here/3
 *
 * To set the time and mode:
 * curl -k https://api.spark.io/v1/devices/<yourSparkID>/phoneyTV  -d access_token=<yourAccessToken> -d params=solarMode=<MODE 0/1>#onTime=<float_decimal_time>#offset=<float_decimal_time>#offTime=<float_decimal_time>?
 *
 * for time, enter 8:15pm like this: 20.25  (eight and one-quarter hours pm)
 *
 * To turn on/off
 * curl -k https://api.spark.io/v1/devices/<yourSparkID>/phoneyPower -d access_token=<yourAccessToken> -d params=1
 *
 **********LED DETAILS**********
 *                             *
 * A0      // White using PWM  *
 * A1      // White using PWM  *
 * A2      // Green No PWM     *
 * A3      // Red   No PWM     *
 * A4      // Blue  using PWM  *
 * A5      // Blue  using PWM  *
 *                             *
 *******************************
 */
#include <application.h>
#include "Sunrise.h"
#include <math.h>
#include <time.h>

#define BUTTON_PIN  D2  // Digital I/O pin number for button
#define EEPROM_ID 1     // you can change this to reset your EEPROM storage

class modeCommand {
  String argument;
public:
  void extractValues(String);
  int solarMode () {
    return (argument.substring(argument.indexOf("phoneyMode=") + 11, argument.indexOf("#onTime="))).toInt();
  }
  float onTime () {
    return (argument.substring(argument.indexOf("#onTime=") + 8, argument.indexOf("#offset="))).toFloat();
  }
  float onTimeOffset () {
    return (argument.substring(argument.indexOf("#offset=") + 8, argument.indexOf("#offTime="))).toFloat();
  }
  float offTime () {
    return (argument.substring(argument.indexOf("#offTime=") + 9, argument.indexOf("?"))).toFloat();
  }
};

void modeCommand::extractValues (String stringPassed){
  argument = stringPassed;
}

struct phoneyTime {
  uint8_t phoneyHour;
  uint8_t phoneyMinute;
};

typedef enum {
  TIME_TRIGGER=0, SUNSET_TRIGGER, MANUAL_TRIGGER}
onTrigger;

phoneyTime tvSchedule   = {
  20, 30};  // 8:30pm turning on, we fix the schedule for TIME_TRIGGERs
phoneyTime tvOffTime    = {
  1, 15};  // off at 1:15am, we are always using a schedule to turn it off (bedtime)
phoneyTime delaySetting = {
  0, 15};  // 15 mins of random on/off times, the Start and End times are extended randomly by up to this ammount (independently)

phoneyTime tvOnTime;

bool autoMode = true;
bool sunsetTrigger = false;
int randomDelay_ON;
int randomDelay_OFF;

bool lastTimerState;         // time based state-change dection
unsigned long onTimeDelay;

byte lastButtonState;        // button press state-change detection
unsigned long lastPressTime;

bool phoneyState = false;

// these are the settings for the random flashes of light from the leds
int dipInterval = 10;
int darkTime = 1000;
unsigned long dipStartTime;
unsigned long currentMillis;
int ledState = LOW;
unsigned long previousMillis;
int led = 5;
int interval = 2000;
int twitch = 50;
int dipCount = 0;
int analogLevel = 100;
boolean timeToDip = false;
int pin[]= {
  A0, A1, A2, A3, A4, A5};
//

unsigned long myTimer;  //you can poll the device to get the current settings
char message[75] = "";  // yup, it's a lot of info...
//
// create Sunrise objects (we are only going to be using sunset)
Sunrise winter(40.3571,-74.6702, -5);//Princeton, New Jersey, USA - Latitude/Longitude and UTC offset
Sunrise summer(40.3571, -74.6702,-4);//Princeton, New Jersey, USA - Latitude/Longitude and UTC offset

void setup()
{
  Serial.begin(9600);
  for (int i = 0; i < 6; i++)
  {
    pinMode(pin[i], OUTPUT);
  }
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  if (EEPROM.read(0) == EEPROM_ID)
  {
    tvSchedule.phoneyHour = EEPROM.read(1);
    tvSchedule.phoneyMinute = EEPROM.read(2);
    delaySetting.phoneyHour = EEPROM.read(3);
    delaySetting.phoneyMinute = EEPROM.read(4);
    tvOffTime.phoneyHour = EEPROM.read(5);
    tvOffTime.phoneyMinute = EEPROM.read(6);
    sunsetTrigger = EEPROM.read(7);
    autoMode = EEPROM.read(8);
  }
  Spark.function("phoneyPower", phoneyPower);          // internet based state change detection
  Spark.function("phoneyMode", phoneyMode);            // control phoneyTV's settings
  Spark.variable("getTimes", message, STRING);         // see how phoneyTV is set
  //Spark.variable("delayON", &randomDelay_ON, INT);     //only for debugging delays
  //Spark.variable("delayOFF", &randomDelay_OFF, INT);   //only for debugging delays
  //
  winter.Astronomical(); //Actual, Civil, Nautical, Astronomical
  summer.Astronomical(); // I'm using Astronomical as it effectively eliminates twilight time
  //
  randomDelay_ON = random(((delaySetting.phoneyHour * 60) + delaySetting.phoneyMinute) * 60); // we want seconds because we are using UNIX timestamps
  randomDelay_OFF = random(((delaySetting.phoneyHour * 60) + delaySetting.phoneyMinute) * 60);
}
//
void loop()
{
  bool daylightSavings = IsDST(Time.day(), Time.month(), Time.weekday());
  Time.zone(daylightSavings? -4 : -5);
  if ((millis() - onTimeDelay > 60*60*24*1000UL) && !phoneyMode) // once daily, adjust the random offset
  {
    randomDelay_ON = random(((delaySetting.phoneyHour * 60) + delaySetting.phoneyMinute) * 60); //seconds
    randomDelay_OFF = random(((delaySetting.phoneyHour * 60) + delaySetting.phoneyMinute) * 60); //seconds
    onTimeDelay = millis();
  }

  if (millis() - myTimer > 5000UL)
  {
    char buffer[75] = "";
    sprintf(buffer, "Start=%02d:%02d, Delay=%02d:%02d, End=%02d:%02d, Ctrl:%s, Mode=%s, Device:%s", tvOnTime.phoneyHour, tvOnTime.phoneyMinute, delaySetting.phoneyHour, delaySetting.phoneyMinute, tvOffTime.phoneyHour, tvOffTime.phoneyMinute, autoMode? "AUTO":"MAN", sunsetTrigger? "Solar":"Time", phoneyState? "ON":"OFF");
    strcpy (message, buffer);
    myTimer = millis();
  }
  if(sunsetTrigger)
  {
    if(daylightSavings)
    {
      summer.Set(Time.month(),Time.day());
      tvOnTime.phoneyMinute = summer.sun_Minute();
      tvOnTime.phoneyHour = summer.sun_Hour();
    }
    else
    {
      winter.Set(Time.month(),Time.day());
      tvOnTime.phoneyMinute = winter.sun_Minute();
      tvOnTime.phoneyHour = winter.sun_Hour();
    }
  }
  else
  {
    tvOnTime.phoneyMinute = tvSchedule.phoneyMinute;
    tvOnTime.phoneyHour = tvSchedule.phoneyHour;
  }
  // This block lets you also control it manually during
  // MANUAL_TRIGGER mode because it will change state
  // only when it crosses the time boundry, yet will
  // illuminate correctly after a power cycle (or a reset)
  //
  bool timerState = timerEvaluate();
  if (timerState != lastTimerState && autoMode)
  {
    phoneyState = timerState;
  }
  lastTimerState = timerState;
  //
  int buttonState = digitalRead(BUTTON_PIN);
  if ((buttonState != lastButtonState) && (millis() - lastPressTime > 100UL))
  {
    if (buttonState == LOW)
    {
      Serial.println("Pressed!!!");
      phoneyState = !phoneyState;
      lastPressTime = millis();
    }
  }
  lastButtonState = buttonState;
  //
  if (phoneyState == true)
  {
    if (timeToDip == false)
    {
      currentMillis = millis();
      if(currentMillis-previousMillis > interval)
      {
        previousMillis = currentMillis;
        interval = random(750,4001);//Adjusts the interval for more/less frequent random light changes
        twitch = random(40,100);// Twitch provides motion effect but can be a bit much if too high
        dipCount++;
      }
      if(currentMillis-previousMillis < twitch)
      {
        led = random(0,6);
        analogLevel=random(50,255);// set the range of the 4 pwm leds
        ledState = ! ledState;
        switch (led) //for the four PWM pins
        {
        case 0:
          pwmWrite();
          break;
        case 1:
          pwmWrite();
          break;
        case 4:
          pwmWrite();
          break;
        case 5:
          pwmWrite();
          break;
        default:
          digitalWrite(pin[led], ledState);
        }
        if (dipCount > dipInterval)
        {
          timeToDip = true;
          dipCount = 0;
          dipStartTime = millis();
          darkTime = random(250, 800);
          dipInterval = random(5, 250);// cycles of flicker
        }
      }
    }
    else
    {
      Serial.println("Dip Time");
      if (millis() - dipStartTime < darkTime)
      {
        for (int i=0 ; i<6;i++)
        {
          digitalWrite(pin[i],LOW);
        }
      }
      else
      {
        timeToDip = false;
      }
    }
  }
  else
  {
    for (int i = 0 ; i < 6; i++)
    {
      digitalWrite(pin[i],LOW);
    }
  }
}
//
void pwmWrite()
{
  if (ledState == HIGH)
  {
    analogWrite(pin[led], analogLevel);
  }
  else
  {
    digitalWrite(pin[led], LOW);
  }
}
//
bool IsDST(int dayOfMonth, int month, int dayOfWeek)
{
  if (month < 3 || month > 11)
  {
    return false;
  }
  if (month > 3 && month < 11)
  {
    return true;
  }
  int previousSunday = dayOfMonth - dayOfWeek;
  //In march, we are DST if our previous sunday was on or after the 8th.
  if (month == 3)
  {
    return previousSunday >= 8;
  }
  //In November we must be before the first sunday to be dst.
  //That means the previous sunday must be before the 1st.
  return previousSunday <= 0;
}
//
int phoneyPower(String powerState)
{
  phoneyState = powerState.charAt(0) - '0';
  char buffer[20] = "";
  sprintf(buffer, "phoneyTV: %s", phoneyState? "ON" : "OFF");
  Serial.println(buffer);
  return phoneyState;
}
//
int phoneyMode(String modeArgs)
{
  modeCommand command;
  command.extractValues(modeArgs);
  tvSchedule.phoneyHour = (int) command.onTime();
  if (tvSchedule.phoneyHour > 23) tvSchedule.phoneyHour = tvSchedule.phoneyHour % 23;
  if (tvSchedule.phoneyHour < 0) tvSchedule.phoneyHour = 0;
  tvSchedule.phoneyMinute = (int)((command.onTime() - (int) command.onTime())*60.0);
  delaySetting.phoneyHour = min((int) command.onTimeOffset(), 1);
  delaySetting.phoneyMinute = (int)((command.onTimeOffset() - (int) command.onTimeOffset())*60.0);
  tvOffTime.phoneyHour = (int) command.offTime();
  if (tvOffTime.phoneyHour > 23) tvOffTime.phoneyHour = tvOffTime.phoneyHour % 23;
  if (tvOffTime.phoneyHour < 0) tvOffTime.phoneyHour = 0;
  tvOffTime.phoneyMinute = (int)((command.offTime() - (int) command.offTime())*60.0);

  switch (command.solarMode()) {
  case TIME_TRIGGER:
    sunsetTrigger = false;
    autoMode = true;
    //
    break;
  case SUNSET_TRIGGER:
    sunsetTrigger = true;
    autoMode = true;
    //
    break;
  case MANUAL_TRIGGER:
    autoMode = false;
    //
    break;
  default:
    return -1;
  }
  //sunsetTrigger = command.solarMode();
  EEPROM.write(0, EEPROM_ID); // is EEPROM storage?
  EEPROM.write(1, tvSchedule.phoneyHour);
  EEPROM.write(2, tvSchedule.phoneyMinute);
  EEPROM.write(3, delaySetting.phoneyHour);
  EEPROM.write(4, delaySetting.phoneyMinute);
  EEPROM.write(5, tvOffTime.phoneyHour);
  EEPROM.write(6, tvOffTime.phoneyMinute);
  EEPROM.write(7, sunsetTrigger? 1:0);
  EEPROM.write(8, autoMode? 1:0);
  return command.solarMode();
}
//
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;
  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(), tvOnTime.phoneyHour, tvOnTime.phoneyMinute, 0);
  int off_time = tmConvert_t(Time.year(), Time.month(), Time.day(), tvOffTime.phoneyHour, tvOffTime.phoneyMinute, 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 + randomDelay_ON) && now_time < (off_time + randomDelay_OFF));
  }
  else if (off_time < on_time)
  {
    return (now_time > (on_time + randomDelay_ON) || now_time < (off_time + randomDelay_OFF));
  }
  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;
  }

}

Github

It is also public on the Spark web IDE

Credits

Jim Brower
1 project • 3 followers
Contact
Thanks to Josh Bartlett.

Comments

Please log in or sign up to comment.