mirror of
https://github.com/1technophile/OpenMQTTGateway.git
synced 2026-03-05 15:04:27 +01:00
* init 2G gateway with SMS sending and receiving * A6lib add * add 2Ggateway call from setup and loop * add schematic and image
505 lines
13 KiB
C++
505 lines
13 KiB
C++
#include <Arduino.h>
|
|
#include <SoftwareSerial.h>
|
|
#include "A6lib.h"
|
|
|
|
|
|
/////////////////////////////////////////////
|
|
// Public methods.
|
|
//
|
|
|
|
A6lib::A6lib(int transmitPin, int receivePin) {
|
|
#ifdef ESP8266
|
|
A6conn = new SoftwareSerial(receivePin, transmitPin, false, 1024);
|
|
#else
|
|
A6conn = new SoftwareSerial(receivePin, transmitPin, false);
|
|
#endif
|
|
A6conn->setTimeout(100);
|
|
}
|
|
|
|
|
|
A6lib::~A6lib() {
|
|
delete A6conn;
|
|
}
|
|
|
|
|
|
// Block until the module is ready.
|
|
byte A6lib::blockUntilReady(long baudRate) {
|
|
|
|
byte response = A6_NOTOK;
|
|
while (A6_OK != response) {
|
|
response = begin(baudRate);
|
|
// This means the modem has failed to initialize and we need to reboot
|
|
// it.
|
|
if (A6_FAILURE == response) {
|
|
return A6_FAILURE;
|
|
}
|
|
delay(1000);
|
|
logln("Waiting for module to be ready...");
|
|
}
|
|
return A6_OK;
|
|
}
|
|
|
|
|
|
// Initialize the software serial connection and change the baud rate from the
|
|
// default (autodetected) to the desired speed.
|
|
byte A6lib::begin(long baudRate) {
|
|
|
|
A6conn->flush();
|
|
|
|
if (A6_OK != setRate(baudRate)) {
|
|
return A6_NOTOK;
|
|
}
|
|
|
|
// Factory reset.
|
|
A6command("AT&F", "OK", "yy", A6_CMD_TIMEOUT, 2, NULL);
|
|
|
|
// Echo off.
|
|
A6command("ATE0", "OK", "yy", A6_CMD_TIMEOUT, 2, NULL);
|
|
|
|
// Switch audio to headset.
|
|
enableSpeaker(0);
|
|
|
|
// Set caller ID on.
|
|
A6command("AT+CLIP=1", "OK", "yy", A6_CMD_TIMEOUT, 2, NULL);
|
|
|
|
// Set SMS to text mode.
|
|
A6command("AT+CMGF=1", "OK", "yy", A6_CMD_TIMEOUT, 2, NULL);
|
|
|
|
// Turn SMS indicators off.
|
|
A6command("AT+CNMI=1,0", "OK", "yy", A6_CMD_TIMEOUT, 2, NULL);
|
|
|
|
// Set SMS storage to the GSM modem.
|
|
if (A6_OK != A6command("AT+CPMS=SM,ME,SM", "OK", "yy", A6_CMD_TIMEOUT, 2, NULL))
|
|
// This may sometimes fail, in which case the modem needs to be
|
|
// rebooted.
|
|
{
|
|
return A6_FAILURE;
|
|
}
|
|
|
|
// Set SMS character set.
|
|
setSMScharset("UCS2");
|
|
|
|
return A6_OK;
|
|
}
|
|
|
|
|
|
// Reboot the module by setting the specified pin HIGH, then LOW. The pin should
|
|
// be connected to a P-MOSFET, not the A6's POWER pin.
|
|
void A6lib::powerCycle(int pin) {
|
|
logln("Power-cycling module...");
|
|
|
|
powerOff(pin);
|
|
delay(2000);
|
|
|
|
powerOn(pin);
|
|
delay(4000);
|
|
|
|
powerOff(pin);
|
|
// Give the module some time to settle.
|
|
logln("Done, waiting for the module to initialize...");
|
|
delay(20000);
|
|
logln("Done.");
|
|
|
|
A6conn->flush();
|
|
}
|
|
|
|
|
|
// Turn the modem power completely off.
|
|
void A6lib::powerOff(int pin) {
|
|
pinMode(pin, OUTPUT);
|
|
digitalWrite(pin, LOW);
|
|
}
|
|
|
|
|
|
// Turn the modem power on.
|
|
void A6lib::powerOn(int pin) {
|
|
pinMode(pin, OUTPUT);
|
|
digitalWrite(pin, HIGH);
|
|
}
|
|
|
|
|
|
// Dial a number.
|
|
void A6lib::dial(String number) {
|
|
char buffer[50];
|
|
|
|
logln("Dialing number...");
|
|
|
|
sprintf(buffer, "ATD%s;", number.c_str());
|
|
A6command(buffer, "OK", "yy", A6_CMD_TIMEOUT, 2, NULL);
|
|
}
|
|
|
|
|
|
// Redial the last number.
|
|
void A6lib::redial() {
|
|
logln("Redialing last number...");
|
|
A6command("AT+DLST", "OK", "CONNECT", A6_CMD_TIMEOUT, 2, NULL);
|
|
}
|
|
|
|
|
|
// Answer a call.
|
|
void A6lib::answer() {
|
|
A6command("ATA", "OK", "yy", A6_CMD_TIMEOUT, 2, NULL);
|
|
}
|
|
|
|
|
|
// Hang up the phone.
|
|
void A6lib::hangUp() {
|
|
A6command("ATH", "OK", "yy", A6_CMD_TIMEOUT, 2, NULL);
|
|
}
|
|
|
|
|
|
// Check whether there is an active call.
|
|
callInfo A6lib::checkCallStatus() {
|
|
char number[50];
|
|
String response = "";
|
|
uint32_t respStart = 0, matched = 0;
|
|
callInfo cinfo = (const struct callInfo) {
|
|
0
|
|
};
|
|
|
|
// Issue the command and wait for the response.
|
|
A6command("AT+CLCC", "OK", "+CLCC", A6_CMD_TIMEOUT, 2, &response);
|
|
|
|
// Parse the response if it contains a valid +CLCC.
|
|
respStart = response.indexOf("+CLCC");
|
|
if (respStart >= 0) {
|
|
matched = sscanf(response.substring(respStart).c_str(), "+CLCC: %d,%d,%d,%d,%d,\"%s\",%d", &cinfo.index, &cinfo.direction, &cinfo.state, &cinfo.mode, &cinfo.multiparty, number, &cinfo.type);
|
|
cinfo.number = String(number);
|
|
}
|
|
|
|
uint8_t comma_index = cinfo.number.indexOf('"');
|
|
if (comma_index != -1) {
|
|
logln("Extra comma found.");
|
|
cinfo.number = cinfo.number.substring(0, comma_index);
|
|
}
|
|
|
|
return cinfo;
|
|
}
|
|
|
|
|
|
// Get the strength of the GSM signal.
|
|
int A6lib::getSignalStrength() {
|
|
String response = "";
|
|
uint32_t respStart = 0;
|
|
int strength, error = 0;
|
|
|
|
// Issue the command and wait for the response.
|
|
A6command("AT+CSQ", "OK", "+CSQ", A6_CMD_TIMEOUT, 2, &response);
|
|
|
|
respStart = response.indexOf("+CSQ");
|
|
if (respStart < 0) {
|
|
return 0;
|
|
}
|
|
|
|
sscanf(response.substring(respStart).c_str(), "+CSQ: %d,%d",
|
|
&strength, &error);
|
|
|
|
// Bring value range 0..31 to 0..100%, don't mind rounding..
|
|
strength = (strength * 100) / 31;
|
|
return strength;
|
|
}
|
|
|
|
|
|
// Get the real time from the modem. Time will be returned as yy/MM/dd,hh:mm:ss+XX
|
|
String A6lib::getRealTimeClock() {
|
|
String response = "";
|
|
|
|
// Issue the command and wait for the response.
|
|
A6command("AT+CCLK?", "OK", "yy", A6_CMD_TIMEOUT, 1, &response);
|
|
int respStart = response.indexOf("+CCLK: \"") + 8;
|
|
response.setCharAt(respStart - 1, '-');
|
|
|
|
return response.substring(respStart, response.indexOf("\""));
|
|
}
|
|
|
|
|
|
// Send an SMS.
|
|
byte A6lib::sendSMS(String number, String text) {
|
|
char ctrlZ[2] = { 0x1a, 0x00 };
|
|
char buffer[100];
|
|
|
|
if (text.length() > 159) {
|
|
// We can't send messages longer than 160 characters.
|
|
return A6_NOTOK;
|
|
}
|
|
|
|
log("Sending SMS to ");
|
|
log(number);
|
|
logln("...");
|
|
|
|
sprintf(buffer, "AT+CMGS=\"%s\"", number.c_str());
|
|
A6command(buffer, ">", "yy", A6_CMD_TIMEOUT, 2, NULL);
|
|
delay(100);
|
|
A6conn->println(text.c_str());
|
|
A6conn->println(ctrlZ);
|
|
A6conn->println();
|
|
|
|
return A6_OK;
|
|
}
|
|
|
|
|
|
// Retrieve the number and locations of unread SMS messages.
|
|
int A6lib::getUnreadSMSLocs(int* buf, int maxItems) {
|
|
return getSMSLocsOfType(buf, maxItems, "REC UNREAD");
|
|
}
|
|
|
|
// Retrieve the number and locations of all SMS messages.
|
|
int A6lib::getSMSLocs(int* buf, int maxItems) {
|
|
return getSMSLocsOfType(buf, maxItems, "ALL");
|
|
}
|
|
|
|
// Retrieve the number and locations of all SMS messages.
|
|
int A6lib::getSMSLocsOfType(int* buf, int maxItems, String type) {
|
|
String seqStart = "+CMGL: ";
|
|
String response = "";
|
|
|
|
String command = "AT+CMGL=\"";
|
|
command += type;
|
|
command += "\"";
|
|
|
|
// Issue the command and wait for the response.
|
|
byte status = A6command(command.c_str(), "\xff\r\nOK\r\n", "\r\nOK\r\n", A6_CMD_TIMEOUT, 2, &response);
|
|
|
|
int seqStartLen = seqStart.length();
|
|
int responseLen = response.length();
|
|
int index, occurrences = 0;
|
|
|
|
// Start looking for the +CMGL string.
|
|
for (int i = 0; i < (responseLen - seqStartLen); i++) {
|
|
// If we found a response and it's less than occurrences, add it.
|
|
if (response.substring(i, i + seqStartLen) == seqStart && occurrences < maxItems) {
|
|
// Parse the position out of the reply.
|
|
sscanf(response.substring(i, i + 12).c_str(), "+CMGL: %u,%*s", &index);
|
|
|
|
buf[occurrences] = index;
|
|
occurrences++;
|
|
}
|
|
}
|
|
return occurrences;
|
|
}
|
|
|
|
// Return the SMS at index.
|
|
SMSmessage A6lib::readSMS(int index) {
|
|
String response = "";
|
|
char buffer[30];
|
|
|
|
// Issue the command and wait for the response.
|
|
sprintf(buffer, "AT+CMGR=%d", index);
|
|
A6command(buffer, "\xff\r\nOK\r\n", "\r\nOK\r\n", A6_CMD_TIMEOUT, 2, &response);
|
|
|
|
char message[200];
|
|
char number[50];
|
|
char date[50];
|
|
char type[10];
|
|
int respStart = 0, matched = 0;
|
|
SMSmessage sms = (const struct SMSmessage) {
|
|
"", "", ""
|
|
};
|
|
|
|
// Parse the response if it contains a valid +CLCC.
|
|
respStart = response.indexOf("+CMGR");
|
|
if (respStart >= 0) {
|
|
// Parse the message header.
|
|
matched = sscanf(response.substring(respStart).c_str(), "+CMGR: \"REC %s\",\"%s\",,\"%s\"\r\n", type, number, date);
|
|
sms.number = String(number);
|
|
sms.date = String(date);
|
|
// The rest is the message, extract it.
|
|
sms.message = response.substring(strlen(type) + strlen(number) + strlen(date) + 24, response.length() - 8);
|
|
}
|
|
return sms;
|
|
}
|
|
|
|
// Delete the SMS at index.
|
|
byte A6lib::deleteSMS(int index) {
|
|
char buffer[20];
|
|
sprintf(buffer, "AT+CMGD=%d", index);
|
|
return A6command(buffer, "OK", "yy", A6_CMD_TIMEOUT, 2, NULL);
|
|
}
|
|
|
|
// Delete SMS with special flags
|
|
byte A6lib::deleteSMS(int index, int flag) {
|
|
char buffer[20];
|
|
String command = "AT+CMGD=";
|
|
command += String(index);
|
|
command += ",";
|
|
command += String(flag);
|
|
return A6command(command.c_str(), "OK", "yy", A6_CMD_TIMEOUT, 2, NULL);
|
|
} // AT+CMGD=1,4 delete all SMS from the storage area
|
|
|
|
|
|
// Set the SMS charset.
|
|
byte A6lib::setSMScharset(String charset) {
|
|
char buffer[30];
|
|
|
|
sprintf(buffer, "AT+CSCS=\"%s\"", charset.c_str());
|
|
return A6command(buffer, "OK", "yy", A6_CMD_TIMEOUT, 2, NULL);
|
|
}
|
|
|
|
|
|
// Set the volume for the speaker. level should be a number between 5 and
|
|
// 8 inclusive.
|
|
void A6lib::setVol(byte level) {
|
|
char buffer[30];
|
|
|
|
// level should be between 5 and 8.
|
|
level = _min(_max(level, 5), 8);
|
|
sprintf(buffer, "AT+CLVL=%d", level);
|
|
A6command(buffer, "OK", "yy", A6_CMD_TIMEOUT, 2, NULL);
|
|
}
|
|
|
|
|
|
// Enable the speaker, rather than the headphones. Pass 0 to route audio through
|
|
// headphones, 1 through speaker.
|
|
void A6lib::enableSpeaker(byte enable) {
|
|
char buffer[30];
|
|
|
|
// enable should be between 0 and 1.
|
|
enable = _min(_max(enable, 0), 1);
|
|
sprintf(buffer, "AT+SNFS=%d", enable);
|
|
A6command(buffer, "OK", "yy", A6_CMD_TIMEOUT, 2, NULL);
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////
|
|
// Private methods.
|
|
//
|
|
|
|
|
|
// Autodetect the connection rate.
|
|
|
|
long A6lib::detectRate() {
|
|
unsigned long rate = 0;
|
|
unsigned long rates[] = {9600, 115200};
|
|
|
|
// Try to autodetect the rate.
|
|
logln("Autodetecting connection rate...");
|
|
for (int i = 0; i < countof(rates); i++) {
|
|
rate = rates[i];
|
|
A6conn->begin(rate);
|
|
log("Trying rate ");
|
|
log(rate);
|
|
logln("...");
|
|
|
|
delay(100);
|
|
if (A6command("\rAT", "OK", "+CME", 2000, 2, NULL) == A6_OK) {
|
|
return rate;
|
|
}
|
|
}
|
|
|
|
logln("Couldn't detect the rate.");
|
|
|
|
return A6_NOTOK;
|
|
}
|
|
|
|
|
|
// Set the A6 baud rate.
|
|
char A6lib::setRate(long baudRate) {
|
|
int rate = 0;
|
|
|
|
rate = detectRate();
|
|
if (rate == A6_NOTOK) {
|
|
return A6_NOTOK;
|
|
}
|
|
|
|
// The rate is already the desired rate, return.
|
|
//if (rate == baudRate) return OK;
|
|
|
|
logln("Setting baud rate on the module...");
|
|
|
|
// Change the rate to the requested.
|
|
char buffer[30];
|
|
sprintf(buffer, "AT+IPR=%d", baudRate);
|
|
A6command(buffer, "OK", "+IPR=", A6_CMD_TIMEOUT, 3, NULL);
|
|
|
|
logln("Switching to the new rate...");
|
|
// Begin the connection again at the requested rate.
|
|
A6conn->begin(baudRate);
|
|
logln("Rate set.");
|
|
|
|
return A6_OK;
|
|
}
|
|
|
|
|
|
// Read some data from the A6 in a non-blocking manner.
|
|
String A6lib::read() {
|
|
String reply = "";
|
|
if (A6conn->available()) {
|
|
reply = A6conn->readString();
|
|
}
|
|
|
|
// XXX: Replace NULs with \xff so we can match on them.
|
|
for (int x = 0; x < reply.length(); x++) {
|
|
if (reply.charAt(x) == 0) {
|
|
reply.setCharAt(x, 255);
|
|
}
|
|
}
|
|
return reply;
|
|
}
|
|
|
|
|
|
// Issue a command.
|
|
byte A6lib::A6command(const char *command, const char *resp1, const char *resp2, int timeout, int repetitions, String *response) {
|
|
byte returnValue = A6_NOTOK;
|
|
byte count = 0;
|
|
|
|
// Get rid of any buffered output.
|
|
A6conn->flush();
|
|
|
|
while (count < repetitions && returnValue != A6_OK) {
|
|
log("Issuing command: ");
|
|
logln(command);
|
|
|
|
A6conn->write(command);
|
|
A6conn->write('\r');
|
|
|
|
if (A6waitFor(resp1, resp2, timeout, response) == A6_OK) {
|
|
returnValue = A6_OK;
|
|
} else {
|
|
returnValue = A6_NOTOK;
|
|
}
|
|
count++;
|
|
}
|
|
return returnValue;
|
|
}
|
|
|
|
|
|
// Wait for responses.
|
|
byte A6lib::A6waitFor(const char *resp1, const char *resp2, int timeout, String *response) {
|
|
unsigned long entry = millis();
|
|
int count = 0;
|
|
String reply = "";
|
|
byte retVal = 99;
|
|
do {
|
|
reply += read();
|
|
#ifdef ESP8266
|
|
yield();
|
|
#endif
|
|
} while (((reply.indexOf(resp1) + reply.indexOf(resp2)) == -2) && ((millis() - entry) < timeout));
|
|
|
|
if (reply != "") {
|
|
log("Reply in ");
|
|
log((millis() - entry));
|
|
log(" ms: ");
|
|
logln(reply);
|
|
}
|
|
|
|
if (response != NULL) {
|
|
*response = reply;
|
|
}
|
|
|
|
if ((millis() - entry) >= timeout) {
|
|
retVal = A6_TIMEOUT;
|
|
logln("Timed out.");
|
|
} else {
|
|
if (reply.indexOf(resp1) + reply.indexOf(resp2) > -2) {
|
|
logln("Reply OK.");
|
|
retVal = A6_OK;
|
|
} else {
|
|
logln("Reply NOT OK.");
|
|
retVal = A6_NOTOK;
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|