Files
OpenMQTTGateway/lib/A6lib/A6lib.cpp
Florian 513d71c544 A6 A7 gateway (GSM GPRS) (#195)
* init 2G gateway with SMS sending and receiving

* A6lib add

* add 2Ggateway call from setup and loop

* add schematic and image
2018-04-21 19:22:36 +02:00

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