/* * ThreadedGSM.h * * Created: 20/09/2016 11:14:02 * Author: Neta Yahav */ #ifndef __THREADEDGSM_H__ #define __THREADEDGSM_H__ #include #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;iconfiguration = 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= 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;iconfiguration.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__