GSM Thermostat – Software

This is my first “big” Arduino project so I decided to split the application into modules (read C++ classes), hoping to reuse these modules in future projects. So You will find a TempSensor_AD22100.cpp, a LatchedRelais.cpp, a SerialDebug.cpp, a ModemGSM.cpp and some other.

The Arduino IDE  is very good for small projects, but it shows it’s limits if you have multiple files and You want to navigate or jump from one definition to another.
I’ve found this great free tool that turns Visual Studio 2010 into a wonderful Arduino IDE.

Here You can find the source code

Commands

The Thermostat recognizes the following commands:

  • ON <PIN>,<Temperature> Activates the Thermostat function and set the desired temperature.
  • OFF <PIN> Deactivates the Thermostat function
  • STATUS Reports the Thermostat status and room temperature
  • CHPIN <old PIN>, <new PIN> changes the PIN
  • MAINPHONE <PIN>, <Y|N> if Y is specified the sending telephone number is recorded and an informational SMS is sent when the thermostat resumes from a black out
  • RESET <PIN> resets to initial status

The Thermostat acknowledges every command with an SMS with the execution status of the command.

The PIN is a four digits security code, and is stored in the EEPROM of the Arduino chip.

Pitfalls

As this application needs a lot of timeouts, I’ve designed the Timeout class to make the code more readable. The Timeout class uses the millis() function to calculate the timeout deadline. One pitfall using the millis() function is that the counter overflow that occours every 49.7 days. Usually this is not a big issue but the thermostat is a  7 x 24 x a lot of miles far away application, so we have to take care of it. Take a look at the source code to discover the solution to this subtle problem.

Another pitfall is about RAM memory consumption and the usage of strings. May be not so apparent but when You write a sentence like this:

 printf("Queuing SMS --> ");

You’re “wasting” RAM space, because the compiler, in order to perform operations on the stack and the heap with the literal address,  allocates the string literal into the RAM space. This can exaust you RAM (2Kb for the ATMEGA328) very very quickly.
The solution is to use the xxxx_P functions that, at the cost of extra machine cycles, can copy strings literals stored in the flash memory (32Kb for the ATMEGA328)  into RAM space.

 void Debug_P(PGM_P pStr, boolean pLN)
 {
     char *str = (char *)malloc(strlen_P(pStr) + 1);
     if(str)
     {
         strcpy_P(str, pStr);
         if(pLN)
             DebugSerial.println(str);
         else
             DebugSerial.print(str);
         free(str);
     }
}

the PGM_P is a pointer to a string stored in flash. The strcpy_P takes as first parameter a char * as usual, but the second parameter is PGM_P, allowing the literal to cross the memory spaces bounds.

sprintf_P(tmpStr,PSTR("\"%s\" OK [%s C]"),upperStr, temp);

The PSTR macro instructs the compiler to place the string literal in flash memory.
Writing code in this way requires a little discipline but I think this is worthwhile (and is a must if the project is a little fat), and remember that macros, used properly, can be your friends.

SMS Handling

The main logic of the thermostat (in the .pde file) reads the temperature sensor, processes commands received through SMS,  changes the relais state when needed, handles Leds, and sends confirmation and informational SMS to the user.

After some tought I decided to use an asynchronous model to handle the SMS modem communication. This means that when a SMS is sent the control is immediately returned to the caller without waiting for a response;  command responses and URC are periodically processed in the main loop through a call to the Dispatch method of the ModemGSM class, that reads the input from the GSM Modem serial line, and sets its members variables to reflect status changes.

void loop()
{
    //read current temperature
    LastTemp = TSense.ReadTemperatureInCelsius();            

    //Process modem events
    GSMModem.Dispatch();

    ......

    //If SMS is available, try to handle a command
    //If not Handle the Thermostat logic
    if(GSMModem.IsSMSAvailable())
    {
        TSMS item;

        if(GSMModem.SMSDequeue(&item))
            HandleCommand(&item);
    }
    else
        HandleThermostatLoop();

To make SMS handling asynchronous, I used two queues one for incoming and one for outgoing  SMS.  Often the thermostat function may need to send a SMS when the GSM modem is not ready to send (this may happen at any time because a command is already in progress, or network is not available), so the SMS is posted into the Out Queue avoiding to block the thermostat function. The SMS Out queue is checked in the Dispatch method and when possible pending SMSs are sent. The SMS In queue can be checked for incoming SMS  after calling the Dispatch method.

In some case the thermostat function needs to execute an action with the modem and block execution until the result from the modem is ready (synchronous).

sprintf_P(tmpStr,PSTR("\"%s\" %s"), upperStr,
         (GSMModem.SetPBEntryAtIndex(1, tmp) ? "OK" : "ERROR"));

This may lead to a “out of sequence URC problem”, that means that while you expect an AT command result like “OK” or “ERROR” to return control to the thermostat calling function, You may receive something like “+CIEV 1,2″.

boolean ModemGSM::WaitOK(unsigned int pTimeoutMS)
{
    DEBUG_P("WaitOK --> ");

    for(;;)
    {
        if(Readln(pTimeoutMS, false) == TIMEOUT)
	{
	    DEBUGLN_P("TIMEOUT");
	    return false;
	}

        if(strcmp_P(FRXBuff,PSTR("OK")) == 0)
	{
	    DEBUGLN(FRXBuff);
	    return true;
	}
	else if(strcmp_P(FRXBuff,PSTR("ERROR")) == 0)
	{
	    DEBUGLN(FRXBuff);
	    return false;
	}
	else if(strncmp_P(FRXBuff,PSTR("+CME ERROR:"), 11) == 0)
	{
	    DEBUGLN(FRXBuff);
	    return false;
	}
	else
	{
	    DEBUG_P("Queuing URC --> ");
	    DEBUGLN(FRXBuff);

	    FURCQueue.Enqueue(FRXBuff);
	}
    }
}

The solution to this problem is to post the unespected URC in a Queue and continue to wait for the command response. The URC Queue is processed in the Dispatch function before checking the serial line.
If  a SMS send fails, a sequence of timeout and retry is issued; after some time the SMS is discarded. If during this phase an new SMS is generated by the thermostat function it’s placed in the Out queue and will be processed asap.

Hardware Devices

The two hardware devices, the relais and the temperature sensor are handled by two classes LatchedRelais and TempSensorAD22100. These classes are pretty straightforward and reusable.

Logging

Logging is a fundamental debug tool, so I’ve exploited the SoftwareSerial capability of Arduino to gain a new serial line for logging debug messages. Logging eats up a considerable amount of code space (so many literal strings ….), so it is possible to disable (and free code space) with a conditional compilation define (See SerialDebug.h), and squeeze more code at the cost of reduced debug capabilities.
On eBay You can find very cheap USB to TTL serial adapters,  for less than 4$.

Final considerations

These are the basic guidelines for Thermostat project; there are a lot of details I left out that You can find examining the source code.

This entry was posted in Arduino Projects, GSM Thermostat. Bookmark the permalink.

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>