#include "ThreadedGSM.h" #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 ThreadedGSM::ThreadedGSM(Stream & stream) : mStream(stream), mDte(stream, THREADEDGSM_DTE_BUFFER_SIZE), mRingState(RING_WAIT) { mSMSo.Text.reserve(150); mSMSi.Text.reserve(150); } void ThreadedGSM::nextJob() { mJob = 0; } void ThreadedGSM::setHandlers(conf config) { mConfiguration = config; } void ThreadedGSM::setInterval(IntervalSourceE source, unsigned long interval) { Intervals[source] = interval; tickSync[source] = millis(); } // Initialization void ThreadedGSM::begin() { mRequests = (REQ_STARTUP); TimeBase.attachLoop(this); } // Call this function for executing thread void ThreadedGSM::exec() { if (mDte.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: mRequests |= REQ_CLOCK; break; case INTERVAL_INBOX: mRequests |= REQ_INBOX; break; case INTERVAL_SIGNAL: mRequests |= REQ_SIG; break; case INTERVAL_BATTERY: mRequests |= REQ_BATTERY; break; } tickSync[i] = millis(); } } } if (mJob == 0) { // no assigned mJob, assign it if (mRequests & REQ_CLOCK) mJob = REQ_CLOCK; else if (mRequests & REQ_SIG) mJob = REQ_SIG; else if (mRequests & REQ_INBOX) mJob = REQ_INBOX; else if (mRequests & REQ_OUTBOX) mJob = REQ_OUTBOX; else if (mRequests & REQ_STARTUP) mJob = REQ_STARTUP; else if (mRequests & REQ_BATTERY) mJob = REQ_BATTERY; if (mJob) { mState = 0; DEBUG_PRINT(F("Job ID: ")); DEBUG_PRINTLN(mJob); } } // execute current mJob if (mJob == REQ_STARTUP) Startup(); else if (mJob == REQ_CLOCK) Clock(); else if (mJob == REQ_SIG) Signal(); else if (mJob == REQ_INBOX) Inbox(); else if (mJob == REQ_OUTBOX) Outbox(); else if (mJob == REQ_BATTERY) Battery(); else CheckRing(); } // Requests void ThreadedGSM::sendSMS(String & Number, String & Text) { mRequests |= (REQ_OUTBOX); mSMSo.Number = Number; mSMSo.Text = Text; } void ThreadedGSM::sendSMS(String & Number, const char * Text) { String t = Text; sendSMS(Number, t); } // States void ThreadedGSM::Startup() { int lastState = mState; switch (mState) { case STARTUP_POWER_OFF: if (mConfiguration.power != nullptr) mConfiguration.power(*this, false); mTick = millis(); mState = STARTUP_POWER_OFF_DELAY; break; case STARTUP_POWER_OFF_DELAY: if (millis() - mTick >= THREADEDGSM_STARTUP_POWER_OFF_DELAY) mState = STARTUP_POWER_ON; break; case STARTUP_POWER_ON: if (mConfiguration.power != nullptr) mConfiguration.power(*this, true); // begin delay mTick = millis(); mState = STARTUP_DELAY; break; case STARTUP_DELAY: if (millis() - mTick >= THREADEDGSM_STARTUP_DELAY) { mDte.SendCommand(F("AT\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); mState = STARTUP_ENTER_AT; } break; case STARTUP_ENTER_AT: if (mDte.getResult() == DTE::EXPECT_RESULT) { mDte.SendCommand(F("AT+CPIN?\r"), 10000, "OK\r"); mState = STARTUP_CHK_CPIN; } else { mState = STARTUP_POWER_OFF; } break; case STARTUP_CHK_CPIN: if (mDte.getResult() == DTE::EXPECT_RESULT) { if (mDte.getBuffer().indexOf(F("+CPIN: READY")) != -1) { mDte.SendCommand(F("AT+CREG?\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); mState = STARTUP_CHK_CREG; } else { mState = STARTUP_POWER_OFF; } } else mState = STARTUP_POWER_OFF; break; case STARTUP_CHK_CREG: if (mDte.getResult() == DTE::EXPECT_RESULT) { if ((mDte.getBuffer().indexOf(F(",1")) >= 0) || (mDte.getBuffer().indexOf(F(",5")) >= 0)) { mDte.SendCommand(F("AT+CLTS=1\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); mState = STARTUP_CHK_CLTS; } else mState = STARTUP_POWER_OFF; } else mState = STARTUP_POWER_OFF; break; case STARTUP_CHK_CLTS: if (mDte.getResult() == DTE::EXPECT_RESULT) { mDte.SendCommand(F("AT+CENG=3\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); mState = STARTUP_CHK_CENG; } else mState = STARTUP_POWER_OFF; break; case STARTUP_CHK_CENG: if (mDte.getResult() == DTE::EXPECT_RESULT) { mRequests |= ((REQ_CLOCK) | (REQ_SIG) | (REQ_BATTERY)); clearReq(REQ_STARTUP); for (unsigned long & i : tickSync) i = millis(); if (mConfiguration.ready != nullptr) mConfiguration.ready(*this); } else mState = STARTUP_POWER_OFF; break; } if (mState != lastState) { DEBUG_PRINT(F("STARTUP_STATE: ")); DEBUG_PRINTLN(mState); } } // Threads void ThreadedGSM::Clock() { String clockTime; int lastState = mState; switch (mState) { case CLOCK_REQ: mDte.SendCommand(F("AT+CCLK?\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); mState = CLOCK_VERIFY; break; case CLOCK_VERIFY: int index = mDte.getBuffer().indexOf(F("+CCLK: ")); if (index >= 0) { // parse clock index += 8; int endindex; endindex = mDte.getBuffer().indexOf(F("+"), index); if (endindex >= 0) clockTime = mDte.getBuffer().substring(index, endindex); else { endindex = mDte.getBuffer().indexOf(F("-"), index); if (endindex >= 0) clockTime = mDte.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 (mConfiguration.clock != nullptr) mConfiguration.clock(*this, ClockTime); } } clearReq(REQ_CLOCK); break; } if (mState != lastState) { DEBUG_PRINT(F("CLOCK_STATE: ")); DEBUG_PRINTLN(mState); } } void ThreadedGSM::Signal() { int lastState = mState; switch (mState) { case SIGNAL_REQ: mDte.SendCommand(F("AT+CSQ\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); mState = SIGNAL_VERIFY; break; case SIGNAL_VERIFY: int index = mDte.getBuffer().indexOf(F("+CSQ: ")); if (index >= 0) { // parse signal index += 6; SignalLevel GsmSignal{}; GsmSignal.Value = mDte.getBuffer().substring(index, index + 2).toInt(); GsmSignal.Dbm = mDte.getBuffer().substring(index + 3, index + 5).toInt(); if (GsmSignal.Value != 0) { if (mConfiguration.signal != nullptr) mConfiguration.signal(*this, GsmSignal); } } clearReq(REQ_SIG); break; } if (mState != lastState) { DEBUG_PRINT(F("SIGNAL_STATE: ")); DEBUG_PRINTLN(mState); } } void ThreadedGSM::Battery() { int lastState = mState; switch (mState) { case BATTERY_REQ: mDte.SendCommand(F("AT+CBC\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); mState = BATTERY_VERIFY; break; case BATTERY_VERIFY: int index = mDte.getBuffer().indexOf(F("+CBC:")); if (index >= 0) { BatteryInfo BattInfo{}; // parse battery level String buffer = mDte.getBuffer().substring(index); String buffer2 = buffer.substring(buffer.indexOf(F(",")) + 1); buffer = buffer2; BattInfo.Percent = buffer2.substring(0, buffer2.indexOf(F(","))).toInt(); // converts the mResult to interger buffer2 = buffer.substring(buffer.indexOf(F(",")) + 1); BattInfo.Voltage = buffer2.substring(0, buffer2.indexOf(F("\r"))).toInt(); // converts the mResult to interger if (mConfiguration.battery != nullptr) mConfiguration.battery(*this, BattInfo); } clearReq(REQ_BATTERY); break; } if (mState != lastState) { DEBUG_PRINT(F("BATTERY_STATE: ")); DEBUG_PRINTLN(mState); } } void ThreadedGSM::clearReq(int req) { mRequests &= ~(req); nextJob(); } void ThreadedGSM::Inbox() { String CMD; int lastState = mState; switch (mState) { case READ_REQ: mSMSi.Text = ""; mSMSi.Number = ""; mDte.SendCommand(F("AT+CMGF=0\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); // SMS Message format set as PDU mState = READ_CHK_CMGF; break; case READ_CHK_CMGF: if (mDte.getResult() == DTE::EXPECT_RESULT) { mDte.SendCommand(F("AT+CPMS=\"SM\"\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); // SIM Message storage mState = READ_CHK_CPMS; } else clearReq(REQ_INBOX); break; case READ_CHK_CPMS: if (mDte.getResult() == DTE::EXPECT_RESULT) { CMD = F("AT+CMGL="); // Read all SMS messages CMD += (int) READ_TYPE_ALL; CMD += "\r"; mDte.SendCommand(CMD.c_str(), THREADEDGSM_AT_TIMEOUT, ","); mState = READ_CHK_CMGL; } else clearReq(REQ_INBOX); break; case READ_CHK_CMGL: if (mDte.getResult() == DTE::EXPECT_RESULT) { // fetch index int indexStart = mDte.getBuffer().indexOf(F("+CMGL: ")); if (indexStart >= 0) { Message.Index = mDte.getBuffer().substring(indexStart + 7, mDte.getBuffer().indexOf(F(","))).toInt(); if (Message.Index != 0) { mDte.Delay(2000); mState = READ_DELAY_CLEAR_BUFF; } } } if (mState != READ_DELAY_CLEAR_BUFF) clearReq(REQ_INBOX); break; case READ_DELAY_CLEAR_BUFF: mDte.SendCommand(F("AT+CMGF=1\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); // SMS Message format set as TEXT mState = READ_TEXT_CMGR; break; case READ_TEXT_CMGR: if (mDte.getResult() == DTE::EXPECT_RESULT) { CMD = F("AT+CMGR="); // Read the SMS message CMD += Message.Index; CMD += "\r"; mDte.SendCommand(CMD.c_str(), THREADEDGSM_AT_TIMEOUT, "OK\r"); mState = READ_CHK_CMGR; } else clearReq(REQ_INBOX); break; case READ_CHK_CMGR: if (mDte.getResult() == DTE::EXPECT_RESULT) { int indexStart = mDte.getBuffer().indexOf(F("+CMGR: ")); if (indexStart >= 0) { int indexStartPDU = mDte.getBuffer().indexOf(F("\r\n"), indexStart); if (indexStartPDU >= 0) { indexStartPDU += 2; int indexEndPDU = mDte.getBuffer().indexOf(F("\r"), indexStartPDU); if (indexEndPDU >= 0) mSMSi.Text = mDte.getBuffer().substring(indexStartPDU, indexEndPDU); } indexStartPDU = mDte.getBuffer().indexOf(F(",\""), indexStart); if (indexStartPDU >= 0) { indexStartPDU += 2; int indexEndPDU = mDte.getBuffer().indexOf(F("\","), indexStartPDU); if (indexEndPDU >= 0) mSMSi.Number = mDte.getBuffer().substring(indexStartPDU, indexEndPDU); } } CMD = F("AT+CMGD="); // Delete the SMS message CMD += Message.Index; CMD += "\r"; mDte.SendCommand(CMD.c_str(), THREADEDGSM_AT_TIMEOUT, "OK\r"); mState = READ_CHK_CMGD; } else clearReq(REQ_INBOX); break; case READ_CHK_CMGD: // if( (mDte.getResult() == DTE::EXPECT_RESULT) && (SMS.InboxMsgContents != "")) if (mDte.getResult() == DTE::EXPECT_RESULT) { if (mConfiguration.incoming != nullptr) mConfiguration.incoming(*this, mSMSi); } clearReq(REQ_INBOX); break; } if (mState != lastState) { DEBUG_PRINT(F("INBOX_STATE: ")); DEBUG_PRINTLN(mState); } } void ThreadedGSM::Outbox() { String CMD; // CMD.reserve(200); int lastState = mState; switch (mState) { case SEND_REQ: mDte.SendCommand(F("AT+CMGF=1\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); // SMS Text mode mState = SEND_CHK_CMGF; break; case SEND_CHK_CMGF: if (mDte.getResult() == DTE::EXPECT_RESULT) { CMD = F("AT+CMGS=\""); CMD += mSMSo.Number; CMD += "\"\r"; mDte.SendCommand(CMD.c_str(), 15000, "> "); mState = SEND_CHK_RDY; } else clearReq(REQ_OUTBOX); break; case SEND_CHK_RDY: if (mDte.getResult() == DTE::EXPECT_RESULT) { CMD = mSMSo.Text; CMD += (char) 26; mDte.SendCommand(CMD.c_str(), 10000, "OK\r"); mState = SEND_CHK_OK; } else clearReq(REQ_OUTBOX); break; case SEND_CHK_OK: if (mDte.getResult() == DTE::EXPECT_RESULT) { if (mConfiguration.outgoing != nullptr) mConfiguration.outgoing(*this); } clearReq(REQ_OUTBOX); break; } if (mState != lastState) { DEBUG_PRINT(F("OUTBOX_STATE: ")); DEBUG_PRINTLN(mState); } } void ThreadedGSM::CheckRing() { int lastState = mRingState; switch (mRingState) { case RING_WAIT: break; case RING_CHK: if (mConfiguration.ring != nullptr) { String s = "Ring"; mConfiguration.ring(*this, s); } mRingState = RING_WAIT; break; } if (mRingState != lastState) { DEBUG_PRINT(F("RING_STATE: ")); DEBUG_PRINTLN(mRingState); } }