Open Arduino

Enter Your Project for a chance to win a grand prize for the most innovative use of Arduino or a $200 shopping cart! The Birthday Special: Arduino Projects for Arduino Day!

Back to The Project14 homepage

Project14 Home
Monthly Themes
Monthly Theme Poll

 

This project was a suggestion from the publisher of TheShed magazine  https://www.theshedmag.co.nz

He was using those mechanical timers to turn on lights while he was away, and got sick of them failing.

 

We had been keen to find a solution to switch mains that was safe for readers.

As we've seen here Mains and Safety aren't always sitting together in some posts, and element14 has supported our efforts to rectify this.

I'd looked at the wireless outlets that were available through a NZ and Australian retailer, and were affordable.

 

So the question of replacing his timers with some controller and wireless outlets was more of an exercise in how.

 

 

 

 

Concept

It was obvious that I'd use an Arduino as the controller, and wireless power outlets as the device to replace the mechanical timers.

The issue was how to program it, and get that information into the unit with the skill set I had. (it was 2012)

This Design process was a piece of software on my tablet, and I found it very useful to draw out the various options.

( I must start using it again )

 

As you see from above, the choices for the controller were standard with shield or add-on cards, or special version Arduinos.

I'd put this as Step 2 in my Electronic Diet - Engineering a Solution blog

 

I identified that an SD card could hold the data, and was able to be removed and programmed with the times, etc.

Keeping time was going to be handled by an RTC.

 

Initially we thought about hacking a remote control to provide the 433MHz RF, but while looking around I stumbled across RCSwitch

https://github.com/sui77/rc-switch

This allowed an Arduino and RF transmitter to communicate with these outlets, which also allowed more codes than the remote had.

 

 

Hence the following block diagram became the plan.

 

Having a plan always makes it easier to focus on the various parts required.

 

 

 

 

 

Hardware

I identified that we would use an Arduino, but needed RTC and SD card along with a connection to the RF Transmitter.

We had in mind that this would be useful to readers so a complicated build was out of the question.

 

Luckily I ran across some clever guys at Wyolum (sadly the website is not around any more) who created a nifty board that slotted into an Arduino to interface with a GPS receiver.

 

It included the microSD card, RTC and had a few I/O pins to interface to the outside world.

 

I did run a few jumpers to connect the Analogue inputs to the header where they normally would reside.

     PC0 to P5 pin 1, PC 1 to P5 pin2, PC2 to P5 pin 3 and D to P5 pin 4 along with power to where Vin would be.

 

The transmitter and connectors were fitted onto a piece of a prototyping shield.

The button is used to start it running.

 

 

Programming is via an FTDI cable.

 

 

 

OUTLETS

I used the WattsClever and discovered that they could be programmed for codes greater then the remote provided.

      

The codes are attached below

 

It appears that bit 21 is changed between the On and OFF, so it's very easy when controlling these devices.

 

One of the units had a light, and it seems that all the units have two channels, with bit 24 controlling the 2nd channel.

 

 

 

 

SOFTWARE

I can honestly say I have never spent as much time on what appeared to be a very simple task.

I ran out of memory and each debug I added created more problems.

 

In the end I removed all the Serial.print statements and attacked the RCSwitch library to reduce it's size.

This library includes receiver and other functions, and for this project I only needed transmit.

I also had to modify the DS3231 library.

 

 

The Minder.txt file on the SD card consists of a series of instructions.

It is limited to 20 events, 8 channels and assumes that file is correctly formatted. (for you Americans, this is how we do dates dd/mm/yy)

 

Channels =8

Clock =2359

Date =300812

Time =2300,ON

Channel =1,2,3,4,5,6,7,8

Day =mon,tue,wed thur,fri,sat,sun

Time =2310,OFF

Channel =1,2

Day =mon,tue

Time =2330,OFF

Channel =8

Day =mon,tue,wed,thur,fri,sat,sun

Time =2330,OFF

Channel =1,2

Day =wed,thur,fri,sat,sun

 

 

This file also functions as the method of setting the RTC time and date.

 

These are the notes I wrote at the time :-

When the sketch starts, it attempts to read the file, Minder.txt..

It reads and stores the first 20 Events, and will ignore any further events.

If an Error is detected, it will flash LED1. (see Errors below).

 

If the RTC (Real Time Clock) is less than the date setting in the file, it will set the clock based on lines 2 and 3 of the file.

The sketch will then cycle through channels 1-8 every 10 secs, turning ON a channel and 5 secs later, turning it OFF.

This gives the Home owner a chance to check the outlets are being operated correctly.

This will continue until the ‘Go’ button is pressed, which sets the RTC to the date/time in file.

 

The ‘Go’ button signals that the sketch is ready to run, and after setting the Date/Time, it writes the date to the EEPROM.

The EEPROM data remains when the Arduino is restarted, which may be due to a power failure.

Once it has written to the EEPROM, the sketch checks the time every 10 seconds, and every minute, it will step through the Events to see if one matches the current time.

 

Once it finds a match, it will see if the Day of Week matches the DOW in the Event.

If a match(s) is found, the channel(s) for that Event are sent each time it checks the time (ie every 10 secs).

This ensures the wireless outlet has 5 or 6 chances to detect the code.

 

Each Event is either an ON or an OFF with certain channels (and DOW). It is possible to have an ON Event for channels 1,2 and an OFF Event for channels 3,4 with the same time.

The sketch will cycle through all events, so the order is not important.

 

There is some error checking which will result in a failure to run ie a Fatal Error as well as some indications to the user.

 

My notes at the time are :-

There are a number of Errors that mean the sketch cannot run. I have called these Fatal errors.

If one of these is generated, the sketch will display the appropriate Error Code and not process any Events.

 

      Fatal Error Codes:

      12 Unable to access/set RTC                                      (long and 2 shorts)

      13 Error accessing card/file                                        (long and 3 shorts)

      14 Error in file                                                               (long and 4 shorts)

 

There are a number of other Error codes that indicate a problem was encountered, but was able to be rectified.

In the case of ErrorCode 2, it may mean the time is not accurate, but it will continue running.

 

      Non Fatal Error Codes:

      0 Signals normal operation                          (single flash)

      2 Signals a restart.                                        (2 short)

      5 Clock was reset to card value.                (5 short)

 

 

 

Code

I've included the code which is close to the limitations, so care is needed about which Arduino you program it into.

If you modify it, be aware that you may have memory issues.

The zip file also contains the modified libraries, and the Utility sketch.

 

/*
    
      HOME MINDER
      This uses an DS3231 RTC, SD card and transmitter to control Wireless Power Outlets.
      Events are read from the file 'Minder.txt' into memory and then acted upon.
      A utility program allows the Outlets to be matched against the Channels 1-8.
      The RTC time/date can also be set.
      
      The code to transmit is based on the work of the guys at RCSwitch ( see http://code.google.com/p/rc-switch/ )      
      The sketch runs a POST which checks it can find and read 'Minder.txt' on the card.
      This is then broken down into its component parts and an error generated if the syntax is wrong.
      The RTC is checked and if invalid, the sketch uses the time/date in the file to set the RTC.
      A non fatal error code is generated if the time can be set.
      
      A successful POST will :-
          Turn the LED ON for 5 secs, and then flash regularily as a watchdog.
          Cycle through all the channels set in the file, until the 'Go' button is pressed
          Write the Date and Time from Minder.txt to the EEPROM, to use for subsequent restarts.
          It will also set the Date/Time from Minder.txt into the RTC when the Go buton is pressed.
          
      At sebsequent startup :-
          Performs POST
          Reads in Events to memory
          Checks EEPROM to match date/time.
          
      
      The Article was written using an I2GPS Board from www.wyolum.com (The Shed has stocks as well), which includes a MicroSD Card and DS3231 RTC along with
      an ATmega 328.
      
      The intention is to run the sketch in an ALaMode, which plugs onto a RaspberryPi, and provide greater interaction, and other functions.
      These were only just becoming available when this project was started.
     
      Enjoy
      Mark Beckett 
      

      --------------------------------------------------------------------------
      
     Pin assignments
     Pin 0  Rx
     Pin 1  Tx
     Pin 2  
     Pin 3  
     Pin 4  LED1 (I2GPS_v1 Board) 
     Pin 5  
     Pin 6  
     Pin 7  LED2 (I2GPS_v1 Board)
     Pin 10  SD_CS ,       SS     }
     Pin 11  SD_DATA_IN,   MOSI   }    Card
     Pin 12  SD_DATA_OUT,  MISO   }    Reader
     Pin 13  SC_CLK,       SCK    }
     Pin A0 (D14) Go Button
     Pin A1 (D15)
     Pin A2 (D16)
     Pin A3 (D17) Transmit data
     Pin A4  SDA  I2C Data        } RTC (i2GPS Board)
     Pin A5  SCA  I2C Clock       } RTC (I2GPS Board)
     
     created 1 Sept 2012
     by Mark Beckett
     
     
     Version
     0.1  Initial Code  started 01 Apr 2012
     1.0  Rewrite of code and libraries Dec 2012.
          Problems with memory resulted in libraries optimised to essentials only.
          This resulted in code changes and variable changes to compensate.
          Some features have been eliminated, and some assummed, in order to ensure stable operation.
    
     ---------------------------------------------------
     To Do :
     Utilise the RaspberryPi to do logging functions, which will eliminate the memory issues in Arduino.

     ******************************************
     I make no apologies for the code used in the sketch below.
     The series is designed to help introduce novice programmers (including me), and as such code that can be followed is more important.
        
     Also I fix things for a living, rather than write software, so to all you programmers ....I'm sorry
    
     Mark Beckett
     *****************************************
     */

    #include 
    #include <mod_rcswitch.h><mod_rcswitch.h>              // Heavily modified
    #include 
    File myFile;
    #include 
    #include<mod_ds3231.h> <mod_ds3231.h>                // Heavily modified I2GPS RTC
    DS3231 Clock;                          // Init the DS3231
    
    RCSwitch mySwitch = RCSwitch();           //( see http://code.google.com/p/rc-switch/ )

    // Card reading variables
    String fileString;
    String fileLineSub;
    char inChar;
    int Event[20][3];                 //Event number to the maximum of 20, with three element for each Event
    byte EventNum=0;                  //counter for the Events 0-19
    byte n =0;                        // size of the line
    byte linepos;                     // line position integer
    byte linenum =0;                  // line number integer
    
    // Inputs and Outputs
    const int ledPin1 = 4;           // I2GPS_v1 Board LED 1 (additional LED)
    const int ledPin2 = 7;           // I2GPS_v1 Board LED 2 (normally pin 13)
    const int GoButton = A0;         // Go button on A0
    
    //General purpose
    byte ErrorCode=0;                 // see DisplayError()
    byte FlashCount=0;                // Used in loop to count errorcode flashes
    byte Eventcounter =0;             // counter used for looping Events
    byte Channelcounter =0;           // counter used for looping Events
    byte EventChannels=0;             // byte to hold the binary of the channels for the Event
    boolean GoButtonState = false;    // Used to detect if sketch has started
        
    // Channel OFF codes 1 to 8 for Watts Clever. Add 8 for ON code 
    const unsigned long ChannelCodes[] = {13998166,13998164,13998162,13998176,13998178,13998180,13998182,13998192};
   
    // RTC related variables
    unsigned long RTCDateCalc;         //used to hold the manipulated reading from RTC
    unsigned long CardDateCalc;        //used to hold the manipulated reading from SD card
    byte RTCsetHour;                   //RTC Hour
    byte RTCsetMin;                    //RTC Minute
    byte RTCsetYear;                   //RTC Year
    byte RTCsetMonth;                  //RTC Month
    byte RTCsetDay;                    //RTC Day
    byte DOW;                          //RTC Day of Week mon =1
    String RTCsetDOW;                  //RTC Day of the week (1=mon, 7=sun), Minder.txt uses mon, tue to avoid confusion
    char* DaysOfWeek[]={"mon", "tue", "wed", "thur", "fri", "sat", "sun"};
    bool Century;
    bool h12;
    bool PM;
    byte DoW;                            //Clock DOW (Day Of Week)
   
    // Timer variables
    int LastTime;                              // value to hold the last time we checked the read time
    int CurrentTime;                           // value to hold the current read time
    unsigned long LastTimerCheck = 0;          // Time the Timer was read.    
    unsigned long ChannelOn = 0;               // Time the Channel was turned On.
    boolean CodeSent = false;                  //used in StartUpCheck()

  
  void setup() 
  {
 
    Serial.begin(9600);
    showString(PSTR("Home Minder test Sketch\n"));
     
    pinMode(ledPin1, OUTPUT);
    pinMode(ledPin2, OUTPUT);
    pinMode(GoButton, INPUT);
    digitalWrite(GoButton, HIGH);          // activate the Pullup resistor
    pinMode(10, OUTPUT);                   // used by SD card
    pinMode(A3, OUTPUT);                   // used by Transmitter
    myFile.close();
    Wire.begin();

    if (!SD.begin(10)) 
    {
      showString(PSTR("initialization failed!\n"));
      ErrorCode = 13;                     // Error accesing card/file
      DisplayError();
    }    

    // Transmitter is connected to Arduino Pin A3 
    //mySwitch.enableTransmit(A3);                    // hardcoded in RCSwitch
  
    // Optional set protocol (default is 1, will work for most outlets)
    mySwitch.setProtocol(1);
    
    // Optional set number of transmission repetitions.
    // mySwitch.setRepeatTransmit(15);
    // We read the clock every 10 secs, therefore we repeat the send 6 times per minute, so not needed.
    
    myFile = SD.open("Minder.txt");
    ReadFile();
    myFile.close();
    ClockRead();
   
    if (RTCDateCalc < CardDateCalc)
    {
       ErrorCode = 5;            // set ErrorCode to show Clock was reset 
       ClockSet();               // set clock using setting in Minder.txt
       ClockRead();              // check to see it was able to be set
        
        if ((RTCDateCalc) != (CardDateCalc))
        {
          ErrorCode = 12;
          DisplayError();          //Can't access/set RTC so Fatal Error.
        }
    }
    
    StartUpCheck();                //Check to see if the sketch has been running, or waiting for the 'Go' button 
  }
    
 void loop(void)
 {
   
   if (GoButtonState == true)        // has sketch been started yet, but should never get here unless started.
   {
       if (ErrorCode >= 10)          // Errors above 10 are Fatal, but should never arrive here, so send it away.
       {
         DisplayError();
       }
       
       if ( (millis() - LastTimerCheck) > 10000)      // only need to check every 10.0 secs
       {
         LastTimerCheck = millis();
         ClockRead();              //Go check the RTC time.
         EventCheck();
          switch (ErrorCode)
          {
            case 0:
                digitalWrite(ledPin1, HIGH);
                delay (300);
                digitalWrite(ledPin1, LOW);
            break;
            case 2:
                do
                {
                  digitalWrite(ledPin1, HIGH);      //flash the short pulses to represent the units
                  delay(1000);
                  digitalWrite(ledPin1, LOW);
                  delay(500);
                  FlashCount ++;
                }
                while (FlashCount < 2);
                FlashCount =0;
            break;
            case 5:
                do 
                {
                  digitalWrite(ledPin1, HIGH);      //flash the short pulses to represent the units
                  delay(1000);
                  digitalWrite(ledPin1, LOW);
                  delay(500);
                  FlashCount ++;
                }
                while (FlashCount < 5);
                FlashCount =0;
            break;
          }
       }
   }
 }

void ReadFile()
  {
    if (!myFile)
    {
      ErrorCode = 13;
      return;
    }

    /* read lines from the file
            Channels =8
            Clock =2359
            Date =300812,mon
    */
     
   ReadFileHeader:                            //This is a label to send the loop back to
      if (myFile.available())
      {
        inChar = myFile.read();
           if(inChar < 0)
          {
            if (EventNum < 1)
            {
              ErrorCode = 14;              //Error in file no entries
            }
            return;                        // File is closed on return
            
          }
          if (inChar != 13)
          {
            fileString.concat(inChar);
            goto ReadFileHeader;
          }
            inChar = myFile.read();    //This should grab the LF char.
            n = fileString.length();
            fileString = fileString.toLowerCase();                    //ensure we know the characters
            linepos = fileString.indexOf('=');
            fileLineSub = fileString.substring(linepos +1, n);        //fileLineSub contains from the '=' onwards. 
            switch (linenum)
            {
              case 0:
                  //setCNLnum = fileLineSub.toInt();                      // Channels =8 (not used)
              break;
              
              case 1:
                  RTCsetHour = (fileLineSub.substring(0,2)).toInt();
                  RTCsetMin = (fileLineSub.substring(2,4)).toInt();
              break;
              
              case 2:
                  RTCsetDay = (fileLineSub.substring(0,2)).toInt();                        // extract DD and convert to an integer
                  RTCsetMonth = (fileLineSub.substring(2,4)).toInt();                      // extract MM and convert to an integer
                  RTCsetYear = (fileLineSub.substring(4,6)).toInt();                       // extract YY and convert to an integer
                  RTCsetDOW = fileLineSub.substring((fileLineSub.indexOf(','))+1, n);     // DOW =mon
                  CardDateCalc = ((RTCsetYear) *10000UL) + ((RTCsetMonth) *100) + (RTCsetDay);
              break;
            }

            fileString="";
            if (linenum <2)
            {
              linenum ++;
              goto ReadFileHeader;
            }

    /* first three lines read in, now to pick up the events max is 20 loaded as 0-19
          Time =2300,ON
          Channel =1,2,3,4,5,6,7,8
          Day =mon,tue,wed,thur,fri,sat,sun
    */
          linenum=0;

       ReadFileEvents:        //This is a label to send the loop back to

          inChar = myFile.read();
          if(inChar <= 0 || ((EventNum) > 19))     //sketch limited to 20 events, so ignore the end ones.
          {
             return;                                // File is closed on return
          }
          
          if (inChar != 13)                      //build up the string until a CR
          {
            fileString.concat(inChar);
            goto ReadFileEvents;
          }
            inChar = myFile.read();              //This should grab the LF char.
            n = fileString.length();
            fileString = fileString.toLowerCase();                    //ensure we know the characters
            linepos = fileString.indexOf('=');
            fileLineSub = fileString.substring(linepos +1, n);      //fileLineSub contains from the '=' onwards. 
                     
          switch (linenum)
          {
            case 0:
              // This adds either 4096 to the Event Time for OFF, and 8192 for an ON.
              if ((fileLineSub.indexOf('off'))> -1) 
              {
                fileLineSub = fileLineSub.substring(0, fileLineSub.indexOf(','));
                Event[EventNum][linenum] = 4096 + fileLineSub.toInt();        // line upto the ',' then turn in into a number and add 4096 for 'OFF'
              }
              if ((fileLineSub.indexOf('on'))> -1)
              {
                fileLineSub = fileLineSub.substring(0, fileLineSub.indexOf(','));
                Event[EventNum][linenum] = 8192 + fileLineSub.toInt();        // line upto the ',' then turn in into a number and add 8192 for 'ON'                
              }
            break;
            
            case 1:
              // This steps through the line and makes a binary number 0-254 based on each Channel
              
              // We modified 'fileLineSub' in the above case, so reload the substring.
              fileLineSub = fileString.substring(linepos +1, n);      //linepart contains from the '=' onwards. 
              
              if ((fileLineSub.indexOf("1"))> -1)
              {
                Event[EventNum][linenum]= 1+ (Event[EventNum][linenum]);
              }
              if ((fileLineSub.indexOf("2"))> -1)
              {
                Event[EventNum][linenum]= 2+ (Event[EventNum][linenum]);
              }
              if ((fileLineSub.indexOf("3"))> -1)
              {
                Event[EventNum][linenum]= 4+ (Event[EventNum][linenum]);
              }
              if ((fileLineSub.indexOf("4"))> -1)
              {
                Event[EventNum][linenum]= 8+ (Event[EventNum][linenum]);
              }
              if ((fileLineSub.indexOf("5"))> -1)
              {
                Event[EventNum][linenum]= 16+(Event[EventNum][linenum]);
              }
              if ((fileLineSub.indexOf("6"))> -1)
              {
                Event[EventNum][linenum]= 32+ (Event[EventNum][linenum]);
              }
              if ((fileLineSub.indexOf("7"))> -1)
              {
                Event[EventNum][linenum]= 64+ (Event[EventNum][linenum]);
              }
              if ((fileLineSub.indexOf("8"))> -1)
              {
                 Event[EventNum][linenum]= 128+ (Event[EventNum][linenum]);
              }              
            break;
            
            case 2:
              // This steps through the line and makes a binary number 0-127 based on each day
              if ((fileLineSub.indexOf("mon"))> -1)
              {
                Event[EventNum][linenum]= 1+ (Event[EventNum][linenum]);
              }
              if ((fileLineSub.indexOf("tue"))> -1)
              {
                Event[EventNum][linenum]= 2+ (Event[EventNum][linenum]);
              }
              if ((fileLineSub.indexOf("wed"))> -1)
              {
                Event[EventNum][linenum]= 4+ (Event[EventNum][linenum]);
              }
              if ((fileLineSub.indexOf("thur"))> -1)
              {
                Event[EventNum][linenum]= 8+ (Event[EventNum][linenum]);
              }
              if ((fileLineSub.indexOf("fri"))> -1)
              {
                Event[EventNum][linenum]= 16+(Event[EventNum][linenum]);
              }
              if ((fileLineSub.indexOf("sat"))> -1)
              {
                Event[EventNum][linenum]= 32+ (Event[EventNum][linenum]);
              }
              if ((fileLineSub.indexOf("sun"))> -1)
              {
                Event[EventNum][linenum]= 64+ (Event[EventNum][linenum]);
              }
              EventNum ++;
            break;
          }
          
          linenum ++;
          if (linenum ==3)
            {
              linenum =0;
            }
          fileString="";
          fileLineSub="";
          goto ReadFileEvents;
 
      }
      else
      {
        ErrorCode = 13;                     // Error accesing card/file
        myFile.close();
        return;
      }
  } 

  
  void StartUpCheck()
  {
  // This checks to see if the sketch is running for the first time
      if (RTCsetYear != (EEPROM.read(1)) || RTCsetMonth != (EEPROM.read(2)) ||  RTCsetDay != (EEPROM.read(3)))
      {
          if (ErrorCode ==5)
          {
            ErrorCode =0;                       // reset the code as not started yet.
          }
          digitalWrite(ledPin1, HIGH);          // Turn On Led1 to show no errors
          LastTimerCheck = millis();            //  Borrow the timer variable

          Channelcounter = 0;
          while (GoButtonState == false)
          {
            if (millis()-LastTimerCheck >=5000)   // Led1 should be on for 5 secs
            {
              digitalWrite(ledPin1, LOW);
              LastTimerCheck =0;                 // Reset the borrowed timer variable
            }
            
            if (digitalRead(GoButton) == LOW)
            {
              delay(100);      //check to see the button is pressed by waiting 100mS (We can accept a delay here)
              if (digitalRead(GoButton) == LOW)
              {
                 
                 GoButtonState = true;                  // Sketch has been started
                 EEPROM.write(1, RTCsetYear);           // Write it to EEPROM
                 EEPROM.write(2, RTCsetMonth);
                 EEPROM.write(3, RTCsetDay);
                 digitalWrite(ledPin1, LOW);            // Turn off the LED
                 LastTimerCheck =0;                     // Reset the borrowed timer variable
                 ClockSet();                            // Set the clock based on 'Minder.txt' setting
                 mySwitch.send(13998161, 24);           // All OFF command
                 digitalWrite(ledPin2, LOW);
                 showString(PSTR("Go Button pressed\n"));
                 return;                                // Exit here so we don't set ErrorCode at the end. 
              }
            }
            
            //Cycle thru codes at set rate
            if (LastTimerCheck ==0)
            {
              if (ChannelOn ==0 || (millis()-ChannelOn) > 10000)        // 10 seconds
              {
                digitalWrite(ledPin2, HIGH);              
                ChannelOn= millis();
                CodeSent = true;
                mySwitch.send(((ChannelCodes[Channelcounter])+8), 24);    //send on code
                showString(PSTR("Channel "));
                Serial.print(((Channelcounter)+1),DEC);
                showString(PSTR("  ON "));
              }
            }
             if (CodeSent == true && (millis()-ChannelOn) > 6000)        // 6 seconds
             {
                //send off code
                digitalWrite(ledPin2, LOW);
                CodeSent = false;
                showString(PSTR(" : OFF\n"));
                mySwitch.send(ChannelCodes[Channelcounter], 24);
                Channelcounter ++;
              if (Channelcounter == 8)          //Channelcounter is zero based, 8 channels max
              {
                Channelcounter =0;
              }
            }

          }
      }
      else
      {
        if (digitalRead(GoButton) == LOW)
            {
              delay(100);      //check to see the button is pressed by waiting 100mS (We can accept a delay here)
              if (digitalRead(GoButton) == LOW)
              {
                showString(PSTR("Go Button pressed\n"));
                GoButtonState = true;
                ErrorCode = 0;                    // Restart has been detected so set the ErrorCode
              }
            }
            else
            {
              //we have to assume we have started so change GoButtonState to true.
              GoButtonState = true;
              ErrorCode = 2;                    // Restart has been detected so set the ErrorCode
              showString(PSTR("Restart detected\n"));
            }
      }
    return;
  } 
  void showString (PGM_P s)
 {
   char c;
    while ((c = pgm_read_byte(s++)) != 0)
       Serial.print(c);
 }

 void EventCheck()
 {
     //This process will check each event against the time
      for (Eventcounter=0; Eventcounter <= 19; Eventcounter++)      //cycle thru 20 events
      {
       
        if (((Event[Eventcounter][1])-4096) == CurrentTime)
        {
          SendOffChannels();
        }
        if (((Event[Eventcounter][1])-8192) == CurrentTime)
        {
          SendOnChannels();
        }
      }
 return;
 }
      
 void SendOffChannels()
 {
                  
  if (Event[Eventcounter][3] & (1<< (DoW-1)) >0)          //Event[Eventcounter][2] will be binary 0-254 AND with DOW shifted 
  {
    // cycle through the channels and send the code from ChannelCodes[]
    EventChannels = Event[Eventcounter][2];
    for (Channelcounter=0; Channelcounter <= 7; Channelcounter++)                    //cycle thru 8 Channels
      {
        if (EventChannels & (1<< Channelcounter) )        //EventChannels will be binary 0-254 AND with Channelcounter shifted
        {
              digitalWrite(ledPin2, HIGH);
              mySwitch.send(ChannelCodes[Channelcounter], 24);
              delay(300);                                //300mS delay here is subtracted by the millis() timers 
              digitalWrite(ledPin2, LOW);
        }
      } 
  }
  return;
 }

 void SendOnChannels()
 {
  if (Event[Eventcounter][3] & (1<< (DoW-1)) >0)          //Event[Eventcounter][2] will be binary 0-254 AND with DOW shifted 
  {
    // cycle through the channels and send the code from ChannelCodes[]
    EventChannels = Event[Eventcounter][2];
    
    for (Channelcounter=0; Channelcounter <= 7; Channelcounter++)                    //cycle thru 8 Channels
      {
        if (EventChannels & (1<< Channelcounter) )        //EventChannels will be binary 0-254 AND with Channelcounter shifted
        {            
              digitalWrite(ledPin2, HIGH);
              mySwitch.send((ChannelCodes[Channelcounter]+8), 24);
              delay(300);                                //300mS delay here is subtracted by the millis() timers 
              digitalWrite(ledPin2, LOW);
        }
      } 
  }
  return;
 }

    
    void DisplayError()
    {
      /*
      Flashes the LED to show an error, so delays are acceptable.
      It should stay in this mode, until reset by power on/off.
      Tens are shown as a long, while units are a short 
      
      Non Fatal Error Codes:
      2  Signals a restart.
      5  Clock was reset to card value.

      Fatal Error Codes:
      12  Unable to access/set RTC        (long and 2 shorts)
      13  Error accesing card/file        (long and 3 shorts)
      14  Error in file                   (long and 4 shorts)

      */
    
       // This flashes the LED to the number of the ErrorCode
      if (ErrorCode >= 10)
      {
        showString(PSTR("Fatal Error : Code "));
        Serial.println(ErrorCode,DEC);
          do
          {
            digitalWrite(ledPin1, HIGH);         //display the long pulse to represent the 10
            delay(1000);
            digitalWrite(ledPin1, LOW);
            
            for (int x=10; x <= (ErrorCode); x++)
            {
              digitalWrite(ledPin1, HIGH);      //flash the short pulses to represent the units
              delay(300);
              digitalWrite(ledPin1, LOW);
              delay(500);
            }
            delay(2000);
    
          } while (ErrorCode >10);              //Errorcode 10 or above, which stops it leaving.

      }
    }
    
    void ClockSet()
    {
        Clock.setClockMode(false);           // set to 24h
        Clock.setYear(RTCsetYear);
    Clock.setMonth(RTCsetMonth);
    Clock.setDate(RTCsetDay);
        for (int z=0; z < 6; z++)              //cycle thru DaysOfWeek array to find a match
        {
          if (String(DaysOfWeek[z])== RTCsetDOW)
          {
            Clock.setDoW(z +1);                  //DaysOfWeek array is zero based so add 1, to get mon=1
          }
        }
        Clock.setHour(RTCsetHour);             //setRTCtime 2359
    Clock.setMinute(RTCsetMin);
        Clock.setSecond(00);
    
  return;  
  }
    
    
  void ClockRead()
  {
    DoW = Clock.getDoW();
        RTCDateCalc = ((Clock.getYear()) *10000UL) + ((Clock.getMonth(Century))*100) + (Clock.getDate());
        CurrentTime = ((Clock.getHour(h12, PM)) *100) + (Clock.getMinute());

  return;
  }

 

Looking at some of the code I can already see small increments to help save memory.

ie the Channel Codes have a common 13998 at the front, so I only needed to save the last three digits as a byte, rather than an unsigned long, and simply add it before transmitting.

 

Video

This will be an interesting exercise.

The outlets can't be placed too close to each other as they interfere, so the placement for a video to cpature both might need some thinking.

 

*** video to come ****

 

I do have some photos showing a single light placed in the Hobby area which is near the front door.

Yes my plug looks rather battered ...

 

The result of just this light on from outside is :-

 

So careful placement of the light could give anyone the impression that someone was home.

 

 

Feel free to ask questions ... after you download the zip file ... and I'll do my best to answer them.

 

Mark