diff --git a/src/DTE.cpp b/src/DTE.cpp new file mode 100644 index 0000000..738a24b --- /dev/null +++ b/src/DTE.cpp @@ -0,0 +1,122 @@ +#include "DTE.h" + +DTE::DTE(Stream & stream, unsigned int size) + : stream(stream), bufferSize(size), result(EXPECT_RESULT) +{ + buffer.reserve(size); +} + +void DTE::SendCommand(const char * command, unsigned long timeout, const char * response1, const char * response2, const char * response3) +{ + match = 0; + result = EXPECT_BUSY; + response[0] = response1; + response[1] = response2; + response[2] = response3; + this->timeout = timeout; + flush(); + stream.print(command); + buffer = ""; + tick = millis(); + process(); +} + +void DTE::SendCommand(const __FlashStringHelper * command, unsigned long timeout, const char * response1, const char * response2, const char * response3) +{ + + match = 0; + result = EXPECT_BUSY; + response[0] = response1; + response[1] = response2; + response[2] = response3; + this->timeout = timeout; + // clear rx buffer + flush(); + // send command + stream.print((const __FlashStringHelper *) command); + buffer = ""; + tick = millis(); + process(); +} + +void DTE::Delay(unsigned long delay) +{ + timeout = delay; + result = EXPECT_DELAY; + tick = millis(); + process(); +} + +bool DTE::getIsBusy() +{ + process(); + return (result == EXPECT_BUSY) || (result == EXPECT_DELAY); +} + +DTE::CommandResult DTE::getResult() +{ + return result; +} + +unsigned int DTE::getMatch() const +{ + return match; +} + +String & DTE::getBuffer() +{ + return buffer; +} + +void DTE::flush() +{ + // clear rx buffer + while (stream.available()) { + stream.read(); + } +} + +void DTE::process() +{ + if (result == EXPECT_DELAY) { + if (millis() - tick >= timeout) + result = EXPECT_RESULT; + + return; + } + + if (result != EXPECT_BUSY) + return; + + char c; + unsigned long now = millis(); + + while (millis() - tick < timeout) { + while (stream.available() && (buffer.length() < bufferSize)) { + c = stream.read(); + buffer.concat(c); + if (buffer.endsWith(response[0])) { + match = 0; + result = EXPECT_RESULT; + return; + } else if (response[1].length() != 0) { + if (buffer.endsWith(response[1])) { + match = 1; + result = EXPECT_RESULT; + return; + } + } else if (response[2].length() != 0) { + if (buffer.endsWith(response[2])) { + match = 2; + result = EXPECT_RESULT; + return; + } + } + } + if (millis() - now > 5) + return; + } + + // time out + result = EXPECT_TIMEOUT; +} diff --git a/src/DTE.h b/src/DTE.h index 154f9d2..63c928e 100644 --- a/src/DTE.h +++ b/src/DTE.h @@ -1,8 +1,7 @@ /* * DTE.h * - * Created: 20/09/2016 15:40:51 - * Author: Neta Yahav + * Author: Neta Yahav, Pavel Brychta */ #pragma once @@ -18,7 +17,7 @@ class DTE { EXPECT_RESULT }; - private: + protected: String buffer; Stream & stream; unsigned int bufferSize; @@ -29,127 +28,26 @@ class DTE { CommandResult result; public: - DTE(Stream & stream, unsigned int size) - : stream(stream), bufferSize(size), result(EXPECT_RESULT) - { - buffer.reserve(size); - } + DTE(Stream & stream, unsigned int size); ~DTE() = default; - void SendCommand(const char * command, unsigned long timeout, const char * response1, const char * response2 = nullptr, const char * response3 = nullptr) - { - match = 0; - result = EXPECT_BUSY; - response[0] = response1; - response[1] = response2; - response[2] = response3; - this->timeout = timeout; - flush(); - stream.print(command); - buffer = ""; - tick = millis(); - proccess(); - } + void SendCommand(const char * command, unsigned long timeout, const char * response1, const char * response2 = nullptr, const char * response3 = nullptr); - void SendCommand(const __FlashStringHelper * command, unsigned long timeout, const char * response1, const char * response2 = nullptr, const char * response3 = nullptr) - { + void SendCommand(const __FlashStringHelper * command, unsigned long timeout, const char * response1, const char * response2 = nullptr, const char * response3 = nullptr); - match = 0; - result = EXPECT_BUSY; - response[0] = response1; - response[1] = response2; - response[2] = response3; - this->timeout = timeout; - // clear rx buffer - flush(); - // send command - stream.print((const __FlashStringHelper *) command); - buffer = ""; - tick = millis(); - proccess(); - } + void Delay(unsigned long delay); - void Delay(unsigned long delay) - { - timeout = delay; - result = EXPECT_DELAY; - tick = millis(); - proccess(); - } + bool getIsBusy(); - bool getIsBusy() - { - proccess(); - return (result == EXPECT_BUSY) || (result == EXPECT_DELAY); - } + CommandResult getResult(); - CommandResult getResult() - { - return result; - } + [[nodiscard]] unsigned int getMatch() const; - unsigned int getMatch() const - { - return match; - } + String & getBuffer(); - String & getBuffer() - { - return buffer; - } + protected: + void flush(); - private: - void flush() - { - // clear rx buffer - while (stream.available()) { - stream.read(); - } - } - - void proccess() - { - if (result == EXPECT_DELAY) { - if (millis() - tick >= timeout) - result = EXPECT_RESULT; - - return; - } - - if (result != EXPECT_BUSY) - return; - - char c; - unsigned long now = millis(); - - while (millis() - tick < timeout) { - while (stream.available() && (buffer.length() < bufferSize)) { - c = stream.read(); - buffer.concat(c); - if (buffer.endsWith(response[0])) { - match = 0; - result = EXPECT_RESULT; - return; - } else if (response[1].length() != 0) { - if (buffer.endsWith(response[1])) { - match = 1; - result = EXPECT_RESULT; - return; - } - } else if (response[2].length() != 0) { - if (buffer.endsWith(response[2])) { - match = 2; - result = EXPECT_RESULT; - return; - } - } - } - if (millis() - now > 5) - return; - } - - // time out - result = EXPECT_TIMEOUT; - } + void process(); }; // DTE diff --git a/src/ThreadedGSM.cpp b/src/ThreadedGSM.cpp new file mode 100644 index 0000000..5747e87 --- /dev/null +++ b/src/ThreadedGSM.cpp @@ -0,0 +1,512 @@ +#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); + } +} diff --git a/src/ThreadedGSM.h b/src/ThreadedGSM.h index 95fc3aa..bc08a7b 100644 --- a/src/ThreadedGSM.h +++ b/src/ThreadedGSM.h @@ -1,19 +1,19 @@ /* * ThreadedGSM.h * - * Created: 20/09/2016 11:14:02 - * Author: Neta Yahav + * Author: Neta Yahav, Pavel Brychta */ #pragma once #include "DTE.h" -#include +//#include +#include // Defaults #define THREADEDGSM_DEF_DTE_BUF_SIZ 512 -#define THREADEDGSM_DEF_AT_TIMEOUT 5000 -#define THREADEDGSM_DEF_STA_PON 10000 +#define THREADEDGSM_DEF_AT_TIMEOUT 2000 +#define THREADEDGSM_DEF_STA_PON 15000 #define THREADEDGSM_DEF_STA_POF 1000 // Use custom values or default ones @@ -30,17 +30,10 @@ #define THREADEDGSM_STARTUP_POWER_OFF_DELAY THREADEDGSM_DEF_STA_POF #endif -#define THREADEDGSM_INTERVAL_COUNT 4 +#define THREADEDGSM_INTERVAL_COUNT 3 -#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 { +class ThreadedGSM : public Executable +{ // variables public: struct NetworkTime { @@ -101,7 +94,6 @@ class ThreadedGSM { }; protected: - private: enum StatesStartup { STARTUP_POWER_OFF, STARTUP_POWER_OFF_DELAY, @@ -186,509 +178,43 @@ class ThreadedGSM { int ringState; // functions public: - ThreadedGSM(Stream & stream) - : stream(stream), dte(stream, THREADEDGSM_DTE_BUFFER_SIZE), ringState(RING_WAIT) - { - for (unsigned long & Interval : Intervals) - Interval = 0; + explicit ThreadedGSM(Stream & stream); - job = state = requests = 0; - // SMSo.Text.reserve(150); - // SMSi.Text.reserve(150); - } + ~ThreadedGSM() override = default; - ~ThreadedGSM() = default; + void nextJob(); - void nextJob() - { - job = 0; - } + void setHandlers(conf config); - void setHandlers(conf config) - { - - this->configuration = config; - } - - void setInterval(IntervalSourceE source, unsigned long interval) - { - Intervals[source] = interval; - tickSync[source] = millis(); - } + void setInterval(IntervalSourceE source, unsigned long interval); // Initialization - void begin() - { - requests = (REQ_STARTUP); - } + void begin(); // 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(); - else - CheckRing(); - } + void exec() override; // Requests - void sendSMS(String & Number, String & Text) - { - requests |= (REQ_OUTBOX); - SMSo.Number = Number; - SMSo.Text = Text; - } + void sendSMS(String & Number, String & Text); - void sendSMS(String & Number, const char * Text) - { - String t = Text; - - sendSMS(Number, t); - } + void sendSMS(String & Number, const char * Text); protected: - private: // States - void 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); - } - } + void Startup(); // 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; + void Clock(); - 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); - } + void Signal(); - if (endindex >= 0) { - NetworkTime ClockTime{}; + void Battery(); - 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 clearReq(int req); - 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; + void Inbox(); - 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 Outbox(); - 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 != nullptr) - 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 != nullptr) - 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 != nullptr) - this->configuration.outgoing(*this); - } - clearReq(REQ_OUTBOX); - break; - } - if (state != lastState) { - DEBUG_PRINT(F("OUTBOX_STATE: ")); - DEBUG_PRINTLN(state); - } - } - - void 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); - } - } + void CheckRing(); }; // ThreadedGSM