ThreadedGSM/ThreadedGSM.h

702 lines
16 KiB
C++

/*
* ThreadedGSM.h
*
* Created: 20/09/2016 11:14:02
* Author: Neta Yahav
*/
#ifndef __THREADEDGSM_H__
#define __THREADEDGSM_H__
#include <Arduino.h>
#include "DTE.h"
// Defaults
#define THREADEDGSM_DEF_DTE_BUF_SIZ 512
#define THREADEDGSM_DEF_AT_TIMEOUT 5000
#define THREADEDGSM_DEF_STA_PON 10000
#define THREADEDGSM_DEF_STA_POF 1000
// Use custom values or default ones
#ifndef THREADEDGSM_DTE_BUFFER_SIZE
#define THREADEDGSM_DTE_BUFFER_SIZE THREADEDGSM_DEF_DTE_BUF_SIZ
#endif
#ifndef THREADEDGSM_AT_TIMEOUT
#define THREADEDGSM_AT_TIMEOUT THREADEDGSM_DEF_AT_TIMEOUT
#endif
#ifndef THREADEDGSM_STARTUP_DELAY
#define THREADEDGSM_STARTUP_DELAY THREADEDGSM_DEF_STA_PON
#endif
#ifndef THREADEDGSM_STARTUP_POWER_OFF_DELAY
#define THREADEDGSM_STARTUP_POWER_OFF_DELAY THREADEDGSM_DEF_STA_POF
#endif
#define THREADEDGSM_INTERVAL_COUNT 4
#ifdef THREADEDGSM_DEBUG
#define DEBUG_PRINT(x) THREADEDGSM_DEBUG.print (x)
#define DEBUG_PRINTLN(x) THREADEDGSM_DEBUG.println (x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#endif
class ThreadedGSM
{
//variables
public:
struct NetworkTime
{
int year;
int month;
int day;
int hour;
int minute;
int second;
};
enum ReadMessagesListTypeE {
READ_TYPE_UNREAD = 0,
READ_TYPE_READ = 1,
READ_TYPE_UNSENT = 2,
READ_TYPE_SENT = 3,
READ_TYPE_ALL = 4
};
enum IntervalSourceE
{
INTERVAL_CLOCK,
INTERVAL_INBOX,
INTERVAL_SIGNAL,
INTERVAL_BATTERY
};
struct SignalLevel
{
int Dbm;
int Value;
};
struct BatteryInfo
{
int Percent;
int Voltage;
};
struct SMSInfo
{
String Number;
String Text;
};
typedef void (*ThreadedGSMCallbackSignal)(ThreadedGSM&, SignalLevel&);
typedef void (*ThreadedGSMCallbackClock)(ThreadedGSM&, NetworkTime&);
typedef void (*ThreadedGSMCallbackBattery)(ThreadedGSM&, BatteryInfo&);
typedef void (*ThreadedGSMCallbackIncomingSMS)(ThreadedGSM&, SMSInfo&);
typedef void (*ThreadedGSMCallbackBool)(ThreadedGSM&, bool);
typedef void (*ThreadedGSMCallback)(ThreadedGSM&);
struct conf
{
ThreadedGSMCallbackSignal signal;
ThreadedGSMCallbackClock clock;
ThreadedGSMCallbackIncomingSMS incoming;
ThreadedGSMCallback ready;
ThreadedGSMCallback outgoing;
ThreadedGSMCallbackBool power;
ThreadedGSMCallbackBattery battery;
};
protected:
private:
enum StatesStartup
{
STARTUP_POWER_OFF,
STARTUP_POWER_OFF_DELAY,
STARTUP_POWER_ON,
STARTUP_DELAY,
STARTUP_ENTER_AT,
STARTUP_CHK_CPIN,
STARTUP_CHK_CREG,
STARTUP_CHK_CLTS,
STARTUP_CHK_CENG
};
enum StatesClock
{
CLOCK_REQ,
CLOCK_VERIFY
};
enum StatesSignal
{
SIGNAL_REQ,
SIGNAL_VERIFY
};
enum StatesBattry
{
BATTERY_REQ,
BATTERY_VERIFY
};
enum StatesInbox
{
READ_REQ,
READ_CHK_CMGF,
READ_CHK_CPMS,
READ_CHK_CMGL,
READ_DELAY_CLEAR_BUFF,
READ_TEXT_CMGR,
READ_CHK_CMGR,
READ_CHK_CMGD
};
enum StatesOutbox
{
SEND_REQ,
SEND_CHK_CMGF,
SEND_CHK_RDY,
SEND_CHK_OK
};
unsigned long tick;
struct
{
int Index; // Index of readed message
}Message;
SMSInfo SMSi; // Inbox SMS (incoming)
SMSInfo SMSo; // Outbox SMS (outgoing)
Stream& stream;
DTE dte;
unsigned long tickSync[THREADEDGSM_INTERVAL_COUNT];
unsigned long Intervals[THREADEDGSM_INTERVAL_COUNT];
// callbacks
conf configuration = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
enum ReqTypes
{
REQ_CLOCK = 1,
REQ_SIG = 2,
REQ_INBOX = 4,
REQ_OUTBOX = 8,
REQ_STARTUP = 16,
REQ_BATTERY = 32,
};
int requests;
int state;
int job;
//functions
public:
ThreadedGSM(Stream& stream) : stream(stream), dte(stream, THREADEDGSM_DTE_BUFFER_SIZE)
{
for(int i=0;i<THREADEDGSM_INTERVAL_COUNT;i++)
Intervals[i] = 0;
job = state = requests = 0;
//SMSo.Text.reserve(150);
//SMSi.Text.reserve(150);
}
~ThreadedGSM(){};
void nextJob()
{
job = 0;
}
void setHandlers(conf config)
{
this->configuration = config;
}
void setInterval(IntervalSourceE source, unsigned long interval)
{
Intervals[source] = interval;
tickSync[source] = millis();
}
// Initialization
void begin()
{
requests = (REQ_STARTUP);
}
// Call this function for executing thread
void loop()
{
if(dte.getIsBusy()) return;
// intervals
for(int i=0;i<THREADEDGSM_INTERVAL_COUNT;i++)
{
if(Intervals[i])
{
if(millis() - tickSync[i] >= Intervals[i])
{
switch (i) {
case INTERVAL_CLOCK:
requests |= REQ_CLOCK;
break;
case INTERVAL_INBOX:
requests |= REQ_INBOX;
break;
case INTERVAL_SIGNAL:
requests |= REQ_SIG;
break;
case INTERVAL_BATTERY:
requests |= REQ_BATTERY;
break;
}
tickSync[i] = millis();
}
}
}
if(job == 0)
{
// no assigned job, assign it
if(requests & REQ_CLOCK)
job = REQ_CLOCK;
else if(requests & REQ_SIG)
job = REQ_SIG;
else if(requests & REQ_INBOX)
job = REQ_INBOX;
else if(requests & REQ_OUTBOX)
job = REQ_OUTBOX;
else if(requests & REQ_STARTUP)
job = REQ_STARTUP;
else if(requests & REQ_BATTERY)
job = REQ_BATTERY;
if(job)
{
state = 0;
DEBUG_PRINT(F("Job ID: "));
DEBUG_PRINTLN(job);
}
}
// execute current job
if(job == REQ_STARTUP)
Startup();
else if(job == REQ_CLOCK)
Clock();
else if(job == REQ_SIG)
Signal();
else if(job == REQ_INBOX)
Inbox();
else if(job == REQ_OUTBOX)
Outbox();
else if(job == REQ_BATTERY)
Battery();
}
// Requests
void sendSMS(String& Number, String& Text)
{
requests |= (REQ_OUTBOX);
SMSo.Number = Number;
SMSo.Text = Text;
}
void sendSMS(String& Number, char *Text)
{
requests |= (REQ_OUTBOX);
SMSo.Number = Number;
SMSo.Text = Text;
}
protected:
private:
// States
void Startup()
{
int lastState = state;
switch(state)
{
case STARTUP_POWER_OFF:
if(this->configuration.power != NULL)
this->configuration.power(*this, false);
tick = millis();
state = STARTUP_POWER_OFF_DELAY;
break;
case STARTUP_POWER_OFF_DELAY:
if(millis() - tick >= THREADEDGSM_STARTUP_POWER_OFF_DELAY)
state = STARTUP_POWER_ON;
break;
case STARTUP_POWER_ON:
if(this->configuration.power != NULL)
this->configuration.power(*this, true);
// begin delay
tick = millis();
state = STARTUP_DELAY;
break;
case STARTUP_DELAY:
if(millis() - tick >= THREADEDGSM_STARTUP_DELAY)
{
dte.SendCommand(F("AT\r"), THREADEDGSM_AT_TIMEOUT, "OK\r");
state = STARTUP_ENTER_AT;
}
break;
case STARTUP_ENTER_AT:
if(dte.getResult() == DTE::EXPECT_RESULT)
{
dte.SendCommand(F("AT+CPIN?\r"), 10000, "OK\r");
state = STARTUP_CHK_CPIN;
}
else
{
state = STARTUP_POWER_OFF;
}
break;
case STARTUP_CHK_CPIN:
if(dte.getResult() == DTE::EXPECT_RESULT)
{
if(dte.getBuffer().indexOf(F("+CPIN: READY")) != -1)
{
dte.SendCommand(F("AT+CREG?\r"), THREADEDGSM_AT_TIMEOUT, "OK\r");
state = STARTUP_CHK_CREG;
}else
{
state = STARTUP_POWER_OFF;
}
}else
state = STARTUP_POWER_OFF;
break;
case STARTUP_CHK_CREG:
if(dte.getResult() == DTE::EXPECT_RESULT)
{
if((dte.getBuffer().indexOf(F(",1")) >= 0) || (dte.getBuffer().indexOf(F(",5")) >= 0))
{
dte.SendCommand(F("AT+CLTS=1\r"), THREADEDGSM_AT_TIMEOUT, "OK\r");
state = STARTUP_CHK_CLTS;
}else
state = STARTUP_POWER_OFF;
}else
state = STARTUP_POWER_OFF;
break;
case STARTUP_CHK_CLTS:
if(dte.getResult() == DTE::EXPECT_RESULT)
{
dte.SendCommand(F("AT+CENG=3\r"), THREADEDGSM_AT_TIMEOUT, "OK\r");
state = STARTUP_CHK_CENG;
}else
state = STARTUP_POWER_OFF;
break;
case STARTUP_CHK_CENG:
if(dte.getResult() == DTE::EXPECT_RESULT)
{
requests |= ((REQ_CLOCK)|(REQ_SIG)|(REQ_BATTERY));
clearReq(REQ_STARTUP);
for(int i=0;i<THREADEDGSM_INTERVAL_COUNT;i++)
tickSync[i] = millis();
if(this->configuration.ready != NULL)
this->configuration.ready(*this);
}else
state = STARTUP_POWER_OFF;
break;
}
if(state != lastState)
{
DEBUG_PRINT(F("STARTUP_STATE: "));
DEBUG_PRINTLN(state);
}
}
// Threads
void Clock()
{
String clockTime;
int lastState = state;
switch(state)
{
case CLOCK_REQ:
dte.SendCommand(F("AT+CCLK?\r"), THREADEDGSM_AT_TIMEOUT, "OK\r");
state = CLOCK_VERIFY;
break;
case CLOCK_VERIFY:
int index = dte.getBuffer().indexOf(F("+CCLK: "));
if(index >= 0)
{
// parse clock
index+=8;
int endindex;
endindex = dte.getBuffer().indexOf(F("+"), index);
if(endindex >= 0)
clockTime = dte.getBuffer().substring(index, endindex);
else
{
endindex = dte.getBuffer().indexOf(F("-"), index);
if(endindex >= 0)
clockTime = dte.getBuffer().substring(index, endindex);
}
if(endindex >= 0)
{
NetworkTime ClockTime;
ClockTime.year = 2000+clockTime.substring(0,2).toInt();
ClockTime.month = clockTime.substring(3,5).toInt();
ClockTime.day = clockTime.substring(6,8).toInt();
ClockTime.hour = clockTime.substring(9,11).toInt();
ClockTime.minute = clockTime.substring(12,14).toInt();
ClockTime.second = clockTime.substring(15,17).toInt();
if(this->configuration.clock != NULL)
this->configuration.clock(*this, ClockTime);
}
}
clearReq(REQ_CLOCK);
break;
}
if(state != lastState)
{
DEBUG_PRINT(F("CLOCK_STATE: "));
DEBUG_PRINTLN(state);
}
}
void Signal()
{
int lastState = state;
switch(state)
{
case SIGNAL_REQ:
dte.SendCommand(F("AT+CSQ\r"), THREADEDGSM_AT_TIMEOUT, "OK\r");
state = SIGNAL_VERIFY;
break;
case SIGNAL_VERIFY:
int index = dte.getBuffer().indexOf(F("+CSQ: "));
if(index >= 0)
{
// parse signal
index+=6;
SignalLevel GsmSignal;
GsmSignal.Value = dte.getBuffer().substring(index,index+2).toInt();
GsmSignal.Dbm = dte.getBuffer().substring(index+3,index+5).toInt();
if(GsmSignal.Value != 0)
{
if(this->configuration.signal != NULL)
this->configuration.signal(*this, GsmSignal);
}
}
clearReq(REQ_SIG);
break;
}
if(state != lastState)
{
DEBUG_PRINT(F("SIGNAL_STATE: "));
DEBUG_PRINTLN(state);
}
}
void Battery()
{
int lastState = state;
switch(state)
{
case BATTERY_REQ:
dte.SendCommand(F("AT+CBC\r"), THREADEDGSM_AT_TIMEOUT, "OK\r");
state = BATTERY_VERIFY;
break;
case BATTERY_VERIFY:
int index = dte.getBuffer().indexOf(F("+CBC:"));
if(index >= 0)
{
BatteryInfo BattInfo;
// parse battery level
String buffer = dte.getBuffer().substring(index);
String buffer2 = buffer.substring(buffer.indexOf(F(",")) + 1);
buffer = buffer2;
BattInfo.Percent = buffer2.substring(0, buffer2.indexOf(F(","))).toInt(); // converts the result to interger
buffer2 = buffer.substring(buffer.indexOf(F(",")) + 1);
BattInfo.Voltage = buffer2.substring(0, buffer2.indexOf(F("\r"))).toInt(); // converts the result to interger
if(this->configuration.battery != NULL)
this->configuration.battery(*this, BattInfo);
}
clearReq(REQ_BATTERY);
break;
}
if(state != lastState)
{
DEBUG_PRINT(F("BATTERY_STATE: "));
DEBUG_PRINTLN(state);
}
}
void clearReq(int req)
{
requests &= ~(req);
nextJob();
}
void Inbox()
{
String CMD;
int lastState = state;
switch(state)
{
case READ_REQ:
SMSi.Text = "";
SMSi.Number = "";
dte.SendCommand(F("AT+CMGF=0\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); // SMS Message format set as PDU
state = READ_CHK_CMGF;
break;
case READ_CHK_CMGF:
if(dte.getResult() == DTE::EXPECT_RESULT)
{
dte.SendCommand(F("AT+CPMS=\"SM\"\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); // SIM Message storage
state = READ_CHK_CPMS;
}
else clearReq(REQ_INBOX);
break;
case READ_CHK_CPMS:
if(dte.getResult() == DTE::EXPECT_RESULT)
{
CMD = F("AT+CMGL="); // Read all SMS messages
CMD += (int) READ_TYPE_ALL;
CMD += "\r";
dte.SendCommand(CMD.c_str(), THREADEDGSM_AT_TIMEOUT, ",");
state = READ_CHK_CMGL;
}
else clearReq(REQ_INBOX);
break;
case READ_CHK_CMGL:
if(dte.getResult() == DTE::EXPECT_RESULT)
{
//fetch index
int indexStart = dte.getBuffer().indexOf(F("+CMGL: "));
if (indexStart >= 0)
{
Message.Index = dte.getBuffer().substring(indexStart + 7, dte.getBuffer().indexOf(F(","))).toInt();
if(Message.Index != 0)
{
dte.Delay(2000);
state = READ_DELAY_CLEAR_BUFF;
}
}
}
if(state != READ_DELAY_CLEAR_BUFF)
clearReq(REQ_INBOX);
break;
case READ_DELAY_CLEAR_BUFF:
dte.SendCommand(F("AT+CMGF=1\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); // SMS Message format set as TEXT
state = READ_TEXT_CMGR;
break;
case READ_TEXT_CMGR:
if (dte.getResult() == DTE::EXPECT_RESULT)
{
CMD = F("AT+CMGR="); // Read the SMS message
CMD += Message.Index;
CMD += "\r";
dte.SendCommand(CMD.c_str(), THREADEDGSM_AT_TIMEOUT, "OK\r");
state = READ_CHK_CMGR;
}else
clearReq(REQ_INBOX);
break;
case READ_CHK_CMGR:
if(dte.getResult() == DTE::EXPECT_RESULT)
{
int indexStart = dte.getBuffer().indexOf(F("+CMGR: "));
if(indexStart >= 0)
{
int indexStartPDU = dte.getBuffer().indexOf(F("\r\n"), indexStart);
if (indexStartPDU >= 0)
{
indexStartPDU+=2;
int indexEndPDU = dte.getBuffer().indexOf(F("\r"), indexStartPDU);
if(indexEndPDU >= 0)
SMSi.Text = dte.getBuffer().substring(indexStartPDU, indexEndPDU);
}
indexStartPDU = dte.getBuffer().indexOf(F(",\""), indexStart);
if (indexStartPDU >= 0)
{
indexStartPDU += 2;
int indexEndPDU = dte.getBuffer().indexOf(F("\","), indexStartPDU);
if (indexEndPDU >= 0)
SMSi.Number = dte.getBuffer().substring(indexStartPDU, indexEndPDU);
}
}
CMD = F("AT+CMGD="); // Delete the SMS message
CMD += Message.Index;
CMD += "\r";
dte.SendCommand(CMD.c_str(), THREADEDGSM_AT_TIMEOUT, "OK\r");
state = READ_CHK_CMGD;
}else
clearReq(REQ_INBOX);
break;
case READ_CHK_CMGD:
//if( (dte.getResult() == DTE::EXPECT_RESULT) && (SMS.InboxMsgContents != ""))
if(dte.getResult() == DTE::EXPECT_RESULT)
{
if(this->configuration.incoming != NULL)
this->configuration.incoming(*this, SMSi);
}
clearReq(REQ_INBOX);
break;
}
if(state != lastState)
{
DEBUG_PRINT(F("INBOX_STATE: "));
DEBUG_PRINTLN(state);
}
}
void Outbox()
{
String CMD;
//CMD.reserve(200);
int lastState = state;
switch(state)
{
case SEND_REQ:
dte.SendCommand(F("AT+CMGF=1\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); // SMS Text mode
state = SEND_CHK_CMGF;
break;
case SEND_CHK_CMGF:
if(dte.getResult() == DTE::EXPECT_RESULT)
{
CMD = F("AT+CMGS=\"");
CMD += SMSo.Number;
CMD += "\"\r";
dte.SendCommand(CMD.c_str(), 15000, "> ");
state = SEND_CHK_RDY;
}
else clearReq(REQ_OUTBOX);
break;
case SEND_CHK_RDY:
if(dte.getResult() == DTE::EXPECT_RESULT)
{
CMD = SMSo.Text;
CMD += (char)26;
dte.SendCommand(CMD.c_str(), 10000, "OK\r");
state = SEND_CHK_OK;
}else clearReq(REQ_OUTBOX);
break;
case SEND_CHK_OK:
if(dte.getResult() == DTE::EXPECT_RESULT)
{
if(this->configuration.outgoing != NULL)
this->configuration.outgoing(*this);
}
clearReq(REQ_OUTBOX);
break;
}
if(state != lastState)
{
DEBUG_PRINT(F("OUTBOX_STATE: "));
DEBUG_PRINTLN(state);
}
}
}; //ThreadedGSM
#endif //__THREADEDGSM_H__