diff --git a/OpenMQTTGateway.ino b/OpenMQTTGateway.ino index bfefb01..c970e53 100644 --- a/OpenMQTTGateway.ino +++ b/OpenMQTTGateway.ino @@ -73,6 +73,9 @@ #ifdef ZgatewayIR #include "config_IR.h" #endif +#ifdef Zgateway2G + #include "config_2G.h" +#endif #ifdef ZactuatorONOFF #include "config_ONOFF.h" #endif @@ -329,6 +332,9 @@ void setup() #ifdef ZactuatorONOFF setupONOFF(); #endif + #ifdef Zgateway2G + setup2G(); + #endif #ifdef ZgatewayIR setupIR(); #endif @@ -625,6 +631,11 @@ void loop() trc(F("BTtoMQTT OK")); #endif #endif + #ifdef Zgateway2G + if(_2GtoMQTT()){ + trc(F("2GtoMQTT OK")); + } + #endif #ifdef ZgatewayRFM69 if(RFM69toMQTT()) trc(F("RFM69toMQTT OK")); @@ -705,6 +716,9 @@ digitalWrite(led_send, LOW); #ifdef ZgatewayRF2 MQTTtoRF2(topicOri, datacallback); #endif +#ifdef Zgateway2G + MQTTto2G(topicOri, datacallback); +#endif #ifdef ZgatewaySRFB MQTTtoSRFB(topicOri, datacallback); #endif @@ -761,3 +775,22 @@ void trc(String msg){ Serial.println(msg); } } + + +void trc(int msg){ + if (TRACE) { + Serial.println(msg); + } +} + +void trc(uint32_t msg){ + if (TRACE) { + Serial.println(msg); + } +} + +void trc(float msg){ + if (TRACE) { + Serial.println(msg); + } +} diff --git a/Zgateway2G.ino b/Zgateway2G.ino new file mode 100644 index 0000000..ff4de9f --- /dev/null +++ b/Zgateway2G.ino @@ -0,0 +1,133 @@ +/* + OpenMQTTGateway - ESP8266 or Arduino program for home automation + + Act as a wifi or ethernet gateway between your 433mhz/infrared IR signal and a MQTT broker + Send and receiving command by MQTT + + This gateway enables to: + - receive MQTT data from a topic and send SMS corresponding to the received MQTT data + - publish MQTT data to a different topic related to received SMS + + Copyright: (c)Florian ROBERT + + This file is part of OpenMQTTGateway. + + OpenMQTTGateway is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenMQTTGateway is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifdef Zgateway2G + +#include // library for controling A6 or A7 module + +// Instantiate the library with TxPin, RxPin. +A6lib A6l(_2G_TX_PIN, _2G_RX_PIN); //D6 to A6 RX, D7 to A6 TX + +int unreadSMSLocs[50] = {0}; +int unreadSMSNum = 0; +SMSmessage sms; + +void setup2G(){ + + trc(F("_2G_TX_PIN ")); + trc(String(_2G_TX_PIN)); + trc(F("_2G_RX_PIN ")); + trc(String(_2G_RX_PIN)); + setupGSM(false); + trc(F("Zgateway2G setup done ")); +} + +void setupGSM(boolean deleteSMS){ + trc("Init 2G module: "); + trc(_2G_PWR_PIN); + delay(1000); + // Power-cycle the module to reset it. + A6l.powerCycle(_2G_PWR_PIN); + trc("waiting for network connection"); + A6l.blockUntilReady(_2G_MODULE_BAUDRATE); + trc("A6/A7 gsm ready"); + signalStrengthAnalysis(); + delay(1000); + // deleting all sms + if (deleteSMS){ + if (A6l.deleteSMS(1,4) == A6_OK ) { + trc("delete SMS OK"); + }else{ + trc("delete SMS KO"); + } + } +} + +void signalStrengthAnalysis(){ + int signalStrength = 0; + signalStrength = A6l.getSignalStrength(); + trc("Signal strength: "); + trc(signalStrength); + if (signalStrength < _2G_MIN_SIGNAL || signalStrength > _2G_MAX_SIGNAL ) { + trc("Signal too low restart the module"); + setupGSM(false); // if we are below or above a threshold signal we relaunch the setup of GSM module + } +} + +boolean _2GtoMQTT(){ + // Get the memory locations of unread SMS messages. + unreadSMSNum = A6l.getUnreadSMSLocs(unreadSMSLocs, 512); + for (int i = 0; i < unreadSMSNum; i++) { + trc("New message at index: "); + trc(unreadSMSNum); + sms = A6l.readSMS(unreadSMSLocs[i]); + trc(sms.number); + trc(sms.date); + trc(sms.message); + A6l.deleteSMS(unreadSMSLocs[i]); // we delete the SMS received + trc(F("data 2GtoMQTT")); + client.publish(subject2GtoMQTTphone,(char *)sms.number.c_str()); + client.publish(subject2GtoMQTTdate,(char *)sms.date.c_str()); + client.publish(subject2GtoMQTTmessage,(char *)sms.message.c_str()); + return true; + } + return false; +} + +void MQTTto2G(char * topicOri, char * datacallback) { + + String data = datacallback; // we will not be able to pass values > 4294967295 + + // 2G DATA ANALYSIS + //We look into the subject to see if a special RF protocol is defined + String topic = topicOri; + String phone_number = ""; + int pos0 = topic.lastIndexOf(_2GPhoneKey); + if (pos0 != -1) { + pos0 = pos0 + strlen(_2GPhoneKey); + phone_number = topic.substring(pos0); + trc(F("Phone number to send SMS:")); + trc(phone_number); + } + if((strstr(topicOri, subjectMQTTto2G) != NULL) && (pos0 != -1)){ + trc(F("MQTTto2G")); + trc(data); + if (A6l.sendSMS(phone_number,data) == A6_OK ) { + trc("SMS OK"); + }else{ + trc("SMS KO"); + } + // Acknowledgement to the GTW2G topic + boolean result = client.publish(subjectGTW2GtoMQTT, datacallback);// we acknowledge the sending by publishing the value to an acknowledgement topic, for the moment even if it is a signal repetition we acknowledge also + if (result){ + trc(F("MQTTto2G ack pub.")); + trc(String(data)); + } + } + +} +#endif diff --git a/config_2G.h b/config_2G.h new file mode 100644 index 0000000..f62aad6 --- /dev/null +++ b/config_2G.h @@ -0,0 +1,46 @@ +/* + OpenMQTTGateway - ESP8266 or Arduino program for home automation + + Act as a wifi or ethernet gateway between your 433mhz/infrared IR signal and a MQTT broker + Send and receiving command by MQTT + + This gateway enables to set your parameters for the 2G gateway + + Copyright: (c)Florian ROBERT + + This file is part of OpenMQTTGateway. + + OpenMQTTGateway is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenMQTTGateway is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/*-------------------2G topics & parameters----------------------*/ +//433Mhz MQTT Subjects and keys +#define subjectMQTTto2G Base_Topic Gateway_Name "/commands/MQTTto2G" +#define subject2GtoMQTT Base_Topic Gateway_Name "/2GtoMQTT" +#define subjectGTW2GtoMQTT Base_Topic Gateway_Name "/2GtoMQTT" +#define subject2GtoMQTTphone Base_Topic Gateway_Name "/2GtoMQTT/phone" +#define subject2GtoMQTTdate Base_Topic Gateway_Name "/2GtoMQTT/date" +#define subject2GtoMQTTmessage Base_Topic Gateway_Name "/2GtoMQTT/message" +#define _2GPhoneKey "PHO_" // phone number define the phone number to send the SMS MQTT->2G + +#define _2G_MODULE_BAUDRATE 9600 +#define _2G_MIN_SIGNAL 30 +#define _2G_MAX_SIGNAL 1000 + +/*-------------------PIN DEFINITIONS----------------------*/ +#define _2G_TX_PIN D6 //D6 to A6 RX, +#define _2G_RX_PIN D7 //D7 to A6 TX +#define _2G_PWR_PIN D5 +#define _2G_RST_PIN D0 + diff --git a/img/OpenMQTTgateway_ESP8266_Addon_2G.fzz b/img/OpenMQTTgateway_ESP8266_Addon_2G.fzz new file mode 100644 index 0000000..fbfd5fb Binary files /dev/null and b/img/OpenMQTTgateway_ESP8266_Addon_2G.fzz differ diff --git a/img/OpenMQTTgateway_ESP8266_Addon_2G.png b/img/OpenMQTTgateway_ESP8266_Addon_2G.png new file mode 100644 index 0000000..f324209 Binary files /dev/null and b/img/OpenMQTTgateway_ESP8266_Addon_2G.png differ diff --git a/lib/A6lib/.editorconfig b/lib/A6lib/.editorconfig new file mode 100644 index 0000000..384f9b2 --- /dev/null +++ b/lib/A6lib/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig file: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = false + +# 4 space indentation +[*.{cpp,h}] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +# 2 space indentation +[*.json] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true diff --git a/lib/A6lib/A6lib.cpp b/lib/A6lib/A6lib.cpp new file mode 100644 index 0000000..a1ecfa5 --- /dev/null +++ b/lib/A6lib/A6lib.cpp @@ -0,0 +1,504 @@ +#include +#include +#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; +} diff --git a/lib/A6lib/A6lib.h b/lib/A6lib/A6lib.h new file mode 100644 index 0000000..b4a6cfe --- /dev/null +++ b/lib/A6lib/A6lib.h @@ -0,0 +1,111 @@ +#ifndef A6lib_h +#define A6lib_h + +#include +#include "SoftwareSerial.h" + +#ifdef DEBUG +#define log(msg) Serial.print(msg) +#define logln(msg) Serial.println(msg) +#else +#define log(msg) +#define logln(msg) +#endif + +#define countof(a) (sizeof(a) / sizeof(a[0])) + +#define A6_OK 0 +#define A6_NOTOK 1 +#define A6_TIMEOUT 2 +#define A6_FAILURE 3 + +#define A6_CMD_TIMEOUT 5000 + + +enum call_direction { + DIR_OUTGOING = 0, + DIR_INCOMING = 1 +}; + +enum call_state { + CALL_ACTIVE = 0, + CALL_HELD = 1, + CALL_DIALING = 2, + CALL_ALERTING = 3, + CALL_INCOMING = 4, + CALL_WAITING = 5, + CALL_RELEASE = 7 +}; + +enum call_mode { + MODE_VOICE = 0, + MODE_DATA = 1, + MODE_FAX = 2, + MODE_VOICE_THEN_DATA_VMODE = 3, + MODE_VOICE_AND_DATA_VMODE = 4, + MODE_VOICE_AND_FAX_VMODE = 5, + MODE_VOICE_THEN_DATA_DMODE = 6, + MODE_VOICE_AND_DATA_DMODE = 7, + MODE_VOICE_AND_FAX_FMODE = 8, + MODE_UNKNOWN = 9 +}; + +struct SMSmessage { + String number; + String date; + String message; +}; + +struct callInfo { + int index; + call_direction direction; + call_state state; + call_mode mode; + int multiparty; + String number; + int type; +}; + + +class A6lib { +public: + A6lib(int transmitPin, int receivePin); + ~A6lib(); + + byte begin(long baudRate); + byte blockUntilReady(long baudRate); + + void powerCycle(int pin); + void powerOn(int pin); + void powerOff(int pin); + + void dial(String number); + void redial(); + void answer(); + void hangUp(); + callInfo checkCallStatus(); + int getSignalStrength(); + + byte sendSMS(String number, String text); + int getUnreadSMSLocs(int* buf, int maxItems); + int getSMSLocs(int* buf, int maxItems); + int getSMSLocsOfType(int* buf, int maxItems, String type); + SMSmessage readSMS(int index); + byte deleteSMS(int index); + byte deleteSMS(int index, int flag); + byte setSMScharset(String charset); + + void setVol(byte level); + void enableSpeaker(byte enable); + + String getRealTimeClock(); + + SoftwareSerial *A6conn; +private: + String read(); + byte A6command(const char *command, const char *resp1, const char *resp2, int timeout, int repetitions, String *response); + byte A6waitFor(const char *resp1, const char *resp2, int timeout, String *response); + long detectRate(); + char setRate(long baudRate); +}; +#endif diff --git a/lib/A6lib/LICENSE.md b/lib/A6lib/LICENSE.md new file mode 100644 index 0000000..87c0133 --- /dev/null +++ b/lib/A6lib/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Stavros Korokithakis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/A6lib/README.md b/lib/A6lib/README.md new file mode 100644 index 0000000..83a00fc --- /dev/null +++ b/lib/A6lib/README.md @@ -0,0 +1,65 @@ +# A6lib + +An ESP8266/Arduino library for communicating with the AI-Thinker A6 GSM module. +It will probably also work with other GSM modules that use the AT command set, +like the SIM900. + +## Details + +This library is an adaptation of +[this sample module](https://github.com/SensorsIot/A6-GSM-Module). It is still +very incomplete, but at least it works somewhat. Please feel free to issue pull +requests to improve it. + +## Usage + +If you have the A6 breakout board, connect the Tx and Rx pins to your ESP8266. +The example assumes the TX pin is connected to D5 and the RX to D6. A6lib can +power-cycle the module if you connect a MOSFET to a pin and control the A6's +power supply with it. + +For a sample circuit that uses this library, have a look at [the A6/ESP8266 +breakout board](https://gitlab.com/stavros/A6-ESP8266-breakout/) I designed. + +The A6's PWR pin should be permanently connected to Vcc (if you think that's +wrong or know a better way, please open an issue). + +The code looks something like this: + +~~~ +// Instantiate the class with Tx, Rx (remember to swap them when connecting to +the A6, i.e. connect the A6's Rx pin to D6). +A6lib A6c(D6, D5); + +// Initialize the modem, rebooting it if it fails to become ready. +do { + // Power-cycle the module to reset it. + A6c.powerCycle(D0); +} while (A6c.blockUntilReady(9600) != A6_OK); + +// Start and place a call. +A6c.dial("1234567890"); +delay(8000); + +A6c.hangUp(); +delay(8000); + +A6c.redial(); +delay(8000); + +// Send a message. +A6c.sendSMS("+1234567890", "Hello there!"); + +// Get an SMS message from memory. +SMSmessage sms = A6c.readSMS(3); + +// Delete an SMS message. +A6c.deleteSMS(3); + +callInfo cinfo = A6c.checkCallStatus(); +// This will be the calling number, "1234567890". +cinfo.number; +~~~ + +This library doesn't currently include any code to connect to the internet, but +a PR adding that would be very welcome. diff --git a/lib/A6lib/examples/dial/dial.ino b/lib/A6lib/examples/dial/dial.ino new file mode 100644 index 0000000..0a96541 --- /dev/null +++ b/lib/A6lib/examples/dial/dial.ino @@ -0,0 +1,44 @@ +#include + +#ifndef ESP8266 +#define D0 0 +#define D5 8 +#define D6 7 +#endif + +// Instantiate the library with TxPin, RxPin. +A6lib A6l(D6, D5); + +void setup() { + Serial.begin(115200); + + delay(1000); + + // Power-cycle the module to reset it. + A6l.powerCycle(D0); + A6l.blockUntilReady(9600); +} + +void loop() { + Serial.println("Checking call status..."); + callInfo cinfo = A6l.checkCallStatus(); + Serial.println("Call status checked."); + + int sigStrength = A6l.getSignalStrength(); + Serial.print("Signal strength percentage: "); + Serial.println(sigStrength); + + delay(5000); + + if (cinfo.number != NULL) { + if (cinfo.direction == DIR_INCOMING && cinfo.number == "919999999999") { + A6l.answer(); + } else { + A6l.hangUp(); + } + delay(1000); + } else { + Serial.println("No number yet."); + delay(1000); + } +} diff --git a/lib/A6lib/examples/serial_relay/serial_relay.ino b/lib/A6lib/examples/serial_relay/serial_relay.ino new file mode 100644 index 0000000..535aa34 --- /dev/null +++ b/lib/A6lib/examples/serial_relay/serial_relay.ino @@ -0,0 +1,30 @@ +#include + +#ifndef ESP8266 +#define D0 0 +#define D5 8 +#define D6 7 +#endif + +// Instantiate the library with TxPin, RxPin. +A6lib A6l(D6, D5); + +void setup() { + Serial.begin(115200); + + delay(1000); + + // Power-cycle the module to reset it. + A6l.powerCycle(D0); + A6l.blockUntilReady(9600); +} + +void loop() { + // Relay things between Serial and the module's SoftSerial. + while (A6l.A6conn->available() > 0) { + Serial.write(A6l.A6conn->read()); + } + while (Serial.available() > 0) { + A6l.A6conn->write(Serial.read()); + } +} diff --git a/lib/A6lib/examples/sms/sms.ino b/lib/A6lib/examples/sms/sms.ino new file mode 100644 index 0000000..cd483de --- /dev/null +++ b/lib/A6lib/examples/sms/sms.ino @@ -0,0 +1,51 @@ +#include + +#ifdef ESP8266 +#define D0 0 +#define D5 8 +#define D6 7 +#endif + +// Instantiate the library with TxPin, RxPin. +A6lib A6l(D6, D5); + +int unreadSMSLocs[30] = {0}; +int unreadSMSNum = 0; +SMSmessage sms; + +void setup() { + Serial.begin(115200); + + delay(1000); + + // Power-cycle the module to reset it. + A6l.powerCycle(D0); + A6l.blockUntilReady(9600); +} + +void loop() { + String myNumber = "+1132352890"; + + callInfo cinfo = A6l.checkCallStatus(); + if (cinfo.direction == DIR_INCOMING) { + if (myNumber.endsWith(cinfo.number)) { + // If the number that sent the SMS is ours, reply. + A6l.sendSMS(myNumber, "I can't come to the phone right now, I'm a machine."); + A6l.hangUp(); + } + + // Get the memory locations of unread SMS messages. + unreadSMSNum = A6l.getUnreadSMSLocs(unreadSMSLocs, 30); + + for (int i = 0; i < unreadSMSNum; i++) { + Serial.print("New message at index: "); + Serial.println(unreadSMSLocs[i], DEC); + + sms = A6l.readSMS(unreadSMSLocs[i]); + Serial.println(sms.number); + Serial.println(sms.date); + Serial.println(sms.message); + } + delay(1000); + } +} diff --git a/lib/A6lib/keywords.txt b/lib/A6lib/keywords.txt new file mode 100644 index 0000000..1076a88 --- /dev/null +++ b/lib/A6lib/keywords.txt @@ -0,0 +1,35 @@ +####################################### +# A6 GSM communication module +# (esp8266) +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +A6lib KEYWORD1 +callInfo KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +powerCycle KEYWORD2 + +dial KEYWORD2 +redial KEYWORD2 +hangUp KEYWORD2 +checkCallStatus KEYWORD2 +getSignalStrength KEYWORD2 + +sendSMS KEYWORD2 + +setVol KEYWORD2 +enableSpeaker KEYWORD2 + +A6conn KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/lib/A6lib/library.json b/lib/A6lib/library.json new file mode 100644 index 0000000..9023366 --- /dev/null +++ b/lib/A6lib/library.json @@ -0,0 +1,14 @@ +{ + "name": "A6lib", + "keywords": [ + "A6", "GSM", "mobile" + ], + "description": "An ESP8266/Arduino library for communicating with the A6 GSM module.", + "repository": + { + "type": "git", + "url": "https://github.com/skorokithakis/A6lib" + }, + "frameworks": "arduino", + "platforms": "*" +} diff --git a/lib/A6lib/library.properties b/lib/A6lib/library.properties new file mode 100644 index 0000000..e7b6598 --- /dev/null +++ b/lib/A6lib/library.properties @@ -0,0 +1,9 @@ +name=A6lib +version=0.1.3 +author=Stavros Korokithakis +maintainer=Stavros Korokithakis (hi@stavros.io) +sentence=An ESP8266/Arduino library for communicating with the A6 GSM module. +paragraph= +category=Communication +url=https://github.com/skorokithakis/A6lib +architectures=* diff --git a/lib/A6lib/sscanf.cpp b/lib/A6lib/sscanf.cpp new file mode 100644 index 0000000..bafaeea --- /dev/null +++ b/lib/A6lib/sscanf.cpp @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2000-2002 Opsycon AB (www.opsycon.se) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Opsycon AB. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include + +#define MAXLN 200 +#define ISSPACE " \t\n\r\f\v\"" + +size_t strcspn(const char *p, const char *s) { + int i, j; + + for (i = 0; p[i]; i++) { + for (j = 0; s[j]; j++) { + if (s[j] == p[i]) { + break; + } + } + if (s[j]) { + break; + } + } + return (i); +} + +char * _getbase(char *p, int *basep) { + if (p[0] == '0') { + switch (p[1]) { + case 'x': + *basep = 16; + break; + case 't': + case 'n': + *basep = 10; + break; + case 'o': + *basep = 8; + break; + default: + *basep = 10; + return (p); + } + return (p + 2); + } + *basep = 10; + return (p); +} + +/* + * _atob(vp,p,base) + */ +int _atob(uint32_t *vp, char *p, int base) { + uint32_t value, v1, v2; + char *q, tmp[20]; + int digit; + + if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { + base = 16; + p += 2; + } + + if (base == 16 && (q = strchr(p, '.')) != 0) { + if (q - p > sizeof(tmp) - 1) { + return (0); + } + + strncpy(tmp, p, q - p); + tmp[q - p] = '\0'; + if (!_atob(&v1, tmp, 16)) { + return (0); + } + + q++; + if (strchr(q, '.')) { + return (0); + } + + if (!_atob(&v2, q, 16)) { + return (0); + } + *vp = (v1 << 16) + v2; + return (1); + } + + value = *vp = 0; + for (; *p; p++) { + if (*p >= '0' && *p <= '9') { + digit = *p - '0'; + } else if (*p >= 'a' && *p <= 'f') { + digit = *p - 'a' + 10; + } else if (*p >= 'A' && *p <= 'F') { + digit = *p - 'A' + 10; + } else { + return (0); + } + + if (digit >= base) { + return (0); + } + value *= base; + value += digit; + } + *vp = value; + return (1); +} + +/* + * atob(vp,p,base) + * converts p to binary result in vp, rtn 1 on success + */ +int atob(uint32_t *vp, char *p, int base) { + uint32_t v; + if (base == 0) { + p = _getbase(p, &base); + } + if (_atob(&v, p, base)) { + *vp = v; + return (1); + } + return (0); +} + +/* + * vsscanf(buf,fmt,ap) + */ + +int vsscanf(const char *buf, const char *s, va_list ap) { + uint32_t count, noassign, width, base, lflag; + const char *tc; + char *t, tmp[MAXLN]; + + count = noassign = width = lflag = 0; + while (*s && *buf) { + while (isspace(*s)) { + s++; + } + if (*s == '%') { + s++; + for (; *s; s++) { + if (strchr("dibouxcsefg%", *s)) { + break; + } + if (*s == '*') { + noassign = 1; + } else if (*s == 'l' || *s == 'L') { + lflag = 1; + } else if (*s >= '1' && *s <= '9') { + for (tc = s; isdigit(*s); s++); + strncpy(tmp, tc, s - tc); + tmp[s - tc] = '\0'; + atob(&width, tmp, 10); + s--; + } + } + if (*s == 's') { + while (isspace(*buf)) { + buf++; + } + if (!width) { + width = strcspn(buf, ISSPACE); + } + if (!noassign) { + strncpy(t = va_arg(ap, char *), buf, width); + t[width] = '\0'; + } + buf += width; + } else if (*s == 'c') { + if (!width) { + width = 1; + } + if (!noassign) { + strncpy(t = va_arg(ap, char *), buf, width); + t[width] = '\0'; + } + buf += width; + } else if (strchr("dobxu", *s)) { + while (isspace(*buf)) { + buf++; + } + if (*s == 'd' || *s == 'u') { + base = 10; + } else if (*s == 'x') { + base = 16; + } else if (*s == 'o') { + base = 8; + } else if (*s == 'b') { + base = 2; + } + if (!width) { + if (isspace(*(s + 1)) || *(s + 1) == 0) { + width = strcspn(buf, ISSPACE); + } else { + width = strchr(buf, *(s + 1)) - buf; + } + } + strncpy(tmp, buf, width); + tmp[width] = '\0'; + buf += width; + if (!noassign) { + atob(va_arg(ap, uint32_t *), tmp, base); + } + } + if (!noassign) { + count++; + } + width = noassign = lflag = 0; + s++; + } else { + while (isspace(*buf)) { + buf++; + } + if (*s != *buf) { + break; + } else { + s++, buf++; + } + } + } + return (count); +} + + +int __attribute__((used)) sscanf(const char *buf, const char *fmt, ...) { + int count; + va_list ap; + + va_start(ap, fmt); + count = vsscanf(buf, fmt, ap); + va_end(ap); + return (count); +} diff --git a/tests/Test_config.h b/tests/Test_config.h index df1d4f5..07477f3 100644 --- a/tests/Test_config.h +++ b/tests/Test_config.h @@ -90,6 +90,7 @@ const byte subnet[] = { 255, 255, 255, 0 }; //ip adress #define ZgatewayRF2 #define ZgatewayIR #define ZgatewayBT + #define Zgateway2G #define ZactuatorONOFF #define ZsensorINA226 #define ZsensorHCSR501 @@ -102,6 +103,7 @@ const byte subnet[] = { 255, 255, 255, 0 }; //ip adress #elif ESP32 #define ZgatewayRF #define ZgatewayRF2 + //#define Zgateway2G (not tested yet) //#define ZgatewayIR #define ZgatewayBT #define ZactuatorONOFF @@ -114,6 +116,7 @@ const byte subnet[] = { 255, 255, 255, 0 }; //ip adress //#define ZgatewayRFM69 // If you uncomment this you can't use RF and BT due to the fact that RF use also D8 and BT use also D6/D7 #else // for arduino mega + W5100 #define ZgatewayRF + //#define Zgateway2G (not tested yet) //#define ZgatewayRF2 // too big for UNO //#define ZgatewayRFM69 not tested //#define ZgatewayIR