#include "ThreadedGSM.h" #include #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; default: break; } tickSync[i] = millis(); } } } if (mJob == 0 && mRequests != 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; else if (mRequests & REQ_HANGUP) mJob = REQ_HANGUP; 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 if (mJob == REQ_HANGUP) Hangup(); 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) 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) 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) { mDte.SendCommand(F("AT+CLIP=1\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); mState = STARTUP_CHK_CLIP; } else mState = STARTUP_POWER_OFF; break; case STARTUP_CHK_CLIP: if (mDte.getResult() == DTE::EXPECT_RESULT) { startupDone(); } 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) 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) 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) mConfiguration.battery(*this, BattInfo); } clearReq(REQ_BATTERY); break; } if (mState != lastState) { DEBUG_PRINT(F("BATTERY_STATE: ")); DEBUG_PRINTLN(mState); } } void ThreadedGSM::Hangup() { int lastState = mState; switch (mState) { case HANGUP_REQ: mDte.SendCommand(F("ATH\r"), THREADEDGSM_AT_TIMEOUT, "OK\r"); mState = HANGUP_CHK; break; case HANGUP_CHK: if (mDte.getResult() == DTE::EXPECT_RESULT) { clearReq(REQ_HANGUP); } else clearReq(REQ_HANGUP); break; } if (mState != lastState) { DEBUG_PRINT(F("HANGUP_STATE: ")); DEBUG_PRINTLN(mState); } } void ThreadedGSM::clearReq(int req) { mRequests &= ~(req); mDte.clearBuffer(); 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 String & buffer = mDte.getBuffer(); int indexStart = buffer.indexOf(F("+CMGL: ")); if (indexStart >= 0) { Message.Index = buffer.substring(indexStart + 7, buffer.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) 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) mConfiguration.outgoing(*this); } clearReq(REQ_OUTBOX); break; } if (mState != lastState) { DEBUG_PRINT(F("OUTBOX_STATE: ")); DEBUG_PRINTLN(mState); } } void ThreadedGSM::CheckRing() { if (mDte.buffer()) { String & buffer = mDte.getBuffer(); if (buffer.length() > 0) { bool dump = true; if (buffer.startsWith(F("+CLIP: \""))) { // CLIP (RING) detected if (mConfiguration.ring) { int index = 8; int endindex; endindex = buffer.indexOf(F("\""), index); if (endindex >= 0) { String num = buffer.substring(index, endindex); mConfiguration.ring(*this, num); dump = false; } } } else if (buffer.startsWith(F("NO CARRIER"))) { // end of call detected if (mConfiguration.ring) { String num = F("end"); mConfiguration.ring(*this, num); dump = false; } } else if (buffer.startsWith(F("+CMTI: \"SM\""))) { // SMS receive detected if (mConfiguration.incoming) { mRequests |= (REQ_INBOX); dump = false; } } if (dump) { TRACE(TRACE_DEBUG, F("Gsm: %s"), buffer.c_str()); } mDte.clearBuffer(); } } } void ThreadedGSM::startupDone() { if (mConfiguration.clock) mRequests |= (REQ_CLOCK); if (mConfiguration.battery) mRequests |= (REQ_BATTERY); if (mConfiguration.signal) mRequests |= (REQ_SIG); if (mConfiguration.incoming) mRequests |= (REQ_INBOX); clearReq(REQ_STARTUP); for (unsigned long & i : tickSync) i = millis(); if (mConfiguration.ready) mConfiguration.ready(*this); } void ThreadedGSM::hangup() { mRequests |= (REQ_HANGUP); } void ThreadedGSM::restartModem() { mRequests |= (REQ_STARTUP); }