A simple WIP that logs data from a Grove sensor, and can send and receive information over USB and SMS.

Dependencies:   DHT DS_1337 SDFileSystem USBDevice mbed

Files at this revision

API Documentation at this revision

Comitter:
Joseph Radford
Date:
Sun Apr 10 15:47:33 2016 +1000
Commit message:
Initial commit

Changed in this revision

DHT.lib Show annotated file Show diff for this revision Revisions of this file
DS_1337.lib Show annotated file Show diff for this revision Revisions of this file
Handlers/AbstractHandler.h Show annotated file Show diff for this revision Revisions of this file
Handlers/GprsHandler.cpp Show annotated file Show diff for this revision Revisions of this file
Handlers/GprsHandler.h Show annotated file Show diff for this revision Revisions of this file
Handlers/GroveDht22.cpp Show annotated file Show diff for this revision Revisions of this file
Handlers/GroveDht22.h Show annotated file Show diff for this revision Revisions of this file
Handlers/SdHandler.cpp Show annotated file Show diff for this revision Revisions of this file
Handlers/SdHandler.h Show annotated file Show diff for this revision Revisions of this file
Handlers/UsbComms.cpp Show annotated file Show diff for this revision Revisions of this file
Handlers/UsbComms.h Show annotated file Show diff for this revision Revisions of this file
Handlers/measurementhandler.cpp Show annotated file Show diff for this revision Revisions of this file
Handlers/measurementhandler.h Show annotated file Show diff for this revision Revisions of this file
SDFileSystem.lib Show annotated file Show diff for this revision Revisions of this file
USBDevice.lib Show annotated file Show diff for this revision Revisions of this file
circbuff.cpp Show annotated file Show diff for this revision Revisions of this file
circbuff.h Show annotated file Show diff for this revision Revisions of this file
config.h Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
readme.md Show annotated file Show diff for this revision Revisions of this file
rtc.cpp Show annotated file Show diff for this revision Revisions of this file
rtc.h Show annotated file Show diff for this revision Revisions of this file
timers.cpp Show annotated file Show diff for this revision Revisions of this file
timers.h Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DHT.lib	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,1 @@
+http://developer.mbed.org/users/Wimpie/code/DHT/#9b5b3200688f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DS_1337.lib	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,1 @@
+http://mbed.org/users/loovee/code/DS_1337/#f4f9b627adf9
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Handlers/AbstractHandler.h	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,34 @@
+#ifndef __ABSTRACT_HANDLER_H__
+#define __ABSTRACT_HANDLER_H__
+
+#include "../timers.h"
+
+/*!
+ * \brief The AbstractHandler class is inherited by all handlers. It forms the basis of any handler, by having
+ * a simple \a run function, called in main.cpp, and a \a setRequest function which is used to set a request
+ * specific to the reimplemented class.
+ */
+class AbstractHandler
+{
+public:
+    AbstractHandler(MyTimers *_timer) : m_timer(_timer) {}
+    ~AbstractHandler() {}
+
+    /*!
+     * \brief run is inherited by each Handler class. It is called from the main while loop in main.cpp.
+     * It runs through the state machine, specific to each Handler class.
+     */
+    virtual void run() = 0; // run the handler
+
+    /*!
+     * \brief setRequest sets a request that the handler will complete when it is able to
+     * \param request unique to the reimplemented class (an enum) that will be completed in the state machine
+     * \param message an optional array of information relevant to the \a request
+     */
+    virtual void setRequest(int request, void *data = 0) = 0;
+
+protected:
+    MyTimers *m_timer;  ///< Handler classes use timers to pause in the state machine, and continue after delay has finished
+};
+
+#endif // __ABSTRACT_HANDLER_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Handlers/GprsHandler.cpp	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,260 @@
+#ifdef ENABLE_GPRS_TESTING
+#include "GprsHandler.h"
+#include "mbed.h"
+#include "config.h"
+#include "UsbComms.h"
+#include "circbuff.h"
+#define TX_GSM P1_27
+#define RX_GSM P1_26
+
+// declare led3 to display GPRS handler state
+extern DigitalOut myled3;
+
+/*
+ * PINPWR to low on Q10 drives 3V3 to Q7, which drives Q5 to ground, which powers VBAT_900, with
+ * either VCC_BUCK or VCC_BAT
+ */
+#define PINPWR                  P1_2
+#define PINONOFF                P1_7
+
+#define REQ_SEND_SMS     0b00000001
+
+#define USB_BUFF_SIZE 256
+
+#define SIM900_SERIAL_TIMEOUT 10000
+
+GprsHandler::GprsHandler(MyTimers * _timer, UsbComms *_usb) : AbstractHandler(_timer)
+{
+    m_serial = new Serial(TX_GSM, RX_GSM);	// create object for UART comms
+    mode = gprs_Start;		// initialise state machine
+
+    m_sim900_pwr = new DigitalOut(PINPWR);		// create pin out for
+    m_sim900_on  = new DigitalOut(PINONOFF);
+    m_reqReg = 0;
+
+    m_usb = _usb;
+    m_rxBuff = new CircBuff(USB_BUFF_SIZE);
+
+    m_atReq = atreq_Test;
+}
+
+GprsHandler::~GprsHandler()
+{
+    // TODO Auto-generated destructor stub
+    delete m_serial;
+    delete m_rxBuff;
+    delete m_sim900_pwr;
+    delete m_sim900_on;
+}
+
+void GprsHandler::run()
+{
+    switch(mode)
+    {
+    case gprs_Start:
+
+        mode = gprs_PowerOff;
+        break;
+
+
+        // POWER HANDLERS
+
+    case gprs_PowerOff:
+        m_sim900_pwr->write(1);				// turn power supply off
+        m_sim900_on->write(1);
+        m_timer->SetTimer(MyTimers::tmr_GprsPower, 500);	// wait to settle
+        mode = gprs_PowerOffWait;
+        break;
+
+    case gprs_PowerOffWait:
+        if (!m_timer->GetTimer(MyTimers::tmr_GprsPower))
+        {
+            mode = gprs_PowerSupplyOn;		// timer has elapsed
+        }
+        break;
+
+    case gprs_PowerSupplyOn:
+        m_sim900_pwr->write(0);		// turn power supply on
+        m_sim900_on->write(0);		// from the ref: "drive the PWRKEY to a low level for 1 second then release."
+
+
+        m_timer->SetTimer(MyTimers::tmr_GprsPower, 1000);	// wait for one second
+        mode = gprs_PowerSupplyOnWait;						// go to wait state
+        break;
+
+    case gprs_PowerSupplyOnWait:
+        if (!m_timer->GetTimer(MyTimers::tmr_GprsPower))
+        {
+            mode = gprs_PowerSwitchOn;		// timer has elapsed
+        }
+        break;
+
+    case gprs_PowerSwitchOn:
+        m_sim900_on->write(1);		// release power key
+        m_timer->SetTimer(MyTimers::tmr_GprsPower, 500);	// wait to settle
+        mode = gprs_PowerSwitchOnWait;
+        break;
+
+    case gprs_PowerSwitchOnWait:
+        if (!m_timer->GetTimer(MyTimers::tmr_GprsPower))
+        {
+            mode = gprs_CheckATReqs;		// timer has elapsed
+        }
+        break;
+
+
+        // REQUEST HANDLERS
+
+    case gprs_CheckATReqs:
+        switch (m_atReq) {
+        case atreq_Test:
+            sprintf((char*)txBuf, "AT\r\n");
+            txBufLen = 4;
+            mode = gprs_PostTx;
+            break;
+
+        case atreq_CheckSMS:
+            sprintf((char*)txBuf, "AT+CMGL=\"ALL\"");
+            txBufLen = 13;
+            mode = gprs_PostTx;
+            break;
+
+        default:
+            m_atReq = atreq_Test;
+        }
+        break;
+
+
+        // TX/RX HANDLERS
+
+    case gprs_PostTx:
+        // use putc, other write functions in serial don't really seem to work
+        for (int i = 0; i < txBufLen; i++) {
+            m_serial->putc(txBuf[i]);
+        }
+
+        // make sure buffer is null terminated before printing to USB
+        txBuf[txBufLen+1] = 0;
+        m_usb->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, txBuf);
+
+        // clear the buffer
+        txBufLen = 0;
+
+        // set timeout
+        m_timer->SetTimer(MyTimers::tmr_GprsRxTx, SIM900_SERIAL_TIMEOUT);
+
+        // wait for a response
+        mode = gprs_WaitRx;
+        break;
+
+    case gprs_WaitRx:
+        if (m_timer->GetTimer(MyTimers::tmr_GprsRxTx))
+        {
+            // we have not timed out yet
+            // we need a generic rx handler here.
+            while (m_serial->readable())
+            {
+                char ch = m_serial->getc();
+
+                // save this to our rx circular buffer
+                m_rxBuff->putc(ch);
+
+                mode = gprs_CheckRx;
+    			wait(0.1);
+            }
+            // we have not timed out, and have not got anything back yet
+            // keep waiting in this state
+        }
+        else {
+        	m_usb->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, (char*)"SIM900 TIMEOUT!");
+            mode = gprs_RxTimeout;
+        }
+        break;
+
+    case gprs_CheckRx:
+        if (m_rxBuff->dataAvailable()) {
+
+            // read out
+            unsigned char s[50];
+            uint16_t len = m_rxBuff->read(s, 50);
+
+            // write to USB
+            m_usb->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, s);
+
+            // process the reply
+            switch(m_atReq) {
+            case atreq_Test:
+                // should have just gotten an ok back
+                bool bOk = false;
+                for (int i = 0; i < (len - 1); i++) {
+                    if ((s[i] == 'O') && (s[i+1] == 'K')) {
+                        bOk = true;
+                    }
+                }
+
+                if (bOk) {
+                    myled3 = 1;
+                    // so we know that comms are definitely OK.
+                    // now check to see what requests need to get fulfilled
+
+                    if (m_reqReg&REQ_SEND_SMS) {
+                        // we want to check what sms is
+                        m_atReq = atreq_SendSMS;
+
+                        m_reqReg &= ~REQ_SEND_SMS;
+                    }
+                    else {
+                        // no requests, but see if there are any received SMSs
+                        m_atReq = atreq_CheckSMS;
+                    }
+                }
+                else {
+                    // did not get the reply we were hoping for.
+                }
+                break;
+
+            default:
+                // todo: handle replies for checking/sending SMSs
+                m_atReq = atreq_Test;
+                break;
+            }
+        }
+        else {
+
+        }
+        m_timer->SetTimer(MyTimers::tmr_GprsRxTx, 2000);
+        // now that we're done here, go check what needs to get sent to the SIM900 next
+        mode = gprs_WaitUntilNextRequest;
+        break;
+        
+    case gprs_WaitUntilNextRequest:
+    	if (!m_timer->GetTimer(MyTimers::tmr_GprsRxTx)) {
+			mode = gprs_CheckATReqs;
+		}
+		break;
+    case gprs_RxTimeout:
+    case gprs_RxError:
+    default:
+        mode = gprs_Start;
+        break;
+
+
+    }
+}
+
+void GprsHandler::setRequest(int request, void *data)
+{
+    m_lastRequest = (request_t)request;
+    switch(request) {
+    case gprsreq_SmsSend:
+        GprsRequest *req = (GprsRequest*)data;
+
+        // make a copy
+        m_lastMessage = *req;     // there are strings, do i have to copy these manually?
+
+        // set the request
+        m_reqReg |= REQ_SEND_SMS;
+        break;
+    }
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Handlers/GprsHandler.h	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,112 @@
+#ifndef GPRSHANDLER_H_
+#define GPRSHANDLER_H_
+
+#include "config.h"
+#ifdef ENABLE_GPRS_TESTING
+/*
+ *  This section of the program is currently under development. The rest of the functions
+ * can run wtihout this component by using the ENABLE_GPRS_TESTING flag.
+ */
+
+#include "USBDevice.h"	// need to include this, so that USBSerial has correct typedefs!
+#include "USBSerial.h"
+#include "AbstractHandler.h"
+
+#define GPRS_BUF_LEN 20
+
+#define GPRS_MESSAGE_MAXLEN 160
+#define GPRS_RECIPIENTS_MAXLEN 20
+
+struct GprsRequest
+{
+    char message[GPRS_MESSAGE_MAXLEN];
+    char recipients[GPRS_RECIPIENTS_MAXLEN];
+};
+class UsbComms;
+class CircBuff;
+
+/*!
+ * \brief The GprsHandler class saves recipients and looks after incoming and outgoing messages
+ *
+ * Options: save recipients internally to this class.
+ * Or - request sends a struct that includes recipients list and message string
+ */
+class GprsHandler : public AbstractHandler
+{
+public:
+	GprsHandler(MyTimers * _timer, UsbComms *_usb);
+	virtual ~GprsHandler();
+
+	void run();
+
+    void setRequest(int request, void *data = 0);
+
+    enum request_t{
+        gprsreq_GprsNone,       ///< No request (for tracking what the last request was, this is initial value for that)
+        gprsreq_SmsSend,        ///< got a string to send to recipient(s)
+        gprsreq_SetRecipients   ///< got a string holding the number(s) we want to send
+    };
+
+private:
+    enum mode_t{
+        gprs_Start,          	///< Set up the state machine and the hardware
+
+        // Restart sequence
+        gprs_PowerOff,          ///< Ensure module is powered down first, so we know it is in its initial state
+        gprs_PowerOffWait,      ///< Give module time to power down
+        gprs_PowerSupplyOn,		///< Once module has fully powered down, power back up to begin using it
+        gprs_PowerSupplyOnWait,	///< Give time to power on properly
+        gprs_PowerSwitchOn,
+        gprs_PowerSwitchOnWait,
+
+        gprs_CheckATReqs,       ///< Check was AT request is next
+
+        // read AT+CMGR=?
+        // write AT+CMGS=?
+        gprs_PostTx,            ///< Send the current buffer to SIM900 and setup timeouts
+        gprs_WaitRx,            ///< Wait for a response over serial
+        gprs_CheckRx,           ///< Check the response. Go back into state machine depending on response
+        
+        gprs_WaitUntilNextRequest, 
+
+        gprs_RxTimeout,         ///< no response
+        gprs_RxError            ///< wrong response
+
+
+    };
+    mode_t mode;            ///< the current state in the state machine
+    mode_t returnMode;      ///< the state to go back to when the expected reply returns from the SIM900
+
+    ///
+    /// \brief The at_req enum stores what AT command should be sent/was just sent to the SIM900
+    ///
+    enum at_req {
+        atreq_Test,         ///< Ping the SIM900
+        atreq_CheckSMS,     ///< Check if an SMS is available
+        atreq_SendSMS       ///< Send an SMS, according to m_lastRequest
+    };
+    at_req m_atReq;
+
+    request_t m_lastRequest;
+    GprsRequest m_lastMessage;
+
+    Serial * m_serial; //!< Serial port for comms with SIM900
+
+    DigitalOut * m_sim900_pwr;	//!< pin used to enable the SIM900 power switch
+    DigitalOut * m_sim900_on;	//!< pin used to drive the power key
+
+    uint8_t m_reqReg;   ///< request register
+
+    unsigned char txBuf[GPRS_BUF_LEN];
+    uint16_t txBufLen;
+    unsigned char rxBuf[GPRS_BUF_LEN];
+    
+    UsbComms *m_usb;
+    CircBuff *m_rxBuff;
+    
+};
+
+#endif
+
+#endif /* GPRSHANDLER_H_ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Handlers/GroveDht22.cpp	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,121 @@
+#include "GroveDht22.h"
+#include "measurementhandler.h"
+
+#define GROVE_NUM_RETRIES 10        // number of retries for reading sensor
+
+DigitalOut grovePwr(P1_3);          // if anything else is interfaced to uart/adc/i2c connectors, this will have to change, as they share this enable line
+
+GroveDht22::GroveDht22(MeasurementHandler *_measure, MyTimers * _timer) : AbstractHandler(_timer), m_measure(_measure)
+{
+    // initialise class variables
+    mode            = dht_StartTurnOff;
+    _lastCelcius    = 0.0f;
+    _lastHumidity   = 0.0f;
+    _lastDewpoint   = 0.0f;
+    _lastError      = ERROR_NONE;
+    _retries        = 0;
+    _newInfo        = 0;
+
+    m_sensor = new DHT(P1_14,SEN51035P);        // Use the SEN51035P sensor
+}
+
+GroveDht22::~GroveDht22()
+{
+    delete m_sensor;
+}
+
+void GroveDht22::run()
+{
+    switch(mode)
+    {
+    case dht_StartTurnOff:
+        powerOn(false);
+        m_timer->SetTimer(MyTimers::tmr_GroveMeasure, 1000);     // leave off for 1 second
+        mode = dht_StartTurnOffWait;
+        break;
+
+    case dht_StartTurnOffWait:
+        if (!m_timer->GetTimer(MyTimers::tmr_GroveMeasure))      // wait until timer has elapsed
+            mode = dht_StartTurnOn;
+        // else come back here
+        break;
+
+    case dht_StartTurnOn:
+        powerOn(true);
+        m_timer->SetTimer(MyTimers::tmr_GroveMeasure, 1000);     // wait one second for sensor to settle
+        mode = dht_StartTurnOnWait;
+        break;
+
+    case dht_StartTurnOnWait:
+        if (!m_timer->GetTimer(MyTimers::tmr_GroveMeasure))      // wait until timer has elapsed
+            mode = dht_TakeMeasurement;
+        // else come back here
+        break;
+
+    case dht_TakeMeasurement:
+        _lastError = (eError)m_sensor->readData();     // take the measurement (todo: see if nonblocking is available)
+        if (_lastError == ERROR_NONE)
+        {
+            _retries = 0;                   // reset retries as measurement was successful
+            _lastCelcius = m_sensor->ReadTemperature(CELCIUS);
+            _lastHumidity = m_sensor->ReadHumidity();
+            _lastDewpoint = m_sensor->CalcdewPoint(_lastCelcius, _lastHumidity);
+            m_timer->SetTimer(MyTimers::tmr_GroveMeasure, 3000); // wait three seconds
+            
+            // add the date time
+            time_t _time = time(NULL); // get the seconds since dawn of time
+            Dht22Result data = {_time, _lastCelcius, _lastHumidity, _lastDewpoint};
+            m_measure->setRequest(MeasurementHandler::measreq_DhtResult, (void*)&data);
+            mode = dht_WaitMeasurement;
+        }
+        else
+        {
+            _retries++;     // there was an error. See if we have reached critical retries
+            if (_retries >= GROVE_NUM_RETRIES)
+            {
+                _retries = 0;
+                mode = dht_StartTurnOff; // restart the sensor
+            }
+            else
+            {
+                // haven't reach critical retries yet, so try again, after waiting
+                m_timer->SetTimer(MyTimers::tmr_GroveMeasure, 3000); // wait three seconds
+                mode = dht_WaitMeasurement;
+            }
+            int sendData = (int)_lastError; // just to make sure nothing funny happens with the enum
+            m_measure->setRequest(MeasurementHandler::measreq_DhtError, (int*)&sendData);
+        }
+        _newInfo = 1;
+        break;
+
+    case dht_WaitMeasurement:
+        if (!m_timer->GetTimer(MyTimers::tmr_GroveMeasure))  // if timer elapsed
+            mode = dht_TakeMeasurement;
+        // else come back here
+        break;
+
+    }
+}
+
+void GroveDht22::setRequest(int request, void *data)
+{
+    // no requests (yet)
+}
+
+// check if there is new information and reset the flag once the check occurs
+unsigned char GroveDht22::newInfo()
+{
+    unsigned char retval = _newInfo;
+    _newInfo = 0;
+    return retval;
+}
+
+// turn the enable pin for the peripheral plugins on the Arch GPRS v2
+// it is active low and acts on Q1
+void GroveDht22::powerOn(unsigned char ON)
+{
+    if (ON)
+        grovePwr = 0;   // active low, will turn power on
+    else
+        grovePwr = 1;   // will turn power off
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Handlers/GroveDht22.h	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,81 @@
+#ifndef __GROVE_DHT_22_H__
+#define __GROVE_DHT_22_H__
+
+#include "DHT.h"
+#include "mbed.h"
+#include "AbstractHandler.h"
+
+class MeasurementHandler;
+
+/*!
+ * \brief The Dht22Result struct is the information read from a Dht22 Grove sensor
+ */
+struct Dht22Result {
+    time_t resultTime;      ///< timestamp of when the result was returned from the Dht22
+    float  lastCelcius;     ///< Temperature result (degC)
+    float  lastHumidity;    ///< Humidity result
+    float  lastDewpoint;    ///< Dewpoint result
+};
+
+/*!
+ * \brief The GroveDht22 class handles the interface to the DHT22 humidity and temperature sensor.
+ *
+ * The state machine checks for errors and retries and will power cycle the sensor if there are
+ * GROVE_NUM_RETRIES number of retries.
+
+ * The state machine also ensures that at least two seconds is left between readings.
+
+ * At any time the parent class can access the last good readings, or the last error.
+
+ * The newInfo flag exists so that the parent can decide to only notify (print to terminal or otherwise) when there
+ * is new information available. Calling the newInfo getter will clear the newInfo flag.
+ */
+class GroveDht22 : public AbstractHandler
+{
+public:
+    GroveDht22(MeasurementHandler *_measure, MyTimers * _timer);
+    ~GroveDht22();
+
+    void run();
+
+    void setRequest(int request, void *data = 0);
+
+    // getters
+    float  lastCelcius()  { return _lastCelcius; }
+    float  lastHumidity() { return _lastHumidity; }
+    float  lastDewPoint() { return _lastDewpoint; }
+    eError lastError()    { return _lastError; }
+    unsigned char newInfo();
+
+private:
+    // state machine
+    typedef enum {
+        dht_StartTurnOff,       ///< Begin by ensuring the sensor is switched off
+        dht_StartTurnOffWait,   ///< Allow it to power down completely
+        dht_StartTurnOn,        ///< Turn the sensor on
+        dht_StartTurnOnWait,    ///< Allow sensor to settle after powering on
+        dht_TakeMeasurement,    ///< Take a measurement, check if valid, update measurment vars, set newInfo
+        dht_WaitMeasurement     ///< Wait for 2 seconds between measurements
+    } mode_t;
+
+    mode_t mode;            ///< The current state in the state machine
+
+    float _lastCelcius;     ///< Last temperature reading from the Dht22 sensor
+    float _lastHumidity;    ///< Last humidity reading from the Dht22 sensor
+    float _lastDewpoint;    ///< Last dewpoint calculation from last temp, humidity vales
+    unsigned char _newInfo; ///< This flag indicates there is new information (an error, or a measurement)
+    int _retries;           ///< Number of bad readings from the Dht22 sensor
+    eError _lastError;      ///< The last error, or lack thereof
+
+    /*!
+     * \brief powerOn powers the Dht22 on or off, by toggling the enable pin
+     * \param ON true to power on, false to power off
+     */
+    void powerOn(unsigned char ON);
+
+    MeasurementHandler *m_measure;  ///< Reference to send measurement results and errors to for handling
+
+    DHT *m_sensor;                  ///< Interface to hardware DHT sensor
+};
+
+#endif // __GROVE_DHT_22_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Handlers/SdHandler.cpp	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,224 @@
+#include "SdHandler.h"
+
+#include "GroveDht22.h" // for interpreting the result struct
+#include "circbuff.h"
+
+#define SD_BUFFER_LEN 256u   // length of circular buffers
+
+// define pins for communicating with SD card
+#define PIN_MOSI        P1_22
+#define PIN_MISO        P1_21
+#define PIN_SCK         P1_20
+#define PIN_CS          P1_23
+
+#define DATA_FILE_NAME   "/sd/data.csv"
+#define SYSLOG_FILE_NAME "/sd/log.txt"
+
+// declare led that will be used to express state of SD card
+extern DigitalOut myled2;
+
+SdHandler::SdHandler(MyTimers * _timer) : AbstractHandler(_timer)
+{
+    // init file system and files
+    m_sdfs = new SDFileSystem(PIN_MOSI, PIN_MISO, PIN_SCK, PIN_CS, "sd");
+    m_data = NULL;
+    m_syslog = NULL;
+
+    // initialise circular buffers
+    m_dataLogBuff = new CircBuff(SD_BUFFER_LEN);
+    m_sysLogBuff  = new CircBuff(SD_BUFFER_LEN);
+    
+    mode = sd_Start;
+    m_lastRequest = sdreq_SdNone;
+}
+
+SdHandler::~SdHandler()
+{
+    delete m_dataLogBuff;
+    delete m_sysLogBuff;
+}
+
+void SdHandler::run()
+{
+    uint16_t tempInt = 0, tempInt2 = 0;
+
+    unsigned char tempBuff[SD_BUFFER_LEN];
+
+    switch(mode)
+    {
+    case sd_Start:              /* Set up the state machine */
+        // check if card is inserted??
+        //m_sdfs->unmount();
+        // close both files if necessary
+        if (m_data != NULL)
+            fclose(m_data);
+        if (m_syslog != NULL)
+            fclose(m_syslog);
+
+        m_data = NULL;
+        m_syslog = NULL;
+
+        //mkdir("/sd", 0777);
+        //m_sdfs->mount();
+        m_data = fopen(DATA_FILE_NAME, "a");
+        m_syslog = fopen(SYSLOG_FILE_NAME, "a");
+
+        if ((m_data != NULL) && (m_syslog != NULL))
+        {
+            // both opened successfully
+            fprintf(m_syslog, "Unit booted OK\n");
+            fclose(m_syslog);
+            
+            // write the header in on startup
+            fprintf(m_data, "Timestamp, Temperature (degC), Humidity (pc), Dewpoint\n");            
+            fclose(m_data);
+            mode = sd_CheckSysLogBuffer;
+        }
+        break;
+
+    case sd_CheckSysLogBuffer:     /* See if any data should be written to the log file */
+        if (m_sysLogBuff->dataAvailable())
+        {
+            tempInt = m_sysLogBuff->read(tempBuff, SD_BUFFER_LEN);
+            tempInt2 = fprintf(m_syslog, (const char*)tempBuff);
+            if (tempInt == tempInt2)
+            {
+                // success
+                mode = sd_CheckDataLogBuffer;
+            }
+            else
+            {
+                // something went wrong
+                m_timer->SetTimer(MyTimers::tmr_SdWaitError, 2000);
+                mode = sd_WaitError;
+            }
+        }
+        else {
+            mode = sd_CheckDataLogBuffer;
+        }
+        break;
+
+    case sd_CheckDataLogBuffer:    /* See if any data should be written to the data file */
+        if (m_dataLogBuff->dataAvailable())
+        {            
+            m_data = fopen(DATA_FILE_NAME, "a");
+
+            if (m_data != NULL)
+            {
+                // opened successfully            
+                tempInt = m_dataLogBuff->read(tempBuff, SD_BUFFER_LEN);
+                tempInt2 = fprintf(m_data, (const char*)tempBuff);            
+                fclose(m_data);
+                if (tempInt == tempInt2)
+                {
+                    // success
+                    myled2 = 0;
+                    mode = sd_CheckSysLogBuffer;
+                }
+                else
+                {
+                    // something went wrong
+                    m_timer->SetTimer(MyTimers::tmr_SdWaitError, 2000);
+                    mode = sd_WaitError;
+                }
+            }            
+            else
+            {
+                // something went wrong
+                m_timer->SetTimer(MyTimers::tmr_SdWaitError, 2000);
+                mode = sd_WaitError;
+            }
+        }
+        else {
+            mode = sd_CheckSysLogBuffer;
+        }
+        break;
+
+    case sd_WaitError:     /* Many fails, much wow, wait for a while */
+        if (!m_timer->GetTimer(MyTimers::tmr_SdWaitError))
+        {
+            // timer has elapsed. go back to start.
+            mode = sd_Start;
+        }
+        break;
+    }
+}
+
+void SdHandler::setRequest(int request, void *data)
+{
+    request_t req = (request_t)request;
+    m_lastRequest = req;
+    switch(req) {
+    case sdreq_SdNone:
+    
+        break;
+    case sdreq_LogData:
+            
+        myled2 = 1;
+        // have received the data struct. cast it
+        Dht22Result *result = (Dht22Result*)data;
+        
+        // write things to sd card buffer
+        char s[50]; // buffer
+        int temp;   // length of buffer
+        csvStart(result->resultTime);
+        temp = sprintf(s, "%4.2f", result->lastCelcius);
+        csvData(s, temp);
+        temp = sprintf(s, "%4.2f",    result->lastHumidity);
+        csvData(s, temp);
+        temp = sprintf(s, "%4.2f",    result->lastDewpoint);
+        csvData(s, temp);
+        csvEnd();
+        break;
+    case sdreq_LogSystem:
+        char *str = (char*)data;
+        logEvent(str);
+        break;
+    }
+}
+
+void SdHandler::csvStart(time_t _time)
+{
+    unsigned char sTemp[17];
+    
+    
+    // extract time_t to time info struct
+    
+    struct tm * timeinfo = localtime(&_time);
+    
+    // print the formatted timestamp at the start of terminalBuffer, with comma
+    sprintf((char*)&sTemp[0], "%04d%02d%02d %02d%02d%02d,", (timeinfo->tm_year + 1900),
+                                                    (timeinfo->tm_mon + 1),
+                                                    timeinfo->tm_mday,
+                                                    timeinfo->tm_hour,
+                                                    timeinfo->tm_min,
+                                                    timeinfo->tm_sec);
+                      
+    sTemp[16] = 0;
+                          
+    m_dataLogBuff->add(sTemp);
+}
+
+void SdHandler::csvData(const char * s, int len)
+{
+    unsigned char sTemp[50];
+    int idx = 0;
+    // add a column to the buffer
+    sprintf((char*)&sTemp[idx], s);
+    idx += len;
+    sTemp[idx++] = ',';
+    sTemp[idx] = 0;
+    m_dataLogBuff->add(sTemp);
+}
+
+void SdHandler::csvEnd()
+{
+    unsigned char sTemp[2];
+    sTemp[0] = '\n';
+    sTemp[1] = 0;
+    m_dataLogBuff->add(sTemp);
+}
+
+void SdHandler::logEvent(const char * s)
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Handlers/SdHandler.h	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,59 @@
+#ifndef __SD_HANDLER_H__
+#define __SD_HANDLER_H__
+
+#include "SDFileSystem.h"
+#include "AbstractHandler.h"
+
+class CircBuff;
+
+/*!
+ * \brief The SdHandler class writes messages to file and handles SD card status
+ * 
+ * A data CSV file is written with timestamps and the result from the GroveDht22.
+ * A system log, not yet fully implemented, tracks system events for debugging purposes.
+ */
+class SdHandler : public AbstractHandler
+{
+public:
+    SdHandler(MyTimers * _timer);
+    ~SdHandler();
+
+    void run();
+
+    void setRequest(int request, void *data = 0);
+
+    bool sdOk();
+
+    enum request_t {
+        sdreq_SdNone,       ///< to init
+        sdreq_LogData,      ///< Send struct containing a result and timestamp. This turns it into a line in a csv file
+        sdreq_LogSystem     ///< write raw string to system logging file (errors, events, etc)
+    };
+
+private:
+    SDFileSystem * m_sdfs;
+    FILE * m_data;
+    FILE * m_syslog;
+
+    enum mode_t{
+        sd_Start,                   ///< Set up the state machine
+        sd_CheckSysLogBuffer,       ///< See if any data should be written to the log file
+        sd_CheckDataLogBuffer,      ///< See if any data should be written to the data file
+
+        sd_WaitError                ///< Error. wait for a while
+    };
+    mode_t mode;
+
+    request_t m_lastRequest;
+    
+    // helpers
+    void csvStart(time_t time);
+    void csvData(const char * s, int len);
+    void csvEnd();
+    void logEvent(const char * s);
+    
+    CircBuff *m_dataLogBuff;        ///< Data waiting to be written to the data CSV file
+    CircBuff *m_sysLogBuff;         ///< Data waiting to be written to the system log file (not yet implemented)
+};
+
+#endif // __SD_HANDLER_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Handlers/UsbComms.cpp	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,125 @@
+#include "UsbComms.h"
+#include "mbed.h"
+
+#include "USBDevice.h"
+#include "USBSerial.h"
+
+#include "circbuff.h"
+
+#define USB_CIRC_BUFF 256
+
+extern DigitalOut myled1; // this led is used to notify state of USB comms
+
+UsbComms::UsbComms(MyTimers *_timer) : AbstractHandler(_timer)
+{
+    mode = usb_Start;
+
+    m_circBuff = new CircBuff(USB_CIRC_BUFF);
+
+    // Declare serial port for communication with PC over USB
+    _serial = new USBSerial;
+}
+
+UsbComms::~UsbComms()
+{
+    delete _serial;
+    delete m_circBuff;
+}
+
+void UsbComms::run()
+{
+    unsigned char temp;
+    switch(mode) {
+    case usb_Start:
+        mode = usb_CheckInput;
+        break;
+    case usb_CheckInput:
+        if (_serial->readable()) {
+            _serial->getc();
+            // todo: do something with it! this is where config events are started
+            // uncomment to print a message confirming input
+            // _serial->writeBlock((unsigned char*)"getc\r\n", 6);
+            //mode = usb_WaitForInput;
+            mode = usb_CheckOutput;
+        } else {
+            mode = usb_CheckOutput;
+        }
+        break;
+    case usb_CheckOutput:
+        if (m_circBuff->dataAvailable() && _serial->writeable()) {
+            // ensure only 64 bytes or less are written at a time
+            unsigned char s[TX_USB_MSG_MAX];
+            int len = m_circBuff->read(s, TX_USB_MSG_MAX);
+            _serial->writeBlock((unsigned char*)s, len);
+            myled1 = 1;
+
+        } else {
+            myled1 = 0;
+        }
+        mode = usb_CheckInput;
+        break;
+    }
+}
+
+void UsbComms::setRequest(int request, void *data)
+{
+    request_t req = (request_t)request;
+
+    switch (req) {
+    case usbreq_PrintToTerminal:
+        printToTerminal((char*)data);
+        break;
+    case usbreq_PrintToTerminalTimestamp:
+        printToTerminalEx((char*)data);
+        break;
+    }
+}
+
+void UsbComms::printToTerminal(char *s)
+{
+    // simply add this string to the circular buffer
+    m_circBuff->add((unsigned char*)s);
+}
+
+// print the message, but prepend it with a standard timestamp
+// YYYYMMDD HHMMSS: 
+// 01234567890123456
+void UsbComms::printToTerminalEx(char *s)
+{
+    unsigned char tempBuff[TX_USB_BUFF_SIZE];
+    // this won't work! needs to be a circular buffer...
+    uint16_t sSize = 0, i = 0, j = 0;
+
+    // get the length of the passed array (it should be null terminated, but include max buffer size for caution)
+    for (sSize = 0; (sSize < TX_USB_BUFF_SIZE) && (s[sSize] != 0); sSize++);
+
+
+    time_t _time = time(NULL); // get the seconds since dawn of time
+
+    // extract time_t to time info struct
+    struct tm * timeinfo = localtime(&_time);
+
+    // print the formatted timestamp at the start of terminalBuffer
+    sprintf((char*)&tempBuff[0], "%04d%02d%02d %02d%02d%02d:", (timeinfo->tm_year + 1900),
+            (timeinfo->tm_mon + 1),
+            timeinfo->tm_mday,
+            timeinfo->tm_hour,
+            timeinfo->tm_min,
+            timeinfo->tm_sec);
+
+
+    // copy the string into the remainder of the terminal buffer
+    for (i = 16; i < (16 + sSize); i++) {
+        tempBuff[i] = s[j++];
+    }
+
+    // add a carriage return and new line to the output buffer
+    tempBuff[i++] = '\r';
+    tempBuff[i++] = '\n';
+    tempBuff[i++] = 0;
+
+    // add it to the circular buffer
+    m_circBuff->add(tempBuff);
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Handlers/UsbComms.h	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,51 @@
+#ifndef __USB_COMMS_H__
+#define __USB_COMMS_H__
+#include "AbstractHandler.h"
+
+#define TX_USB_MSG_MAX 64u       // only send 64 bytes at a time
+#define TX_USB_BUFF_SIZE 256u    // the tx buffer can hold up to 256 bytes
+
+class USBSerial;
+class CircBuff;
+
+/*!
+ * \brief The UsbComms class handles input and output for the serial port connected to a PC
+ * The main run() function cycles through checking for input and copying to a buffer to be handled,
+ * and checking the output buffer to see if there is anything to be sent out.
+ *
+ * Data can be queued for output by copying it to the circular buffer
+ */
+class UsbComms : public AbstractHandler
+{
+public:
+    UsbComms(MyTimers *_timer);
+    ~UsbComms();
+
+    void run();
+
+    void setRequest(int request, void *data = 0);
+    
+    enum request_t{
+        usbreq_PrintToTerminal,         ///< Print to terminal normally
+        usbreq_PrintToTerminalTimestamp ///< Print to terminal, including the timestamp
+    };
+
+private:
+    USBSerial *_serial;         ///< Interface to the serial port
+    CircBuff *m_circBuff;       ///< Data waiting to be printed to the serial port
+
+    // state machine
+    enum mode_t{
+        usb_Start,          ///< Set up the state machine
+        usb_CheckInput,     ///< See if any data is waiting to be read in - currently does not process anything
+        usb_CheckOutput,    ///< See if any data should be sent over serial
+    };
+    mode_t mode;
+    
+    // helpers
+    void printToTerminal(char *s);  // raw
+    void printToTerminalEx(char *s); // add timestamp
+};
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Handlers/measurementhandler.cpp	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,234 @@
+#include "measurementhandler.h"
+#include "mbed.h"
+#include "SdHandler.h"
+#include "UsbComms.h"
+
+// declare led4 so we can flash it to reflect state of this handler
+extern DigitalOut myled4;
+
+// flags for the request register
+#define REQ_RESULT 0b00000001
+#define REQ_ERROR  0b00000010
+#ifdef ENABLE_GPRS_TESTING
+#define REQ_SMS    0b00000100
+#endif
+
+#ifdef ENABLE_GPRS_TESTING
+MeasurementHandler::MeasurementHandler(SdHandler *_sd, UsbComms *_usb, GprsHandler *_gprs, MyTimers *_timer)
+    : AbstractHandler(_timer), m_sd(_sd), m_usb(_usb), m_gprs(_gprs)
+#else
+MeasurementHandler::MeasurementHandler(SdHandler *_sd, UsbComms *_usb, MyTimers *_timer)
+    : AbstractHandler(_timer), m_sd(_sd), m_usb(_usb)
+#endif
+{
+    m_lastError         = ERROR_NONE;
+    mode                = meas_Start;
+    m_lastRequest       = measreq_MeasReqNone;
+    m_flashOn           = false;
+    m_requestRegister   = 0;
+
+#ifdef ENABLE_GPRS_TESTING
+    for (int i = 0; i < GPRS_RECIPIENTS_MAXLEN; i++) {
+        m_lastSender[i] = 0;
+    }
+#endif
+}
+
+void MeasurementHandler::run()
+{
+    switch(mode) {
+    case meas_Start:
+        // start here, and come back here if an error has been flushed out and waited
+        mode = meas_CheckRequest;
+        break;
+
+    case meas_CheckRequest:
+        if (m_requestRegister) {
+            // check what has been requested, starting from most highest priority
+            
+#ifdef ENABLE_GPRS_TESTING
+            if (m_requestRegister&REQ_SMS) {
+                // an SMS has been requested
+                mode = meas_PostStateSMS;
+            }
+            else if (m_requestRegister&REQ_RESULT) {
+#else
+            if (m_requestRegister&REQ_RESULT) {
+#endif
+                // a result has been sent, we need to post it
+                mode = meas_PostResult;
+            }
+            else if (m_requestRegister&REQ_ERROR) {
+                // an error has been sent, we need to post it
+                mode = meas_PostError;
+            }
+            else {
+                // something went wrong, a flag was set that isn't defined
+                m_requestRegister = 0;
+                mode = meas_FlashTimer;
+            }
+        }
+        else {
+            // no requests, check if running led needs to be flashed
+            mode = meas_FlashTimer;
+        }
+        break;
+
+#ifdef ENABLE_GPRS_TESTING
+    case meas_PostStateSMS:
+        if (m_requestRegister&REQ_SMS) {
+            char s[64];
+            // usb print
+            sprintf(s, "Temperature is %4.2f degC\nHumidity is %4.2f pc\nDew point is %4.2f", m_lastResult.lastCelcius, m_lastResult.lastHumidity, m_lastResult.lastDewpoint);
+
+            GprsRequest req;
+            strcpy(req.message, s);
+            strcpy(req.recipients, m_lastSender);
+            m_gprs->setRequest(GprsHandler::gprsreq_SmsSend, &req);
+
+            // clear the request reqister's sms flag
+            m_requestRegister &= ~REQ_SMS;
+        }
+        mode = meas_CheckRequest;
+        break;
+#endif
+
+    case meas_PostResult:
+        if (m_requestRegister&REQ_RESULT) {
+            // we have a result, post it
+
+            // TODO: check when the last result came in. if it has not been very long (< 5s? < 1s?) avoid posting, so we don't hammer it
+
+            char s[50];
+            // usb print
+            sprintf(s, "Temperature is %4.2f degC", m_lastResult.lastCelcius);
+            m_usb->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, s);
+            sprintf(s, "Humidity is %4.2f pc",      m_lastResult.lastHumidity);
+            m_usb->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, s);
+            sprintf(s, "Dew point is %4.2f ",    m_lastResult.lastDewpoint);
+            m_usb->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, s);
+            
+            // post to SD card
+            m_sd->setRequest(SdHandler::sdreq_LogData, &m_lastResult);
+
+            // clear the request
+            m_requestRegister &= ~REQ_RESULT;
+        }
+
+        // go back to check if there are more requests
+        mode = meas_CheckRequest;
+        break;
+
+
+    case meas_PostError:
+        if (m_requestRegister&REQ_ERROR) {
+            // there is an error, check the value of it and post the corresponding string to USB
+            // TODO: post to SD syslog
+            switch (m_lastError)
+            {
+            case BUS_BUSY:
+                m_usb->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, (char*)"BUSY!");
+                break;
+            case ERROR_NOT_PRESENT:
+                m_usb->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, (char*)"NOT PRESENT");
+                break;
+            case ERROR_ACK_TOO_LONG:
+                m_usb->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, (char*)"TOO LONG");
+                break;
+            case ERROR_SYNC_TIMEOUT:
+                m_usb->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, (char*)"SYNC TIMEOUTr\n");
+                break;
+            case ERROR_DATA_TIMEOUT:
+                m_usb->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, (char*)"DATA TIMEOUT");
+                break;
+            case ERROR_CHECKSUM:
+                m_usb->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, (char*)"CHECKSUM");
+                break;
+            case ERROR_NO_PATIENCE:
+                m_usb->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, (char*)"NO PATIENCE!");
+                break;
+            default:
+                m_usb->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, (char*)"UNKNOWN");
+                break;
+            }
+
+            // we have posted it, clear the flag
+            m_requestRegister &= ~REQ_ERROR;
+        }
+        // check if there are any more requests
+        mode = meas_CheckRequest;
+        break;
+        
+    case meas_FlashTimer:
+        // flash timer to know that we are still alive.        
+        if (!m_timer->GetTimer(MyTimers::tmr_MeasFlash)) {      // wait until timer has elapsed
+            if (m_flashOn) {
+                // turn off
+                myled4 = 0;
+                m_timer->SetTimer(MyTimers::tmr_MeasFlash, 2000); // stay off for 2 seconds
+                m_flashOn = false;
+            }
+            else {
+                // turn on
+                myled4 = 1;
+                m_timer->SetTimer(MyTimers::tmr_MeasFlash, 1000); // stay on for 1 second
+                m_flashOn = true;
+            }
+                
+        }
+        mode = meas_CheckRequest;
+        break;
+
+    case meas_WaitError:
+        // TODO: timer
+        mode = meas_Start;
+        break;
+    }
+}
+
+void MeasurementHandler::setRequest(int request, void *data)
+{
+    m_lastRequest = (request_t)request;
+    switch(request) {
+    case measreq_DhtResult:
+        // this should contain time as well
+        Dht22Result *result = (Dht22Result*)data;
+
+        // copy the structure into our own structure
+        m_lastResult = *result;
+
+        // set the request
+        m_requestRegister |= REQ_RESULT;
+
+        break;
+
+    case measreq_DhtError:
+        int *iResult = (int*)data;
+        m_lastError = *iResult;
+        m_requestRegister |= REQ_ERROR;
+
+        break;
+
+#ifdef ENABLE_GPRS_TESTING
+    case measreq_Status:
+        // need to send SMS of last result
+        // the sender's number is the sent data
+        char *sender = (char*)data;
+
+        int i = 0;
+        // copy the sender's number
+        for (i = 0; (i < GPRS_RECIPIENTS_MAXLEN) && (sender[i] != 0); i++) {
+            m_lastSender[i] = sender[i];
+        }
+
+        // set the rest of the destination array to null
+        for (i; i < GPRS_RECIPIENTS_MAXLEN; i++) {
+            m_lastSender[i] = 0;
+        }
+
+        // set the request
+        m_requestRegister |= REQ_SMS;
+        break;
+#endif
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Handlers/measurementhandler.h	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,94 @@
+#ifndef MEASUREMENTHANDLER_H
+#define MEASUREMENTHANDLER_H
+
+#include "AbstractHandler.h"
+#include "GroveDht22.h"
+#include "config.h"
+#ifdef ENABLE_GPRS_TESTING
+#include "GprsHandler.h"
+#endif
+
+class SdHandler;
+class UsbComms;
+
+
+/*!
+ * \brief The MeasurementHandler class forms the link between data generation and data output, and stores settings.
+ *
+ * Receives requests from \a GroveDht22 when a new measurement has been taken or error has occurred. This handler then
+ * sends that information on to \a UsbComms for printing that information to terminal and \a SdHandler for printing
+ * that information to the CSV data file.
+ *
+ * This handler also determines if the necessary conditions have been met to send an SMS. This is based on last measurement,
+ * the set alert threshold, and time since last alert was sent. An SMS is sent using \a GprsHandler.
+ *
+ * Other requests from inputs asking for current measurement states (such as the latest measurement) may come from \a UsbComms
+ * or \a GprsHandler. The string inspection and matching is handled here, and responses sent to the data outputs.
+ *
+ *
+ * Flashes LED4 constantly to inform that normal operation is occurring.
+ */
+class MeasurementHandler : public AbstractHandler
+{
+public:
+#ifdef ENABLE_GPRS_TESTING
+    MeasurementHandler(SdHandler *_sd, UsbComms *_usb, GprsHandler *_gprs, MyTimers *_timer);
+#else
+    MeasurementHandler(SdHandler *_sd, UsbComms *_usb, MyTimers *_timer);
+#endif
+
+    void run();
+
+    void setRequest(int request, void *data = 0);
+
+    Dht22Result lastResult() const { return m_lastResult; }
+
+    enum request_t{
+        measreq_MeasReqNone,        ///< No request (for tracking what the last request was, this is initial value for that)
+        measreq_DhtResult,          ///< Dht22 returned with a result
+        measreq_DhtError,           ///< Dht22 returned with an error
+#ifdef ENABLE_GPRS_TESTING
+        measreq_Status,             ///< We got an SMS asking for the status (time, last error, last result)
+#endif
+    };
+
+private:
+    SdHandler *m_sd;            ///< Reference to write to SD card
+    UsbComms *m_usb;            ///< Reference to write to USB serial port
+
+#ifdef ENABLE_GPRS_TESTING
+    GprsHandler *m_gprs;        ///< Reference to write to GPRS (SMS)
+#endif
+
+    Dht22Result m_lastResult;   ///< Copy of the last result that came from Dht22
+    int  m_lastError;           ///< Copy of the last error that came from Dht22
+
+#ifdef ENABLE_GPRS_TESTING
+    char m_lastSender[GPRS_RECIPIENTS_MAXLEN];         ///< The last sender of an SMS
+#endif
+
+    bool m_flashOn;             ///< LED is currently on when true
+
+    enum mode_t{
+        meas_Start,             ///< Set up the state machine
+        meas_CheckRequest,      ///< Check request register
+
+#ifdef ENABLE_GPRS_TESTING
+        meas_PostStateSMS,      ///< Send an SMS of the last result and state
+#endif
+        meas_PostResult,        ///< Write the last Grove result to SD and USB
+        meas_PostError,         ///< Write the last Grove error to USB (and in future, SD syslog)
+
+        meas_FlashTimer,        ///< Flash an LED on and off so user knows device is still running
+
+        meas_WaitError          ///< Lots of fails, wait for a while
+    };
+    mode_t mode;
+
+    request_t m_lastRequest;    ///< tracks if result or error was the last request
+
+    uint8_t m_requestRegister;  ///< contains the current pending requests as bitwise flags
+
+};
+
+#endif // MEASUREMENTHANDLER_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SDFileSystem.lib	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,1 @@
+http://developer.mbed.org/teams/mbed/code/SDFileSystem/#7b35d1709458
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/USBDevice.lib	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/USBDevice/#d17693b10ae6
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/circbuff.cpp	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,106 @@
+#include "circbuff.h"
+
+CircBuff::CircBuff(uint16_t buffSize)
+{
+    // set up the buffer with parsed size
+    m_buffSize = buffSize;
+    m_buf = new unsigned char [m_buffSize];
+    for (int i = 0; i < m_buffSize; i++) {
+        m_buf[i] = 0;
+    }
+
+    // init indexes
+    m_start = 0;
+    m_end   = 0;
+}
+
+CircBuff::~CircBuff()
+{
+    delete m_buf;
+}
+
+void CircBuff::putc(unsigned char c)
+{
+    // get the remaining space left in the buffer by checking end and start idx
+    uint16_t remSize = remainingSize();
+
+    // check we have enough room for the new array passed in
+    if (remSize == 0) {
+        return;
+    }
+
+    // else copy the byte in
+    m_buf[m_end++] = c;
+    if (m_end == m_buffSize) {
+        m_end = 0;  // wrap around
+    }
+
+}
+
+void CircBuff::add(unsigned char *s)
+{
+    // check if can write? How to check if we have connected.
+    uint16_t sSize = 0, i = 0, j = 0;
+
+    // get the length of the passed array (it should be null terminated, but include max buffer size for caution)
+    for (sSize = 0; (sSize < m_buffSize) && (s[sSize] != 0); sSize++);
+
+    // get the remaining space left in the buffer by checking end and start idx
+    uint16_t remSize = remainingSize();
+
+    // check we have enough room for the new array passed in
+    if (sSize > remSize) {
+        return;
+    }
+
+    // copy the array in
+    for (i = 0; i < sSize; i++) {
+        m_buf[m_end++] = s[i];
+        if (m_end == m_buffSize) {
+            m_end = 0;  // wrap around
+        }
+    }
+}
+
+uint16_t CircBuff::remainingSize()
+{
+    // get the remaining space left in the buffer by checking end and start idx
+    uint16_t retval = 0;
+    if (m_start == m_end) {
+        retval = m_buffSize;
+    }
+    else if (m_start < m_end) {
+        // the distance between the start and end point
+        // subtract that from the total length of the buffer
+        retval = m_buffSize - (m_end - m_start);
+    }
+    else {
+        // the amount of space left is whatever is between the end point and the start point
+        retval = m_start - m_end;
+    }
+    return retval;
+}
+
+uint16_t CircBuff::read(unsigned char *s, uint16_t len)
+{
+    if (m_start == m_end) {
+        return 0; // there is nothing stored in the circular buffer
+    }
+
+    // start copying the desired amount over
+    for (int i = 0; i < len; i++) {
+        s[i] = m_buf[m_start++];
+        
+        if (m_start == m_buffSize) {
+            m_start = 0;    // wrap start pointer
+        }
+
+        if (m_start == m_end) {
+            s[++i] = 0;
+            return (i); // we have reached the end of the buffer
+        }
+    }
+    return len;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/circbuff.h	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,46 @@
+#ifndef __CIRC_BUFF_H__
+#define __CIRC_BUFF_H__
+
+#include "mbed.h"
+
+/*!
+ * \brief The CircBuff class writes in and reads out byte arrays into a circular buffer
+ */
+class CircBuff {
+public:
+    CircBuff(uint16_t buffSize = 256);
+    ~CircBuff();
+
+    /*!
+     * \brief putc adds a single byte, \a c, into the array
+     * \param c is the byte copied into the circular buffer.
+     */
+    void putc(unsigned char c);
+
+    /*!
+     * \brief add adds \a s into the buffer, up until the NULL byte
+     * \param s is the byte array copied into the buffer
+     */
+    void add(unsigned char *s);
+
+    /*!
+     * \brief read puts the current data from the buffer into \a s
+     * \param s is the reference buffer to copy current data into. Caller's responsibility to make it the correct size
+     * \param len is the number of bytes to copy into \a s
+     * \return the number of bytes copied into \a s. Will be less than \a len if there was less data in the buffer.
+     */
+    uint16_t read(unsigned char *s, uint16_t len);
+    bool dataAvailable() { return (m_start != m_end); }
+    
+
+private:
+    uint16_t m_start;       ///< The start index of the circular buffer, where the current data starts
+    uint16_t m_end;         ///< The end index, where the current data goes to
+    unsigned char *m_buf;   ///< the byte array
+    uint16_t m_buffSize;    ///< size of \a m_buf
+    
+    // helper
+    uint16_t remainingSize();
+};
+
+#endif // __CIRC_BUFF_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/config.h	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,11 @@
+#ifndef CONFIG_H_
+#define CONFIG_H_
+
+/*
+ * This file contains global definitions to configure the program
+ */
+
+// uncomment this to continue development and testing with GPRS
+// #define ENABLE_GPRS_TESTING
+
+#endif /* CONFIG_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,205 @@
+/*!
+ * Project:     Humidity SMS Alert
+ * Author:      Joseph Radford
+ * Date start:  January 2016
+ * Date mod:
+ * Target:      Arch GPRS V2
+ * Peripherals: SD Card
+ *              SIM Card
+ *              Grove Humidity and Temperature Sensor Pro (DHT22 on a breakout board)
+ *
+ *
+ * Takes intermittent readings from the humidity temperature sensor. These are written to a timestamped CSV file
+ * on the SD card and printed over serial if there is a connection to a terminal window (Screen on Linux, Putty on Windows, etc)
+ *
+ * When conditions have been met, an SMS alert is sent notifying the recipient of the last sensor readings.
+ *
+ * States can be requested, and configurations changed, by sending requests over USB serial or SMS.
+ *
+ * Inputs:
+ * \a GroveDht22
+ *
+ * Outputs:
+ * \a SdHandler
+ *
+ *
+ * Inputs and outputs:
+ * \a GprsHandler
+ * \a UsbComms
+ *
+ * Linker:
+ * \a MeasurementHandler
+ *
+ * 10/04/2016 v0.0.1 - Writes timestamped humidity data to an SD card
+ *
+ * Issues:
+ * Stops communicating over USB after ~10 mins. 
+ * Will not work if USB not present.
+ * Will not work if SD card is not present
+ * GPRS yet to be implemented
+ * RTC is set to 01/01/2000 00:00:00 at startup. No way of setting it yet.
+ */
+
+#include "config.h"
+
+#include "mbed.h"
+#include "math.h"
+
+// Clocks and timers
+#include "DS1337.h"
+#include "rtc.h"
+#include "timers.h"
+
+// Handlers
+#include "Handlers/GroveDht22.h"
+#include "Handlers/UsbComms.h"
+#include "Handlers/SdHandler.h"
+#include "Handlers/measurementhandler.h"
+
+#ifdef ENABLE_GPRS_TESTING
+#include "Handlers/GprsHandler.h"
+#endif
+
+/* Declare hardware classes */
+ 
+DigitalOut myled1(LED2);    ///< startup, and USB handler when writing occurring (right most)
+DigitalOut myled2(LED3);    ///< SD handler - on when buffer pending, off when written. If error, will be on as buffer won't clear
+DigitalOut myled3(LED4);    ///< GPRS handler information
+DigitalOut myled4(LED1);    ///< measurement handler flashes, periodically to inform running (left most)
+
+//DigitalOut myenable(P0_6);
+
+DS1337 * RTC_DS1337;        ///< RTC interface class
+
+I2C i2c(P0_5, P0_4);        ///< I2C comms - this is required by the RTC - do NOT change the name of the instance
+
+
+/* Declare helpers */
+MyTimers *mytimer;         ///< declare timers class - required for other classes to use timers (do not change name)
+
+
+/* Declare handlers */
+GroveDht22          *grove;         ///< grove humidity sensor handler class
+UsbComms            *usbcomms;      ///< reading and writing to usb
+SdHandler           *sdhandler;     ///< Writing data to a file on SD
+MeasurementHandler  *measure;       ///< Handle measurements, and route data to the user and user to configuration
+
+#ifdef ENABLE_GPRS_TESTING
+GprsHandler * gprs; ///< Reading and writing to the SIM900
+#endif
+
+#ifdef ENABLE_GPRS_TESTING
+#define NUM_HANDLERS 5
+#else
+#define NUM_HANDLERS 4
+#endif
+
+#define PROGRAM_TITLE   "Arch GPRS V2 Alert and Request"
+#define PROGRAM_INFO    "v0.0.1, released 09/04/2016"
+
+/*!
+ * \brief main pulses LED1, creates all classes, pulses LED1, then runs through all handlers forever
+ * \return
+ */
+int main()
+{
+    /* start up sequence, so we know we reached main */
+    myled1 = 1;
+    wait(0.2);
+    myled1 = 0;
+    wait(0.2);
+    myled1 = 1;
+    wait(0.2);
+    myled1 = 0;
+    wait(1);
+    myled1 = 1;
+    wait(0.2);
+    myled1 = 0;
+    wait(0.2);
+    myled1 = 1;
+    wait(0.2);
+    myled1 = 0;
+    wait(1);
+    
+    /* Declare all classes */
+
+    // create MyTimers object, which can be used for waiting in handlers
+    mytimer = new MyTimers();
+
+    // RTC interface class
+    RTC_DS1337 = new DS1337();
+    attach_rtc(&my_rtc_read, &my_rtc_write, &my_rtc_init, &my_rtc_enabled);
+
+    // set the time to the start of the year 2000, by default
+    // this will be removed when time can be set, so that time is not reset each time the program starts.
+    tm timeinfo;
+    timeinfo.tm_hour = 0;   timeinfo.tm_min = 0; timeinfo.tm_sec = 0;
+    timeinfo.tm_year = (2001 - 1900); timeinfo.tm_mon = 0; timeinfo.tm_mday = 1;
+    set_time(mktime(&timeinfo));
+    
+    // declare usbcomms
+    usbcomms = new UsbComms(mytimer);
+    
+    // declare sd handler
+    sdhandler = new SdHandler(mytimer);
+
+#ifdef ENABLE_GPRS_TESTING
+    gprs = new GprsHandler(mytimer, usbcomms);
+    measure = new MeasurementHandler(sdhandler, usbcomms, gprs, mytimer);
+#else
+    measure = new MeasurementHandler(sdhandler, usbcomms, mytimer);
+#endif
+
+    // declare grove
+    grove = new GroveDht22(measure, mytimer);
+
+    // put the handlers in an array for easy reference
+#ifdef ENABLE_GPRS_TESTING
+    AbstractHandler* handlers[] = {grove, usbcomms, sdhandler, measure, gprs};
+#else
+    AbstractHandler* handlers[] = {grove, usbcomms, measure, sdhandler};
+#endif
+
+    // send startup message to terminal
+    usbcomms->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, (char*)PROGRAM_TITLE);
+    usbcomms->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, (char*)PROGRAM_INFO);
+    usbcomms->setRequest(UsbComms::usbreq_PrintToTerminalTimestamp, (char*)"\r\n******************\r\n");
+
+    // flush output
+    fflush(stdout); 
+
+    
+    /* Pulse LED1 again to signify start up was successful */
+    myled1 = 1;
+    wait(0.2);
+    myled1 = 0;
+    wait(0.2);
+    myled1 = 1;
+    wait(0.2);
+    myled1 = 0;
+    wait(1);
+    myled1 = 1;
+    wait(0.2);
+    myled1 = 0;
+    wait(0.2);
+    myled1 = 1;
+    wait(0.2);
+    myled1 = 0;
+    wait(1);
+
+    
+    while(1) 
+    {
+        // perform run functions for all handlers, one after the other
+        for (int i = 0; i < NUM_HANDLERS; i++) {
+            handlers[i]->run();
+        }
+    }   // while
+
+    for (int i = 0; i < NUM_HANDLERS; i++) {
+        delete handlers[i];
+    }
+}   // main
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed.bld	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/mbed/builds/4f6c30876dfa
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/readme.md	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,59 @@
+This project is written for the Arch GPRS V2.
+
+It aims to provide a convenient method of alerting a user of humidity. However, the grove DHT22
+sensor could easily be replaced with another Grove sensor with small changes to the code.
+
+Directories:
+ * DHT 
+	- for accessing the grove
+ * DS_1337 
+	- interface to the real time clock (which is a separate chip, i.e. not part of the micro)
+ * mbed 
+	- all mbed libraries
+ * SDFileSystem 
+	- for accessing the SD card. You need to be careful to import the right one (link to my blog)
+ * USBDevice 
+	- interface to serial comms over USB (talk to it from a PC)
+ * Handlers 
+	- this is where almost all of my code lives
+
+Handlers
+The handlers have the same structure (although they do not inherit from a common base class, but they should). They have a run function, which is a state machine called from the main while loop in main.cpp. This will run continuous routines such as polling and checking if a request has been raised.
+
+A request might be raised through a common request interface that passes an enum. However this might have to be more specific. Either way, the request is then handled in the state machine.
+
+GroveDht22
+ * Takes a measurement from the DHT22 hardware at a set interval
+ * On success, sends to measurement handler
+
+SdHandler
+ * Initialises and polls the SD card, checks for errors, etc
+ * Receives requests for writing a system message to the log, or writing a measurement to CSV
+
+UsbComms
+ * Checks to see if there is a connection to a PC
+ * Receives requests to send messages to PC
+ * Diverts incoming messages from PC to appropriate handlers
+
+MeasurementHandler
+ * GroveDht22 sends a measurement to this and it decides what to do with it
+ * Stores values for schedules, thresholds, last measurements
+ * Decides if a new measurement should be sent over SMS, SD
+ * Receives a request for last measurement, state, etc, from either UsbComms or SmsHandler
+
+GprsHandler (WIP)
+ * Checks to see if there are any incoming messages, directs them appropriately
+ * Gets requests from other handlers to send an SMS
+ 
+ 
+ 
+ 
+To do:
+	* Complete basic GprsHandler
+    * Set a threshold for humidity (hardcoded to start with) and send messages at this threshold
+    * Avoid sending more than x messages per y time (so don't get bombarded with messages)
+    * Send messages to a list of numbers, not just one
+    * Send messages on a schedule, e.g. send current humidity and temp once a day, so user knows it's OK
+    * Make the above three points configurable over USB (threshold, time off, list of numbers, schedule)
+    * Respond to certain messages, over SMS, to configure and make status known.
+    * Make a base class for handlers to inherit from, so that interface stays consistent
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rtc.cpp	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,60 @@
+#include "rtc.h"
+#include "DS1337.h"
+
+// declare reference to interface to the hardware real time clock
+extern DS1337* RTC_DS1337;
+
+// RTC enabled flag
+static int _rtcEnabled;
+
+time_t my_rtc_read()
+{
+    time_t  retval = 0;     // time since start of epoch
+    tm      _time_tm;
+
+    RTC_DS1337->readTime();
+
+    // extract values from RTC to tm struct
+    _time_tm.tm_year = ((int)RTC_DS1337->getYears()) - 1900;       // struct tm stores (year - 1900)
+    _time_tm.tm_mon  = (int)RTC_DS1337->getMonths() - 1;         // struct tm stores months 0 - 11, DS1337 stores 1-12
+    _time_tm.tm_mday = (int)RTC_DS1337->getDays();
+    _time_tm.tm_hour = (int)RTC_DS1337->getHours();
+    _time_tm.tm_min  = (int)RTC_DS1337->getMinutes();
+    _time_tm.tm_sec  = (int)RTC_DS1337->getSeconds();
+
+    // convert to time_t    
+    retval = mktime(&_time_tm);
+    if (retval == (time_t) -1)
+        return (time_t)1;               // error
+    else
+        return retval;
+}
+
+
+//https://developer.mbed.org/teams/Seeed/code/Seeed_Arch_GPRS_V2_RTC_HelloWorld/file/6db7dfbab0f1/main.cpp
+void my_rtc_write(time_t _time)
+{
+    // extract time_t to time info struct
+    tm * timeinfo = localtime(&_time);
+
+    RTC_DS1337->setSeconds   (timeinfo->tm_sec);
+    RTC_DS1337->setMinutes   (timeinfo->tm_min);
+    RTC_DS1337->setHours     (timeinfo->tm_hour);
+    RTC_DS1337->setDays      (timeinfo->tm_mday);           // day of month
+    RTC_DS1337->setDayOfWeek (timeinfo->tm_wday);
+    RTC_DS1337->setMonths    (timeinfo->tm_mon + 1);        // struct tm stores months 0 - 11, DS1337 stores 1-12
+    RTC_DS1337->setYears     (timeinfo->tm_year + 1900);    // struct tm subtracts 1900 from year
+
+    RTC_DS1337->setTime();
+}
+
+void my_rtc_init()
+{
+    RTC_DS1337->start();
+    _rtcEnabled = 1;
+}
+
+int my_rtc_enabled()
+{
+    return _rtcEnabled;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rtc.h	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,50 @@
+#ifndef __RTC_H__
+#define __RTC_H__
+
+#include "mbed.h"
+
+//! The functions needed to configure time in the micro, used in \sa attach_rtc, using the on board DS_1337
+/*!
+  As there is no on-chip RTC, connecting the time functionality to the DS_1337 enables more convenient use of time
+
+  The function which is called to configure is as such:
+  void attach_rtc(time_t (*read_rtc)(void), void (*write_rtc)(time_t), void (*init_rtc)(void), int (*isenabled_rtc)(void))
+
+ read_rtc is assigned \sa my_rtc_read
+
+ write_rtc is assigned \sa my_rtc_write
+
+ init_rtc is assigned \sa my_rtc_init
+
+ isenabled_rtc is assigned \sa my_rtc_enabled
+ */
+
+
+/*!
+ * \brief my_rtc_read Interfaces to the on board RTC DS1337 and converts read values to time_t
+ * \return the value read from the on board RTC converted to system struct time_t
+ */
+time_t my_rtc_read();
+
+
+/*!
+ * \brief my_rtc_write Interfaces to the on board RTC DS1337 and converts and writes the time given as time_t
+ * \param _time is the time in system time_t format which will be written to DS1337
+ */
+void my_rtc_write(time_t _time);
+
+
+/*!
+ * \brief my_rtc_init Initialises the RTC DS1337
+ */
+void my_rtc_init();
+
+
+/*!
+ * \brief my_rtc_enabled Checks if RTC DS1337 is enabled
+ * \return 1 if enabled, 0 if not enabled (i.e. \sa my_rtc_init never called)
+ */
+int my_rtc_enabled();
+
+
+#endif // __RTC_H__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/timers.cpp	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,74 @@
+#include "timers.h"
+
+MyTimers::MyTimers()
+{
+    // initialise timers
+    groveMeasureTimer = 0;
+    gprsPowerTimer    = 0;
+    gprsRxTxTimer     = 0;
+    sdWaitErrorTimer  = 0;
+    measFlashTimer    = 0;
+
+    m_tick = new Ticker();
+    // configure the ticker object to run every 1ms, and to call \sa run when it does so.
+    m_tick->attach(this, &MyTimers::run, 0.001);
+}
+
+MyTimers::~MyTimers()
+{
+    delete m_tick;
+}
+
+void MyTimers::run()
+{
+    // decrement each timer in the class
+    if (groveMeasureTimer) groveMeasureTimer--;
+    if (gprsPowerTimer   ) gprsPowerTimer--;
+    if (gprsRxTxTimer    ) gprsRxTxTimer--;
+    if (sdWaitErrorTimer ) sdWaitErrorTimer--;
+    if (measFlashTimer   ) measFlashTimer--;
+}
+
+
+void MyTimers::SetTimer(eTimerType timertype, unsigned long time_ms)
+{
+    // see which timer we have to set, and set it
+    switch(timertype)
+    {
+    case tmr_GroveMeasure:
+        groveMeasureTimer = time_ms;
+        break;
+    case tmr_GprsPower:
+        gprsPowerTimer = time_ms;
+        break;
+    case tmr_GprsRxTx:
+        gprsRxTxTimer = time_ms;
+        break;
+    case tmr_SdWaitError:
+        sdWaitErrorTimer = time_ms;
+        break;
+    case tmr_MeasFlash:
+        measFlashTimer = time_ms;
+        break;
+    }
+}
+
+unsigned long MyTimers::GetTimer(eTimerType timertype)
+{
+    // see which timer we have to get, and return it.
+    switch(timertype)
+    {
+    case tmr_GroveMeasure:
+        return groveMeasureTimer;
+    case tmr_GprsPower:
+        return gprsPowerTimer;
+    case tmr_GprsRxTx:
+        return gprsRxTxTimer;
+    case tmr_SdWaitError:
+        return sdWaitErrorTimer;
+    case tmr_MeasFlash:
+        return measFlashTimer;
+    }
+    return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/timers.h	Sun Apr 10 15:47:33 2016 +1000
@@ -0,0 +1,57 @@
+#ifndef __TIMERS_H__
+#define __TIMERS_H__
+
+#include "mbed.h"
+
+/*!
+ * \brief The MyTimers class creates a Ticker and decrements a collection of unsigned longs that act as timers across the program
+ *
+ *  \sa eTimerType is used to link external callers with a collection of unsigned longs which are decremented
+ *  each ms by \sa run().
+ *  This acts as a collection of timers.
+ *  This collection can be set by \sa SetTimer and retrieved by \sa GetTimer.
+ */
+class MyTimers
+{
+public:
+    MyTimers();
+    ~MyTimers();
+
+    ///< eTimerType identifies each of the timers in program, and is used to set and get a timer
+    typedef enum
+    {
+        tmr_GroveMeasure,       ///< Used in GroveDht22 handler
+        tmr_GprsPower,          ///< Used to power the SIM900 on and off
+        tmr_GprsRxTx,           ///< Timeout waiting for a response from the SIM900 over the serial line
+        tmr_SdWaitError,        ///< Sd card has hit an error, wait before retrying
+        tmr_MeasFlash           ///< Flash once every 2 seconds for heartbeat
+    } eTimerType;
+
+    //! run is called each time Ticker fires, which is every 1ms, and decrements all timers if necessary
+    void run();
+
+    /*!
+     * \brief SetTimer sets the value of the timer
+     * \param timertype identifies the timer whose value we want to set
+     * \param time_ms is the value the timer will be set to, to start counting down from, in ms
+     */
+    void SetTimer(eTimerType timertype, unsigned long time_ms);
+
+    /*!
+     * \brief GetTimer gets the value of a timer
+     * \param timertype identifies the timer whose value we want to get
+     * \return the value of the timer
+     */
+    unsigned long GetTimer(eTimerType timertype);
+
+private:
+    unsigned long groveMeasureTimer;    ///< current value of timer for \sa tmr_GroveMeasure
+    unsigned long gprsPowerTimer;       ///< current value of timer for \sa tmr_GprsPower
+    unsigned long gprsRxTxTimer;        ///< current value of timer for \sa tmr_GprsRxTx
+    unsigned long sdWaitErrorTimer;     ///< current value of timer for \sa tmr_SdWaitError
+    unsigned long measFlashTimer;       ///< current value of timer for \sa tmr_MeasFlash
+
+    Ticker *m_tick;        ///< Used to call \a run a function periodically
+};
+
+#endif