Rozdeleni h/cpp, prechod na potomka Executable

This commit is contained in:
Pavel Brychta 2023-09-03 12:05:34 +02:00
parent 3f0e33c421
commit 5bccbd1113
4 changed files with 672 additions and 614 deletions

122
src/DTE.cpp Normal file
View File

@ -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;
}

128
src/DTE.h
View File

@ -1,8 +1,7 @@
/* /*
* DTE.h * DTE.h
* *
* Created: 20/09/2016 15:40:51 * Author: Neta Yahav, Pavel Brychta
* Author: Neta Yahav
*/ */
#pragma once #pragma once
@ -18,7 +17,7 @@ class DTE {
EXPECT_RESULT EXPECT_RESULT
}; };
private: protected:
String buffer; String buffer;
Stream & stream; Stream & stream;
unsigned int bufferSize; unsigned int bufferSize;
@ -29,127 +28,26 @@ class DTE {
CommandResult result; CommandResult result;
public: public:
DTE(Stream & stream, unsigned int size) DTE(Stream & stream, unsigned int size);
: stream(stream), bufferSize(size), result(EXPECT_RESULT)
{
buffer.reserve(size);
}
~DTE() = default; ~DTE() = default;
void SendCommand(const char * command, unsigned long timeout, const char * response1, const char * response2 = nullptr, const char * response3 = nullptr) 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 __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; void Delay(unsigned long delay);
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) bool getIsBusy();
{
timeout = delay;
result = EXPECT_DELAY;
tick = millis();
proccess();
}
bool getIsBusy() CommandResult getResult();
{
proccess();
return (result == EXPECT_BUSY) || (result == EXPECT_DELAY);
}
CommandResult getResult() [[nodiscard]] unsigned int getMatch() const;
{
return result;
}
unsigned int getMatch() const String & getBuffer();
{
return match;
}
String & getBuffer() protected:
{ void flush();
return buffer;
}
private: void process();
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;
}
}; // DTE }; // DTE

512
src/ThreadedGSM.cpp Normal file
View File

@ -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);
}
}

View File

@ -1,19 +1,19 @@
/* /*
* ThreadedGSM.h * ThreadedGSM.h
* *
* Created: 20/09/2016 11:14:02 * Author: Neta Yahav, Pavel Brychta
* Author: Neta Yahav
*/ */
#pragma once #pragma once
#include "DTE.h" #include "DTE.h"
#include <Arduino.h> //#include <Arduino.h>
#include <Chronos.hpp>
// Defaults // Defaults
#define THREADEDGSM_DEF_DTE_BUF_SIZ 512 #define THREADEDGSM_DEF_DTE_BUF_SIZ 512
#define THREADEDGSM_DEF_AT_TIMEOUT 5000 #define THREADEDGSM_DEF_AT_TIMEOUT 2000
#define THREADEDGSM_DEF_STA_PON 10000 #define THREADEDGSM_DEF_STA_PON 15000
#define THREADEDGSM_DEF_STA_POF 1000 #define THREADEDGSM_DEF_STA_POF 1000
// Use custom values or default ones // Use custom values or default ones
@ -30,17 +30,10 @@
#define THREADEDGSM_STARTUP_POWER_OFF_DELAY THREADEDGSM_DEF_STA_POF #define THREADEDGSM_STARTUP_POWER_OFF_DELAY THREADEDGSM_DEF_STA_POF
#endif #endif
#define THREADEDGSM_INTERVAL_COUNT 4 #define THREADEDGSM_INTERVAL_COUNT 3
#ifdef THREADEDGSM_DEBUG class ThreadedGSM : public Executable
#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 // variables
public: public:
struct NetworkTime { struct NetworkTime {
@ -101,7 +94,6 @@ class ThreadedGSM {
}; };
protected: protected:
private:
enum StatesStartup { enum StatesStartup {
STARTUP_POWER_OFF, STARTUP_POWER_OFF,
STARTUP_POWER_OFF_DELAY, STARTUP_POWER_OFF_DELAY,
@ -186,509 +178,43 @@ class ThreadedGSM {
int ringState; int ringState;
// functions // functions
public: public:
ThreadedGSM(Stream & stream) explicit 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; ~ThreadedGSM() override = default;
// SMSo.Text.reserve(150);
// SMSi.Text.reserve(150);
}
~ThreadedGSM() = default; void nextJob();
void nextJob() void setHandlers(conf config);
{
job = 0;
}
void setHandlers(conf config) void setInterval(IntervalSourceE source, unsigned long interval);
{
this->configuration = config;
}
void setInterval(IntervalSourceE source, unsigned long interval)
{
Intervals[source] = interval;
tickSync[source] = millis();
}
// Initialization // Initialization
void begin() void begin();
{
requests = (REQ_STARTUP);
}
// Call this function for executing thread // Call this function for executing thread
void loop() void exec() override;
{
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 // Requests
void sendSMS(String & Number, String & Text) void sendSMS(String & Number, String & Text);
{
requests |= (REQ_OUTBOX);
SMSo.Number = Number;
SMSo.Text = Text;
}
void sendSMS(String & Number, const char * Text) void sendSMS(String & Number, const char * Text);
{
String t = Text;
sendSMS(Number, t);
}
protected: protected:
private:
// States // States
void Startup() 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);
}
}
// Threads // Threads
void Clock() 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: void Signal();
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) { void Battery();
NetworkTime ClockTime{};
ClockTime.year = 2000 + clockTime.substring(0, 2).toInt(); void clearReq(int req);
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 Signal() void Inbox();
{
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: void Outbox();
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 Battery() void CheckRing();
{
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);
}
}
}; // ThreadedGSM }; // ThreadedGSM