PhoneyTV for Vera with Arduino (FakeTV) Spark Core

I’m copying over some of my post at MySensors in case some of you are interested in making a really quick, fun and inexpensive project for Arduino and Vera Home Automation.

DeltaNu1142 had a good idea to build off of the FakeTV product and enable it for Home Automation. Basically this project builds a device that is intended reproduce the seemingly random ambient light of a television. This may provide increased security by making the house appear occupied and hopefully detracting would-be burglars whilst you are away.

Build it, set it in a room and project it against the ceiling, walls or even onto a translucent window covering for the perfect effect. Set a scene, use a PLEG schedule or Vacation Ghost to have it come on at different times during the evening adding an even more realistic occupancy effect.

I have attached a few photos of my build. The sketch, wiring diagrams (so simple) and other information can be found at the link above…

Added a pushbutton ON/OFF and updated sketch at MySensors.org.

I have been developing with Spark Core for a couple of weeks and ported this over…

I thought I would share the code if anyone was working with Spark… No Arduino needed, no gateway needed, just a Spark Core, a few ultrabright LEDs (a few transistors) and a board to solder it up… If anyone is interested I can share the revised schematic for the 3V3 output of Spark.

Here is the code and the library is on GitHub GitHub - BulldogLowell/Sunrise: Returns Time for Sunrise, Sunset and Solar Noon for the current day including if isDAY() :

[code]/*

  • 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
  • PhoneyTV for Vera is Here! | MySensors Forum
  • To set the time and mode:
  • curl -k https://api.spark.io/v1/devices//phoneyTV -d access_token= -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//phoneyPower -d access_token= -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 > 606024*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;
}

}
[/code]