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