diff --git a/.ci/arduino.groovy b/.ci/arduino.groovy
index b26e72b..ce51237 100644
--- a/.ci/arduino.groovy
+++ b/.ci/arduino.groovy
@@ -46,6 +46,7 @@ def buildMySensorsMicro(config, sketches, String key) {
if (sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' &&
sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' &&
sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' &&
+ sketches[sketch].path != config.library_root+'examples/GatewayGSMMQTTClient/GatewayGSMMQTTClient.ino' &&
sketches[sketch].path != config.library_root+'examples/SensebenderGatewaySerial/SensebenderGatewaySerial.ino') {
buildArduino(config, fqbn, sketches[sketch].path, key+'_MySensorsMicro')
}
@@ -78,6 +79,7 @@ def buildMySensorsGw(config, sketches, String key) {
sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' &&
sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' &&
sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' &&
+ sketches[sketch].path != config.library_root+'examples/GatewayGSMMQTTClient/GatewayGSMMQTTClient.ino' &&
sketches[sketch].path != config.library_root+'examples/GatewaySerialRS485/GatewaySerialRS485.ino' &&
sketches[sketch].path != config.library_root+'examples/MotionSensorRS485/MotionSensorRS485.ino') {
buildArduino(config, fqbn, sketches[sketch].path, key+'_MySensorsGw')
@@ -206,6 +208,7 @@ def buildEsp8266(config, sketches, String key) {
sketches[sketch].path != config.library_root+'examples/GatewaySerialRS485/GatewaySerialRS485.ino' &&
sketches[sketch].path != config.library_root+'examples/GatewayW5100/GatewayW5100.ino' &&
sketches[sketch].path != config.library_root+'examples/GatewayW5100MQTTClient/GatewayW5100MQTTClient.ino' &&
+ sketches[sketch].path != config.library_root+'examples/GatewayGSMMQTTClient/GatewayGSMMQTTClient.ino' &&
sketches[sketch].path != config.library_root+'examples/MotionSensorRS485/MotionSensorRS485.ino' &&
sketches[sketch].path != config.library_root+'examples/SensebenderGatewaySerial/SensebenderGatewaySerial.ino' &&
sketches[sketch].path != config.library_root+'examples/SoilMoistSensor/SoilMoistSensor.ino') {
@@ -241,6 +244,7 @@ def buildnRF5(config, sketches, String key) {
sketches[sketch].path != config.library_root+'examples/DustSensorDSM/DustSensorDSM.ino' &&
sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' &&
sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' &&
+ sketches[sketch].path != config.library_root+'examples/GatewayGSMMQTTClient/GatewayGSMMQTTClient.ino' &&
sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' &&
sketches[sketch].path != config.library_root+'examples/GatewaySerialRS485/GatewaySerialRS485.ino' &&
sketches[sketch].path != config.library_root+'examples/GatewayW5100/GatewayW5100.ino' &&
diff --git a/.ci/doxygen.groovy b/.ci/doxygen.groovy
index 5c69ae9..9e68b38 100644
--- a/.ci/doxygen.groovy
+++ b/.ci/doxygen.groovy
@@ -6,7 +6,7 @@ def call(config) {
Documentation/doxygen.sh"""
warnings canComputeNew: false, canResolveRelativePaths: false,
defaultEncoding: '',
- excludePattern: '''.*/sha204_library\\.h,.*/drivers/Linux/.*,.*/cores/esp8266/.*,hardware/.*''',
+ excludePattern: '''.*/sha204_library\\.h,.*/drivers/Linux/.*,.*/drivers/TinyGSM/.*,.*/cores/esp8266/.*,hardware/.*''',
failedTotalAll: '', healthy: '', includePattern: '', messagesPattern: '',
parserConfigurations: [[parserName: 'Doxygen', pattern: config.repository_root+'doxygen.log']],
unHealthy: '', unstableTotalAll: '0'
diff --git a/.mystools/cppcheck/config/suppressions.cfg b/.mystools/cppcheck/config/suppressions.cfg
index 6a8b585..9820b3d 100644
--- a/.mystools/cppcheck/config/suppressions.cfg
+++ b/.mystools/cppcheck/config/suppressions.cfg
@@ -1,4 +1,6 @@
ConfigurationNotChecked
unmatchedSuppression
// This suppression is because the problem is in an in-lined macro so in-line-suppressions does not appear to take effect
-unreadVariable:*/MyHwNRF5.cpp
\ No newline at end of file
+unreadVariable:*/MyHwNRF5.cpp
+uninitMemberVar:*/TinyGsmFifo.h
+
diff --git a/MyConfig.h b/MyConfig.h
index ce73dea..4dd9d3a 100644
--- a/MyConfig.h
+++ b/MyConfig.h
@@ -1344,12 +1344,15 @@
* @brief Define this for Ethernet GW based on the ESP8266.
* @def MY_GATEWAY_LINUX
* @brief Define this for Ethernet GW based on Linux.
+ * @def MY_GATEWAY_TINYGSM
+ * @brief Define this for Ethernet GW based on GSM modems supported by TinyGSM library.
*/
// The gateway options available
//#define MY_GATEWAY_W5100
//#define MY_GATEWAY_ENC28J60
//#define MY_GATEWAY_ESP8266
//#define MY_GATEWAY_LINUX
+//#define MY_GATEWAY_TINYGSM
/**
@@ -1888,7 +1891,7 @@
* MY_IS_GATEWAY is true when @ref MY_GATEWAY_FEATURE is set.
* MY_NODE_TYPE contain a string describing the class of sketch/node (gateway/repeater/node).
*/
-#if defined(MY_GATEWAY_SERIAL) || defined(MY_GATEWAY_W5100) || defined(MY_GATEWAY_ENC28J60) || defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_LINUX) || defined(MY_GATEWAY_MQTT_CLIENT)
+#if defined(MY_GATEWAY_SERIAL) || defined(MY_GATEWAY_W5100) || defined(MY_GATEWAY_ENC28J60) || defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_LINUX) || defined(MY_GATEWAY_MQTT_CLIENT) || defined(MY_GATEWAY_TINYGSM)
#define MY_GATEWAY_FEATURE
#define MY_IS_GATEWAY (true)
#define MY_NODE_TYPE "GW"
@@ -2051,9 +2054,51 @@
#define MY_GATEWAY_ENC28J60
#define MY_GATEWAY_ESP8266
#define MY_GATEWAY_LINUX
+#define MY_GATEWAY_TINYGSM
#define MY_IP_ADDRESS 192,168,178,66
#define MY_USE_UDP
#define MY_CONTROLLER_IP_ADDRESS 192,168,178,254
+// TinyGSM
+/**
+ * @def MY_GSM_APN
+ * @brief APN from your cell carrier / mobile provider. Example: 4g.tele2.se
+ */
+#define MY_GSM_APN
+/**
+ * @def MY_GSM_BAUDRATE
+ * @brief Baudrate for your GSM modem. If left undefined, TinyGSM will try to auto detect the correct rate
+ */
+#define MY_GSM_BAUDRATE
+/**
+ * @def MY_GSM_PIN
+ * @brief PIN code for your SIM card, if PIN lock is active.
+ */
+#define MY_GSM_PIN
+/**
+ * @def MY_GSM_PSW
+ * @brief If using a GSM modem, this is the password supplied by your cell carrier / mobile provider. If using ESP8266 as a WiFi modem, this is your WiFi network password
+ */
+#define MY_GSM_PSW
+/**
+ * @def MY_GSM_RX
+ * @brief If defined, uses softSerial using defined pins (must also define MY_GSM_TX)
+ */
+#define MY_GSM_RX
+/**
+ * @def MY_GSM_SSID
+ * @brief If using ESP8266 as WiFi modem, this is your network SSID
+ */
+#define MY_GSM_SSID
+/**
+ * @def MY_GSM_TX
+ * @brief If defined, uses softSerial using defined pins (must also define MY_GSM_RX)
+ */
+#define MY_GSM_TX
+/**
+ * @def MY_GSM_USR
+ * @brief Supplied by your cell carrier / mobile operator. If not required, leave undefined.
+ */
+#define MY_GSM_USR
// LED
#define MY_DEFAULT_ERR_LED_PIN
#define MY_DEFAULT_TX_LED_PIN
diff --git a/MySensors.h b/MySensors.h
index dd233e1..3f4facf 100644
--- a/MySensors.h
+++ b/MySensors.h
@@ -152,11 +152,16 @@ MY_DEFAULT_RX_LED_PIN in your sketch instead to enable LEDs
#endif
// GATEWAY - COMMON FUNCTIONS
-// We support MQTT Client using W5100, ESP8266 and Linux
-#if !defined(MY_GATEWAY_CLIENT_MODE)
+// We support MQTT Client using W5100, ESP8266, GSM modems supported by TinyGSM library and Linux
+#if !defined(MY_GATEWAY_CLIENT_MODE) && !defined(MY_GATEWAY_TINYGSM)
#error You must specify MY_CONTROLLER_IP_ADDRESS or MY_CONTROLLER_URL_ADDRESS
#endif
+#if defined(MY_GATEWAY_TINYGSM) && !defined(MY_GATEWAY_MQTT_CLIENT)
+// TinyGSM currently only supports MQTTClient mode.
+#error MY_GATEWAY_TINYGSM only works with MY_GATEWAY_MQTT_CLIENT
+#endif
+
#if !defined(MY_MQTT_PUBLISH_TOPIC_PREFIX)
#error You must specify a topic publish prefix MY_MQTT_PUBLISH_TOPIC_PREFIX for this MQTT client
#endif
@@ -172,6 +177,10 @@ MY_DEFAULT_RX_LED_PIN in your sketch instead to enable LEDs
#include "core/MyGatewayTransport.cpp"
#include "core/MyProtocolMySensors.cpp"
+#if defined(MY_GATEWAY_TINYGSM)
+#include "drivers/TinyGSM/TinyGsmClient.h"
+#endif
+
#if defined(MY_GATEWAY_LINUX)
#include "drivers/Linux/EthernetClient.h"
#include "drivers/Linux/EthernetServer.h"
@@ -192,7 +201,7 @@ MY_DEFAULT_RX_LED_PIN in your sketch instead to enable LEDs
#endif
#if !defined(MY_PORT)
-#error You must define MY_PORT (controller or gatway port to open)
+#error You must define MY_PORT (controller or gateway port to open)
#endif
#if defined(MY_GATEWAY_ESP8266)
// GATEWAY - ESP8266
@@ -307,6 +316,15 @@ MY_DEFAULT_RX_LED_PIN in your sketch instead to enable LEDs
#include "drivers/AVR/DigitalIO/DigitalIO.h"
#endif
+// SOFTSERIAL
+#if defined(MY_GSM_TX) != defined(MY_GSM_RX)
+#error Both, MY_GSM_TX and MY_GSM_RX need to be defined when using SoftSerial
+#endif
+
+#if defined(MY_GATEWAY_TINYGSM) && !defined(SerialAT) && (!defined(MY_GSM_TX) || !defined(MY_GSM_RX))
+#error You need to define either SerialAT or MY_GSM_RX and MY_GSM_TX pins
+#endif
+
// POWER PIN
#ifndef DOXYGEN
#if defined(MY_RF24_POWER_PIN) || defined(MY_RFM69_POWER_PIN) || defined(MY_RFM95_POWER_PIN) || defined(MY_RADIO_NRF5_ESB)
diff --git a/core/MyGatewayTransportMQTTClient.cpp b/core/MyGatewayTransportMQTTClient.cpp
index 4a92ae9..d7016bc 100644
--- a/core/MyGatewayTransportMQTTClient.cpp
+++ b/core/MyGatewayTransportMQTTClient.cpp
@@ -50,8 +50,24 @@ IPAddress _subnetIp(255, 255, 255, 0);
uint8_t _MQTT_clientMAC[] = { MY_MAC_ADDRESS };
#endif /* End of MY_GATEWAY_ESP8266 */
+#if defined(MY_GATEWAY_TINYGSM)
+#if defined(MY_GSM_RX) && defined(MY_GSM_TX)
+SoftwareSerial SerialAT(MY_GSM_RX, MY_GSM_TX);
+#endif
+static TinyGsm modem(SerialAT);
+static TinyGsmClient _MQTT_gsmClient(modem);
+static PubSubClient _MQTT_client(_MQTT_gsmClient);
+#if defined(MY_GSM_BAUDRATE)
+uint32_t rate = MY_GSM_BAUDRATE;
+#else /* Else part of MY_GSM_BAUDRATE */
+uint32_t rate = 0;
+#endif /* End of MY_GSM_BAUDRATE */
+#else /* Else part of MY_GATEWAY_TINYGSM */
static EthernetClient _MQTT_ethClient;
static PubSubClient _MQTT_client(_MQTT_ethClient);
+#endif /* End of MY_GATEWAY_TINYGSM */
+
+
static bool _MQTT_connecting = true;
static bool _MQTT_available = false;
static MyMessage _MQTT_msg;
@@ -114,6 +130,8 @@ bool gatewayTransportConnect(void)
#if defined(MY_IP_ADDRESS)
_MQTT_ethClient.bind(_MQTT_clientIp);
#endif /* End of MY_IP_ADDRESS */
+#elif defined(MY_GATEWAY_TINYGSM) /* Elif part of MY_GATEWAY_ESP8266 */
+ GATEWAY_DEBUG(PSTR("GWT:TPC:IP=%s\n"), modem.getLocalIP().c_str());
#else /* Else part of MY_GATEWAY_ESP8266 */
#if defined(MY_IP_ADDRESS)
Ethernet.begin(_MQTT_clientMAC, _MQTT_clientIp);
@@ -137,6 +155,46 @@ bool gatewayTransportConnect(void)
bool gatewayTransportInit(void)
{
_MQTT_connecting = true;
+
+#if defined(MY_GATEWAY_TINYGSM)
+
+#if !defined(MY_GSM_BAUDRATE)
+ rate = TinyGsmAutoBaud(SerialAT);
+#endif /* End of MY_GSM_BAUDRATE */
+
+ SerialAT.begin(rate);
+ delay(3000);
+
+ modem.restart();
+
+#if defined(MY_GSM_PIN) && !defined(TINY_GSM_MODEM_ESP8266)
+ modem.simUnlock(MY_GSM_PIN);
+#endif /* End of MY_GSM_PIN */
+
+#ifndef TINY_GSM_MODEM_ESP8266
+ if (!modem.waitForNetwork()) {
+ GATEWAY_DEBUG(PSTR("!GWT:TIN:ETH FAIL\n"));
+ while (true);
+ }
+ GATEWAY_DEBUG(PSTR("GWT:TIN:ETH OK\n"));
+
+ if (!modem.gprsConnect(MY_GSM_APN, MY_GSM_USR, MY_GSM_PSW)) {
+ GATEWAY_DEBUG(PSTR("!GWT:TIN:ETH FAIL\n"));
+ while (true);
+ }
+ GATEWAY_DEBUG(PSTR("GWT:TIN:ETH OK\n"));
+ delay(1000);
+#else /* Else part of TINY_GSM_MODEM_ESP8266 */
+ if (!modem.networkConnect(MY_GSM_SSID, MY_GSM_PSW)) {
+ GATEWAY_DEBUG(PSTR("!GWT:TIN:ETH FAIL\n"));
+ while (true);
+ }
+ GATEWAY_DEBUG(PSTR("GWT:TIN:ETH OK\n"));
+ delay(1000);
+#endif /* End of TINY_GSM_MODEM_ESP8266 */
+
+#endif /* End of MY_GATEWAY_TINYGSM */
+
#if defined(MY_CONTROLLER_IP_ADDRESS)
_MQTT_client.setServer(_brokerIp, MY_PORT);
#else
diff --git a/drivers/TinyGSM/LICENSE b/drivers/TinyGSM/LICENSE
new file mode 100644
index 0000000..65c5ca8
--- /dev/null
+++ b/drivers/TinyGSM/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/drivers/TinyGSM/TinyGsmClient.h b/drivers/TinyGSM/TinyGsmClient.h
new file mode 100644
index 0000000..8badab1
--- /dev/null
+++ b/drivers/TinyGSM/TinyGsmClient.h
@@ -0,0 +1,67 @@
+/**
+ * file TinyGsmClient.h
+ * author Volodymyr Shymanskyy
+ * license LGPL-3.0
+ * copyright Copyright (c) 2016 Volodymyr Shymanskyy
+ * date Nov 2016
+ */
+
+#ifndef TinyGsmClient_h
+#define TinyGsmClient_h
+
+#if defined(TINY_GSM_MODEM_SIM800) || defined(TINY_GSM_MODEM_SIM868) || defined(TINY_GSM_MODEM_U201) || defined(TINY_GSM_MODEM_ESP8266)
+#define TINY_GSM_MODEM_HAS_SSL
+#endif
+
+#if defined(TINY_GSM_MODEM_SIM808) || defined(TINY_GSM_MODEM_SIM868) || defined(TINY_GSM_MODEM_A7)
+#define TINY_GSM_MODEM_HAS_GPS
+#endif
+
+#if defined(TINY_GSM_MODEM_SIM800) || defined(TINY_GSM_MODEM_SIM900)
+#define TINY_GSM_MODEM_HAS_GPRS
+#include "TinyGsmClientSIM800.h"
+typedef TinyGsmSim800 TinyGsm;
+typedef TinyGsmSim800::GsmClient TinyGsmClient;
+typedef TinyGsmSim800::GsmClientSecure TinyGsmClientSecure;
+
+#elif defined(TINY_GSM_MODEM_SIM808) || defined(TINY_GSM_MODEM_SIM868)
+#define TINY_GSM_MODEM_HAS_GPRS
+#include "TinyGsmClientSIM808.h"
+typedef TinyGsmSim808 TinyGsm;
+typedef TinyGsmSim808::GsmClient TinyGsmClient;
+typedef TinyGsmSim808::GsmClientSecure TinyGsmClientSecure;
+
+#elif defined(TINY_GSM_MODEM_A6) || defined(TINY_GSM_MODEM_A7)
+#define TINY_GSM_MODEM_HAS_GPRS
+#include "TinyGsmClientA6.h"
+typedef TinyGsm::GsmClient TinyGsmClient;
+
+#elif defined(TINY_GSM_MODEM_M590)
+#define TINY_GSM_MODEM_HAS_GPRS
+#include "TinyGsmClientM590.h"
+typedef TinyGsm::GsmClient TinyGsmClient;
+
+#elif defined(TINY_GSM_MODEM_U201)
+#define TINY_GSM_MODEM_HAS_GPRS
+#include "TinyGsmClientU201.h"
+typedef TinyGsmU201 TinyGsm;
+typedef TinyGsmU201::GsmClient TinyGsmClient;
+typedef TinyGsmU201::GsmClientSecure TinyGsmClientSecure;
+
+#elif defined(TINY_GSM_MODEM_ESP8266)
+#define TINY_GSM_MODEM_HAS_WIFI
+#include "TinyGsmClientESP8266.h"
+typedef TinyGsm::GsmClient TinyGsmClient;
+typedef TinyGsm::GsmClientSecure TinyGsmClientSecure;
+
+#elif defined(TINY_GSM_MODEM_XBEE)
+#define TINY_GSM_MODEM_HAS_GPRS
+#define TINY_GSM_MODEM_HAS_WIFI
+#include "TinyGsmClientXBee.h"
+typedef TinyGsm::GsmClient TinyGsmClient;
+
+#else
+#error "Please define GSM modem model"
+#endif
+
+#endif
diff --git a/drivers/TinyGSM/TinyGsmClientA6.h b/drivers/TinyGSM/TinyGsmClientA6.h
new file mode 100644
index 0000000..7630378
--- /dev/null
+++ b/drivers/TinyGSM/TinyGsmClientA6.h
@@ -0,0 +1,843 @@
+/**
+ * file TinyGsmClientA6.h
+ * author Volodymyr Shymanskyy
+ * license LGPL-3.0
+ * copyright Copyright (c) 2016 Volodymyr Shymanskyy
+ * date Nov 2016
+ */
+
+#ifndef TinyGsmClientA6_h
+#define TinyGsmClientA6_h
+
+//#define TINY_GSM_DEBUG Serial
+
+#if !defined(TINY_GSM_RX_BUFFER)
+#define TINY_GSM_RX_BUFFER 256
+#endif
+
+#define TINY_GSM_MUX_COUNT 8
+
+#include "TinyGsmCommon.h"
+
+#define GSM_NL "\r\n"
+static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
+static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
+
+enum SimStatus {
+ SIM_ERROR = 0,
+ SIM_READY = 1,
+ SIM_LOCKED = 2,
+};
+
+enum RegStatus {
+ REG_UNREGISTERED = 0,
+ REG_SEARCHING = 2,
+ REG_DENIED = 3,
+ REG_OK_HOME = 1,
+ REG_OK_ROAMING = 5,
+ REG_UNKNOWN = 4,
+};
+
+
+class TinyGsm
+{
+
+public:
+
+ class GsmClient : public Client
+ {
+ friend class TinyGsm;
+ typedef TinyGsmFifo RxFifo;
+
+ public:
+ GsmClient() {}
+
+ GsmClient(TinyGsm& modem)
+ {
+ init(&modem);
+ }
+
+ bool init(TinyGsm* modem)
+ {
+ this->at = modem;
+ this->mux = -1;
+ sock_connected = false;
+
+ return true;
+ }
+
+ public:
+ virtual int connect(const char *host, uint16_t port)
+ {
+ stop();
+ TINY_GSM_YIELD();
+ rx.clear();
+ uint8_t newMux = -1;
+ sock_connected = at->modemConnect(host, port, &newMux);
+ if (sock_connected) {
+ mux = newMux;
+ at->sockets[mux] = this;
+ }
+ return sock_connected;
+ }
+
+ virtual int connect(IPAddress ip, uint16_t port)
+ {
+ String host;
+ host.reserve(16);
+ host += ip[0];
+ host += ".";
+ host += ip[1];
+ host += ".";
+ host += ip[2];
+ host += ".";
+ host += ip[3];
+ return connect(host.c_str(), port);
+ }
+
+ virtual void stop()
+ {
+ TINY_GSM_YIELD();
+ at->sendAT(GF("+CIPCLOSE="), mux);
+ sock_connected = false;
+ at->waitResponse();
+ rx.clear();
+ }
+
+ virtual size_t write(const uint8_t *buf, size_t size)
+ {
+ TINY_GSM_YIELD();
+ //at->maintain();
+ return at->modemSend(buf, size, mux);
+ }
+
+ virtual size_t write(uint8_t c)
+ {
+ return write(&c, 1);
+ }
+
+ virtual int available()
+ {
+ TINY_GSM_YIELD();
+ if (!rx.size() && sock_connected) {
+ at->maintain();
+ }
+ return rx.size();
+ }
+
+ virtual int read(uint8_t *buf, size_t size)
+ {
+ TINY_GSM_YIELD();
+ size_t cnt = 0;
+ while (cnt < size) {
+ size_t chunk = TinyGsmMin(size-cnt, rx.size());
+ if (chunk > 0) {
+ rx.get(buf, chunk);
+ buf += chunk;
+ cnt += chunk;
+ continue;
+ }
+ // TODO: Read directly into user buffer?
+ if (!rx.size() && sock_connected) {
+ at->maintain();
+ //break;
+ }
+ }
+ return cnt;
+ }
+
+ virtual int read()
+ {
+ uint8_t c;
+ if (read(&c, 1) == 1) {
+ return c;
+ }
+ return -1;
+ }
+
+ virtual int peek()
+ {
+ return -1; //TODO
+ }
+ virtual void flush()
+ {
+ at->stream.flush();
+ }
+
+ virtual uint8_t connected()
+ {
+ if (available()) {
+ return true;
+ }
+ return sock_connected;
+ }
+ virtual operator bool()
+ {
+ return connected();
+ }
+
+ /*
+ * Extended API
+ */
+
+ String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ private:
+ TinyGsm* at;
+ uint8_t mux;
+ bool sock_connected;
+ RxFifo rx;
+ };
+
+public:
+
+ explicit TinyGsm(Stream& stream)
+ : stream(stream)
+ {
+ memset(sockets, 0, sizeof(sockets));
+ }
+
+ /*
+ * Basic functions
+ */
+ bool begin()
+ {
+ return init();
+ }
+
+ bool init()
+ {
+ if (!testAT()) {
+ return false;
+ }
+ sendAT(GF("&FZE0")); // Factory + Reset + Echo Off
+ if (waitResponse() != 1) {
+ return false;
+ }
+ sendAT(GF("+CMEE=0"));
+ waitResponse();
+
+ sendAT(GF("+CMER=3,0,0,2"));
+ waitResponse();
+
+ getSimStatus();
+ return true;
+ }
+
+ void setBaud(unsigned long baud)
+ {
+ sendAT(GF("+IPR="), baud);
+ }
+
+ bool testAT(unsigned long timeout = 10000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ sendAT(GF(""));
+ if (waitResponse(200) == 1) {
+ delay(100);
+ return true;
+ }
+ delay(100);
+ }
+ return false;
+ }
+
+ void maintain()
+ {
+ waitResponse(10, NULL, NULL);
+ }
+
+ bool factoryDefault()
+ {
+ sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write
+ waitResponse();
+ sendAT(GF("&W")); // Write configuration
+ return waitResponse() == 1;
+ }
+
+ String getModemInfo()
+ {
+ sendAT(GF("I"));
+ String res;
+ if (waitResponse(1000L, res) != 1) {
+ return "";
+ }
+ res.replace(GSM_NL "OK" GSM_NL, "");
+ res.replace(GSM_NL, " ");
+ res.trim();
+ return res;
+ }
+
+ bool hasSSL()
+ {
+ return false;
+ }
+
+ /*
+ * Power functions
+ */
+
+ bool restart()
+ {
+ if (!testAT()) {
+ return false;
+ }
+ sendAT(GF("+RST=1"));
+ delay(3000);
+ return init();
+ }
+
+ bool poweroff()
+ {
+ sendAT(GF("+CPOF"));
+ return waitResponse() == 1;
+ }
+
+ bool radioOff() TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ bool sleepEnable(bool enable = true) TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ /*
+ * SIM card functions
+ */
+
+ bool simUnlock(const char *pin)
+ {
+ sendAT(GF("+CPIN=\""), pin, GF("\""));
+ return waitResponse() == 1;
+ }
+
+ String getSimCCID()
+ {
+ sendAT(GF("+CCID"));
+ if (waitResponse(GF(GSM_NL "+SCID: SIM Card ID:")) != 1) {
+ return "";
+ }
+ String res = stream.readStringUntil('\n');
+ waitResponse();
+ res.trim();
+ return res;
+ }
+
+ String getIMEI()
+ {
+ sendAT(GF("+GSN"));
+ if (waitResponse(GF(GSM_NL)) != 1) {
+ return "";
+ }
+ String res = stream.readStringUntil('\n');
+ waitResponse();
+ res.trim();
+ return res;
+ }
+
+ SimStatus getSimStatus(unsigned long timeout = 10000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ sendAT(GF("+CPIN?"));
+ if (waitResponse(GF(GSM_NL "+CPIN:")) != 1) {
+ delay(1000);
+ continue;
+ }
+ int status = waitResponse(GF("READY"), GF("SIM PIN"), GF("SIM PUK"));
+ waitResponse();
+ switch (status) {
+ case 2:
+ case 3:
+ return SIM_LOCKED;
+ case 1:
+ return SIM_READY;
+ default:
+ return SIM_ERROR;
+ }
+ }
+ return SIM_ERROR;
+ }
+
+ RegStatus getRegistrationStatus()
+ {
+ sendAT(GF("+CREG?"));
+ if (waitResponse(GF(GSM_NL "+CREG:")) != 1) {
+ return REG_UNKNOWN;
+ }
+ streamSkipUntil(','); // Skip format (0)
+ int status = stream.readStringUntil('\n').toInt();
+ waitResponse();
+ return (RegStatus)status;
+ }
+
+ String getOperator()
+ {
+ sendAT(GF("+COPS=3,0")); // Set format
+ waitResponse();
+
+ sendAT(GF("+COPS?"));
+ if (waitResponse(GF(GSM_NL "+COPS:")) != 1) {
+ return "";
+ }
+ streamSkipUntil('"'); // Skip mode and format
+ String res = stream.readStringUntil('"');
+ waitResponse();
+ return res;
+ }
+
+ /*
+ * Generic network functions
+ */
+
+ int getSignalQuality()
+ {
+ sendAT(GF("+CSQ"));
+ if (waitResponse(GF(GSM_NL "+CSQ:")) != 1) {
+ return 99;
+ }
+ int res = stream.readStringUntil(',').toInt();
+ waitResponse();
+ return res;
+ }
+
+ bool isNetworkConnected()
+ {
+ RegStatus s = getRegistrationStatus();
+ return (s == REG_OK_HOME || s == REG_OK_ROAMING);
+ }
+
+ bool waitForNetwork(unsigned long timeout = 60000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ if (isNetworkConnected()) {
+ return true;
+ }
+ delay(250);
+ }
+ return false;
+ }
+
+ /*
+ * GPRS functions
+ */
+ bool gprsConnect(const char* apn, const char* user = NULL, const char* pwd = NULL)
+ {
+ gprsDisconnect();
+
+ sendAT(GF("+CGATT=1"));
+ if (waitResponse(60000L) != 1) {
+ return false;
+ }
+
+ // TODO: wait AT+CGATT?
+
+ sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"');
+ waitResponse();
+
+ if (!user) {
+ user = "";
+ }
+ if (!pwd) {
+ pwd = "";
+ }
+ sendAT(GF("+CSTT=\""), apn, GF("\",\""), user, GF("\",\""), pwd, GF("\""));
+ if (waitResponse(60000L) != 1) {
+ return false;
+ }
+
+ sendAT(GF("+CGACT=1,1"));
+ waitResponse(60000L);
+
+ sendAT(GF("+CIPMUX=1"));
+ if (waitResponse() != 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool gprsDisconnect()
+ {
+ // Shut the TCP/IP connection
+ sendAT(GF("+CIPSHUT"));
+ if (waitResponse(60000L) != 1) {
+ return false;
+ }
+
+ for (int i = 0; i<3; i++) {
+ sendAT(GF("+CGATT=0"));
+ if (waitResponse(5000L) == 1) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool isGprsConnected()
+ {
+ sendAT(GF("+CGATT?"));
+ if (waitResponse(GF(GSM_NL "+CGATT:")) != 1) {
+ return false;
+ }
+ int res = stream.readStringUntil('\n').toInt();
+ waitResponse();
+ return (res == 1);
+ }
+
+ String getLocalIP()
+ {
+ sendAT(GF("+CIFSR"));
+ String res;
+ if (waitResponse(10000L, res) != 1) {
+ return "";
+ }
+ res.replace(GSM_NL "OK" GSM_NL, "");
+ res.replace(GSM_NL, "");
+ res.trim();
+ return res;
+ }
+
+ IPAddress localIP()
+ {
+ return TinyGsmIpFromString(getLocalIP());
+ }
+
+ /*
+ * Phone Call functions
+ */
+
+ bool setGsmBusy(bool busy = true) TINY_GSM_ATTR_NOT_AVAILABLE;
+
+ bool callAnswer()
+ {
+ sendAT(GF("A"));
+ return waitResponse() == 1;
+ }
+
+ // Returns true on pick-up, false on error/busy
+ bool callNumber(const String& number)
+ {
+ if (number == GF("last")) {
+ sendAT(GF("DLST"));
+ } else {
+ sendAT(GF("D\""), number, "\";");
+ }
+
+ if (waitResponse(5000L) != 1) {
+ return false;
+ }
+
+ if (waitResponse(60000L,
+ GF(GSM_NL "+CIEV: \"CALL\",1"),
+ GF(GSM_NL "+CIEV: \"CALL\",0"),
+ GFP(GSM_ERROR)) != 1) {
+ return false;
+ }
+
+ int rsp = waitResponse(60000L,
+ GF(GSM_NL "+CIEV: \"SOUNDER\",0"),
+ GF(GSM_NL "+CIEV: \"CALL\",0"));
+
+ int rsp2 = waitResponse(300L, GF(GSM_NL "BUSY" GSM_NL), GF(GSM_NL "NO ANSWER" GSM_NL));
+
+ return rsp == 1 && rsp2 == 0;
+ }
+
+ bool callHangup()
+ {
+ sendAT(GF("H"));
+ return waitResponse() == 1;
+ }
+
+ // 0-9,*,#,A,B,C,D
+ bool dtmfSend(char cmd, unsigned duration_ms = 100)
+ {
+ duration_ms = constrain(duration_ms, 100, 1000);
+
+ // The duration parameter is not working, so we simulate it using delay..
+ // TODO: Maybe there's another way...
+
+ //sendAT(GF("+VTD="), duration_ms / 100);
+ //waitResponse();
+
+ sendAT(GF("+VTS="), cmd);
+ if (waitResponse(10000L) == 1) {
+ delay(duration_ms);
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * Audio functions
+ */
+
+ bool audioSetHeadphones()
+ {
+ sendAT(GF("+SNFS=0"));
+ return waitResponse() == 1;
+ }
+
+ bool audioSetSpeaker()
+ {
+ sendAT(GF("+SNFS=1"));
+ return waitResponse() == 1;
+ }
+
+ bool audioMuteMic(bool mute)
+ {
+ sendAT(GF("+CMUT="), mute);
+ return waitResponse() == 1;
+ }
+
+ /*
+ * Messaging functions
+ */
+
+ String sendUSSD(const String& code)
+ {
+ sendAT(GF("+CMGF=1"));
+ waitResponse();
+ sendAT(GF("+CSCS=\"HEX\""));
+ waitResponse();
+ sendAT(GF("+CUSD=1,\""), code, GF("\",15"));
+ if (waitResponse(10000L) != 1) {
+ return "";
+ }
+ if (waitResponse(GF(GSM_NL "+CUSD:")) != 1) {
+ return "";
+ }
+ stream.readStringUntil('"');
+ String hex = stream.readStringUntil('"');
+ stream.readStringUntil(',');
+ int dcs = stream.readStringUntil('\n').toInt();
+
+ if (dcs == 15) {
+ return TinyGsmDecodeHex7bit(hex);
+ } else if (dcs == 72) {
+ return TinyGsmDecodeHex16bit(hex);
+ } else {
+ return hex;
+ }
+ }
+
+ bool sendSMS(const String& number, const String& text)
+ {
+ sendAT(GF("+CMGF=1"));
+ waitResponse();
+ sendAT(GF("+CMGS=\""), number, GF("\""));
+ if (waitResponse(GF(">")) != 1) {
+ return false;
+ }
+ stream.print(text);
+ stream.write((char)0x1A);
+ stream.flush();
+ return waitResponse(60000L) == 1;
+ }
+
+
+ /*
+ * Location functions
+ */
+
+ String getGsmLocation() TINY_GSM_ATTR_NOT_AVAILABLE;
+
+ /*
+ * Battery functions
+ */
+
+ uint16_t getBattVoltage() TINY_GSM_ATTR_NOT_AVAILABLE;
+
+ int getBattPercent()
+ {
+ sendAT(GF("+CBC?"));
+ if (waitResponse(GF(GSM_NL "+CBC:")) != 1) {
+ return false;
+ }
+ stream.readStringUntil(',');
+ int res = stream.readStringUntil('\n').toInt();
+ waitResponse();
+ return res;
+ }
+
+protected:
+
+ bool modemConnect(const char* host, uint16_t port, uint8_t* mux)
+ {
+ sendAT(GF("+CIPSTART="), GF("\"TCP"), GF("\",\""), host, GF("\","), port);
+
+ if (waitResponse(75000L, GF(GSM_NL "+CIPNUM:")) != 1) {
+ return false;
+ }
+ int newMux = stream.readStringUntil('\n').toInt();
+
+ int rsp = waitResponse(75000L,
+ GF("CONNECT OK" GSM_NL),
+ GF("CONNECT FAIL" GSM_NL),
+ GF("ALREADY CONNECT" GSM_NL));
+ if (waitResponse() != 1) {
+ return false;
+ }
+ *mux = newMux;
+
+ return (1 == rsp);
+ }
+
+ int modemSend(const void* buff, size_t len, uint8_t mux)
+ {
+ sendAT(GF("+CIPSEND="), mux, ',', len);
+ if (waitResponse(2000L, GF(GSM_NL ">")) != 1) {
+ return 0;
+ }
+ stream.write((uint8_t*)buff, len);
+ stream.flush();
+ if (waitResponse(10000L, GFP(GSM_OK), GF(GSM_NL "FAIL")) != 1) {
+ return 0;
+ }
+ return len;
+ }
+
+ bool modemGetConnected(uint8_t mux)
+ {
+ sendAT(GF("+CIPSTATUS")); //TODO mux?
+ int res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""), GF(",\"CLOSING\""),
+ GF(",\"INITIAL\""));
+ waitResponse();
+ return 1 == res;
+ }
+
+public:
+
+ /* Utilities */
+
+ template
+ void streamWrite(T last)
+ {
+ stream.print(last);
+ }
+
+ template
+ void streamWrite(T head, Args... tail)
+ {
+ stream.print(head);
+ streamWrite(tail...);
+ }
+
+ bool streamSkipUntil(char c) //TODO: timeout
+ {
+ while (true) {
+ while (!stream.available()) {
+ TINY_GSM_YIELD();
+ }
+ if (stream.read() == c) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template
+ void sendAT(Args... cmd)
+ {
+ streamWrite("AT", cmd..., GSM_NL);
+ stream.flush();
+ TINY_GSM_YIELD();
+ //DBG("### AT:", cmd...);
+ }
+
+ // TODO: Optimize this!
+ uint8_t waitResponse(uint32_t timeout, String& data,
+ GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ /*String r1s(r1); r1s.trim();
+ String r2s(r2); r2s.trim();
+ String r3s(r3); r3s.trim();
+ String r4s(r4); r4s.trim();
+ String r5s(r5); r5s.trim();
+ DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/
+ data.reserve(64);
+ int index = 0;
+ unsigned long startMillis = millis();
+ do {
+ TINY_GSM_YIELD();
+ while (stream.available() > 0) {
+ int a = stream.read();
+ if (a <= 0) {
+ continue; // Skip 0x00 bytes, just in case
+ }
+ data += (char)a;
+ if (r1 && data.endsWith(r1)) {
+ index = 1;
+ goto finish;
+ } else if (r2 && data.endsWith(r2)) {
+ index = 2;
+ goto finish;
+ } else if (r3 && data.endsWith(r3)) {
+ index = 3;
+ goto finish;
+ } else if (r4 && data.endsWith(r4)) {
+ index = 4;
+ goto finish;
+ } else if (r5 && data.endsWith(r5)) {
+ index = 5;
+ goto finish;
+ } else if (data.endsWith(GF("+CIPRCV:"))) {
+ int mux = stream.readStringUntil(',').toInt();
+ int len = stream.readStringUntil(',').toInt();
+ int len_orig = len;
+ if (len > sockets[mux]->rx.free()) {
+ DBG("### Buffer overflow: ", len, "->", sockets[mux]->rx.free());
+ } else {
+ DBG("### Got: ", len, "->", sockets[mux]->rx.free());
+ }
+ while (len--) {
+ while (!stream.available()) {
+ TINY_GSM_YIELD();
+ }
+ sockets[mux]->rx.put(stream.read());
+ }
+ if (len_orig > sockets[mux]->available()) { // TODO
+ DBG("### Fewer characters received than expected: ", sockets[mux]->available(), " vs ", len_orig);
+ }
+ data = "";
+ } else if (data.endsWith(GF("+TCPCLOSED:"))) {
+ int mux = stream.readStringUntil('\n').toInt();
+ if (mux >= 0 && mux < TINY_GSM_MUX_COUNT) {
+ sockets[mux]->sock_connected = false;
+ }
+ data = "";
+ DBG("### Closed: ", mux);
+ }
+ }
+ } while (millis() - startMillis < timeout);
+finish:
+ if (!index) {
+ data.trim();
+ if (data.length()) {
+ DBG("### Unhandled:", data);
+ }
+ data = "";
+ }
+ return index;
+ }
+
+ uint8_t waitResponse(uint32_t timeout,
+ GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ String data;
+ return waitResponse(timeout, data, r1, r2, r3, r4, r5);
+ }
+
+ uint8_t waitResponse(GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ return waitResponse(1000, r1, r2, r3, r4, r5);
+ }
+
+public:
+ Stream& stream;
+
+protected:
+ GsmClient* sockets[TINY_GSM_MUX_COUNT];
+};
+
+#endif
diff --git a/drivers/TinyGSM/TinyGsmClientESP8266.h b/drivers/TinyGSM/TinyGsmClientESP8266.h
new file mode 100644
index 0000000..76d88ea
--- /dev/null
+++ b/drivers/TinyGSM/TinyGsmClientESP8266.h
@@ -0,0 +1,591 @@
+/**
+ * file TinyGsmClientESP8266.h
+ * author Volodymyr Shymanskyy
+ * license LGPL-3.0
+ * copyright Copyright (c) 2016 Volodymyr Shymanskyy
+ * date Nov 2016
+ */
+
+#ifndef TinyGsmClientESP8266_h
+#define TinyGsmClientESP8266_h
+
+//#define TINY_GSM_DEBUG Serial
+
+#if !defined(TINY_GSM_RX_BUFFER)
+#define TINY_GSM_RX_BUFFER 512
+#endif
+
+#define TINY_GSM_MUX_COUNT 5
+
+#include "TinyGsmCommon.h"
+
+#define GSM_NL "\r\n"
+static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
+static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
+static unsigned TINY_GSM_TCP_KEEP_ALIVE = 120;
+
+class TinyGsm
+{
+
+public:
+
+ class GsmClient : public Client
+ {
+ friend class TinyGsm;
+ typedef TinyGsmFifo RxFifo;
+
+ public:
+ GsmClient() {}
+
+ GsmClient(TinyGsm& modem, uint8_t mux = 1)
+ {
+ init(&modem, mux);
+ }
+
+ bool init(TinyGsm* modem, uint8_t mux = 1)
+ {
+ this->at = modem;
+ this->mux = mux;
+ sock_connected = false;
+
+ at->sockets[mux] = this;
+
+ return true;
+ }
+
+ public:
+ virtual int connect(const char *host, uint16_t port)
+ {
+ stop();
+ TINY_GSM_YIELD();
+ rx.clear();
+ sock_connected = at->modemConnect(host, port, mux);
+ return sock_connected;
+ }
+
+ virtual int connect(IPAddress ip, uint16_t port)
+ {
+ String host;
+ host.reserve(16);
+ host += ip[0];
+ host += ".";
+ host += ip[1];
+ host += ".";
+ host += ip[2];
+ host += ".";
+ host += ip[3];
+ return connect(host.c_str(), port);
+ }
+
+ virtual void stop()
+ {
+ TINY_GSM_YIELD();
+ at->sendAT(GF("+CIPCLOSE="), mux);
+ sock_connected = false;
+ at->waitResponse();
+ rx.clear();
+ }
+
+ virtual size_t write(const uint8_t *buf, size_t size)
+ {
+ TINY_GSM_YIELD();
+ //at->maintain();
+ return at->modemSend(buf, size, mux);
+ }
+
+ virtual size_t write(uint8_t c)
+ {
+ return write(&c, 1);
+ }
+
+ virtual int available()
+ {
+ TINY_GSM_YIELD();
+ if (!rx.size() && sock_connected) {
+ at->maintain();
+ }
+ return rx.size();
+ }
+
+ virtual int read(uint8_t *buf, size_t size)
+ {
+ TINY_GSM_YIELD();
+ size_t cnt = 0;
+ while (cnt < size) {
+ size_t chunk = TinyGsmMin(size-cnt, rx.size());
+ if (chunk > 0) {
+ rx.get(buf, chunk);
+ buf += chunk;
+ cnt += chunk;
+ continue;
+ }
+ // TODO: Read directly into user buffer?
+ if (!rx.size() && sock_connected) {
+ at->maintain();
+ //break;
+ }
+ }
+ return cnt;
+ }
+
+ virtual int read()
+ {
+ uint8_t c;
+ if (read(&c, 1) == 1) {
+ return c;
+ }
+ return -1;
+ }
+
+ virtual int peek()
+ {
+ return -1; //TODO
+ }
+ virtual void flush()
+ {
+ at->stream.flush();
+ }
+
+ virtual uint8_t connected()
+ {
+ if (available()) {
+ return true;
+ }
+ return sock_connected;
+ }
+ virtual operator bool()
+ {
+ return connected();
+ }
+
+ /*
+ * Extended API
+ */
+
+ String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ private:
+ TinyGsm* at;
+ uint8_t mux;
+ bool sock_connected;
+ RxFifo rx;
+ };
+
+ class GsmClientSecure : public GsmClient
+ {
+ public:
+ GsmClientSecure() {}
+
+ GsmClientSecure(TinyGsm& modem, uint8_t mux = 1)
+ : GsmClient(modem, mux)
+ {}
+
+ public:
+ virtual int connect(const char *host, uint16_t port)
+ {
+ stop();
+ TINY_GSM_YIELD();
+ rx.clear();
+ sock_connected = at->modemConnect(host, port, mux, true);
+ return sock_connected;
+ }
+ };
+
+public:
+
+ explicit TinyGsm(Stream& stream)
+ : stream(stream)
+ {
+ memset(sockets, 0, sizeof(sockets));
+ }
+
+ /*
+ * Basic functions
+ */
+ bool begin()
+ {
+ return init();
+ }
+
+ bool init()
+ {
+ if (!testAT()) {
+ return false;
+ }
+ sendAT(GF("E0")); // Echo Off
+ if (waitResponse() != 1) {
+ return false;
+ }
+ return true;
+ }
+
+ void setBaud(unsigned long baud)
+ {
+ sendAT(GF("+IPR="), baud);
+ }
+
+ bool testAT(unsigned long timeout = 10000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ sendAT(GF(""));
+ if (waitResponse(200) == 1) {
+ delay(100);
+ return true;
+ }
+ delay(100);
+ }
+ return false;
+ }
+
+ void maintain()
+ {
+ waitResponse(10, NULL, NULL);
+ }
+
+ bool factoryDefault()
+ {
+ sendAT(GF("+RESTORE"));
+ return waitResponse() == 1;
+ }
+
+ String getModemInfo()
+ {
+ sendAT(GF("+GMR"));
+ String res;
+ if (waitResponse(1000L, res) != 1) {
+ return "";
+ }
+ res.replace(GSM_NL "OK" GSM_NL, "");
+ res.replace(GSM_NL, " ");
+ res.trim();
+ return res;
+ }
+
+ bool hasSSL()
+ {
+ return true;
+ }
+
+ /*
+ * Power functions
+ */
+
+ bool restart()
+ {
+ if (!testAT()) {
+ return false;
+ }
+ sendAT(GF("+RST"));
+ if (waitResponse(10000L) != 1) {
+ return false;
+ }
+ if (waitResponse(10000L, GF(GSM_NL "ready" GSM_NL)) != 1) {
+ return false;
+ }
+ delay(500);
+ return init();
+ }
+
+
+ /*
+ * Generic network functions
+ */
+
+ int getSignalQuality()
+ {
+ sendAT(GF("+CWJAP_CUR?"));
+ int res1 = waitResponse(GF("No AP"), GF("+CWJAP_CUR:"));
+ if (res1 != 2) {
+ waitResponse();
+ return 0;
+ }
+ streamSkipUntil(','); // Skip SSID
+ streamSkipUntil(','); // Skip BSSID/MAC address
+ streamSkipUntil(','); // Skip Chanel number
+ int res2 = stream.parseInt(); // Read RSSI
+ waitResponse(); // Returns an OK after the value
+ return res2;
+ }
+
+ bool isNetworkConnected()
+ {
+ sendAT(GF("+CIPSTATUS"));
+ int res1 = waitResponse(3000, GF("STATUS:"));
+ int res2 = 0;
+ if (res1 == 1) {
+ res2 = waitResponse(GFP(GSM_ERROR), GF("2"), GF("3"), GF("4"), GF("5"));
+ }
+ // status of ESP8266 station interface
+ // 2 : ESP8266 station connected to an AP and has obtained IP
+ // 3 : ESP8266 station created a TCP or UDP transmission
+ // 4 : the TCP or UDP transmission of ESP8266 station disconnected (but AP is connected)
+ // 5 : ESP8266 station did NOT connect to an AP
+ waitResponse(); // Returns an OK after the status
+ if (res2 == 2 || res2 == 3 || res2 == 4) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ bool waitForNetwork(unsigned long timeout = 60000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ sendAT(GF("+CIPSTATUS"));
+ int res1 = waitResponse(3000, GF("busy p..."), GF("STATUS:"));
+ if (res1 == 2) {
+ int res2 = waitResponse(GFP(GSM_ERROR), GF("2"), GF("3"), GF("4"), GF("5"));
+ if (res2 == 2 || res2 == 3 || res2 == 4) {
+ waitResponse();
+ return true;
+ }
+ }
+ delay(250);
+ }
+ return false;
+ }
+
+ /*
+ * WiFi functions
+ */
+ bool networkConnect(const char* ssid, const char* pwd)
+ {
+
+ sendAT(GF("+CIPMUX=1"));
+ if (waitResponse() != 1) {
+ return false;
+ }
+
+ sendAT(GF("+CWMODE_CUR=1"));
+ if (waitResponse() != 1) {
+ return false;
+ }
+
+ sendAT(GF("+CWJAP_CUR=\""), ssid, GF("\",\""), pwd, GF("\""));
+ if (waitResponse(30000L, GFP(GSM_OK), GF(GSM_NL "FAIL" GSM_NL)) != 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool networkDisconnect()
+ {
+ sendAT(GF("+CWQAP"));
+ bool retVal = waitResponse(10000L) == 1;
+ waitResponse(GF("WIFI DISCONNECT"));
+ return retVal;
+ }
+
+ String getLocalIP()
+ {
+ sendAT(GF("+CIPSTA_CUR??"));
+ int res1 = waitResponse(GF("ERROR"), GF("+CWJAP_CUR:"));
+ if (res1 != 2) {
+ return "";
+ }
+ String res2 = stream.readStringUntil('"');
+ waitResponse();
+ return res2;
+ }
+
+ IPAddress localIP()
+ {
+ return TinyGsmIpFromString(getLocalIP());
+ }
+
+protected:
+
+ bool modemConnect(const char* host, uint16_t port, uint8_t mux, bool ssl = false)
+ {
+ if (ssl) {
+ sendAT(GF("+CIPSSLSIZE=4096"));
+ waitResponse();
+ }
+ sendAT(GF("+CIPSTART="), mux, ',', ssl ? GF("\"SSL") : GF("\"TCP"), GF("\",\""), host, GF("\","),
+ port, GF(","), TINY_GSM_TCP_KEEP_ALIVE);
+ // TODO: Check mux
+ int rsp = waitResponse(75000L,
+ GFP(GSM_OK),
+ GFP(GSM_ERROR),
+ GF("ALREADY CONNECT"));
+ // if (rsp == 3) waitResponse(); // May return "ERROR" after the "ALREADY CONNECT"
+ return (1 == rsp);
+ }
+
+ int modemSend(const void* buff, size_t len, uint8_t mux)
+ {
+ sendAT(GF("+CIPSEND="), mux, ',', len);
+ if (waitResponse(GF(">")) != 1) {
+ return 0;
+ }
+ stream.write((uint8_t*)buff, len);
+ stream.flush();
+ if (waitResponse(10000L, GF(GSM_NL "SEND OK" GSM_NL)) != 1) {
+ return 0;
+ }
+ return len;
+ }
+
+ bool modemGetConnected(uint8_t mux)
+ {
+ // TODO: re-check this
+ sendAT(GF("+CIPSTATUS="), mux);
+ int res1 = waitResponse(3000, GF("STATUS:"));
+ int res2;
+ if (res1 == 1) {
+ res2 = waitResponse(GFP(GSM_ERROR), GF("2"), GF("3"), GF("4"), GF("5"));
+ }
+ // status of ESP8266 station interface
+ // 2 : ESP8266 station connected to an AP and has obtained IP
+ // 3 : ESP8266 station created a TCP or UDP transmission
+ // 4 : the TCP or UDP transmission of ESP8266 station disconnected (but AP is connected)
+ // 5 : ESP8266 station did NOT connect to an AP
+ waitResponse(); // Returns an OK after the status
+ if (res2 == 2 || res2 == 3 || res2 == 4) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+public:
+
+ /* Utilities */
+
+ template
+ void streamWrite(T last)
+ {
+ stream.print(last);
+ }
+
+ template
+ void streamWrite(T head, Args... tail)
+ {
+ stream.print(head);
+ streamWrite(tail...);
+ }
+
+ bool streamSkipUntil(char c) //TODO: timeout
+ {
+ while (true) {
+ while (!stream.available()) {
+ TINY_GSM_YIELD();
+ }
+ if (stream.read() == c) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template
+ void sendAT(Args... cmd)
+ {
+ streamWrite("AT", cmd..., GSM_NL);
+ stream.flush();
+ TINY_GSM_YIELD();
+ //DBG("### AT:", cmd...);
+ }
+
+ // TODO: Optimize this!
+ uint8_t waitResponse(uint32_t timeout, String& data,
+ GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ /*String r1s(r1); r1s.trim();
+ String r2s(r2); r2s.trim();
+ String r3s(r3); r3s.trim();
+ String r4s(r4); r4s.trim();
+ String r5s(r5); r5s.trim();
+ DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/
+ data.reserve(64);
+ int index = 0;
+ unsigned long startMillis = millis();
+ do {
+ TINY_GSM_YIELD();
+ while (stream.available() > 0) {
+ int a = stream.read();
+ if (a <= 0) {
+ continue; // Skip 0x00 bytes, just in case
+ }
+ data += (char)a;
+ if (r1 && data.endsWith(r1)) {
+ index = 1;
+ goto finish;
+ } else if (r2 && data.endsWith(r2)) {
+ index = 2;
+ goto finish;
+ } else if (r3 && data.endsWith(r3)) {
+ index = 3;
+ goto finish;
+ } else if (r4 && data.endsWith(r4)) {
+ index = 4;
+ goto finish;
+ } else if (r5 && data.endsWith(r5)) {
+ index = 5;
+ goto finish;
+ } else if (data.endsWith(GF(GSM_NL "+IPD,"))) {
+ int mux = stream.readStringUntil(',').toInt();
+ int len = stream.readStringUntil(':').toInt();
+ int len_orig = len;
+ if (len > sockets[mux]->rx.free()) {
+ DBG("### Buffer overflow: ", len, "->", sockets[mux]->rx.free());
+ } else {
+ DBG("### Got: ", len, "->", sockets[mux]->rx.free());
+ }
+ while (len--) {
+ while (!stream.available()) {
+ TINY_GSM_YIELD();
+ }
+ sockets[mux]->rx.put(stream.read());
+ }
+ if (len_orig > sockets[mux]->available()) { // TODO
+ DBG("### Fewer characters received than expected: ", sockets[mux]->available(), " vs ", len_orig);
+ }
+ data = "";
+ } else if (data.endsWith(GF("CLOSED"))) {
+ int muxStart = max(0,data.lastIndexOf(GSM_NL, data.length()-8));
+ int coma = data.indexOf(',', muxStart);
+ int mux = data.substring(muxStart, coma).toInt();
+ if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
+ sockets[mux]->sock_connected = false;
+ }
+ data = "";
+ DBG("### Closed: ", mux);
+ }
+ }
+ } while (millis() - startMillis < timeout);
+finish:
+ if (!index) {
+ data.trim();
+ if (data.length()) {
+ DBG("### Unhandled:", data);
+ }
+ data = "";
+ }
+ return index;
+ }
+
+ uint8_t waitResponse(uint32_t timeout,
+ GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ String data;
+ return waitResponse(timeout, data, r1, r2, r3, r4, r5);
+ }
+
+ uint8_t waitResponse(GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ return waitResponse(1000, r1, r2, r3, r4, r5);
+ }
+
+public:
+ Stream& stream;
+
+protected:
+ GsmClient* sockets[TINY_GSM_MUX_COUNT];
+};
+
+#endif
diff --git a/drivers/TinyGSM/TinyGsmClientM590.h b/drivers/TinyGSM/TinyGsmClientM590.h
new file mode 100644
index 0000000..5c6288f
--- /dev/null
+++ b/drivers/TinyGSM/TinyGsmClientM590.h
@@ -0,0 +1,781 @@
+/**
+ * file TinyGsmClientM590.h
+ * author Volodymyr Shymanskyy
+ * license LGPL-3.0
+ * copyright Copyright (c) 2016 Volodymyr Shymanskyy
+ * date Nov 2016
+ */
+
+#ifndef TinyGsmClientM590_h
+#define TinyGsmClientM590_h
+
+//#define TINY_GSM_DEBUG Serial
+
+#if !defined(TINY_GSM_RX_BUFFER)
+#define TINY_GSM_RX_BUFFER 256
+#endif
+
+#define TINY_GSM_MUX_COUNT 2
+
+#include "TinyGsmCommon.h"
+
+#define GSM_NL "\r\n"
+static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
+static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
+
+enum SimStatus {
+ SIM_ERROR = 0,
+ SIM_READY = 1,
+ SIM_LOCKED = 2,
+};
+
+enum RegStatus {
+ REG_UNREGISTERED = 0,
+ REG_SEARCHING = 3,
+ REG_DENIED = 2,
+ REG_OK_HOME = 1,
+ REG_OK_ROAMING = 5,
+ REG_UNKNOWN = 4,
+};
+
+
+class TinyGsm
+{
+
+public:
+
+ class GsmClient : public Client
+ {
+ friend class TinyGsm;
+ typedef TinyGsmFifo RxFifo;
+
+ public:
+ GsmClient() {}
+
+ GsmClient(TinyGsm& modem, uint8_t mux = 1)
+ {
+ init(&modem, mux);
+ }
+
+ bool init(TinyGsm* modem, uint8_t mux = 1)
+ {
+ this->at = modem;
+ this->mux = mux;
+ sock_connected = false;
+
+ at->sockets[mux] = this;
+
+ return true;
+ }
+
+ public:
+ virtual int connect(const char *host, uint16_t port)
+ {
+ stop();
+ TINY_GSM_YIELD();
+ rx.clear();
+ sock_connected = at->modemConnect(host, port, mux);
+ return sock_connected;
+ }
+
+ virtual int connect(IPAddress ip, uint16_t port)
+ {
+ String host;
+ host.reserve(16);
+ host += ip[0];
+ host += ".";
+ host += ip[1];
+ host += ".";
+ host += ip[2];
+ host += ".";
+ host += ip[3];
+ return connect(host.c_str(), port);
+ }
+
+ virtual void stop()
+ {
+ TINY_GSM_YIELD();
+ at->sendAT(GF("+TCPCLOSE="), mux);
+ sock_connected = false;
+ at->waitResponse();
+ rx.clear();
+ }
+
+ virtual size_t write(const uint8_t *buf, size_t size)
+ {
+ TINY_GSM_YIELD();
+ //at->maintain();
+ return at->modemSend(buf, size, mux);
+ }
+
+ virtual size_t write(uint8_t c)
+ {
+ return write(&c, 1);
+ }
+
+ virtual int available()
+ {
+ TINY_GSM_YIELD();
+ if (!rx.size() && sock_connected) {
+ at->maintain();
+ }
+ return rx.size();
+ }
+
+ virtual int read(uint8_t *buf, size_t size)
+ {
+ TINY_GSM_YIELD();
+ size_t cnt = 0;
+ while (cnt < size) {
+ size_t chunk = TinyGsmMin(size-cnt, rx.size());
+ if (chunk > 0) {
+ rx.get(buf, chunk);
+ buf += chunk;
+ cnt += chunk;
+ continue;
+ }
+ // TODO: Read directly into user buffer?
+ if (!rx.size() && sock_connected) {
+ at->maintain();
+ //break;
+ }
+ }
+ return cnt;
+ }
+
+ virtual int read()
+ {
+ uint8_t c;
+ if (read(&c, 1) == 1) {
+ return c;
+ }
+ return -1;
+ }
+
+ virtual int peek()
+ {
+ return -1; //TODO
+ }
+ virtual void flush()
+ {
+ at->stream.flush();
+ }
+
+ virtual uint8_t connected()
+ {
+ if (available()) {
+ return true;
+ }
+ return sock_connected;
+ }
+ virtual operator bool()
+ {
+ return connected();
+ }
+
+ /*
+ * Extended API
+ */
+
+ String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ private:
+ TinyGsm* at;
+ uint8_t mux;
+ bool sock_connected;
+ RxFifo rx;
+ };
+
+public:
+
+ explicit TinyGsm(Stream& stream)
+ : stream(stream)
+ {
+ memset(sockets, 0, sizeof(sockets));
+ }
+
+ /*
+ * Basic functions
+ */
+ bool begin()
+ {
+ return init();
+ }
+
+ bool init()
+ {
+ if (!testAT()) {
+ return false;
+ }
+ sendAT(GF("&FZE0")); // Factory + Reset + Echo Off
+ if (waitResponse() != 1) {
+ return false;
+ }
+#ifdef TINY_GSM_DEBUG
+ sendAT(GF("+CMEE=2"));
+ waitResponse();
+#endif
+
+ getSimStatus();
+ return true;
+ }
+
+ void setBaud(unsigned long baud)
+ {
+ sendAT(GF("+IPR="), baud);
+ }
+
+ bool testAT(unsigned long timeout = 10000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ sendAT(GF(""));
+ if (waitResponse(200) == 1) {
+ delay(100);
+ return true;
+ }
+ delay(100);
+ }
+ return false;
+ }
+
+ void maintain()
+ {
+ //while (stream.available()) {
+ waitResponse(10, NULL, NULL);
+ //}
+ }
+
+ bool factoryDefault()
+ {
+ sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write
+ waitResponse();
+ sendAT(GF("+ICF=3,1")); // 8 data 0 parity 1 stop
+ waitResponse();
+ sendAT(GF("+ENPWRSAVE=0")); // Disable PWR save
+ waitResponse();
+ sendAT(GF("+XISP=0")); // Use internal stack
+ waitResponse();
+ sendAT(GF("&W")); // Write configuration
+ return waitResponse() == 1;
+ }
+
+ String getModemInfo()
+ {
+ sendAT(GF("I"));
+ String res;
+ if (waitResponse(1000L, res) != 1) {
+ return "";
+ }
+ res.replace(GSM_NL "OK" GSM_NL, "");
+ res.replace(GSM_NL, " ");
+ res.trim();
+ return res;
+ }
+
+ bool hasSSL()
+ {
+ return false;
+ }
+
+ /*
+ * Power functions
+ */
+
+ bool restart()
+ {
+ if (!testAT()) {
+ return false;
+ }
+ sendAT(GF("+CFUN=15"));
+ if (waitResponse(10000L) != 1) {
+ return false;
+ }
+ //MODEM:STARTUP
+ waitResponse(60000L, GF(GSM_NL "+PBREADY" GSM_NL));
+ return init();
+ }
+
+ bool poweroff()
+ {
+ sendAT(GF("+CPWROFF"));
+ return waitResponse(3000L) == 1;
+ }
+
+ bool radioOff() TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ bool sleepEnable(bool enable = true)
+ {
+ sendAT(GF("+ENPWRSAVE="), enable);
+ return waitResponse() == 1;
+ }
+
+ /*
+ * SIM card functions
+ */
+
+ bool simUnlock(const char *pin)
+ {
+ sendAT(GF("+CPIN=\""), pin, GF("\""));
+ return waitResponse() == 1;
+ }
+
+ String getSimCCID()
+ {
+ sendAT(GF("+CCID"));
+ if (waitResponse(GF(GSM_NL "+CCID:")) != 1) {
+ return "";
+ }
+ String res = stream.readStringUntil('\n');
+ waitResponse();
+ res.trim();
+ return res;
+ }
+
+ String getIMEI()
+ {
+ sendAT(GF("+GSN"));
+ if (waitResponse(GF(GSM_NL)) != 1) {
+ return "";
+ }
+ String res = stream.readStringUntil('\n');
+ waitResponse();
+ res.trim();
+ return res;
+ }
+
+ SimStatus getSimStatus(unsigned long timeout = 10000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ sendAT(GF("+CPIN?"));
+ if (waitResponse(GF(GSM_NL "+CPIN:")) != 1) {
+ delay(1000);
+ continue;
+ }
+ int status = waitResponse(GF("READY"), GF("SIM PIN"), GF("SIM PUK"));
+ waitResponse();
+ switch (status) {
+ case 2:
+ case 3:
+ return SIM_LOCKED;
+ case 1:
+ return SIM_READY;
+ default:
+ return SIM_ERROR;
+ }
+ }
+ return SIM_ERROR;
+ }
+
+ RegStatus getRegistrationStatus()
+ {
+ sendAT(GF("+CREG?"));
+ if (waitResponse(GF(GSM_NL "+CREG:")) != 1) {
+ return REG_UNKNOWN;
+ }
+ streamSkipUntil(','); // Skip format (0)
+ int status = stream.readStringUntil('\n').toInt();
+ waitResponse();
+ return (RegStatus)status;
+ }
+
+ String getOperator()
+ {
+ sendAT(GF("+COPS?"));
+ if (waitResponse(GF(GSM_NL "+COPS:")) != 1) {
+ return "";
+ }
+ streamSkipUntil('"'); // Skip mode and format
+ String res = stream.readStringUntil('"');
+ waitResponse();
+ return res;
+ }
+
+ /*
+ * Generic network functions
+ */
+
+ int getSignalQuality()
+ {
+ sendAT(GF("+CSQ"));
+ if (waitResponse(GF(GSM_NL "+CSQ:")) != 1) {
+ return 99;
+ }
+ int res = stream.readStringUntil(',').toInt();
+ waitResponse();
+ return res;
+ }
+
+ bool isNetworkConnected()
+ {
+ RegStatus s = getRegistrationStatus();
+ return (s == REG_OK_HOME || s == REG_OK_ROAMING);
+ }
+
+ bool waitForNetwork(unsigned long timeout = 60000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ if (isNetworkConnected()) {
+ return true;
+ }
+ delay(250);
+ }
+ return false;
+ }
+
+ /*
+ * GPRS functions
+ */
+ bool gprsConnect(const char* apn, const char* user = NULL, const char* pwd = NULL)
+ {
+ gprsDisconnect();
+
+ sendAT(GF("+XISP=0"));
+ waitResponse();
+
+ sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"');
+ waitResponse();
+
+ if (!user) {
+ user = "";
+ }
+ if (!pwd) {
+ pwd = "";
+ }
+ sendAT(GF("+XGAUTH=1,1,\""), user, GF("\",\""), pwd, GF("\""));
+ waitResponse();
+
+ sendAT(GF("+XIIC=1"));
+ waitResponse();
+
+ const unsigned long timeout = 60000L;
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ if (isGprsConnected()) {
+ //goto set_dns; // TODO
+ return true;
+ }
+ delay(500);
+ }
+ return false;
+
+set_dns:
+ sendAT(GF("+DNSSERVER=1,8.8.8.8"));
+ waitResponse();
+
+ sendAT(GF("+DNSSERVER=2,8.8.4.4"));
+ waitResponse();
+
+ return true;
+ }
+
+ bool gprsDisconnect()
+ {
+ // TODO: There is no command in AT command set
+ // XIIC=0 does not work
+ return true;
+ }
+
+ bool isGprsConnected()
+ {
+ sendAT(GF("+XIIC?"));
+ if (waitResponse(GF(GSM_NL "+XIIC:")) != 1) {
+ return false;
+ }
+ int res = stream.readStringUntil(',').toInt();
+ waitResponse();
+ return res == 1;
+ }
+
+ String getLocalIP()
+ {
+ sendAT(GF("+XIIC?"));
+ if (waitResponse(GF(GSM_NL "+XIIC:")) != 1) {
+ return "";
+ }
+ stream.readStringUntil(',');
+ String res = stream.readStringUntil('\n');
+ waitResponse();
+ res.trim();
+ return res;
+ }
+
+ IPAddress localIP()
+ {
+ return TinyGsmIpFromString(getLocalIP());
+ }
+
+ /*
+ * Phone Call functions
+ */
+
+ bool setGsmBusy(bool busy = true) TINY_GSM_ATTR_NOT_AVAILABLE;
+
+ bool callAnswer() TINY_GSM_ATTR_NOT_AVAILABLE;
+
+ bool callNumber(const String& number) TINY_GSM_ATTR_NOT_AVAILABLE;
+
+ bool callHangup() TINY_GSM_ATTR_NOT_AVAILABLE;
+
+ /*
+ * Messaging functions
+ */
+
+ String sendUSSD(const String& code)
+ {
+ sendAT(GF("+CMGF=1"));
+ waitResponse();
+ sendAT(GF("+CSCS=\"HEX\""));
+ waitResponse();
+ sendAT(GF("D"), code);
+ if (waitResponse(10000L, GF(GSM_NL "+CUSD:")) != 1) {
+ return "";
+ }
+ stream.readStringUntil('"');
+ String hex = stream.readStringUntil('"');
+ stream.readStringUntil(',');
+ int dcs = stream.readStringUntil('\n').toInt();
+
+ if (waitResponse() != 1) {
+ return "";
+ }
+
+ if (dcs == 15) {
+ return TinyGsmDecodeHex8bit(hex);
+ } else if (dcs == 72) {
+ return TinyGsmDecodeHex16bit(hex);
+ } else {
+ return hex;
+ }
+ }
+
+ bool sendSMS(const String& number, const String& text)
+ {
+ sendAT(GF("+CSCS=\"GSM\""));
+ waitResponse();
+ sendAT(GF("+CMGF=1"));
+ waitResponse();
+ sendAT(GF("+CMGS=\""), number, GF("\""));
+ if (waitResponse(GF(">")) != 1) {
+ return false;
+ }
+ stream.print(text);
+ stream.write((char)0x1A);
+ stream.flush();
+ return waitResponse(60000L) == 1;
+ }
+
+ bool sendSMS_UTF16(const String& number, const void* text, size_t len)
+ TINY_GSM_ATTR_NOT_AVAILABLE;
+
+ /*
+ * Location functions
+ */
+
+ String getGsmLocation() TINY_GSM_ATTR_NOT_AVAILABLE;
+
+ /*
+ * Battery functions
+ */
+
+ uint16_t getBattVoltage() TINY_GSM_ATTR_NOT_AVAILABLE;
+
+ int getBattPercent() TINY_GSM_ATTR_NOT_AVAILABLE;
+
+protected:
+
+ bool modemConnect(const char* host, uint16_t port, uint8_t mux)
+ {
+ for (int i=0; i<3; i++) { // TODO: no need for loop?
+ String ip = dnsIpQuery(host);
+
+ sendAT(GF("+TCPSETUP="), mux, GF(","), ip, GF(","), port);
+ int rsp = waitResponse(75000L,
+ GF(",OK" GSM_NL),
+ GF(",FAIL" GSM_NL),
+ GF("+TCPSETUP:Error" GSM_NL));
+ if (1 == rsp) {
+ return true;
+ } else if (3 == rsp) {
+ sendAT(GF("+TCPCLOSE="), mux);
+ waitResponse();
+ }
+ delay(1000);
+ }
+ return false;
+ }
+
+ int modemSend(const void* buff, size_t len, uint8_t mux)
+ {
+ sendAT(GF("+TCPSEND="), mux, ',', len);
+ if (waitResponse(GF(">")) != 1) {
+ return 0;
+ }
+ stream.write((uint8_t*)buff, len);
+ stream.write((char)0x0D);
+ stream.flush();
+ if (waitResponse(30000L, GF(GSM_NL "+TCPSEND:")) != 1) {
+ return 0;
+ }
+ stream.readStringUntil('\n');
+ return len;
+ }
+
+ bool modemGetConnected(uint8_t mux)
+ {
+ sendAT(GF("+CIPSTATUS="), mux);
+ int res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""), GF(",\"CLOSING\""),
+ GF(",\"INITIAL\""));
+ waitResponse();
+ return 1 == res;
+ }
+
+ String dnsIpQuery(const char* host)
+ {
+ sendAT(GF("+DNS=\""), host, GF("\""));
+ if (waitResponse(10000L, GF(GSM_NL "+DNS:")) != 1) {
+ return "";
+ }
+ String res = stream.readStringUntil('\n');
+ waitResponse(GF("+DNS:OK" GSM_NL));
+ res.trim();
+ return res;
+ }
+
+public:
+
+ /* Utilities */
+
+ template
+ void streamWrite(T last)
+ {
+ stream.print(last);
+ }
+
+ template
+ void streamWrite(T head, Args... tail)
+ {
+ stream.print(head);
+ streamWrite(tail...);
+ }
+
+ bool streamSkipUntil(char c) //TODO: timeout
+ {
+ while (true) {
+ while (!stream.available()) {
+ TINY_GSM_YIELD();
+ }
+ if (stream.read() == c) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template
+ void sendAT(Args... cmd)
+ {
+ streamWrite("AT", cmd..., GSM_NL);
+ stream.flush();
+ TINY_GSM_YIELD();
+ //DBG("### AT:", cmd...);
+ }
+
+ // TODO: Optimize this!
+ uint8_t waitResponse(uint32_t timeout, String& data,
+ GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ /*String r1s(r1); r1s.trim();
+ String r2s(r2); r2s.trim();
+ String r3s(r3); r3s.trim();
+ String r4s(r4); r4s.trim();
+ String r5s(r5); r5s.trim();
+ DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/
+ data.reserve(64);
+ int index = 0;
+ unsigned long startMillis = millis();
+ do {
+ TINY_GSM_YIELD();
+ while (stream.available() > 0) {
+ int a = stream.read();
+ if (a <= 0) {
+ continue; // Skip 0x00 bytes, just in case
+ }
+ data += (char)a;
+ if (r1 && data.endsWith(r1)) {
+ index = 1;
+ goto finish;
+ } else if (r2 && data.endsWith(r2)) {
+ index = 2;
+ goto finish;
+ } else if (r3 && data.endsWith(r3)) {
+ index = 3;
+ goto finish;
+ } else if (r4 && data.endsWith(r4)) {
+ index = 4;
+ goto finish;
+ } else if (r5 && data.endsWith(r5)) {
+ index = 5;
+ goto finish;
+ } else if (data.endsWith(GF("+TCPRECV:"))) {
+ int mux = stream.readStringUntil(',').toInt();
+ int len = stream.readStringUntil(',').toInt();
+ int len_orig = len;
+ if (len > sockets[mux]->rx.free()) {
+ DBG("### Buffer overflow: ", len, "->", sockets[mux]->rx.free());
+ } else {
+ DBG("### Got: ", len, "->", sockets[mux]->rx.free());
+ }
+ while (len--) {
+ while (!stream.available()) {
+ TINY_GSM_YIELD();
+ }
+ sockets[mux]->rx.put(stream.read());
+ }
+ if (len_orig > sockets[mux]->available()) { // TODO
+ DBG("### Fewer characters received than expected: ", sockets[mux]->available(), " vs ", len_orig);
+ }
+ data = "";
+ } else if (data.endsWith(GF("+TCPCLOSE:"))) {
+ int mux = stream.readStringUntil(',').toInt();
+ stream.readStringUntil('\n');
+ if (mux >= 0 && mux < TINY_GSM_MUX_COUNT) {
+ sockets[mux]->sock_connected = false;
+ }
+ data = "";
+ DBG("### Closed: ", mux);
+ }
+ }
+ } while (millis() - startMillis < timeout);
+finish:
+ if (!index) {
+ data.trim();
+ if (data.length()) {
+ DBG("### Unhandled:", data);
+ }
+ data = "";
+ }
+ return index;
+ }
+
+ uint8_t waitResponse(uint32_t timeout,
+ GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ String data;
+ return waitResponse(timeout, data, r1, r2, r3, r4, r5);
+ }
+
+ uint8_t waitResponse(GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ return waitResponse(1000, r1, r2, r3, r4, r5);
+ }
+
+public:
+ Stream& stream;
+
+protected:
+ GsmClient* sockets[TINY_GSM_MUX_COUNT];
+};
+
+#endif
diff --git a/drivers/TinyGSM/TinyGsmClientSIM800.h b/drivers/TinyGSM/TinyGsmClientSIM800.h
new file mode 100644
index 0000000..265bdc0
--- /dev/null
+++ b/drivers/TinyGSM/TinyGsmClientSIM800.h
@@ -0,0 +1,1057 @@
+/**
+ * file TinyGsmClientSIM800.h
+ * author Volodymyr Shymanskyy
+ * license LGPL-3.0
+ * copyright Copyright (c) 2016 Volodymyr Shymanskyy
+ * date Nov 2016
+ */
+
+#ifndef TinyGsmClientSIM800_h
+#define TinyGsmClientSIM800_h
+
+//#define TINY_GSM_DEBUG Serial
+//#define TINY_GSM_USE_HEX
+
+#if !defined(TINY_GSM_RX_BUFFER)
+#define TINY_GSM_RX_BUFFER 64
+#endif
+
+#define TINY_GSM_MUX_COUNT 5
+
+#include "TinyGsmCommon.h"
+
+#define GSM_NL "\r\n"
+static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
+static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
+
+enum SimStatus {
+ SIM_ERROR = 0,
+ SIM_READY = 1,
+ SIM_LOCKED = 2,
+};
+
+enum RegStatus {
+ REG_UNREGISTERED = 0,
+ REG_SEARCHING = 2,
+ REG_DENIED = 3,
+ REG_OK_HOME = 1,
+ REG_OK_ROAMING = 5,
+ REG_UNKNOWN = 4,
+};
+
+
+class TinyGsmSim800
+{
+
+public:
+
+ class GsmClient : public Client
+ {
+ friend class TinyGsmSim800;
+ typedef TinyGsmFifo RxFifo;
+
+ public:
+ GsmClient() {}
+
+ GsmClient(TinyGsmSim800& modem, uint8_t mux = 1)
+ {
+ init(&modem, mux);
+ }
+
+ bool init(TinyGsmSim800* modem, uint8_t mux = 1)
+ {
+ this->at = modem;
+ this->mux = mux;
+ sock_available = 0;
+ prev_check = 0;
+ sock_connected = false;
+ got_data = false;
+
+ at->sockets[mux] = this;
+
+ return true;
+ }
+
+ public:
+ virtual int connect(const char *host, uint16_t port)
+ {
+ stop();
+ TINY_GSM_YIELD();
+ rx.clear();
+ sock_connected = at->modemConnect(host, port, mux);
+ return sock_connected;
+ }
+
+ virtual int connect(IPAddress ip, uint16_t port)
+ {
+ String host;
+ host.reserve(16);
+ host += ip[0];
+ host += ".";
+ host += ip[1];
+ host += ".";
+ host += ip[2];
+ host += ".";
+ host += ip[3];
+ return connect(host.c_str(), port);
+ }
+
+ virtual void stop()
+ {
+ TINY_GSM_YIELD();
+ at->sendAT(GF("+CIPCLOSE="), mux);
+ sock_connected = false;
+ at->waitResponse();
+ rx.clear();
+ }
+
+ virtual size_t write(const uint8_t *buf, size_t size)
+ {
+ TINY_GSM_YIELD();
+ at->maintain();
+ return at->modemSend(buf, size, mux);
+ }
+
+ virtual size_t write(uint8_t c)
+ {
+ return write(&c, 1);
+ }
+
+ virtual int available()
+ {
+ TINY_GSM_YIELD();
+ if (!rx.size() && sock_connected) {
+ // Workaround: sometimes SIM800 forgets to notify about data arrival.
+ // TODO: Currently we ping the module periodically,
+ // but maybe there's a better indicator that we need to poll
+ if (millis() - prev_check > 500) {
+ got_data = true;
+ prev_check = millis();
+ }
+ at->maintain();
+ }
+ return rx.size() + sock_available;
+ }
+
+ virtual int read(uint8_t *buf, size_t size)
+ {
+ TINY_GSM_YIELD();
+ at->maintain();
+ size_t cnt = 0;
+ while (cnt < size && sock_connected) {
+ size_t chunk = TinyGsmMin(size-cnt, rx.size());
+ if (chunk > 0) {
+ rx.get(buf, chunk);
+ buf += chunk;
+ cnt += chunk;
+ continue;
+ }
+ // TODO: Read directly into user buffer?
+ at->maintain();
+ if (sock_available > 0) {
+ at->modemRead(rx.free(), mux);
+ } else {
+ break;
+ }
+ }
+ return cnt;
+ }
+
+ virtual int read()
+ {
+ uint8_t c;
+ if (read(&c, 1) == 1) {
+ return c;
+ }
+ return -1;
+ }
+
+ virtual int peek()
+ {
+ return -1; //TODO
+ }
+ virtual void flush()
+ {
+ at->stream.flush();
+ }
+
+ virtual uint8_t connected()
+ {
+ if (available()) {
+ return true;
+ }
+ return sock_connected;
+ }
+ virtual operator bool()
+ {
+ return connected();
+ }
+
+ /*
+ * Extended API
+ */
+
+ String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ private:
+ TinyGsmSim800* at;
+ uint8_t mux;
+ uint16_t sock_available;
+ uint32_t prev_check;
+ bool sock_connected;
+ bool got_data;
+ RxFifo rx;
+ };
+
+ class GsmClientSecure : public GsmClient
+ {
+ public:
+ GsmClientSecure() {}
+
+ GsmClientSecure(TinyGsmSim800& modem, uint8_t mux = 1)
+ : GsmClient(modem, mux)
+ {}
+
+ public:
+ virtual int connect(const char *host, uint16_t port)
+ {
+ stop();
+ TINY_GSM_YIELD();
+ rx.clear();
+ sock_connected = at->modemConnect(host, port, mux, true);
+ return sock_connected;
+ }
+ };
+
+public:
+
+ explicit TinyGsmSim800(Stream& stream)
+ : stream(stream)
+ {
+ memset(sockets, 0, sizeof(sockets));
+ }
+
+ /*
+ * Basic functions
+ */
+ bool begin()
+ {
+ return init();
+ }
+
+ bool init()
+ {
+ if (!testAT()) {
+ return false;
+ }
+ sendAT(GF("&FZ")); // Factory + Reset
+ waitResponse();
+ sendAT(GF("E0")); // Echo Off
+ if (waitResponse() != 1) {
+ return false;
+ }
+ getSimStatus();
+ return true;
+ }
+
+ void setBaud(unsigned long baud)
+ {
+ sendAT(GF("+IPR="), baud);
+ }
+
+ bool testAT(unsigned long timeout = 10000L)
+ {
+ //streamWrite(GF("AAAAA" GSM_NL)); // TODO: extra A's to help detect the baud rate
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ sendAT(GF(""));
+ if (waitResponse(200) == 1) {
+ delay(100);
+ return true;
+ }
+ delay(100);
+ }
+ return false;
+ }
+
+ void maintain()
+ {
+ for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) {
+ GsmClient* sock = sockets[mux];
+ if (sock && sock->got_data) {
+ sock->got_data = false;
+ sock->sock_available = modemGetAvailable(mux);
+ }
+ }
+ while (stream.available()) {
+ waitResponse(10, NULL, NULL);
+ }
+ }
+
+ bool factoryDefault()
+ {
+ sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write
+ waitResponse();
+ sendAT(GF("+IPR=0")); // Auto-baud
+ waitResponse();
+ sendAT(GF("+IFC=0,0")); // No Flow Control
+ waitResponse();
+ sendAT(GF("+ICF=3,3")); // 8 data 0 parity 1 stop
+ waitResponse();
+ sendAT(GF("+CSCLK=0")); // Disable Slow Clock
+ waitResponse();
+ sendAT(GF("&W")); // Write configuration
+ return waitResponse() == 1;
+ }
+
+ String getModemInfo()
+ {
+ sendAT(GF("I"));
+ String res;
+ if (waitResponse(1000L, res) != 1) {
+ return "";
+ }
+ res.replace(GSM_NL "OK" GSM_NL, "");
+ res.replace(GSM_NL, " ");
+ res.trim();
+ return res;
+ }
+
+ bool hasSSL()
+ {
+#if defined(TINY_GSM_MODEM_SIM900)
+ return false;
+#else
+ sendAT(GF("+CIPSSL=?"));
+ if (waitResponse(GF(GSM_NL "+CIPSSL:")) != 1) {
+ return false;
+ }
+ return waitResponse() == 1;
+#endif
+ }
+
+ /*
+ * Power functions
+ */
+
+ bool restart()
+ {
+ if (!testAT()) {
+ return false;
+ }
+ sendAT(GF("+CFUN=0"));
+ if (waitResponse(10000L) != 1) {
+ return false;
+ }
+ sendAT(GF("+CFUN=1,1"));
+ if (waitResponse(10000L) != 1) {
+ return false;
+ }
+ delay(3000);
+ return init();
+ }
+
+ bool poweroff()
+ {
+ sendAT(GF("+CPOWD=1"));
+ return waitResponse(GF("NORMAL POWER DOWN")) == 1;
+ }
+
+ bool radioOff()
+ {
+ sendAT(GF("+CFUN=0"));
+ if (waitResponse(10000L) != 1) {
+ return false;
+ }
+ delay(3000);
+ return true;
+ }
+
+ /*
+ During sleep, the SIM800 module has its serial communication disabled. In order to reestablish communication
+ pull the DRT-pin of the SIM800 module LOW for at least 50ms. Then use this function to disable sleep mode.
+ The DTR-pin can then be released again.
+ */
+ bool sleepEnable(bool enable = true)
+ {
+ sendAT(GF("+CSCLK="), enable);
+ return waitResponse() == 1;
+ }
+
+ /*
+ * SIM card functions
+ */
+
+ bool simUnlock(const char *pin)
+ {
+ sendAT(GF("+CPIN=\""), pin, GF("\""));
+ return waitResponse() == 1;
+ }
+
+ String getSimCCID()
+ {
+ sendAT(GF("+ICCID"));
+ if (waitResponse(GF(GSM_NL "+ICCID:")) != 1) {
+ return "";
+ }
+ String res = stream.readStringUntil('\n');
+ waitResponse();
+ res.trim();
+ return res;
+ }
+
+ String getIMEI()
+ {
+ sendAT(GF("+GSN"));
+ if (waitResponse(GF(GSM_NL)) != 1) {
+ return "";
+ }
+ String res = stream.readStringUntil('\n');
+ waitResponse();
+ res.trim();
+ return res;
+ }
+
+ SimStatus getSimStatus(unsigned long timeout = 10000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ sendAT(GF("+CPIN?"));
+ if (waitResponse(GF(GSM_NL "+CPIN:")) != 1) {
+ delay(1000);
+ continue;
+ }
+ int status = waitResponse(GF("READY"), GF("SIM PIN"), GF("SIM PUK"), GF("NOT INSERTED"));
+ waitResponse();
+ switch (status) {
+ case 2:
+ case 3:
+ return SIM_LOCKED;
+ case 1:
+ return SIM_READY;
+ default:
+ return SIM_ERROR;
+ }
+ }
+ return SIM_ERROR;
+ }
+
+ RegStatus getRegistrationStatus()
+ {
+ sendAT(GF("+CREG?"));
+ if (waitResponse(GF(GSM_NL "+CREG:")) != 1) {
+ return REG_UNKNOWN;
+ }
+ streamSkipUntil(','); // Skip format (0)
+ int status = stream.readStringUntil('\n').toInt();
+ waitResponse();
+ return (RegStatus)status;
+ }
+
+ String getOperator()
+ {
+ sendAT(GF("+COPS?"));
+ if (waitResponse(GF(GSM_NL "+COPS:")) != 1) {
+ return "";
+ }
+ streamSkipUntil('"'); // Skip mode and format
+ String res = stream.readStringUntil('"');
+ waitResponse();
+ return res;
+ }
+
+ /*
+ * Generic network functions
+ */
+
+ int getSignalQuality()
+ {
+ sendAT(GF("+CSQ"));
+ if (waitResponse(GF(GSM_NL "+CSQ:")) != 1) {
+ return 99;
+ }
+ int res = stream.readStringUntil(',').toInt();
+ waitResponse();
+ return res;
+ }
+
+ bool isNetworkConnected()
+ {
+ RegStatus s = getRegistrationStatus();
+ return (s == REG_OK_HOME || s == REG_OK_ROAMING);
+ }
+
+ bool waitForNetwork(unsigned long timeout = 60000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ if (isNetworkConnected()) {
+ return true;
+ }
+ delay(250);
+ }
+ return false;
+ }
+
+ /*
+ * GPRS functions
+ */
+ bool gprsConnect(const char* apn, const char* user = NULL, const char* pwd = NULL)
+ {
+ gprsDisconnect();
+
+ // Set the Bearer for the IP
+ sendAT(GF("+SAPBR=3,1,\"Contype\",\"GPRS\"")); // Set the connection type to GPRS
+ waitResponse();
+
+ sendAT(GF("+SAPBR=3,1,\"APN\",\""), apn, '"'); // Set the APN
+ waitResponse();
+
+ if (user && strlen(user) > 0) {
+ sendAT(GF("+SAPBR=3,1,\"USER\",\""), user, '"'); // Set the user name
+ waitResponse();
+ }
+ if (pwd && strlen(pwd) > 0) {
+ sendAT(GF("+SAPBR=3,1,\"PWD\",\""), pwd, '"'); // Set the password
+ waitResponse();
+ }
+
+ // Define the PDP context
+ sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"');
+ waitResponse();
+
+ // Activate the PDP context
+ sendAT(GF("+CGACT=1,1"));
+ waitResponse(60000L);
+
+ // Open the definied GPRS bearer context
+ sendAT(GF("+SAPBR=1,1"));
+ waitResponse(85000L);
+ // Query the GPRS bearer context status
+ sendAT(GF("+SAPBR=2,1"));
+ if (waitResponse(30000L) != 1) {
+ return false;
+ }
+
+ // Attach to GPRS
+ sendAT(GF("+CGATT=1"));
+ if (waitResponse(60000L) != 1) {
+ return false;
+ }
+
+ // TODO: wait AT+CGATT?
+
+ // Set to multi-IP
+ sendAT(GF("+CIPMUX=1"));
+ if (waitResponse() != 1) {
+ return false;
+ }
+
+ // Put in "quick send" mode (thus no extra "Send OK")
+ sendAT(GF("+CIPQSEND=1"));
+ if (waitResponse() != 1) {
+ return false;
+ }
+
+ // Set to get data manually
+ sendAT(GF("+CIPRXGET=1"));
+ if (waitResponse() != 1) {
+ return false;
+ }
+
+ // Start Task and Set APN, USER NAME, PASSWORD
+ sendAT(GF("+CSTT=\""), apn, GF("\",\""), user, GF("\",\""), pwd, GF("\""));
+ if (waitResponse(60000L) != 1) {
+ return false;
+ }
+
+ // Bring Up Wireless Connection with GPRS or CSD
+ sendAT(GF("+CIICR"));
+ if (waitResponse(60000L) != 1) {
+ return false;
+ }
+
+ // Get Local IP Address, only assigned after connection
+ sendAT(GF("+CIFSR;E0"));
+ if (waitResponse(10000L) != 1) {
+ return false;
+ }
+
+ // Configure Domain Name Server (DNS)
+ sendAT(GF("+CDNSCFG=\"8.8.8.8\",\"8.8.4.4\""));
+ if (waitResponse() != 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool gprsDisconnect()
+ {
+ // Shut the TCP/IP connection
+ sendAT(GF("+CIPSHUT"));
+ if (waitResponse(60000L) != 1) {
+ return false;
+ }
+
+ sendAT(GF("+CGATT=0")); // Deactivate the bearer context
+ if (waitResponse(60000L) != 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool isGprsConnected()
+ {
+ sendAT(GF("+CGATT?"));
+ if (waitResponse(GF(GSM_NL "+CGATT:")) != 1) {
+ return false;
+ }
+ int res = stream.readStringUntil('\n').toInt();
+ waitResponse();
+ if (res != 1) {
+ return false;
+ }
+
+ sendAT(GF("+CIFSR;E0")); // Another option is to use AT+CGPADDR=1
+ if (waitResponse() != 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ String getLocalIP()
+ {
+ sendAT(GF("+CIFSR;E0"));
+ String res;
+ if (waitResponse(10000L, res) != 1) {
+ return "";
+ }
+ res.replace(GSM_NL "OK" GSM_NL, "");
+ res.replace(GSM_NL, "");
+ res.trim();
+ return res;
+ }
+
+ IPAddress localIP()
+ {
+ return TinyGsmIpFromString(getLocalIP());
+ }
+
+ /*
+ * Phone Call functions
+ */
+
+ bool setGsmBusy(bool busy = true)
+ {
+ sendAT(GF("+GSMBUSY="), busy ? 1 : 0);
+ return waitResponse() == 1;
+ }
+
+ bool callAnswer()
+ {
+ sendAT(GF("A"));
+ return waitResponse() == 1;
+ }
+
+ // Returns true on pick-up, false on error/busy
+ bool callNumber(const String& number)
+ {
+ if (number == GF("last")) {
+ sendAT(GF("DL"));
+ } else {
+ sendAT(GF("D"), number, ";");
+ }
+ int status = waitResponse(60000L,
+ GFP(GSM_OK),
+ GF("BUSY" GSM_NL),
+ GF("NO ANSWER" GSM_NL),
+ GF("NO CARRIER" GSM_NL));
+ switch (status) {
+ case 1:
+ return true;
+ case 2:
+ case 3:
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ bool callHangup()
+ {
+ sendAT(GF("H"));
+ return waitResponse() == 1;
+ }
+
+ // 0-9,*,#,A,B,C,D
+ bool dtmfSend(char cmd, int duration_ms = 100)
+ {
+ duration_ms = constrain(duration_ms, 100, 1000);
+
+ sendAT(GF("+VTD="), duration_ms / 100); // VTD accepts in 1/10 of a second
+ waitResponse();
+
+ sendAT(GF("+VTS="), cmd);
+ return waitResponse(10000L) == 1;
+ }
+
+ /*
+ * Messaging functions
+ */
+
+ String sendUSSD(const String& code)
+ {
+ sendAT(GF("+CMGF=1"));
+ waitResponse();
+ sendAT(GF("+CSCS=\"HEX\""));
+ waitResponse();
+ sendAT(GF("+CUSD=1,\""), code, GF("\""));
+ if (waitResponse() != 1) {
+ return "";
+ }
+ if (waitResponse(10000L, GF(GSM_NL "+CUSD:")) != 1) {
+ return "";
+ }
+ stream.readStringUntil('"');
+ String hex = stream.readStringUntil('"');
+ stream.readStringUntil(',');
+ int dcs = stream.readStringUntil('\n').toInt();
+
+ if (dcs == 15) {
+ return TinyGsmDecodeHex8bit(hex);
+ } else if (dcs == 72) {
+ return TinyGsmDecodeHex16bit(hex);
+ } else {
+ return hex;
+ }
+ }
+
+ bool sendSMS(const String& number, const String& text)
+ {
+ sendAT(GF("+CMGF=1"));
+ waitResponse();
+ //Set GSM 7 bit default alphabet (3GPP TS 23.038)
+ sendAT(GF("+CSCS=\"GSM\""));
+ waitResponse();
+ sendAT(GF("+CMGS=\""), number, GF("\""));
+ if (waitResponse(GF(">")) != 1) {
+ return false;
+ }
+ stream.print(text);
+ stream.write((char)0x1A);
+ stream.flush();
+ return waitResponse(60000L) == 1;
+ }
+
+ bool sendSMS_UTF16(const String& number, const void* text, size_t len)
+ {
+ sendAT(GF("+CMGF=1"));
+ waitResponse();
+ sendAT(GF("+CSCS=\"HEX\""));
+ waitResponse();
+ sendAT(GF("+CSMP=17,167,0,8"));
+ waitResponse();
+
+ sendAT(GF("+CMGS=\""), number, GF("\""));
+ if (waitResponse(GF(">")) != 1) {
+ return false;
+ }
+
+ uint16_t* t = (uint16_t*)text;
+ for (size_t i=0; i> 8;
+ if (c < 0x10) {
+ stream.print('0');
+ }
+ stream.print(c, HEX);
+ c = t[i] & 0xFF;
+ if (c < 0x10) {
+ stream.print('0');
+ }
+ stream.print(c, HEX);
+ }
+ stream.write((char)0x1A);
+ stream.flush();
+ return waitResponse(60000L) == 1;
+ }
+
+
+ /*
+ * Location functions
+ */
+
+ String getGsmLocation()
+ {
+ sendAT(GF("+CIPGSMLOC=1,1"));
+ if (waitResponse(10000L, GF(GSM_NL "+CIPGSMLOC:")) != 1) {
+ return "";
+ }
+ String res = stream.readStringUntil('\n');
+ waitResponse();
+ res.trim();
+ return res;
+ }
+
+ /*
+ * Battery functions
+ */
+ // Use: float vBatt = modem.getBattVoltage() / 1000.0;
+ uint16_t getBattVoltage()
+ {
+ sendAT(GF("+CBC"));
+ if (waitResponse(GF(GSM_NL "+CBC:")) != 1) {
+ return 0;
+ }
+ streamSkipUntil(','); // Skip
+ streamSkipUntil(','); // Skip
+
+ uint16_t res = stream.readStringUntil(',').toInt();
+ waitResponse();
+ return res;
+ }
+
+ int getBattPercent()
+ {
+ sendAT(GF("+CBC"));
+ if (waitResponse(GF(GSM_NL "+CBC:")) != 1) {
+ return false;
+ }
+ stream.readStringUntil(',');
+ int res = stream.readStringUntil(',').toInt();
+ waitResponse();
+ return res;
+ }
+
+protected:
+
+ bool modemConnect(const char* host, uint16_t port, uint8_t mux, bool ssl = false)
+ {
+#if !defined(TINY_GSM_MODEM_SIM900)
+ sendAT(GF("+CIPSSL="), ssl);
+ int rsp = waitResponse();
+ if (ssl && rsp != 1) {
+ return false;
+ }
+#endif
+ sendAT(GF("+CIPSTART="), mux, ',', GF("\"TCP"), GF("\",\""), host, GF("\","), port);
+ rsp = waitResponse(75000L,
+ GF("CONNECT OK" GSM_NL),
+ GF("CONNECT FAIL" GSM_NL),
+ GF("ALREADY CONNECT" GSM_NL),
+ GF("ERROR" GSM_NL),
+ GF("CLOSE OK" GSM_NL) // Happens when HTTPS handshake fails
+ );
+ return (1 == rsp);
+ }
+
+ int modemSend(const void* buff, size_t len, uint8_t mux)
+ {
+ sendAT(GF("+CIPSEND="), mux, ',', len);
+ if (waitResponse(GF(">")) != 1) {
+ return 0;
+ }
+ stream.write((uint8_t*)buff, len);
+ stream.flush();
+ if (waitResponse(GF(GSM_NL "DATA ACCEPT:")) != 1) {
+ return 0;
+ }
+ streamSkipUntil(','); // Skip mux
+ return stream.readStringUntil('\n').toInt();
+ }
+
+ size_t modemRead(size_t size, uint8_t mux)
+ {
+#ifdef TINY_GSM_USE_HEX
+ sendAT(GF("+CIPRXGET=3,"), mux, ',', size);
+ if (waitResponse(GF("+CIPRXGET:")) != 1) {
+ return 0;
+ }
+#else
+ sendAT(GF("+CIPRXGET=2,"), mux, ',', size);
+ if (waitResponse(GF("+CIPRXGET:")) != 1) {
+ return 0;
+ }
+#endif
+ streamSkipUntil(','); // Skip mode 2/3
+ streamSkipUntil(','); // Skip mux
+ size_t len = stream.readStringUntil(',').toInt();
+ sockets[mux]->sock_available = stream.readStringUntil('\n').toInt();
+
+ for (size_t i=0; irx.put(c);
+ }
+ waitResponse();
+ return len;
+ }
+
+ size_t modemGetAvailable(uint8_t mux)
+ {
+ sendAT(GF("+CIPRXGET=4,"), mux);
+ size_t result = 0;
+ if (waitResponse(GF("+CIPRXGET:")) == 1) {
+ streamSkipUntil(','); // Skip mode 4
+ streamSkipUntil(','); // Skip mux
+ result = stream.readStringUntil('\n').toInt();
+ waitResponse();
+ }
+ if (!result) {
+ sockets[mux]->sock_connected = modemGetConnected(mux);
+ }
+ return result;
+ }
+
+ bool modemGetConnected(uint8_t mux)
+ {
+ sendAT(GF("+CIPSTATUS="), mux);
+ int res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""), GF(",\"CLOSING\""),
+ GF(",\"INITIAL\""));
+ waitResponse();
+ return 1 == res;
+ }
+
+public:
+
+ /* Utilities */
+
+ template
+ void streamWrite(T last)
+ {
+ stream.print(last);
+ }
+
+ template
+ void streamWrite(T head, Args... tail)
+ {
+ stream.print(head);
+ streamWrite(tail...);
+ }
+
+ bool streamSkipUntil(char c) //TODO: timeout
+ {
+ while (true) {
+ while (!stream.available()) {
+ TINY_GSM_YIELD();
+ }
+ if (stream.read() == c) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template
+ void sendAT(Args... cmd)
+ {
+ streamWrite("AT", cmd..., GSM_NL);
+ stream.flush();
+ TINY_GSM_YIELD();
+ //DBG("### AT:", cmd...);
+ }
+
+ // TODO: Optimize this!
+ uint8_t waitResponse(uint32_t timeout, String& data,
+ GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ /*String r1s(r1); r1s.trim();
+ String r2s(r2); r2s.trim();
+ String r3s(r3); r3s.trim();
+ String r4s(r4); r4s.trim();
+ String r5s(r5); r5s.trim();
+ DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/
+ data.reserve(64);
+ int index = 0;
+ unsigned long startMillis = millis();
+ do {
+ TINY_GSM_YIELD();
+ while (stream.available() > 0) {
+ int a = stream.read();
+ if (a <= 0) {
+ continue; // Skip 0x00 bytes, just in case
+ }
+ data += (char)a;
+ if (r1 && data.endsWith(r1)) {
+ index = 1;
+ goto finish;
+ } else if (r2 && data.endsWith(r2)) {
+ index = 2;
+ goto finish;
+ } else if (r3 && data.endsWith(r3)) {
+ index = 3;
+ goto finish;
+ } else if (r4 && data.endsWith(r4)) {
+ index = 4;
+ goto finish;
+ } else if (r5 && data.endsWith(r5)) {
+ index = 5;
+ goto finish;
+ } else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) {
+ String mode = stream.readStringUntil(',');
+ if (mode.toInt() == 1) {
+ int mux = stream.readStringUntil('\n').toInt();
+ if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
+ sockets[mux]->got_data = true;
+ }
+ data = "";
+ } else {
+ data += mode;
+ }
+ } else if (data.endsWith(GF("CLOSED" GSM_NL))) {
+ int nl = data.lastIndexOf(GSM_NL, data.length()-8);
+ int coma = data.indexOf(',', nl+2);
+ int mux = data.substring(nl+2, coma).toInt();
+ if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
+ sockets[mux]->sock_connected = false;
+ }
+ data = "";
+ DBG("### Closed: ", mux);
+ }
+ }
+ } while (millis() - startMillis < timeout);
+finish:
+ if (!index) {
+ data.trim();
+ if (data.length()) {
+ DBG("### Unhandled:", data);
+ }
+ data = "";
+ }
+ return index;
+ }
+
+ uint8_t waitResponse(uint32_t timeout,
+ GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ String data;
+ return waitResponse(timeout, data, r1, r2, r3, r4, r5);
+ }
+
+ uint8_t waitResponse(GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ return waitResponse(1000, r1, r2, r3, r4, r5);
+ }
+
+public:
+ Stream& stream;
+
+protected:
+ GsmClient* sockets[TINY_GSM_MUX_COUNT];
+};
+
+#endif
diff --git a/drivers/TinyGSM/TinyGsmClientSIM808.h b/drivers/TinyGSM/TinyGsmClientSIM808.h
new file mode 100644
index 0000000..d6342a1
--- /dev/null
+++ b/drivers/TinyGSM/TinyGsmClientSIM808.h
@@ -0,0 +1,163 @@
+/**
+ * file TinyGsmClientSIM808.h
+ * author Volodymyr Shymanskyy
+ * license LGPL-3.0
+ * copyright Copyright (c) 2016 Volodymyr Shymanskyy
+ * date Nov 2016
+ */
+
+#ifndef TinyGsmClientSIM808_h
+#define TinyGsmClientSIM808_h
+
+#include "TinyGsmClientSIM800.h"
+
+class TinyGsmSim808: public TinyGsmSim800
+{
+
+public:
+
+ explicit TinyGsmSim808(Stream& stream)
+ : TinyGsmSim800(stream)
+ {}
+
+ /*
+ * GPS location functions
+ */
+
+ // enable GPS
+ bool enableGPS()
+ {
+ uint16_t state;
+
+ sendAT(GF("+CGNSPWR=1"));
+ if (waitResponse() != 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool disableGPS()
+ {
+ uint16_t state;
+
+ sendAT(GF("+CGNSPWR=0"));
+ if (waitResponse() != 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ // get the RAW GPS output
+ // works only with ans SIM808 V2
+ String getGPSraw()
+ {
+ sendAT(GF("+CGNSINF"));
+ if (waitResponse(GF(GSM_NL "+CGNSINF:")) != 1) {
+ return "";
+ }
+ String res = stream.readStringUntil('\n');
+ waitResponse();
+ res.trim();
+ return res;
+ }
+
+ // get GPS informations
+ // works only with ans SIM808 V2
+ bool getGPS(float *lat, float *lon, float *speed=0, int *alt=0, int *vsat=0, int *usat=0)
+ {
+ //String buffer = "";
+ char chr_buffer[12];
+ bool fix = false;
+
+ sendAT(GF("+CGNSINF"));
+ if (waitResponse(GF(GSM_NL "+CGNSINF:")) != 1) {
+ return false;
+ }
+
+ stream.readStringUntil(','); // mode
+ if ( stream.readStringUntil(',').toInt() == 1 ) {
+ fix = true;
+ }
+ stream.readStringUntil(','); //utctime
+ *lat = stream.readStringUntil(',').toFloat(); //lat
+ *lon = stream.readStringUntil(',').toFloat(); //lon
+ if (alt != NULL) {
+ *alt = stream.readStringUntil(',').toFloat(); //lon
+ }
+ if (speed != NULL) {
+ *speed = stream.readStringUntil(',').toFloat(); //speed
+ }
+ stream.readStringUntil(',');
+ stream.readStringUntil(',');
+ stream.readStringUntil(',');
+ stream.readStringUntil(',');
+ stream.readStringUntil(',');
+ stream.readStringUntil(',');
+ stream.readStringUntil(',');
+ if (vsat != NULL) {
+ *vsat = stream.readStringUntil(',').toInt(); //viewed satelites
+ }
+ if (usat != NULL) {
+ *usat = stream.readStringUntil(',').toInt(); //used satelites
+ }
+ stream.readStringUntil('\n');
+
+ waitResponse();
+
+ return fix;
+ }
+
+ // get GPS time
+ // works only with SIM808 V2
+ bool getGPSTime(int *year, int *month, int *day, int *hour, int *minute, int *second)
+ {
+ bool fix = false;
+ char chr_buffer[12];
+ sendAT(GF("+CGNSINF"));
+ if (waitResponse(GF(GSM_NL "+CGNSINF:")) != 1) {
+ return false;
+ }
+
+ for (int i = 0; i < 3; i++) {
+ String buffer = stream.readStringUntil(',');
+ buffer.toCharArray(chr_buffer, sizeof(chr_buffer));
+ switch (i) {
+ case 0:
+ //mode
+ break;
+ case 1:
+ //fixstatus
+ if ( buffer.toInt() == 1 ) {
+ fix = buffer.toInt();
+ }
+ break;
+ case 2:
+ *year = buffer.substring(0,4).toInt();
+ *month = buffer.substring(4,6).toInt();
+ *day = buffer.substring(6,8).toInt();
+ *hour = buffer.substring(8,10).toInt();
+ *minute = buffer.substring(10,12).toInt();
+ *second = buffer.substring(12,14).toInt();
+ break;
+
+ default:
+ // if nothing else matches, do the default
+ // default is optional
+ break;
+ }
+ }
+ String res = stream.readStringUntil('\n');
+ waitResponse();
+
+ if (fix) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+};
+
+#endif
diff --git a/drivers/TinyGSM/TinyGsmClientU201.h b/drivers/TinyGSM/TinyGsmClientU201.h
new file mode 100644
index 0000000..d5349ac
--- /dev/null
+++ b/drivers/TinyGSM/TinyGsmClientU201.h
@@ -0,0 +1,789 @@
+/**
+ * file TinyGsmClientU201.h
+ * author Volodymyr Shymanskyy
+ * license LGPL-3.0
+ * copyright Copyright (c) 2016 Volodymyr Shymanskyy
+ * date Nov 2016
+ */
+
+#ifndef TinyGsmClientU201_h
+#define TinyGsmClientU201_h
+
+//#define TINY_GSM_DEBUG Serial
+
+#if !defined(TINY_GSM_RX_BUFFER)
+#define TINY_GSM_RX_BUFFER 64
+#endif
+
+#define TINY_GSM_MUX_COUNT 5
+
+#include "TinyGsmCommon.h"
+
+#define GSM_NL "\r\n"
+static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
+static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
+
+enum SimStatus {
+ SIM_ERROR = 0,
+ SIM_READY = 1,
+ SIM_LOCKED = 2,
+};
+
+enum RegStatus {
+ REG_UNREGISTERED = 0,
+ REG_SEARCHING = 2,
+ REG_DENIED = 3,
+ REG_OK_HOME = 1,
+ REG_OK_ROAMING = 5,
+ REG_UNKNOWN = 4,
+};
+
+
+class TinyGsmU201
+{
+
+public:
+
+ class GsmClient : public Client
+ {
+ friend class TinyGsmU201;
+ typedef TinyGsmFifo RxFifo;
+
+ public:
+ GsmClient() {}
+
+ GsmClient(TinyGsmU201& modem, uint8_t mux = 1)
+ {
+ init(&modem, mux);
+ }
+
+ bool init(TinyGsmU201* modem, uint8_t mux = 1)
+ {
+ this->at = modem;
+ this->mux = mux;
+ sock_available = 0;
+ sock_connected = false;
+ got_data = false;
+
+ return true;
+ }
+
+ public:
+ virtual int connect(const char *host, uint16_t port)
+ {
+ stop();
+ TINY_GSM_YIELD();
+ rx.clear();
+ sock_connected = at->modemConnect(host, port, &mux);
+ at->sockets[mux] = this;
+ return sock_connected;
+ }
+
+ virtual int connect(IPAddress ip, uint16_t port)
+ {
+ String host;
+ host.reserve(16);
+ host += ip[0];
+ host += ".";
+ host += ip[1];
+ host += ".";
+ host += ip[2];
+ host += ".";
+ host += ip[3];
+ return connect(host.c_str(), port);
+ }
+
+ virtual void stop()
+ {
+ TINY_GSM_YIELD();
+ at->sendAT(GF("+USOCL="), mux);
+ sock_connected = false;
+ at->waitResponse();
+ rx.clear();
+ }
+
+ virtual size_t write(const uint8_t *buf, size_t size)
+ {
+ TINY_GSM_YIELD();
+ at->maintain();
+ return at->modemSend(buf, size, mux);
+ }
+
+ virtual size_t write(uint8_t c)
+ {
+ return write(&c, 1);
+ }
+
+ virtual int available()
+ {
+ TINY_GSM_YIELD();
+ if (!rx.size() && sock_connected) {
+ at->maintain();
+ }
+ return rx.size() + sock_available;
+ }
+
+ virtual int read(uint8_t *buf, size_t size)
+ {
+ TINY_GSM_YIELD();
+ at->maintain();
+ size_t cnt = 0;
+ while (cnt < size) {
+ size_t chunk = TinyGsmMin(size-cnt, rx.size());
+ if (chunk > 0) {
+ rx.get(buf, chunk);
+ buf += chunk;
+ cnt += chunk;
+ continue;
+ }
+ // TODO: Read directly into user buffer?
+ at->maintain();
+ if (sock_available > 0) {
+ at->modemRead(rx.free(), mux);
+ } else {
+ break;
+ }
+ }
+ return cnt;
+ }
+
+ virtual int read()
+ {
+ uint8_t c;
+ if (read(&c, 1) == 1) {
+ return c;
+ }
+ return -1;
+ }
+
+ virtual int peek()
+ {
+ return -1; //TODO
+ }
+ virtual void flush()
+ {
+ at->stream.flush();
+ }
+
+ virtual uint8_t connected()
+ {
+ if (available()) {
+ return true;
+ }
+ return sock_connected;
+ }
+ virtual operator bool()
+ {
+ return connected();
+ }
+
+ /*
+ * Extended API
+ */
+
+ String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ private:
+ TinyGsmU201* at;
+ uint8_t mux;
+ uint16_t sock_available;
+ bool sock_connected;
+ bool got_data;
+ RxFifo rx;
+ };
+
+ class GsmClientSecure : public GsmClient
+ {
+ public:
+ GsmClientSecure() {}
+
+ GsmClientSecure(TinyGsmU201& modem, uint8_t mux = 1)
+ : GsmClient(modem, mux)
+ {}
+
+ public:
+ virtual int connect(const char *host, uint16_t port)
+ {
+ stop();
+ TINY_GSM_YIELD();
+ rx.clear();
+ sock_connected = at->modemConnect(host, port, &mux, true);
+ at->sockets[mux] = this;
+ return sock_connected;
+ }
+ };
+
+public:
+
+#ifdef GSM_DEFAULT_STREAM
+ explicit TinyGsmU201(Stream& stream = GSM_DEFAULT_STREAM)
+#else
+ explicit TinyGsmU201(Stream& stream)
+#endif
+ : stream(stream)
+ {
+ memset(sockets, 0, sizeof(sockets));
+ }
+
+ /*
+ * Basic functions
+ */
+ bool begin(const char* pin = NULL)
+ {
+ return init(pin);
+ }
+
+ bool init(const char* pin = NULL)
+ {
+ if (!testAT()) {
+ return false;
+ }
+ sendAT(GF("E0")); // Echo Off
+ if (waitResponse() != 1) {
+ return false;
+ }
+ int ret = getSimStatus();
+ if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
+ simUnlock(pin);
+ }
+ return (getSimStatus() == SIM_READY);
+ }
+
+ void setBaud(unsigned long baud)
+ {
+ sendAT(GF("+IPR="), baud);
+ }
+
+ bool testAT(unsigned long timeout = 10000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ sendAT(GF(""));
+ if (waitResponse(200) == 1) {
+ delay(100);
+ return true;
+ }
+ delay(100);
+ }
+ return false;
+ }
+
+ void maintain()
+ {
+ for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) {
+ GsmClient* sock = sockets[mux];
+ if (sock && sock->got_data) {
+ sock->got_data = false;
+ sock->sock_available = modemGetAvailable(mux);
+ }
+ }
+ while (stream.available()) {
+ waitResponse(10, NULL, NULL);
+ }
+ }
+
+ bool factoryDefault()
+ {
+ sendAT(GF("+UFACTORY=0,1")); // Factory + Reset + Echo Off
+ waitResponse();
+ sendAT(GF("+CFUN=16")); // Auto-baud
+ return waitResponse() == 1;
+ }
+
+ String getModemInfo() TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ bool hasSSL()
+ {
+ return true;
+ }
+
+ /*
+ * Power functions
+ */
+
+ bool restart()
+ {
+ if (!testAT()) {
+ return false;
+ }
+ sendAT(GF("+CFUN=16"));
+ if (waitResponse(10000L) != 1) {
+ return false;
+ }
+ delay(3000);
+ return init();
+ }
+
+ bool poweroff() TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ /*
+ * SIM card functions
+ */
+
+ bool simUnlock(const char *pin)
+ {
+ sendAT(GF("+CPIN=\""), pin, GF("\""));
+ return waitResponse() == 1;
+ }
+
+ String getSimCCID()
+ {
+ sendAT(GF("+CCID"));
+ if (waitResponse(GF(GSM_NL "+CCID:")) != 1) {
+ return "";
+ }
+ String res = stream.readStringUntil('\n');
+ waitResponse();
+ res.trim();
+ return res;
+ }
+
+ String getIMEI()
+ {
+ sendAT(GF("+CGSN"));
+ if (waitResponse(GF(GSM_NL)) != 1) {
+ return "";
+ }
+ String res = stream.readStringUntil('\n');
+ waitResponse();
+ res.trim();
+ return res;
+ }
+
+ SimStatus getSimStatus(unsigned long timeout = 10000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ sendAT(GF("+CPIN?"));
+ if (waitResponse(GF(GSM_NL "+CPIN:")) != 1) {
+ delay(1000);
+ continue;
+ }
+ int status = waitResponse(GF("READY"), GF("SIM PIN"), GF("SIM PUK"), GF("NOT INSERTED"));
+ waitResponse();
+ switch (status) {
+ case 2:
+ case 3:
+ return SIM_LOCKED;
+ case 1:
+ return SIM_READY;
+ default:
+ return SIM_ERROR;
+ }
+ }
+ return SIM_ERROR;
+ }
+
+ RegStatus getRegistrationStatus()
+ {
+ sendAT(GF("+CGREG?"));
+ if (waitResponse(GF(GSM_NL "+CGREG:")) != 1) {
+ return REG_UNKNOWN;
+ }
+ streamSkipUntil(','); // Skip format (0)
+ int status = stream.readStringUntil('\n').toInt();
+ waitResponse();
+ return (RegStatus)status;
+ }
+
+ String getOperator()
+ {
+ sendAT(GF("+COPS?"));
+ if (waitResponse(GF(GSM_NL "+COPS:")) != 1) {
+ return "";
+ }
+ streamSkipUntil('"'); // Skip mode and format
+ String res = stream.readStringUntil('"');
+ waitResponse();
+ return res;
+ }
+
+ /*
+ * Generic network functions
+ */
+
+ int getSignalQuality()
+ {
+ sendAT(GF("+CSQ"));
+ if (waitResponse(GF(GSM_NL "+CSQ:")) != 1) {
+ return 99;
+ }
+ int res = stream.readStringUntil(',').toInt();
+ waitResponse();
+ return res;
+ }
+
+ bool isNetworkConnected()
+ {
+ RegStatus s = getRegistrationStatus();
+ return (s == REG_OK_HOME || s == REG_OK_ROAMING);
+ }
+
+ bool waitForNetwork(unsigned long timeout = 60000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ if (isNetworkConnected()) {
+ return true;
+ }
+ delay(250);
+ }
+ return false;
+ }
+
+ /*
+ * GPRS functions
+ */
+ bool gprsConnect(const char* apn, const char* user = NULL, const char* pwd = NULL)
+ {
+ gprsDisconnect();
+
+ sendAT(GF("+CGATT=1"));
+ waitResponse(5000L);
+
+ sendAT(GF("+UPSD=0,1,\""), apn, '"');
+ waitResponse();
+
+ if (user && strlen(user) > 0) {
+ sendAT(GF("+UPSD=0,2,\""), user, '"');
+ waitResponse();
+ }
+ if (pwd && strlen(pwd) > 0) {
+ sendAT(GF("+UPSD=0,3,\""), pwd, '"');
+ waitResponse();
+ }
+
+ sendAT(GF("+UPSD=0,7,\"0.0.0.0\"")); // Dynamic IP
+ waitResponse();
+
+ sendAT(GF("+UPSDA=0,3"));
+ waitResponse(6000L);
+
+ // Open a GPRS context
+ sendAT(GF("+UPSND=0,8"));
+ if (waitResponse(GF(",8,1")) != 1) {
+ return false;
+ }
+ return true;
+ }
+
+ bool gprsDisconnect()
+ {
+ sendAT(GF("+UPSDA=0,4"));
+ if (waitResponse(60000L) != 1) {
+ return false;
+ }
+
+ sendAT(GF("+CGATT=0"));
+ if (waitResponse(60000L) != 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool isGprsConnected()
+ {
+ sendAT(GF("+CGATT?"));
+ if (waitResponse(GF(GSM_NL "+CGATT:")) != 1) {
+ return false;
+ }
+ int res = stream.readStringUntil('\n').toInt();
+ waitResponse();
+ if (res != 1) {
+ return false;
+ }
+
+ sendAT(GF("+CIFSR"));
+ if (waitResponse() != 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ String getLocalIP()
+ {
+ sendAT(GF("+CIFSR;E0"));
+ String res;
+ if (waitResponse(10000L, res) != 1) {
+ return "";
+ }
+ res.trim();
+ return res;
+ }
+
+ IPAddress localIP()
+ {
+ return TinyGsmIpFromString(getLocalIP());
+ }
+
+ /*
+ * Phone Call functions
+ */
+
+ bool setGsmBusy(bool busy = true) TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ bool callAnswer() TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ bool callNumber(const String& number) TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ bool callHangup() TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ /*
+ * Messaging functions
+ */
+
+ String sendUSSD(const String& code) TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ bool sendSMS(const String& number, const String& text) TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ bool sendSMS_UTF16(const String& number, const void* text,
+ size_t len) TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+
+ /*
+ * Location functions
+ */
+
+ String getGsmLocation()
+ {
+ sendAT(GF("+ULOC=2,3,0,120,1"));
+ if (waitResponse(GF(GSM_NL "+UULOC:")) != 1) {
+ return "";
+ }
+ String res = stream.readStringUntil('\n');
+ waitResponse();
+ res.trim();
+ return res;
+ }
+
+ /*
+ * Battery functions
+ */
+ // Use: float vBatt = modem.getBattVoltage() / 1000.0;
+ uint16_t getBattVoltage()
+ {
+ sendAT(GF("+CIND"));
+ if (waitResponse(GF(GSM_NL "+CIND:")) != 1) {
+ return 0;
+ }
+
+ uint16_t res = stream.readStringUntil(',').toInt();
+ waitResponse();
+ return res;
+ }
+
+ int getBattPercent() TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+protected:
+
+ bool modemConnect(const char* host, uint16_t port, uint8_t* mux, bool ssl = false)
+ {
+ sendAT(GF("+USOCR=6"));
+ if (waitResponse(GF(GSM_NL "+USOCR:")) != 1) {
+ return false;
+ }
+ *mux = stream.readStringUntil('\n').toInt();
+ waitResponse();
+
+ if (ssl) {
+ sendAT(GF("+USOSEC="), *mux, ",1");
+ waitResponse();
+ }
+
+ sendAT(GF("+USOCO="), *mux, ",\"", host, "\",", port);
+ int rsp = waitResponse(75000L);
+ return (1 == rsp);
+ }
+
+ int modemSend(const void* buff, size_t len, uint8_t mux)
+ {
+ sendAT(GF("+USOWR="), mux, ',', len);
+ if (waitResponse(GF("@")) != 1) {
+ return -1;
+ }
+ // 50ms delay, see AT manual section 25.10.4
+ delay(50);
+ stream.write((uint8_t*)buff, len);
+ stream.flush();
+ if (waitResponse(GF(GSM_NL "+USOWR:")) != 1) {
+ return -1;
+ }
+ streamSkipUntil(','); // Skip mux
+ return stream.readStringUntil('\n').toInt();
+ }
+
+ size_t modemRead(size_t size, uint8_t mux)
+ {
+ sendAT(GF("+USORD="), mux, ',', size);
+ if (waitResponse(GF(GSM_NL "+USORD:")) != 1) {
+ return 0;
+ }
+ streamSkipUntil(','); // Skip mux
+ size_t len = stream.readStringUntil(',').toInt();
+ streamSkipUntil('\"');
+
+ for (size_t i=0; irx.put(c);
+ }
+ streamSkipUntil('\"');
+ waitResponse();
+ return len;
+ }
+
+ size_t modemGetAvailable(uint8_t mux)
+ {
+ sendAT(GF("+USORD="), mux, ',', 0);
+ size_t result = 0;
+ if (waitResponse(GF(GSM_NL "+USORD:")) == 1) {
+ streamSkipUntil(','); // Skip mux
+ result = stream.readStringUntil('\n').toInt();
+ waitResponse();
+ }
+ if (!result) {
+ sockets[mux]->sock_connected = modemGetConnected(mux);
+ }
+ return result;
+ }
+
+ bool modemGetConnected(uint8_t mux)
+ {
+ sendAT(GF("+USOCTL="), mux, ",10");
+ if (waitResponse(GF(GSM_NL "+USOCTL:")) != 1) {
+ return false;
+ }
+
+ streamSkipUntil(','); // Skip mux
+ streamSkipUntil(','); // Skip type
+ int result = stream.readStringUntil('\n').toInt();
+
+ return result != 0;
+ }
+
+public:
+
+ /* Utilities */
+
+ template
+ void streamWrite(T last)
+ {
+ stream.print(last);
+ }
+
+ template
+ void streamWrite(T head, Args... tail)
+ {
+ stream.print(head);
+ streamWrite(tail...);
+ }
+
+ bool streamSkipUntil(char c) //TODO: timeout
+ {
+ while (true) {
+ while (!stream.available()) {
+ TINY_GSM_YIELD();
+ }
+ if (stream.read() == c) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template
+ void sendAT(Args... cmd)
+ {
+ streamWrite("AT", cmd..., GSM_NL);
+ stream.flush();
+ TINY_GSM_YIELD();
+ //DBG("### AT:", cmd...);
+ }
+
+ // TODO: Optimize this!
+ uint8_t waitResponse(uint32_t timeout, String& data,
+ GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ /*String r1s(r1); r1s.trim();
+ String r2s(r2); r2s.trim();
+ String r3s(r3); r3s.trim();
+ String r4s(r4); r4s.trim();
+ String r5s(r5); r5s.trim();
+ DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/
+ data.reserve(64);
+ int index = 0;
+ unsigned long startMillis = millis();
+ do {
+ TINY_GSM_YIELD();
+ while (stream.available() > 0) {
+ int a = stream.read();
+ if (a < 0) {
+ continue;
+ }
+ data += (char)a;
+ if (r1 && data.endsWith(r1)) {
+ index = 1;
+ goto finish;
+ } else if (r2 && data.endsWith(r2)) {
+ index = 2;
+ goto finish;
+ } else if (r3 && data.endsWith(r3)) {
+ index = 3;
+ goto finish;
+ } else if (r4 && data.endsWith(r4)) {
+ index = 4;
+ goto finish;
+ } else if (r5 && data.endsWith(r5)) {
+ index = 5;
+ goto finish;
+ } else if (data.endsWith(GF(GSM_NL "+UUSORD:"))) {
+ int mux = stream.readStringUntil(',').toInt();
+ if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
+ sockets[mux]->got_data = true;
+ }
+ data = "";
+ } else if (data.endsWith(GF(GSM_NL "+UUSOCL:"))) {
+ int mux = stream.readStringUntil('\n').toInt();
+ if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
+ sockets[mux]->sock_connected = false;
+ }
+ data = "";
+ DBG("### Closed: ", mux);
+ }
+ }
+ } while (millis() - startMillis < timeout);
+finish:
+ if (!index) {
+ data.trim();
+ if (data.length()) {
+ DBG("### Unhandled:", data);
+ }
+ data = "";
+ }
+ return index;
+ }
+
+ uint8_t waitResponse(uint32_t timeout,
+ GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ String data;
+ return waitResponse(timeout, data, r1, r2, r3, r4, r5);
+ }
+
+ uint8_t waitResponse(GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ return waitResponse(1000, r1, r2, r3, r4, r5);
+ }
+
+public:
+ Stream& stream;
+
+protected:
+ GsmClient* sockets[TINY_GSM_MUX_COUNT];
+};
+
+#endif
diff --git a/drivers/TinyGSM/TinyGsmClientXBee.h b/drivers/TinyGSM/TinyGsmClientXBee.h
new file mode 100644
index 0000000..8efef85
--- /dev/null
+++ b/drivers/TinyGSM/TinyGsmClientXBee.h
@@ -0,0 +1,752 @@
+/**
+ * file TinyGsmClientXBee.h
+ * author Volodymyr Shymanskyy
+ * license LGPL-3.0
+ * copyright Copyright (c) 2016 Volodymyr Shymanskyy
+ * date Nov 2016
+ */
+
+#ifndef TinyGsmClientXBee_h
+#define TinyGsmClientXBee_h
+
+//#define TINY_GSM_DEBUG Serial
+
+#if !defined(TINY_GSM_RX_BUFFER)
+#define TINY_GSM_RX_BUFFER 256
+#endif
+
+#define TINY_GSM_MUX_COUNT 1 // Multi-plexing isn't supported using command mode
+
+#include "TinyGsmCommon.h"
+
+#define GSM_NL "\r"
+static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
+static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
+
+enum SimStatus {
+ SIM_ERROR = 0,
+ SIM_READY = 1,
+ SIM_LOCKED = 2,
+};
+
+enum XBeeType {
+ S6B = 0,
+ LTEC1 = 1,
+};
+
+enum RegStatus {
+ REG_UNREGISTERED = 0,
+ REG_SEARCHING = 2,
+ REG_DENIED = 3,
+ REG_OK_HOME = 1,
+ REG_OK_ROAMING = 5,
+ REG_UNKNOWN = 4,
+};
+
+
+class TinyGsm
+{
+
+public:
+
+ class GsmClient : public Client
+ {
+ friend class TinyGsm;
+
+ public:
+ GsmClient() {}
+
+ GsmClient(TinyGsm& modem, uint8_t mux = 0)
+ {
+ init(&modem, mux);
+ }
+
+ bool init(TinyGsm* modem, uint8_t mux = 0)
+ {
+ this->at = modem;
+ this->mux = mux;
+ sock_connected = false;
+
+ at->sockets[mux] = this;
+
+ return true;
+ }
+
+ public:
+ virtual int connect(const char *host, uint16_t port)
+ {
+ at->streamClear(); // Empty anything remaining in the buffer;
+ at->commandMode();
+ sock_connected = at->modemConnect(host, port, mux, false);
+ at->writeChanges();
+ at->exitCommand();
+ return sock_connected;
+ }
+
+ virtual int connect(IPAddress ip, uint16_t port)
+ {
+ at->streamClear(); // Empty anything remaining in the buffer;
+ at->commandMode();
+ sock_connected = at->modemConnect(ip, port, mux, false);
+ at->writeChanges();
+ at->exitCommand();
+ return sock_connected;
+ }
+
+ // This is a hack to shut the socket by setting the timeout to zero and
+ // then sending an empty line to the server.
+ virtual void stop()
+ {
+ at->streamClear(); // Empty anything remaining in the buffer;
+ at->commandMode();
+ at->sendAT(GF("TM0")); // Set socket timeout to 0;
+ at->waitResponse();
+ at->writeChanges();
+ at->exitCommand();
+ at->modemSend("", 1, mux);
+ at->commandMode();
+ at->sendAT(GF("TM64")); // Set socket timeout back to 10seconds;
+ at->waitResponse();
+ at->writeChanges();
+ at->exitCommand();
+ at->streamClear(); // Empty anything remaining in the buffer;
+ sock_connected = false;
+ }
+
+ virtual size_t write(const uint8_t *buf, size_t size)
+ {
+ TINY_GSM_YIELD();
+ //at->maintain();
+ return at->modemSend(buf, size, mux);
+ }
+
+ virtual size_t write(uint8_t c)
+ {
+ return write(&c, 1);
+ }
+
+ virtual int available()
+ {
+ TINY_GSM_YIELD();
+ return at->stream.available();
+ }
+
+ virtual int read(uint8_t *buf, size_t size)
+ {
+ TINY_GSM_YIELD();
+ return at->stream.readBytes(buf, size);
+ }
+
+ virtual int read()
+ {
+ TINY_GSM_YIELD();
+ return at->stream.read();
+ }
+
+ virtual int peek()
+ {
+ return at->stream.peek();
+ }
+ virtual void flush()
+ {
+ at->stream.flush();
+ }
+
+ virtual uint8_t connected()
+ {
+ if (available()) {
+ return true;
+ }
+ return sock_connected;
+ }
+ virtual operator bool()
+ {
+ return connected();
+ }
+
+ /*
+ * Extended API
+ */
+
+ String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ private:
+ TinyGsm* at;
+ uint8_t mux;
+ bool sock_connected;
+ };
+
+ class GsmClientSecure : public GsmClient
+ {
+ public:
+ GsmClientSecure() {}
+
+ GsmClientSecure(TinyGsm& modem, uint8_t mux = 1)
+ : GsmClient(modem, mux)
+ {}
+
+ public:
+ virtual int connect(const char *host, uint16_t port)
+ {
+ at->streamClear(); // Empty anything remaining in the buffer;
+ at->commandMode();
+ sock_connected = at->modemConnect(host, port, mux, true);
+ at->writeChanges();
+ at->exitCommand();
+ return sock_connected;
+ }
+
+ virtual int connect(IPAddress ip, uint16_t port)
+ {
+ at->streamClear(); // Empty anything remaining in the buffer;
+ at->commandMode();
+ sock_connected = at->modemConnect(ip, port, mux, true);
+ at->writeChanges();
+ at->exitCommand();
+ return sock_connected;
+ }
+ };
+
+public:
+
+ explicit TinyGsm(Stream& stream)
+ : stream(stream)
+ {
+ memset(sockets, 0, sizeof(sockets));
+ }
+
+ /*
+ * Basic functions
+ */
+ bool begin()
+ {
+ return init();
+ }
+
+ bool init()
+ {
+ guardTime = 1100;
+ commandMode();
+ sendAT(GF("AP0")); // Put in transparent mode
+ waitResponse();
+ sendAT(GF("GT64")); // shorten the guard time to 100ms
+ waitResponse();
+ writeChanges();
+ sendAT(GF("HS")); // Get the "Hardware Series"; 0x601 for S6B (Wifi)
+ // wait for the response
+ unsigned long startMillis = millis();
+ while (!stream.available() && millis() - startMillis < 1000) {};
+ String res = streamReadUntil('\r'); // Does not send an OK, just the result
+ exitCommand();
+ if (res == "601") {
+ beeType = S6B;
+ } else {
+ beeType = LTEC1;
+ }
+ guardTime = 125;
+ return true;
+ }
+
+ bool testAT(unsigned long timeout = 10000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ if (commandMode()) {
+ sendAT();
+ if (waitResponse(200) == 1) {
+ return true;
+ }
+ exitCommand();
+ }
+ delay(100);
+ }
+ return false;
+ }
+
+ void maintain() {}
+
+ bool factoryDefault()
+ {
+ commandMode();
+ sendAT(GF("RE"));
+ bool ret_val = waitResponse() == 1;
+ writeChanges();
+ exitCommand();
+ return ret_val;
+ }
+
+ bool hasSSL()
+ {
+ if (beeType == S6B) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /*
+ * Power functions
+ */
+
+ bool restart()
+ {
+ commandMode();
+ sendAT(GF("FR"));
+ if (waitResponse() != 1) {
+ return false;
+ }
+ delay (2000); // Actually resets about 2 seconds later
+ for (unsigned long start = millis(); millis() - start < 60000L; ) {
+ if (commandMode()) {
+ exitCommand();
+ return true;
+ }
+ }
+ exitCommand();
+ return false;;
+ }
+
+ void setupPinSleep()
+ {
+ commandMode();
+ sendAT(GF("SM"),1);
+ waitResponse();
+ if (beeType == S6B) {
+ sendAT(GF("SO"),200);
+ waitResponse();
+ }
+ writeChanges();
+ exitCommand();
+ }
+
+ /*
+ * SIM card functions
+ */
+
+ bool simUnlock(const char *pin) // Not supported
+ {
+ return false;
+ }
+
+ String getSimCCID()
+ {
+ commandMode();
+ sendAT(GF("S#"));
+ // wait for the response
+ unsigned long startMillis = millis();
+ while (!stream.available() && millis() - startMillis < 1000) {};
+ String res = streamReadUntil('\r'); // Does not send an OK, just the result
+ exitCommand();
+ return res;
+ }
+
+ String getIMEI()
+ {
+ commandMode();
+ sendAT(GF("IM"));
+ // wait for the response
+ unsigned long startMillis = millis();
+ while (!stream.available() && millis() - startMillis < 1000) {};
+ String res = streamReadUntil('\r'); // Does not send an OK, just the result
+ exitCommand();
+ return res;
+ }
+
+ SimStatus getSimStatus(unsigned long timeout = 10000L)
+ {
+ return SIM_READY; // unsupported
+ }
+
+ RegStatus getRegistrationStatus()
+ {
+ commandMode();
+ sendAT(GF("AI"));
+ // wait for the response
+ unsigned long startMillis = millis();
+ while (!stream.available() && millis() - startMillis < 1000) {};
+ String res = streamReadUntil('\r'); // Does not send an OK, just the result
+ exitCommand();
+
+ if(res == GF("0")) {
+ return REG_OK_HOME;
+ }
+
+ else if(res == GF("13") || res == GF("2A")) {
+ return REG_UNREGISTERED;
+ }
+
+ else if(res == GF("FF") || res == GF("22") || res == GF("23") ||
+ res == GF("40") || res == GF("41") || res == GF("42")) {
+ return REG_SEARCHING;
+ }
+
+ else if(res == GF("24") || res == GF("25") || res == GF("27")) {
+ return REG_DENIED;
+ }
+
+ else {
+ return REG_UNKNOWN;
+ }
+ }
+
+ String getOperator()
+ {
+ commandMode();
+ sendAT(GF("MN"));
+ // wait for the response
+ unsigned long startMillis = millis();
+ while (!stream.available() && millis() - startMillis < 1000) {};
+ String res = streamReadUntil('\r'); // Does not send an OK, just the result
+ exitCommand();
+ return res;
+ }
+
+ /*
+ * Generic network functions
+ */
+
+ int getSignalQuality()
+ {
+ commandMode();
+ if (beeType == S6B) {
+ sendAT(GF("LM")); // ask for the "link margin" - the dB above sensitivity
+ } else {
+ sendAT(GF("DB")); // ask for the cell strength in dBm
+ }
+ // wait for the response
+ unsigned long startMillis = millis();
+ while (!stream.available() && millis() - startMillis < 1000) {};
+ char buf[2] = {0}; // Set up buffer for response
+ buf[0] = streamRead();
+ buf[1] = streamRead();
+ // DBG(buf[0], buf[1], "\n");
+ exitCommand();
+ int intr = strtol(buf, 0, 16);
+ if (beeType == S6B) {
+ return -93 + intr; // the maximum sensitivity is -93dBm
+ } else {
+ return -1*intr; // need to convert to negative number
+ }
+ }
+
+ bool isNetworkConnected()
+ {
+ RegStatus s = getRegistrationStatus();
+ return (s == REG_OK_HOME || s == REG_OK_ROAMING);
+ }
+
+ bool waitForNetwork(unsigned long timeout = 60000L)
+ {
+ for (unsigned long start = millis(); millis() - start < timeout; ) {
+ if (isNetworkConnected()) {
+ return true;
+ }
+ delay(250);
+ }
+ return false;
+ }
+
+ /*
+ * WiFi functions
+ */
+ bool networkConnect(const char* ssid, const char* pwd)
+ {
+
+ commandMode();
+
+ sendAT(GF("EE"), 2); // Set security to WPA2
+ waitResponse();
+
+ sendAT(GF("ID"), ssid);
+ if (waitResponse() != 1) {
+ goto fail;
+ }
+
+ sendAT(GF("PK"), pwd);
+ if (waitResponse() != 1) {
+ goto fail;
+ }
+
+ writeChanges();
+ exitCommand();
+
+ return true;
+
+fail:
+ exitCommand();
+ return false;
+ }
+
+ bool networkDisconnect()
+ {
+ return false; // Doesn't support disconnecting
+ }
+
+ String getLocalIP()
+ {
+ commandMode();
+ sendAT(GF("MY"));
+ String IPaddr;
+ IPaddr.reserve(16);
+ // wait for the response
+ unsigned long startMillis = millis();
+ while (stream.available() < 8 && millis() - startMillis < 30000) {};
+ IPaddr = streamReadUntil('\r'); // read result
+ return IPaddr;
+ }
+
+ IPAddress localIP()
+ {
+ return TinyGsmIpFromString(getLocalIP());
+ }
+
+ /*
+ * GPRS functions
+ */
+ bool gprsConnect(const char* apn, const char* user = NULL, const char* pwd = NULL)
+ {
+ commandMode();
+ sendAT(GF("AN"), apn); // Set the APN
+ waitResponse();
+ writeChanges();
+ exitCommand();
+ return true;
+ }
+
+ bool gprsDisconnect() // TODO
+ {
+ return false;
+ }
+
+ /*
+ * Messaging functions
+ */
+
+ String sendUSSD(const String& code) TINY_GSM_ATTR_NOT_IMPLEMENTED;
+
+ bool sendSMS(const String& number, const String& text)
+ {
+ commandMode();
+ sendAT(GF("IP"), 2); // Put in text messaging mode
+ waitResponse();
+ sendAT(GF("PH"), number); // Set the phone number
+ waitResponse();
+ sendAT(GF("TDD")); // Set the text delimiter to the standard 0x0D (carriabe return)
+ waitResponse();
+ writeChanges();
+ exitCommand();
+ stream.print(text);
+ stream.write((char)0x0D); // close off with the carriage return
+ return true;
+ }
+
+
+private:
+
+ int modemConnect(const char* host, uint16_t port, uint8_t mux = 0, bool ssl = false)
+ {
+ sendAT(GF("LA"), host);
+ String strIP;
+ strIP.reserve(16);
+ // wait for the response
+ unsigned long startMillis = millis();
+ while (stream.available() < 8 && millis() - startMillis < 30000) {};
+ strIP = streamReadUntil('\r'); // read result
+ IPAddress ip = TinyGsmIpFromString(strIP);
+ return modemConnect(ip, port, mux, ssl);
+ }
+
+ int modemConnect(IPAddress ip, uint16_t port, uint8_t mux = 0, bool ssl = false)
+ {
+ String host;
+ host.reserve(16);
+ host += ip[0];
+ host += ".";
+ host += ip[1];
+ host += ".";
+ host += ip[2];
+ host += ".";
+ host += ip[3];
+ if (ssl) {
+ sendAT(GF("IP"), 4); // Put in TCP mode
+ waitResponse();
+ } else {
+ sendAT(GF("IP"), 1); // Put in TCP mode
+ waitResponse();
+ }
+ sendAT(GF("DL"), host); // Set the "Destination Address Low"
+ waitResponse();
+ sendAT(GF("DE"), String(port, HEX)); // Set the destination port
+ int rsp = waitResponse();
+ return rsp;
+ }
+
+ int modemSend(const void* buff, size_t len, uint8_t mux = 0)
+ {
+ stream.write((uint8_t*)buff, len);
+ stream.flush();
+ return len;
+ }
+
+ bool modemGetConnected(uint8_t mux = 0)
+ {
+ commandMode();
+ sendAT(GF("AI"));
+ int res = waitResponse(GF("0"));
+ exitCommand();
+ return 1 == res;
+ }
+
+public:
+
+ /* Utilities */
+
+ template
+ void streamWrite(T last)
+ {
+ stream.print(last);
+ }
+
+ template
+ void streamWrite(T head, Args... tail)
+ {
+ stream.print(head);
+ streamWrite(tail...);
+ }
+
+ int streamRead()
+ {
+ return stream.read();
+ }
+
+ String streamReadUntil(char c)
+ {
+ TINY_GSM_YIELD();
+ String return_string = stream.readStringUntil(c);
+ return_string.trim();
+ // DBG(return_string, c);
+ return return_string;
+ }
+
+ void streamClear(void)
+ {
+ while (stream.available()) {
+ streamRead();
+ }
+ }
+
+ bool commandMode(void)
+ {
+ delay(guardTime); // cannot send anything for 1 second before entering command mode
+ streamWrite(GF("+++")); // enter command mode
+ // DBG("\r\n+++\r\n");
+ return 1 == waitResponse(guardTime*2);
+ }
+
+ void writeChanges(void)
+ {
+ sendAT(GF("WR")); // Write changes to flash
+ waitResponse();
+ sendAT(GF("AC")); // Apply changes
+ waitResponse();
+ }
+
+ void exitCommand(void)
+ {
+ sendAT(GF("CN")); // Exit command mode
+ waitResponse();
+ }
+
+ template
+ void sendAT(Args... cmd)
+ {
+ streamWrite("AT", cmd..., GSM_NL);
+ stream.flush();
+ TINY_GSM_YIELD();
+ //DBG("### AT:", cmd...);
+ }
+
+ // TODO: Optimize this!
+ uint8_t waitResponse(uint32_t timeout, String& data,
+ GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ /*String r1s(r1); r1s.trim();
+ String r2s(r2); r2s.trim();
+ String r3s(r3); r3s.trim();
+ String r4s(r4); r4s.trim();
+ String r5s(r5); r5s.trim();
+ DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/
+ data.reserve(64);
+ int index = 0;
+ unsigned long startMillis = millis();
+ do {
+ TINY_GSM_YIELD();
+ while (stream.available() > 0) {
+ int a = streamRead();
+ if (a <= 0) {
+ continue; // Skip 0x00 bytes, just in case
+ }
+ data += (char)a;
+ if (r1 && data.endsWith(r1)) {
+ index = 1;
+ goto finish;
+ } else if (r2 && data.endsWith(r2)) {
+ index = 2;
+ goto finish;
+ } else if (r3 && data.endsWith(r3)) {
+ index = 3;
+ goto finish;
+ } else if (r4 && data.endsWith(r4)) {
+ index = 4;
+ goto finish;
+ } else if (r5 && data.endsWith(r5)) {
+ index = 5;
+ goto finish;
+ }
+ }
+ } while (millis() - startMillis < timeout);
+finish:
+ if (!index) {
+ data.trim();
+ data.replace(GSM_NL GSM_NL, GSM_NL);
+ data.replace(GSM_NL, "\r\n" " ");
+ if (data.length()) {
+ DBG("### Unhandled:", data, "\r\n");
+ } else {
+ DBG("### NO RESPONSE!\r\n");
+ }
+ } else {
+ data.trim();
+ data.replace(GSM_NL GSM_NL, GSM_NL);
+ data.replace(GSM_NL, "\r\n ");
+ if (data.length()) {
+ // DBG("<<< ", data);
+ }
+ }
+ return index;
+ }
+
+ uint8_t waitResponse(uint32_t timeout,
+ GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ String data;
+ return waitResponse(timeout, data, r1, r2, r3, r4, r5);
+ }
+
+ uint8_t waitResponse(GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
+ GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
+ {
+ return waitResponse(1000, r1, r2, r3, r4, r5);
+ }
+
+public:
+ Stream& stream;
+
+protected:
+ int guardTime;
+ XBeeType beeType;
+ GsmClient* sockets[TINY_GSM_MUX_COUNT];
+};
+
+#endif
diff --git a/drivers/TinyGSM/TinyGsmCommon.h b/drivers/TinyGSM/TinyGsmCommon.h
new file mode 100644
index 0000000..458cc90
--- /dev/null
+++ b/drivers/TinyGSM/TinyGsmCommon.h
@@ -0,0 +1,195 @@
+/**
+ * file TinyGsmCommon.h
+ * author Volodymyr Shymanskyy
+ * license LGPL-3.0
+ * copyright Copyright (c) 2016 Volodymyr Shymanskyy
+ * date Nov 2016
+ */
+
+#ifndef TinyGsmCommon_h
+#define TinyGsmCommon_h
+
+#if defined(SPARK) || defined(PARTICLE)
+#include "Particle.h"
+#elif defined(ARDUINO)
+#if ARDUINO >= 100
+#include "Arduino.h"
+#else
+#include "WProgram.h"
+#endif
+#endif
+
+#include
+#include "TinyGsmFifo.h"
+
+#ifndef TINY_GSM_YIELD
+#define TINY_GSM_YIELD() { delay(0); }
+#endif
+
+#define TINY_GSM_ATTR_NOT_AVAILABLE __attribute__((error("Not available on this modem type")))
+#define TINY_GSM_ATTR_NOT_IMPLEMENTED __attribute__((error("Not implemented")))
+
+#if defined(__AVR__)
+#define TINY_GSM_PROGMEM PROGMEM
+typedef const __FlashStringHelper* GsmConstStr;
+#define GFP(x) (reinterpret_cast(x))
+#define GF(x) F(x)
+#else
+#define TINY_GSM_PROGMEM
+typedef const char* GsmConstStr;
+#define GFP(x) x
+#define GF(x) x
+#endif
+
+#ifdef TINY_GSM_DEBUG
+namespace
+{
+template
+static void DBG(T last)
+{
+ TINY_GSM_DEBUG.println(last);
+}
+
+template
+static void DBG(T head, Args... tail)
+{
+ TINY_GSM_DEBUG.print(head);
+ TINY_GSM_DEBUG.print(' ');
+ DBG(tail...);
+}
+}
+#else
+#define DBG(...)
+#endif
+
+template
+const T& TinyGsmMin(const T& a, const T& b)
+{
+ return (b < a) ? b : a;
+}
+
+template
+const T& TinyGsmMax(const T& a, const T& b)
+{
+ return (b < a) ? a : b;
+}
+
+template
+uint32_t TinyGsmAutoBaud(T& SerialAT, uint32_t minimum = 9600, uint32_t maximum = 115200)
+{
+ static uint32_t rates[] = { 115200, 57600, 38400, 19200, 9600, 74400, 74880, 230400, 460800, 2400, 4800, 14400, 28800 };
+
+ for (unsigned i = 0; i < sizeof(rates)/sizeof(rates[0]); i++) {
+ uint32_t rate = rates[i];
+ if (rate < minimum || rate > maximum) {
+ continue;
+ }
+
+ DBG("Trying baud rate", rate, "...");
+ SerialAT.begin(rate);
+ delay(10);
+ for (int i=0; i<3; i++) {
+ SerialAT.print("AT\r\n");
+ String input = SerialAT.readString();
+ if (input.indexOf("OK") >= 0) {
+ DBG("Modem responded at rate", rate);
+ return rate;
+ }
+ }
+ }
+ return 0;
+}
+
+static inline
+IPAddress TinyGsmIpFromString(const String& strIP)
+{
+ int Parts[4] = {0, };
+ int Part = 0;
+ for (uint8_t i=0; i 3) {
+ return IPAddress(0,0,0,0);
+ }
+ continue;
+ } else if (c >= '0' && c <= '9') {
+ Parts[Part] *= 10;
+ Parts[Part] += c - '0';
+ } else {
+ if (Part == 3) {
+ break;
+ }
+ }
+ }
+ return IPAddress(Parts[0], Parts[1], Parts[2], Parts[3]);
+}
+
+static inline
+String TinyGsmDecodeHex7bit(String &instr)
+{
+ String result;
+ byte reminder = 0;
+ int bitstate = 7;
+ for (unsigned i=0; i> bitstate;
+ bitstate--;
+ if (bitstate == 0) {
+ char c = reminder;
+ result += c;
+ reminder = 0;
+ bitstate = 7;
+ }
+ }
+ return result;
+}
+
+static inline
+String TinyGsmDecodeHex8bit(String &instr)
+{
+ String result;
+ for (unsigned i=0; i
+
+class TinyGsmFifo
+{
+public:
+
+ TinyGsmFifo()
+ {
+ clear();
+ }
+
+ void clear()
+ {
+ _r = 0;
+ _w = 0;
+ }
+
+ // writing thread/context API
+ //-------------------------------------------------------------
+
+ bool writeable(void)
+ {
+ return free() > 0;
+ }
+
+ int free(void)
+ {
+ int s = _r - _w;
+ if (s <= 0) {
+ s += N;
+ }
+ return s - 1;
+ }
+
+ bool put(const T& c)
+ {
+ int i = _w;
+ int j = i;
+ i = _inc(i);
+ if (i == _r) { // !writeable()
+ return false;
+ }
+ _b[j] = c;
+ _w = i;
+ return true;
+ }
+
+ int put(const T* p, int n, bool t = false)
+ {
+ int c = n;
+ while (c) {
+ int f;
+ while ((f = free()) == 0) { // wait for space
+ if (!t) {
+ return n - c; // no more space and not blocking
+ }
+ /* nothing / just wait */;
+ }
+ // check free space
+ if (c < f) {
+ f = c;
+ }
+ int w = _w;
+ int m = N - w;
+ // check wrap
+ if (f > m) {
+ f = m;
+ }
+ memcpy(&_b[w], p, f);
+ _w = _inc(w, f);
+ c -= f;
+ p += f;
+ }
+ return n - c;
+ }
+
+ // reading thread/context API
+ // --------------------------------------------------------
+
+ bool readable(void)
+ {
+ return (_r != _w);
+ }
+
+ size_t size(void)
+ {
+ int s = _w - _r;
+ if (s < 0) {
+ s += N;
+ }
+ return s;
+ }
+
+ bool get(T* p)
+ {
+ int r = _r;
+ if (r == _w) { // !readable()
+ return false;
+ }
+ *p = _b[r];
+ _r = _inc(r);
+ return true;
+ }
+
+ int get(T* p, int n, bool t = false)
+ {
+ int c = n;
+ while (c) {
+ int f;
+ for (;;) { // wait for data
+ f = size();
+ if (f) {
+ break; // free space
+ }
+ if (!t) {
+ return n - c; // no space and not blocking
+ }
+ /* nothing / just wait */;
+ }
+ // check available data
+ if (c < f) {
+ f = c;
+ }
+ int r = _r;
+ int m = N - r;
+ // check wrap
+ if (f > m) {
+ f = m;
+ }
+ memcpy(p, &_b[r], f);
+ _r = _inc(r, f);
+ c -= f;
+ p += f;
+ }
+ return n - c;
+ }
+
+private:
+ int _inc(int i, int n = 1)
+ {
+ return (i + n) % N;
+ }
+
+ T _b[N];
+ int _w;
+ int _r;
+};
+
+#endif
diff --git a/drivers/TinyGSM/keywords.txt b/drivers/TinyGSM/keywords.txt
new file mode 100644
index 0000000..33f98c4
--- /dev/null
+++ b/drivers/TinyGSM/keywords.txt
@@ -0,0 +1,26 @@
+#######################################
+# Data types (KEYWORD1)
+#######################################
+TinyGsm KEYWORD1
+TinyGsmClient KEYWORD1
+TinyGsmClientSecure KEYWORD1
+
+SerialAT KEYWORD1
+SerialMon KEYWORD1
+
+#######################################
+# Methods and Functions (KEYWORD2)
+#######################################
+begin KEYWORD2
+restart KEYWORD2
+waitForNetwork KEYWORD2
+getSimStatus KEYWORD2
+gprsConnect KEYWORD2
+gprsDisconnect KEYWORD2
+isGprsConnected KEYWORD2
+isNetworkConnected KEYWORD2
+factoryReset KEYWORD2
+
+#######################################
+# Literals (LITERAL1)
+#######################################
diff --git a/examples/GatewayGSMMQTTClient/GatewayGSMMQTTClient.ino b/examples/GatewayGSMMQTTClient/GatewayGSMMQTTClient.ino
new file mode 100644
index 0000000..c9b5728
--- /dev/null
+++ b/examples/GatewayGSMMQTTClient/GatewayGSMMQTTClient.ino
@@ -0,0 +1,137 @@
+/**
+ * The MySensors Arduino library handles the wireless radio link and protocol
+ * between your home built sensors/actuators and HA controller of choice.
+ * The sensors forms a self healing radio network with optional repeaters. Each
+ * repeater and gateway builds a routing tables in EEPROM which keeps track of the
+ * network topology allowing messages to be routed to nodes.
+ *
+ * Created by Henrik Ekblad
+ * Copyright (C) 2013-2015 Sensnology AB
+ * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
+ *
+ * Documentation: http://www.mysensors.org
+ * Support Forum: http://forum.mysensors.org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ *******************************
+ *
+ * REVISION HISTORY
+ * Version 1.0 - Rait Lotamõis
+ *
+ * DESCRIPTION
+ * The TinyGSM MQTT gateway sends radio network (or locally attached sensors) data to your MQTT broker using a GSM modem or optionally an ESP8266 as a WiFi modem.
+ * The node also listens to MY_MQTT_TOPIC_PREFIX and sends out those messages to the radio network
+ *
+ * LED purposes:
+ * - To use the feature, uncomment WITH_LEDS_BLINKING in MyConfig.h
+ * - RX (green) - blink fast on radio message recieved. In inclusion mode will blink fast only on presentation recieved
+ * - TX (yellow) - blink fast on radio message transmitted. In inclusion mode will blink slowly
+ * - ERR (red) - fast blink on error during transmission error or recieve crc error
+ */
+
+
+// Enable debug prints to serial monitor
+#define MY_DEBUG
+
+// Enables and select radio type (if attached)
+#define MY_RADIO_NRF24
+//#define MY_RADIO_RFM69
+//#define MY_RADIO_RFM95
+
+#define MY_GATEWAY_MQTT_CLIENT
+
+// Enable GSM modem support
+#define MY_GATEWAY_TINYGSM
+
+// Define your modem
+#define TINY_GSM_MODEM_SIM800
+// #define TINY_GSM_MODEM_SIM808
+// #define TINY_GSM_MODEM_SIM900
+// #define TINY_GSM_MODEM_A6
+// #define TINY_GSM_MODEM_A7
+// #define TINY_GSM_MODEM_M590
+// #define TINY_GSM_ESP8266
+
+// leave empty anything that does not apply
+#define MY_GSM_APN "internet"
+//#define MY_GSM_PIN "1234"
+#define MY_GSM_USR ""
+//If using a GSM modem, this stands for your GSM connection password. If using WiFi, it's your wireless password.
+#define MY_GSM_PSW ""
+//#define MY_GSM_SSID ""
+
+// Use Hardware Serial on Mega, Leonardo, Micro
+//#define SerialAT Serial1
+// or Software Serial on Uno, Nano
+#include
+#define MY_GSM_RX 4
+#define MY_GSM_TX 5
+
+// If your Mosquitto is old fashioned and does not support 3.1.1
+//#define MQTT_VERSION MQTT_VERSION_3_1
+
+// Set this node's subscribe and publish topic prefix
+#define MY_MQTT_PUBLISH_TOPIC_PREFIX "mygateway1-out"
+#define MY_MQTT_SUBSCRIBE_TOPIC_PREFIX "mygateway1-in"
+
+// Set MQTT client id
+#define MY_MQTT_CLIENT_ID "mysensors-1"
+
+// Enable these if your MQTT broker requires usenrame/password
+//#define MY_MQTT_USER "username"
+//#define MY_MQTT_PASSWORD "password"
+
+// Enable MY_IP_ADDRESS here if you want a static ip address (no DHCP)
+//#define MY_IP_ADDRESS 192,168,32,220
+
+// If using static ip you can define Gateway and Subnet address as well
+//#define MY_IP_GATEWAY_ADDRESS 192,168,32,1
+//#define MY_IP_SUBNET_ADDRESS 255,255,255,0
+
+// MQTT broker ip address or url. Define one or the other.
+//#define MY_CONTROLLER_URL_ADDRESS "mymqttbroker.com"
+#define MY_CONTROLLER_IP_ADDRESS 192, 168, 178, 68
+
+// The MQTT broker port to to open
+#define MY_PORT 1883
+
+/*
+// Enable inclusion mode
+#define MY_INCLUSION_MODE_FEATURE
+// Enable Inclusion mode button on gateway
+//#define MY_INCLUSION_BUTTON_FEATURE
+// Set inclusion mode duration (in seconds)
+#define MY_INCLUSION_MODE_DURATION 60
+// Digital pin used for inclusion mode button
+//#define MY_INCLUSION_MODE_BUTTON_PIN 3
+
+// Set blinking period
+#define MY_DEFAULT_LED_BLINK_PERIOD 300
+
+// Flash leds on rx/tx/err
+// Uncomment to override default HW configurations
+//#define MY_DEFAULT_ERR_LED_PIN 16 // Error led pin
+//#define MY_DEFAULT_RX_LED_PIN 16 // Receive led pin
+//#define MY_DEFAULT_TX_LED_PIN 16 // the PCB, on board LED
+*/
+
+#include
+
+void setup()
+{
+ // Setup locally attached sensors
+}
+
+void presentation()
+{
+ // Present locally attached sensors here
+}
+
+void loop()
+{
+ // Send locally attached sensors data here
+}
+
diff --git a/keywords.txt b/keywords.txt
index 473c5da..476a0ec 100644
--- a/keywords.txt
+++ b/keywords.txt
@@ -112,6 +112,17 @@ MY_NODE_LOCK_COUNTER_MAX LITERAL1
MY_HW_HAS_GETENTROPY LITERAL1
hwGetentropy KEYWORD2
+# TinyGSM
+MY_GATEWAY_TINYGSM LITERAL1
+MY_GSM_APN LITERAL1
+MY_GSM_BAUDRATE LITERAL1
+MY_GSM_PIN LITERAL1
+MY_GSM_PSW LITERAL1
+MY_GSM_RX LITERAL1
+MY_GSM_SSID LITERAL1
+MY_GSM_TX LITERAL1
+MY_GSM_USR LITERAL1
+
# SoftSPI
MY_SOFTSPI LITERAL1
MY_SOFT_SPI_SCK_PIN LITERAL1