#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) : stream(stream), dte(stream, THREADEDGSM_DTE_BUFFER_SIZE), ringState(RING_WAIT) { for (unsigned long & Interval : Intervals) Interval = 0; job = state = requests = 0; // SMSo.Text.reserve(150); // SMSi.Text.reserve(150); } void ThreadedGSM::nextJob() { job = 0; } void ThreadedGSM::setHandlers(conf config) { this->configuration = config; } void ThreadedGSM::setInterval(IntervalSourceE source, unsigned long interval) { Intervals[source] = interval; tickSync[source] = millis(); } // Initialization void ThreadedGSM::begin() { requests = (REQ_STARTUP); TimeBase.attachLoop(this); } // Call this function for executing thread void ThreadedGSM::exec() { 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(); else CheckRing(); } // Requests void ThreadedGSM::sendSMS(String & Number, String & Text) { requests |= (REQ_OUTBOX); SMSo.Number = Number; SMSo.Text = Text; } void ThreadedGSM::sendSMS(String & Number, const char * Text) { String t = Text; sendSMS(Number, t); } // States void ThreadedGSM::Startup() { int lastState = state; switch (state) { case STARTUP_POWER_OFF: if (this->configuration.power != nullptr) 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 != nullptr) 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 != nullptr) this->configuration.ready(*this); } else state = STARTUP_POWER_OFF; break; } if (state != lastState) { DEBUG_PRINT(F("STARTUP_STATE: ")); DEBUG_PRINTLN(state); } } // Threads void ThreadedGSM::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 != nullptr) this->configuration.clock(*this, ClockTime); } } clearReq(REQ_CLOCK); break; } if (state != lastState) { DEBUG_PRINT(F("CLOCK_STATE: ")); DEBUG_PRINTLN(state); } } void ThreadedGSM::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 != nullptr) this->configuration.signal(*this, GsmSignal); } } clearReq(REQ_SIG); break; } if (state != lastState) { DEBUG_PRINT(F("SIGNAL_STATE: ")); DEBUG_PRINTLN(state); } } void ThreadedGSM::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 != nullptr) this->configuration.battery(*this, BattInfo); } clearReq(REQ_BATTERY); break; } if (state != lastState) { DEBUG_PRINT(F("BATTERY_STATE: ")); DEBUG_PRINTLN(state); } } void ThreadedGSM::clearReq(int req) { requests &= ~(req); nextJob(); } void ThreadedGSM::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 != nullptr) this->configuration.incoming(*this, SMSi); } clearReq(REQ_INBOX); break; } if (state != lastState) { DEBUG_PRINT(F("INBOX_STATE: ")); DEBUG_PRINTLN(state); } } void ThreadedGSM::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 != nullptr) this->configuration.outgoing(*this); } clearReq(REQ_OUTBOX); break; } if (state != lastState) { DEBUG_PRINT(F("OUTBOX_STATE: ")); DEBUG_PRINTLN(state); } } void ThreadedGSM::CheckRing() { int lastState = ringState; switch (ringState) { case RING_WAIT: break; case RING_CHK: if (this->configuration.ring != nullptr) { String s = "Ring"; this->configuration.ring(*this, s); } ringState = RING_WAIT; break; } if (ringState != lastState) { DEBUG_PRINT(F("RING_STATE: ")); DEBUG_PRINTLN(ringState); } }