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
Revision 0:2df78a4443cd, committed 2016-04-10
- Comitter:
- Joseph Radford
- Date:
- Sun Apr 10 15:47:33 2016 +1000
- Commit message:
- Initial commit
Changed in this revision
--- /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