Saturday, February 8, 2014

Current configuration brief wrap-up

OK, it's time to wrap up the SMS monitor based on arduino for the time being as I work on the internet-based version, which I am documenting in a separate blog.

The arduino is a Uno costing around $30.00

The GPRS/GSM modem board is a SIM900 gprs/gsm shield from  http://store.linksprite.com/, and cost $39.00.

The SIM card is a gophone card with pay as you go and I have a text plan that costs $10.00 per month for 1000 text messages.

I built the temperature and A/C power interfaces on an Arduino prototyping board. It connects to two 10K NTC temperature sensitive resistors for outside and inside temperature measurements. These connect to Arduino analog inputs 0 and 1. It connects via an opto isolator to a small cube-type 5V USB A/C power supply like those used for iPhone and MP3 charging, which supplies 5 V when A/C power is on and zero when off. This connects to Arduino digital input pin 3. A/C power is indicated by an led connected to pin 5.

The protoboard schematic is:

Power is supplied by a single USB cable that splits to provide power to the SIM900 GSM board, which requires more power than the Arduino board can supply, via a barrel connector and a standard USB B connector to Arduino. 

Here are some photos, invarious stages of undress:





And code:

//  Name

//       tweet_house_status_sec_hp
//
//  Description
//
//    Sketch to measure two temperatures and report via SMS both periodically and 
//    when changes greater than some threshold are observed.
//
//    Hardware needed:
//
//    Arduino (Uno, professional, etc.)
//    SIM900 gsm/gprs shield and activated sim card.
//
// Author: John Zouck
// Initial release date: November 24, 2013
//
#include <SIM900gsm.h>
#include <SoftwareSerial.h>
#include <gsm_timer_sec.h>
#include <thermistor.h>
#include <counts_last_period.h>
#include <Time.h>
#include <EEPROM.h>

static int house_power = 3;     // House power reported on pin 3
static int power_led = 5;       // Power led indicator is pin 5
static SoftwareSerial gprs(7,8);

#define TWEET_PERIOD_MINUTES 240   // 4 hours max per tweet
#define DELTA_TEMP 4.0             // delta temp to cause report: degrees F
#define TEMP_CHECK_TIME_SEC  2   // check temps and power every 2 seconds
#define TMIN_REPORT_TIME_MINUTES 1  // Report no more than once every 1 minutes.
#define SMS_CHECK_PERIOD_SECONDS 15
#define SMS_REPLY_TEST_DIVISOR  1


static gsm_timer_sec tcheck;
static gsm_timer_sec treport;
static gsm_timer_sec tchecksms;
// Last sampled temperature values.
static  float temp0;
static  float temp1;
// Calibraton factors, put in EEPROM.
static  float delta_temp0 = 0.0;
static  float delta_temp1 = 0.0;
static  int hp_last = 0;

static counts_last_period clp( 30 * 60 * 1000); // Limit to 10 SMS per 30 minutes

static char *buildtstr( unsigned long t, char *str);
static void checksms( float t1, float t2, char *reply );
static char *build_report( float t1, float t2, int hp, char *str);
static int report_temps( char *str);
static int calibrate( char *sms, char *sender);
static int rcals( float *cf1, float *cf2);
static int wcals( float *cf1, float *cf2);
static char *build_cal_report( float ct1, float ct2, char *str);

// Noise reduction for analog inputs via averaging values

static int analog_average( int ch, int n ) {
  int i;
  int sum = 0;
  for (i = 0; i < n; i++ ) {
    sum += analogRead( ch );
    delay (1);
  }
  return sum/n;
}

// The following #define is used so we can test without
// actually sending any messages.
#define GSM_SMS_ENABLE

void setup() {
  char str[100];
  unsigned long t;
  int rply;
  uint8_t tm[6];   // 0-5: yy mm dd hh mi ss

  gprs.begin(9600); // Slow down output from modem so we can keep up...

  Serial.begin(115200);

  SIM900poweron( gprs);
  Serial.println("Modem power on and sim card registered.");


  // Set Time package time from Modem BBU clock once
  // Use time package functions from now on so we
  // don't have to query modem each time we want time.

  // Get time and date from modem bbu clock once:

  rply = SIM900date_sec( gprs, &t, str, 95, tm);

  // Set time and date in software clock.
  // Args are: hh, mi, ss, dd, mm, yy
  // i.e. for 18:21:00 on November 23, 2013:
  //    setTime( 18, 21, 0, 23, 11, 13);

  setTime( tm[3], tm[4], tm[5], tm[2], tm[1], tm[0]);

  switch (timeStatus()) {
  case timeSet:
    Serial.print("Program tweet_house_status_sec started at time = ");
    Serial.println( buildtstr(now(), str));
    break;
  case timeNotSet: 
  case timeNeedsSync:
  default: 
    Serial.println("Time not synced.");
  }

  strcat( str, ": tweet_house_status_sec program startup.");
  // Erase all SMS
  //rply = SIM900eraseall( gprs, 0);
  pinMode( house_power, INPUT);
  pinMode( power_led, OUTPUT);
#ifdef GSM_SMS_ENABLE
  rply = SIM900tweet( gprs, str );
#endif

  // Read cal factors from EEPROM
  rcals( &delta_temp0, &delta_temp1);
  // Print cal factors
  Serial.println("EEPROM cal factors 1 and 2: ");
  Serial.println(delta_temp0, 4);
  Serial.println(delta_temp1, 4);

  tcheck.start(TEMP_CHECK_TIME_SEC); // seconds between temperature checks
  treport.start( TWEET_PERIOD_MINUTES * 60 ); // report period if no delta temp detected.
  tchecksms.start( SMS_CHECK_PERIOD_SECONDS/SMS_REPLY_TEST_DIVISOR);  // Period to check for new SMS
}


void loop () {
  float temp0_c;
  float temp1_c;
  int cnt;
  static float lt0 = -50.0;
  static float lt1 = -50.0;
  char report_s[76];
  int hp;
  
  // Read house power pin
  hp = digitalRead( house_power );
  if ( hp == 0 ) {
      digitalWrite( power_led, LOW);
  } else {
      digitalWrite( power_led, HIGH);
  }
  
  if ( tcheck.check()) {
    temp0 = thermistor_tempf (analog_average(0, 10)); // get value
    temp1 = thermistor_tempf (analog_average(1, 10)); // get value
    temp0_c = delta_temp0 + temp0;
    temp1_c = delta_temp1 + temp1;

    build_report( temp0_c, temp1_c, hp, report_s);

    if ( (abs( temp0_c - lt0 ) > DELTA_TEMP  || abs( temp1_c - lt1) > DELTA_TEMP ||
      treport.check() || hp != hp_last) && !clp.check( millis()) ) {

      hp_last = hp;
        Serial.println( report_s );

#ifdef GSM_SMS_ENABLE
      report_temps( report_s);
#endif

      lt0 = temp0_c;
      lt1 = temp1_c;
      treport.start( TWEET_PERIOD_MINUTES * 60);
    }
    tcheck.start( TEMP_CHECK_TIME_SEC );
  }

  if ( tchecksms.check() ) {
    checksms(report_s);
    tchecksms.start( SMS_CHECK_PERIOD_SECONDS / SMS_REPLY_TEST_DIVISOR);
  }
}

// See if there are any SMS messages waiting. Reply with "reply"
// argument and erase SMS if so.

static void checksms( char *reply ) {
  int cnt;
  int rply;
  int smslist[31];
  char sms[200];
  char calreport[70];

  Serial.print("In checksms(), freeRam: ");
  Serial.println( freeRam() );

  cnt = SIM900getsmsixl( gprs, smslist, 32, 10000);
  if ( cnt > 0 ) 
  {
    char sender[20];
    for ( int i = 0; i < cnt; i++) {
      SIM900getsms( gprs, smslist[i], sms, 190, 5000);
      //Serial.println(sms);
      SIM900getsender( sms, sender ); 
      if ( calibrate( sms, sender) ) {
        build_cal_report( delta_temp0, delta_temp1, calreport);
#ifdef GSM_SMS_ENABLE
          SIM900sendsms( gprs, calreport, sender);
#endif
      } 
      else {
        // Reply, but not to twitter (40404)
        if ( NULL == strstr( sender, "40404") ) {
          Serial.print("Replying to sender: ");
          Serial.println( sender);
#ifdef GSM_SMS_ENABLE
          SIM900sendsms( gprs, reply, sender);
#endif
        }
      }
    }

#ifdef GSM_SMS_ENABLE
    SIM900eraseall( gprs, 0);
#endif

  }

// Calibrate therms t1 and t2 by sending SMS with text "CALT1=xx.x" or
// "CALT2=yy.y" where xx.x and yy.y are actual temperatures, like 32.0 if
// therm is in ice bath at 32 degrees.

static int calibrate( char *sms, char *sender) {
  float ct1;
  char *calt1;
  float ct2;
  char *calt2;
  int calrcvd = 0;

  //Serial.println("calibrate() called.");
  if ( NULL != (calt1 = strstr( sms, "CALT1=") )) {
    //Serial.print("Got a CALT1= SMS to calibrate temperature T1 is: ");
    ct1 = strtod( calt1 + 6, NULL);
    //Serial.println(ct1);
    delta_temp0 = ct1 - temp0;
    // Write to EEPROM
    wcals(&delta_temp0, &delta_temp1);
    calrcvd = 1;
  }
  if ( NULL != (calt2 = strstr( sms, "CALT2=") )) {
    //Serial.print("Got a CALT2= SMS to calibrate temperature T2 is: ");
    ct2 = strtod( calt2 + 6, NULL);
    //Serial.println(ct2);
    delta_temp1 = ct2 - temp1;
    // Write to EEPROM
    wcals(&delta_temp0, &delta_temp1);
    calrcvd = 1;
  }
  return calrcvd;
}
// Report temperatures via SMS
// Right now we just tweet to 40404. Later we might add sending
// SMS to a list of cell phones as well.

static int report_temps( char *str) {
  int rply;

  Serial.println("Tweeting: ");
  Serial.println(str);

  rply = SIM900tweet( gprs, str );
}

// Build string with leading time and date (current time) and
// temperatures t1 and t2, like:
//
//   "11/24/2013 12:03:52    48.3 F and    33.6 F."
//
// Need min. of 46 characters space in char array str points to.

static char *build_report( float t1, float t2, int hp, char *str) {
  char ts[15];
  unsigned long t;

  buildtstr( now(),str);
  dtostrf( t1, 7, 1, ts);
  strcat( str, ts );
  strcat( str, " F and ");
  dtostrf( t2, 7, 1, ts);
  strcat( str, ts );
  strcat( str, " F, Power: ");
  if ( hp ) {
     strcat( str, "ON");
  } else {
      strcat( str, "OFF");
  }  
  return str;
}

// Build string with leading time and date (current time) and
// calibration constants.
//   "11/24/2013 12:03:52    -1.2 F and    2.6 F."
//
// Need min. of 66 characters space in char array str points to.

static char *build_cal_report( float ct1, float ct2, char *str) {
  char ts[15];
  unsigned long t;

  buildtstr( now(),str);
  strcat( str, " Calibration consts: ");
  dtostrf( ct1, 7, 1, ts);
  strcat( str, ts );
  strcat( str, " F and ");
  dtostrf( ct2, 7, 1, ts);
  strcat( str, ts );
  strcat( str, " F.");
  return str;
}


// Build time string from time t.
// buildtstr( now(), str) returns:
//       "11/24/2013 12:03:52"


static char *buildtstr( unsigned long t, char *str) {
  sprintf( str, "%02d/%02d/%02d %02d:%02d:%02d ", month(t),
  day(t), year(t), hour(t), minute(t), second(t));
  return str;
}

static int wcals( float *cf1, float *cf2) {
  // write 4-byte floats to locations 0 and 4 in EEPROM
  for (int i = 0; i < 4; i++) {
    EEPROM.write(i, *((byte *) cf1 + i));
  }
  for (int i = 4; i < 8; i++) {
    EEPROM.write(i, *((byte *) cf2 + i - 4));
  }
}

static int rcals( float *cf1, float *cf2) {

  // read 4-byte float 0.0 from locations 0 and 4 in EEPROM
  for (int i = 0; i < 4; i++) {
    *((byte *) cf1 + i) = EEPROM.read(i);
  }
  for (int i = 4; i < 8; i++) {
    *((byte *) cf2 + i - 4) = EEPROM.read(i);
  }
  //Serial.println("EEPROM cal factors 1 and 2: ");
  //Serial.println(*cf1, 4);
  //Serial.println(*cf2, 4);
  //Serial.println("Done!");

}