SMS generator control with Arduino

Driving 20km every day just to turn the generator and water pump on was becoming a drag, so I built an arduino solution with a GPRS shield, relay board, current sensor and real time clock to be able to turn them on remotely and monitor error conditions.

During testing I must have put too much current through one of the Phidgets sensors caused it stopped working properly, would only close, but not open – so switched it out for a 10A solid state version.  Have been using the SMS command system for a week now without a hitch, basically I can start the generator, stop it, query it’s status and turn the timer on or off.  If the unit detects any error condition, it will immediately shut the generator off and send an SMS alert.

The unit also has a built in warm up and cool down time for the generator and uses the current sensor to detect if the well is dry, i.e. if well is dry, pump stops, so current sensors can detect this and the arduino will shut down the generator.  The current sensor will also detect if the generator didn’t come on or if the pump failed to turn on – or if the generator stopped when it should be running.  All result in an error SMS being sent.

Had to mount the Arduino on the ceiling as the antenna cable wasn’t long enough

Relay box as close to the thick cabling as possible

Complete source code below, if anyone needs to rip bits out of it.  Note that you’ll need to change the phone number, and the password “1234″, this password has nothing to do with the sim card, it’s just a number I’ve picked so that only people who know it can send messages to the generator.


#include "Wire.h"
#include 
#define DS1307_I2C_ADDRESS 0x68
#define maxLength 200
#define PHONE 11111111111

String inString = String(maxLength);
int HOUR = 21;
int MINUTE_START = 01;
int MINUTE_STOP = 55;
boolean DEBUG = false;
unsigned long starttime = 0;
const unsigned long OVERRUN_TIME = 2UL*60UL*60UL*1000UL;

const int GEN_START_PIN = 10;
const int LOAD_PIN = 11;
const int LED = 13;
int GEN_START_DELAY = 20000;
int GEN_STOP_DELAY = 20000;
const int RETRY_LIMIT = 3;
const int CURRENT_SENSOR_PIN = 2;
const double MINIMUM_LOAD = 0.2;
long WARMUP_DELAY = 60000;  

long LOAD_DELAY = 10000;
const int GPRSonModulePin = 2;
const double PUMP_LOAD = 4.0;
double load = 0.0;
boolean errorSent = false;
boolean stoppedInWindow = false;

boolean timerOn = true;

int genState = 0; //0 = off, 1=running
int pumpCommandState = 0;
int genCommandState = 0; //0 = stop, 1 = start

int error = 0;
char* errorMsg[] = {"No error","Pump failed to start","Generator failed to start","Generator failed to stop","Generator overrun","Generator still running after stop signal"};
char* msg = "";

void getSerialChars() {
  while (Serial.available() > 0) {
    char inChar = Serial.read();
    if (inString.length() < maxLength) {
       inString.append(inChar);
    }
  }
}

// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
  return ( (val/10*16) + (val%10) );
}

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ( (val/16*10) + (val%16) );
}

void setDateDs1307(byte second,        // 0-59
                   byte minute,        // 0-59
                   byte hour,          // 1-23
                   byte dayOfWeek,     // 1-7
                   byte dayOfMonth,    // 1-28/29/30/31
                   byte month,         // 1-12
                   byte year)          // 0-99
{
   Wire.beginTransmission(DS1307_I2C_ADDRESS);
   Wire.send(0);
   Wire.send(decToBcd(second));    // 0 to bit 7 starts the clock
   Wire.send(decToBcd(minute));
   Wire.send(decToBcd(hour));      // If you want 12 hour am/pm you need to set
                                   // bit 6 (also need to change readDateDs1307)
   Wire.send(decToBcd(dayOfWeek));
   Wire.send(decToBcd(dayOfMonth));
   Wire.send(decToBcd(month));
   Wire.send(decToBcd(year));
   Wire.endTransmission();
}

// Gets the date and time from the ds1307
void getDateDs1307(byte *second,
          byte *minute,
          byte *hour,
          byte *dayOfWeek,
          byte *dayOfMonth,
          byte *month,
          byte *year)
{
  // Reset the register pointer
  Wire.beginTransmission(DS1307_I2C_ADDRESS);
  Wire.send(0);
  Wire.endTransmission();
  Wire.requestFrom(DS1307_I2C_ADDRESS, 7);

  // A few of these need masks because certain bits are control bits
  *second     = bcdToDec(Wire.receive() & 0x7f);
  *minute     = bcdToDec(Wire.receive());
  *hour       = bcdToDec(Wire.receive() & 0x3f);  // Need to change this if 12 hour am/pm
  *dayOfWeek  = bcdToDec(Wire.receive());
  *dayOfMonth = bcdToDec(Wire.receive());
  *month      = bcdToDec(Wire.receive());
  *year       = bcdToDec(Wire.receive());
}

void switchModule(){
  digitalWrite(GPRSonModulePin,HIGH);
  delay(2500);
  digitalWrite(GPRSonModulePin,LOW);
  delay(25000);
  if (DEBUG) {
    Serial.println("GPRS module turned on");
  }
}

boolean genInTimerWindow() {
    byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
    getDateDs1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
    if (DEBUG) {
       Serial.print("Time: ");
       Serial.print(hour,DEC);
       Serial.print(":");
       Serial.println(minute,DEC);
    }
    if (hour != HOUR) {
      stoppedInWindow = false;
    }
    if ((hour == HOUR) && (minute >= MINUTE_START) && (minute <= MINUTE_STOP)) {
      return true;
    } else {
      return false;
    }
}

void startGen() {
  if (DEBUG) {
     Serial.println("Starting gen");
  }
  digitalWrite(GEN_START_PIN, HIGH);
  genState = 1;
}

void stopGen() {
  if (DEBUG) {
     Serial.println("Stopping gen");
  }
  digitalWrite(GEN_START_PIN, LOW);
  genState = 0;
}

void connectLoad() {
   if (DEBUG) {
      Serial.println("Connecting load");
   }
   digitalWrite(LOAD_PIN, HIGH);
}

void disconnectLoad() {
   digitalWrite(LOAD_PIN, LOW);
   if (DEBUG) {
      Serial.println("Disconnecting load");
   }
}

double readLoad() {
  int load = analogRead(CURRENT_SENSOR_PIN);
  return (((double)load)*25.0/1023.0);
}

void deleteAllMsgs() {
  delay(1500);
  Serial.println("AT+CMGD=0,4");
  delay(4000);
}

int smsCommand() {
  int retval=0;
  delay(1500);
  Serial.print("AT+CMGL=");
  Serial.print(34,BYTE);
  Serial.print("REC UNREAD");
  Serial.println(34,BYTE);
  delay(3000);

  if (Serial.available() > 0) {
    inString = "";
    delay(500);
    getSerialChars();
    if (inString.contains("1234#")) {
      String command = String(20);
      command = inString.substring(inString.indexOf('#')+1,inString.length());
      if (command.contains("start")) retval = 1;
      if (command.contains("stop")) retval = 2;
      if (command.contains("clear")) retval = 3;
      if (command.contains("status")) retval = 4;
      if (command.contains("timer on")) retval = 5;
      if (command.contains("timer off")) retval = 6;
      if (retval != 0) deleteAllMsgs();
    }
  }

  return retval;
}

void sendSMSpreamble() {
    delay(1500);
    Serial.print("AT+CMGS=");
    Serial.print(34,BYTE);
    Serial.print(PHONE);
    Serial.println(34,BYTE);
    delay(1500);
}

void sendSMSappend() {
    delay(500);
    Serial.print(0x1A,BYTE);                // end of message command 1A (hex)
    delay(5000);
}

void sendErrorSMS() {
    sendSMSpreamble();
    Serial.print("Error: ");
    Serial.print(errorMsg[error]);
    writeStatus();
    sendSMSappend();
}  

void sendStopSMS() {
    sendSMSpreamble();
    writeStatus();
    sendSMSappend();
}

void writeStatus() {
    load = readLoad();
    Serial.println(msg);
    Serial.print("L: ");
    Serial.println(load);
    Serial.print("Gen: ");
    Serial.println(genState);
    Serial.print("Runtime: ");
    Serial.print((millis()-starttime)/60000UL);
    Serial.println(" min");
    Serial.print("Timer: ");
    if (timerOn) {
      Serial.print(HOUR);
      Serial.print(":");
      Serial.print(MINUTE_START);
      Serial.print(" - ");
      Serial.println(MINUTE_STOP);
    } else {
      Serial.println("Off");
    }
}    

void sendStatusSMS() {
  sendSMSpreamble();
  writeStatus();
  sendSMSappend();
}

void raiseError() {
  stopGen();
  sendErrorSMS();
}

void genStartSequence() {
  if (DEBUG) {
     Serial.println("Gen start sequence");
  }
  disconnectLoad();
  startGen();
  delay(GEN_START_DELAY);
  load = readLoad();
  if (DEBUG) {
      Serial.println(load);
  }
  if (load >= MINIMUM_LOAD) {
        starttime = millis();
        delay(WARMUP_DELAY);
        connectLoad();
        delay(LOAD_DELAY);
        load = readLoad();
        if (load > PUMP_LOAD) {
          if (DEBUG) {
              Serial.print("Pump came on");
          }
        } else {
          error = 1;
          if (DEBUG) {
            Serial.print(load);
            Serial.println(" was less than the pump load.");
          }
        }
   } else {
       error = 2;

       if (DEBUG) {
          Serial.print(load);
          Serial.println(" was less than the minimum load.");
        }

   }
}

void genStopSequence() {
  if (DEBUG) {
     Serial.println("Gen stop sequence");
  }
  disconnectLoad();
  delay(WARMUP_DELAY);
  stopGen();
  delay(GEN_STOP_DELAY);
  load = readLoad();
  if (load >= MINIMUM_LOAD) {
    error = 3;

  } else {
    sendStopSMS();
  }
}

void blinkLED() {
  digitalWrite(LED,HIGH);
  delay(1000);
  digitalWrite(LED,LOW);
}

void setup() {
  Serial.begin(19200);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, HIGH);
  pinMode(GEN_START_PIN, OUTPUT);
  digitalWrite(GEN_START_PIN, LOW);
  pinMode(LOAD_PIN, OUTPUT);
  digitalWrite(LOAD_PIN, LOW);  

  Wire.begin();
  delay(5000);

  starttime = 0UL;
  switchModule();                    // swith the module ON
  Serial.println("AT+CMGF=1");      

  if (DEBUG) {
    //byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
    //getDateDs1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
    //HOUR = hour;
    //MINUTE_START = minute;
    //MINUTE_STOP = MINUTE_START+1;
    WARMUP_DELAY=1000;
    LOAD_DELAY = 1000;

    GEN_START_DELAY = 2000;
    GEN_STOP_DELAY = 2000;
  }
  msg = "Module turned on.";
  //sendStatusSMS();
  msg = "";
  digitalWrite(LED, LOW);
}

void loop() {

  switch (smsCommand()) {
      case 1:
        genCommandState = 1;
        error = 0;
        timerOn = false;
        break;
      case 2:
        genCommandState = 0;
        stoppedInWindow = true;
        msg = "Stop signal from SMS";
        break;
      case 3:
        error = 0;
        break;
      case 4:
        msg = "";
        sendStatusSMS();
        break;
      case 5:
        timerOn = true;
        sendStatusSMS();
        break;
      case 6:
        timerOn = false;
        sendStatusSMS();
        break;
  }

 if (error == 0) {
    errorSent = false;

    if (genState == 0) {
       starttime = 0UL;
       if ((timerOn) && (genInTimerWindow()) && (!stoppedInWindow)) {
          if (DEBUG) {
            Serial.println("In time window");
          }
          genCommandState = 1;
       }
       if (genCommandState == 1) {
          genStartSequence();
       }
    } else {
       if ((timerOn) && !genInTimerWindow()) {
          genCommandState = 0;
          msg = "Stopped by timer";
       }
       if (readLoad() < PUMP_LOAD) {
          msg = "Stop signal from pump";
          if (DEBUG) {
            Serial.println(msg);
          }
          genCommandState = 0;
          stoppedInWindow = true;
       }
       unsigned long time = millis();
       if ((time - starttime) > OVERRUN_TIME) {
         error = 4;
       }
       if (genCommandState == 0) {
         genStopSequence();
       }
    }
 } else {
    if (!errorSent) {
      raiseError();
      errorSent = true;
    }
 }
 delay(2000);
 blinkLED();

 if (DEBUG) {
   Serial.print("gen state=");
   Serial.print(genState);
   Serial.print(" Load=");
   Serial.println(readLoad());
 }  

}
This entry was posted in Off grid power, water. Bookmark the permalink.

15 Responses to SMS generator control with Arduino

  1. Rogan says:

    Sweet! I didn’t realise your place was so big that you would have to drive 20km from one end to the other. Or is it just that the easiest way to get there involves a 20km detour?

    • stephen says:

      LOL, not that big, but we’re not currently living on the land! We’re living in town 20kms away, house plans are still in progress…. sloooooooowwwww progress…

  2. Xavier says:

    Perfecto, me funciona, estoy muy agradecido a este codigo. Eres magnifico.

    No podia leer los SMS que recibia.

    Estoy estudiando el codigo, ya que hay muchas cosas controladas que me gustan. Como por ejemplo que si el SMS recibido puede ser interpretado este SMS es borrado.

    Gracias.

  3. Peter Mounsey says:

    Brilliant! Best arduino project I’ve seen and shows some great coding.
    We have a campo house on Costa Dorada with diesel genny, solars and wind genny. Working on a logger for energy production and consumption. After that will be a smart pool and hot tub controller to switch the pumps and heaters on when most power is available.
    Keep up the good work.

  4. chris says:

    Would be interested to know all the components involved and some relative pricing.

    • stephen says:

      Hi Chris,

      It’s been a while and I canabalised some of the parts from what I had lying around, so the below is a rough estimate if you were starting from scratch.

      • Arduino – many places sell them, about 20 Euros
      • 2 relays – I used the overpriced phidgets one, but a better bet is the dual relay board from nuelectronics.com for 4 quid
      • GPRS shield from Libelium.com (now cooking-hacks.com) – about 50 Euros
      • 10A solid state relay by Celduc (or Celdac?), I need a powerful relay for the primary load which is a 1.5kW water pump – about 20 Euros
      • i-Snail current sensor from active-robots.com, about 40 quid, could probably get something similar elsewhere. I think seeedstudio.com have one at a much better price.
  5. chris says:

    Stephen,

    Thanks for the info. I have a remote cabin and this can really come in handy. Will post how it goes.

  6. Tom "CRUX" says:

    GREATE info here, I just used houres finding som arduino code for controlling som machinery at the plant I work…So this is very handy.

    And I might use it to control som funktions in my old military truck aswell….

    can I get your mail…then I will send you updates on the projects.

  7. Tom "CRUX" says:

    Hey again!
    I tryed out your arduino code (I am a newbee) and it faild in compiling what do I do wrong?

    What is your setup:
    Arduino uno ?
    Arduino Mega?
    And the GSM?

  8. Hemant says:

    Hi
    Very good to know ,but instead of ■GPRS shield from Libelium Iwana use Siemens mg35 modem pl. help
    I am able to send sms not able to receive in txt ,how to display on lcd using Arduino 328
    Thanks
    Ham NZ

    • stephen says:

      Don’t know anything about the Siemens modem. The easiest display I’ve worked with is a Serial Display, there are a few on the market. The advantage is that it only uses 2 IO pins instead of the many pins that the more common LCDs use.

  9. Alta says:

    Hi Stephen,

    thanks for share knowledge, please help me try the script error:
    inString.append (inChar);
    ‘String class’ has no member named ‘append’

    if (inString.contains (“ABCD #”)) {
    ‘String class’ has no member named ‘contains’

    what version of the Arduino I do not fit?
    my Arduino Mega1280, Arduino software version of the 022 and windows vista

    Thank you for helping

  10. Lee Gurley says:

    I have a Cabin that is off grid and I need a way to delay the generator power to the Cabin until the Generator gets up to full speed. The setup would delay the power to the main distribution panel inside. There is no automatic transfer switch and the generator is wired to the main distribution panel. The Generator is a 7kw propane unit.
    Thanks Lee

    • stephen says:

      Hi Lee,

      Some generator controllers have this functionality built in. If yours doesn’t have this feature, you could try asking the question on the wind&sun forum where there are many bright people living with generators/solar/wind, etc:

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>