diff --git a/.github/workflows/build_examples.yaml b/.github/workflows/build_examples.yaml index 4fb896f..b66e837 100644 --- a/.github/workflows/build_examples.yaml +++ b/.github/workflows/build_examples.yaml @@ -32,6 +32,8 @@ jobs: TINY_GSM_MODEM_SIM5360, TINY_GSM_MODEM_SIM7600, TINY_GSM_MODEM_SIM7000, + TINY_GSM_MODEM_SIM7000SSL, + TINY_GSM_MODEM_SIM7070, TINY_GSM_MODEM_UBLOX, TINY_GSM_MODEM_SARAR4, TINY_GSM_MODEM_XBEE, diff --git a/README.md b/README.md index ccc1e2c..c1d31c9 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,31 @@ If you like **TinyGSM** - give it a star, or fork it and contribute! You can also join our chat: [![Gitter](https://img.shields.io/gitter/room/vshymanskyy/TinyGSM.svg)](https://gitter.im/tinygsm) +- [Supported modems](#supported-modems) + - [Supported boards/modules](#supported-boardsmodules) +- [Features](#features) +- [Getting Started](#getting-started) + - [First Steps](#first-steps) + - [Writing your own code](#writing-your-own-code) + - [If you have any issues](#if-you-have-any-issues) +- [How does it work?](#how-does-it-work) +- [API Reference](#api-reference) +- [Troubleshooting](#troubleshooting) + - [Ensure stable data & power connection](#ensure-stable-data--power-connection) + - [Baud rates](#baud-rates) + - [Broken initial configuration](#broken-initial-configuration) + - [Diagnostics sketch](#diagnostics-sketch) + - [Web request formatting problems - "but it works with PostMan"](#web-request-formatting-problems---but-it-works-with-postman) + - [SoftwareSerial problems](#softwareserial-problems) + - [ESP32 Notes](#esp32-notes) + - [HardwareSerial](#hardwareserial) + - [HttpClient](#httpclient) + - [SAMD21](#samd21) + - [Goouuu Tech IOT-GA6 vs AI-Thinker A6 confusion](#goouuu-tech-iot-ga6-vs-ai-thinker-a6-confusion) + - [SIM800 and SSL](#sim800-and-ssl) + - [Which versions of the SIM7000 code to use](#which-versions-of-the-sim7000-code-to-use) + - [License](#license) + ### Arduino Client interface support This library is easy to integrate with lots of sketches which use Ethernet or WiFi. **PubSubClient ([MQTT](http://mqtt.org/))**, **[Blynk](http://blynk.cc)**, **HTTP Client** and **File Download** examples are provided. @@ -41,6 +66,7 @@ TinyGSM also pulls data gently from the modem (whenever possible), so it can ope - SIMCom WCDMA/HSPA/HSPA+ Modules (SIM5360, SIM5320, SIM5300E, SIM5300E/A) - SIMCom LTE Modules (SIM7100E, SIM7500E, SIM7500A, SIM7600C, SIM7600E) - SIMCom SIM7000E/A/G CAT-M1/NB-IoT Module +- SIMCom SIM7070/SIM7080/SIM7090 CAT-M1/NB-IoT Module ***(alpha)*** - AI-Thinker A6, A6C, A7, A20 - ESP8266 (AT commands interface, similar to GSM modems) - Digi XBee WiFi and Cellular (using XBee command mode) @@ -86,7 +112,8 @@ Watch this repo for new updates! And of course, contributions are welcome ;) - Sequans Monarch - 6 - SIM 800/900 - 5 - SIM 5360/5320/5300/7100 - 10 - - SIM7000 - 2 (8 possible without SSL, only 2 with) + - SIM7000 - 8 possible without SSL, only 2 with + - SIM 7070/7080/7090 - 12 - SIM 7500/7600/7800 - 10 - u-blox 2G/3G - 7 - u-blox SARA R4/N4 - 7 @@ -96,7 +123,7 @@ Watch this repo for new updates! And of course, contributions are welcome ;) - SSL/TLS (HTTPS) - Supported on: - SIM800, SIM7000, u-Blox, XBee _cellular_, ESP8266, and Sequans Monarch - - Note: only some device models or firmware revisions have this feature (SIM8xx R14.18, A7, etc.) + - Note: **only some device models or firmware revisions have this feature** (SIM8xx R14.18, A7, etc.) - Not yet supported on: - Quectel modems, SIM 5360/5320/7100, SIM 7500/7600/7800 - Not possible on: @@ -207,7 +234,7 @@ The general flow of your code should be: -#### If you have any issues: +#### If you have any issues 1. Read the whole README (you're looking at it!), particularly the troubleshooting section below. 2. Some boards require [**special configuration**](https://github.com/vshymanskyy/TinyGSM/wiki/Board-configuration). @@ -238,6 +265,13 @@ Improving the power supply actually solves stability problems in **many** cases! - Do not put your wires next to noisy signal sources (buck converters, antennas, oscillators etc.) - If everything else seems to be working but you are unable to connect to the network, check your power supply! +### Baud rates + +Most modules support some sort of "auto-bauding" feature where the module will attempt to adjust it's baud rate to match what it is receiving. +TinyGSM also implements its own auto bauding function (`TinyGsmAutoBaud(SerialAT, GSM_AUTOBAUD_MIN, GSM_AUTOBAUD_MAX);`). +While very useful when initially connecting to a module and doing tests, these should **NOT** be used in any sort of production code. +Once you've established communication with the module, set the baud rate using the `setBaud(#)` function and stick with that rate. + ### Broken initial configuration Sometimes (especially if you played with AT commands), your module configuration may become invalid. @@ -338,6 +372,24 @@ Please [refer to this comment](https://github.com/vshymanskyy/TinyGSM/issues/102 It turns out that **Goouuu Tech IOT-GA6** is not the same as **AI-Thinker A6**. Unfortunately IOT-GA6 is not supported out of the box yet. There are some hints that IOT-GA6 firmware may be updated to match A6... See [this topic](https://github.com/vshymanskyy/TinyGSM/issues/164). +### SIM800 and SSL + +Some, but not all, versions of the SIM800 support SSL. +Having SSL support depends on the firmware version and the individual module. +Users have had varying levels of success in using SSL on the SIM800 even with apparently identical firmware. +If you need SSL and it does not appear to be working on your SIM800, try a different module or try using a secondary SSL library. + +### Which versions of the SIM7000 code to use + +There are two versions of the SIM7000 code, one using `TINY_GSM_MODEM_SIM7000` and another with `TINY_GSM_MODEM_SIM7000SSL`. +The `TINY_GSM_MODEM_SIM7000` version *does not support SSL*. +The `TINY_GSM_MODEM_SIM7000SSL` version supports both SSL *and unsecured connections*. +So why are there two versions? +The "SSL" version uses the SIM7000's "application" commands while the other uses the "TCP-IP toolkit". +Depending on your region/firmware, one or the other may not work for you. +Try both and use whichever is more stable. +If you do not need SSL, I recommend starting with `TINY_GSM_MODEM_SIM7000`. + __________ ### License diff --git a/examples/AllFunctions/AllFunctions.ino b/examples/AllFunctions/AllFunctions.ino index 7f1c3c5..98a0adc 100644 --- a/examples/AllFunctions/AllFunctions.ino +++ b/examples/AllFunctions/AllFunctions.ino @@ -164,9 +164,7 @@ void loop() { #if TINY_GSM_TEST_GPRS // Unlock your SIM card with a PIN if needed - if (GSM_PIN && modem.getSimStatus() != 3) { - modem.simUnlock(GSM_PIN); - } + if (GSM_PIN && modem.getSimStatus() != 3) { modem.simUnlock(GSM_PIN); } #endif #if TINY_GSM_TEST_WIFI @@ -190,9 +188,7 @@ void loop() { return; } - if (modem.isNetworkConnected()) { - DBG("Network connected"); - } + if (modem.isNetworkConnected()) { DBG("Network connected"); } #if TINY_GSM_TEST_GPRS DBG("Connecting to", apn); @@ -233,7 +229,7 @@ void loop() { #if TINY_GSM_TEST_TCP && defined TINY_GSM_MODEM_HAS_TCP TinyGsmClient client(modem, 0); - const int port = 80; + const int port = 80; DBG("Connecting to ", server); if (!client.connect(server, port)) { DBG("... failed"); @@ -252,7 +248,7 @@ void loop() { // Read data start = millis(); - while (client.connected() && millis() - start < 5000L) { + while (client.connected() && millis() - start < 10000L) { while (client.available()) { SerialMon.write(client.read()); start = millis(); @@ -264,7 +260,7 @@ void loop() { #if TINY_GSM_TEST_SSL && defined TINY_GSM_MODEM_HAS_SSL TinyGsmClientSecure secureClient(modem, 1); - const int securePort = 443; + const int securePort = 443; DBG("Connecting to ", server); if (!secureClient.connect(server, securePort)) { DBG("... failed"); @@ -294,7 +290,7 @@ void loop() { #endif #if TINY_GSM_TEST_CALL && defined TINY_GSM_MODEM_HAS_CALLING && \ - defined CALL_TARGET + defined CALL_TARGET DBG("Calling:", CALL_TARGET); // This is NOT supported on M590 @@ -308,9 +304,7 @@ void loop() { modem.dtmfSend('A', 1000); // Play DTMF 0..4, default duration (100ms) - for (char tone = '0'; tone <= '4'; tone++) { - modem.dtmfSend(tone); - } + for (char tone = '0'; tone <= '4'; tone++) { modem.dtmfSend(tone); } delay(5000); @@ -344,7 +338,7 @@ void loop() { int day = 0; int hour = 0; int min = 0; - int sec = 0; + int sec = 0; for (int8_t i = 15; i; i--) { DBG("Requesting current GSM location"); if (modem.getGsmLocation(&lat, &lon, &accuracy, &year, &month, &day, &hour, @@ -384,7 +378,7 @@ void loop() { for (int8_t i = 15; i; i--) { DBG("Requesting current GPS/GNSS/GLONASS location"); if (modem.getGPS(&lat2, &lon2, &speed2, &alt2, &vsat2, &usat2, &accuracy2, - &year2, &month2, &day2, &hour2, &min2, &sec2)) { + &year2, &month2, &day2, &hour2, &min2, &sec2)) { DBG("Latitude:", String(lat2, 8), "\tLongitude:", String(lon2, 8)); DBG("Speed:", speed2, "\tAltitude:", alt2); DBG("Visible Satellites:", vsat2, "\tUsed Satellites:", usat2); @@ -405,17 +399,17 @@ void loop() { #endif #if TINY_GSM_TEST_TIME && defined TINY_GSM_MODEM_HAS_TIME - int year3 = 0; - int month3 = 0; - int day3 = 0; - int hour3 = 0; - int min3 = 0; - int sec3 = 0; + int year3 = 0; + int month3 = 0; + int day3 = 0; + int hour3 = 0; + int min3 = 0; + int sec3 = 0; float timezone = 0; for (int8_t i = 5; i; i--) { DBG("Requesting current network time"); if (modem.getNetworkTime(&year3, &month3, &day3, &hour3, &min3, &sec3, - &timezone)) { + &timezone)) { DBG("Year:", year3, "\tMonth:", month3, "\tDay:", day3); DBG("Hour:", hour3, "\tMinute:", min3, "\tSecond:", sec3); DBG("Timezone:", timezone); @@ -470,7 +464,5 @@ void loop() { DBG("End of tests."); // Do nothing forevermore - while (true) { - modem.maintain(); - } + while (true) { modem.maintain(); } } diff --git a/library.json b/library.json index 208a7d1..5cbd84e 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "TinyGSM", - "version": "0.10.9", + "version": "0.11.0", "description": "A small Arduino library for GPRS modules, that just works. Includes examples for Blynk, MQTT, File Download, and Web Client. Supports many GSM, LTE, and WiFi modules with AT command interfaces.", "keywords": "GSM, AT commands, AT, SIM800, SIM900, A6, A7, M590, ESP8266, SIM7000, SIM800A, SIM800C, SIM800L, SIM800H, SIM808, SIM868, SIM900A, SIM900D, SIM908, SIM968, M95, MC60, MC60E, BG96, ublox, Quectel, SIMCOM, AI Thinker, LTE, LTE-M", "authors": [ diff --git a/src/TinyGsmClient.h b/src/TinyGsmClient.h index 39bfd0b..6eb9d2d 100644 --- a/src/TinyGsmClient.h +++ b/src/TinyGsmClient.h @@ -30,7 +30,19 @@ typedef TinyGsmSim800::GsmClientSim800 TinyGsmClient; #include "TinyGsmClientSIM7000.h" typedef TinyGsmSim7000 TinyGsm; typedef TinyGsmSim7000::GsmClientSim7000 TinyGsmClient; -typedef TinyGsmSim7000::GsmClientSecureSIM7000 TinyGsmClientSecure; + +#elif defined(TINY_GSM_MODEM_SIM7000SSL) +#include "TinyGsmClientSIM7000SSL.h" +typedef TinyGsmSim7000SSL TinyGsm; +typedef TinyGsmSim7000SSL::GsmClientSim7000SSL TinyGsmClient; +typedef TinyGsmSim7000SSL::GsmClientSecureSIM7000SSL TinyGsmClientSecure; + +#elif defined(TINY_GSM_MODEM_SIM7070) || defined(TINY_GSM_MODEM_SIM7080) || \ + defined(TINY_GSM_MODEM_SIM7090) +#include "TinyGsmClientSIM70x0.h" +typedef TinyGsmSim70x0 TinyGsm; +typedef TinyGsmSim70x0::GsmClientSim70x0 TinyGsmClient; +typedef TinyGsmSim70x0::GsmClientSecureSIM70x0 TinyGsmClientSecure; #elif defined(TINY_GSM_MODEM_SIM5320) || defined(TINY_GSM_MODEM_SIM5360) || \ defined(TINY_GSM_MODEM_SIM5300) || defined(TINY_GSM_MODEM_SIM7100) diff --git a/src/TinyGsmClientBG96.h b/src/TinyGsmClientBG96.h index 41892cd..bcc6732 100644 --- a/src/TinyGsmClientBG96.h +++ b/src/TinyGsmClientBG96.h @@ -24,6 +24,7 @@ #include "TinyGsmTCP.tpp" #include "TinyGsmTemperature.tpp" #include "TinyGsmTime.tpp" +#include "TinyGsmNTP.tpp" #define GSM_NL "\r\n" static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; @@ -49,6 +50,7 @@ class TinyGsmBG96 : public TinyGsmModem, public TinyGsmCalling, public TinyGsmSMS, public TinyGsmTime, + public TinyGsmNTP, public TinyGsmGPS, public TinyGsmBattery, public TinyGsmTemperature { @@ -58,6 +60,7 @@ class TinyGsmBG96 : public TinyGsmModem, friend class TinyGsmCalling; friend class TinyGsmSMS; friend class TinyGsmTime; + friend class TinyGsmNTP; friend class TinyGsmGPS; friend class TinyGsmBattery; friend class TinyGsmTemperature; @@ -477,6 +480,27 @@ class TinyGsmBG96 : public TinyGsmModem, return true; } + /* + * NTP server functions + */ + + byte NTPServerSyncImpl(String server = "pool.ntp.org", byte TimeZone = 3) { + // Request network synchronization + // AT+QNTP=,[,][,] + sendAT(GF("+QNTP=1,\""), server, '"'); + if (waitResponse(10000L, GF("+QNTP:"))) { + String result = stream.readStringUntil(','); + streamSkipUntil('\n'); + result.trim(); + if (TinyGsmIsValidNumber(result)) { return result.toInt(); } + } else { + return -1; + } + return -1; + } + + String ShowNTPErrorImpl(byte error) TINY_GSM_ATTR_NOT_IMPLEMENTED; + /* * Battery functions */ diff --git a/src/TinyGsmClientSIM5360.h b/src/TinyGsmClientSIM5360.h index c4c2b83..f181036 100644 --- a/src/TinyGsmClientSIM5360.h +++ b/src/TinyGsmClientSIM5360.h @@ -23,6 +23,7 @@ #include "TinyGsmTCP.tpp" #include "TinyGsmTemperature.tpp" #include "TinyGsmTime.tpp" +#include "TinyGsmNTP.tpp" #define GSM_NL "\r\n" static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; @@ -47,6 +48,7 @@ class TinyGsmSim5360 : public TinyGsmModem, public TinyGsmTCP, public TinyGsmSMS, public TinyGsmTime, + public TinyGsmNTP, public TinyGsmGSMLocation, public TinyGsmBattery, public TinyGsmTemperature { @@ -55,6 +57,7 @@ class TinyGsmSim5360 : public TinyGsmModem, friend class TinyGsmTCP; friend class TinyGsmSMS; friend class TinyGsmTime; + friend class TinyGsmNTP; friend class TinyGsmGSMLocation; friend class TinyGsmBattery; friend class TinyGsmTemperature; @@ -270,12 +273,17 @@ class TinyGsmSim5360 : public TinyGsmModem, return res; } - String setNetworkMode(uint8_t mode) { - sendAT(GF("+CNMP="), mode); - if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return "OK"; } - String res = stream.readStringUntil('\n'); + int16_t getNetworkMode() { + sendAT(GF("+CNMP?")); + if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return false; } + int16_t mode = streamGetIntBefore('\n'); waitResponse(); - return res; + return mode; + } + + bool setNetworkMode(uint8_t mode) { + sendAT(GF("+CNMP="), mode); + return waitResponse() == 1; } String getLocalIPImpl() { @@ -435,6 +443,11 @@ class TinyGsmSim5360 : public TinyGsmModem, protected: // Can follow the standard CCLK function in the template + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + /* * Battery functions */ diff --git a/src/TinyGsmClientSIM7000.h b/src/TinyGsmClientSIM7000.h index 10643b6..e708845 100644 --- a/src/TinyGsmClientSIM7000.h +++ b/src/TinyGsmClientSIM7000.h @@ -12,49 +12,17 @@ // #define TINY_GSM_DEBUG Serial // #define TINY_GSM_USE_HEX -#define TINY_GSM_MUX_COUNT 2 +#define TINY_GSM_MUX_COUNT 8 #define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE -#include "TinyGsmBattery.tpp" -#include "TinyGsmGPRS.tpp" -#include "TinyGsmGPS.tpp" -#include "TinyGsmModem.tpp" -#include "TinyGsmSMS.tpp" +#include "TinyGsmClientSIM70xx.h" #include "TinyGsmTCP.tpp" -#include "TinyGsmTime.tpp" - -#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; -#if defined TINY_GSM_DEBUG -static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; -static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; -#endif -enum RegStatus { - REG_NO_RESULT = -1, - REG_UNREGISTERED = 0, - REG_SEARCHING = 2, - REG_DENIED = 3, - REG_OK_HOME = 1, - REG_OK_ROAMING = 5, - REG_UNKNOWN = 4, -}; -class TinyGsmSim7000 : public TinyGsmModem, - public TinyGsmGPRS, - public TinyGsmTCP, - public TinyGsmSMS, - public TinyGsmGPS, - public TinyGsmTime, - public TinyGsmBattery { - friend class TinyGsmModem; - friend class TinyGsmGPRS; +class TinyGsmSim7000 : public TinyGsmSim70xx, + public TinyGsmTCP { + friend class TinyGsmSim70xx; friend class TinyGsmTCP; - friend class TinyGsmSMS; - friend class TinyGsmGPS; - friend class TinyGsmTime; - friend class TinyGsmBattery; /* * Inner Client @@ -99,7 +67,7 @@ class TinyGsmSim7000 : public TinyGsmModem, void stop(uint32_t maxWaitMs) { dumpModemBuffer(maxWaitMs); - at->sendAT(GF("+CACLOSE="), mux); + at->sendAT(GF("+CIPCLOSE="), mux); sock_connected = false; at->waitResponse(3000); } @@ -117,79 +85,14 @@ class TinyGsmSim7000 : public TinyGsmModem, /* * Inner Secure Client */ - - class GsmClientSecureSIM7000 : public GsmClientSim7000 { - public: - GsmClientSecureSIM7000() {} - - GsmClientSecureSIM7000(TinyGsmSim7000& modem, uint8_t mux = 0) - : GsmClientSim7000(modem, mux) {} - - public: - bool setCertificate(const String& certificateName) { - return at->setCertificate(certificateName, mux); - } - - int connect(const char* host, uint16_t port, int timeout_s) override { - stop(); - TINY_GSM_YIELD(); - rx.clear(); - sock_connected = at->modemConnect(host, port, mux, true, timeout_s); - return sock_connected; - } - TINY_GSM_CLIENT_CONNECT_OVERRIDES - }; - - public: - boolean isValidNumber(String str) { - if (!(str.charAt(0) == '+' || str.charAt(0) == '-' || - isDigit(str.charAt(0)))) - return false; - - for (byte i = 1; i < str.length(); i++) { - if (!(isDigit(str.charAt(i)) || str.charAt(i) == '.')) { return false; } - } - return true; - } - - String ShowNTPError(byte error) { - switch (error) { - case 1: return "Network time synchronization is successful"; - case 61: return "Network error"; - case 62: return "DNS resolution error"; - case 63: return "Connection error"; - case 64: return "Service response error"; - case 65: return "Service response timeout"; - default: return "Unknown error: " + String(error); - } - } - - byte NTPServerSync(String server = "pool.ntp.org", byte TimeZone = 3) { - // Set GPRS bearer profile to associate with NTP sync - sendAT(GF("+CNTPCID=1")); - if (waitResponse(10000L) != 1) { return -1; } - - // Set NTP server and timezone - sendAT(GF("+CNTP="), server, ',', String(TimeZone)); - if (waitResponse(10000L) != 1) { return -1; } - - // Request network synchronization - sendAT(GF("+CNTP")); - if (waitResponse(10000L, GF(GSM_NL "+CNTP:"))) { - String result = stream.readStringUntil('\n'); - result.trim(); - if (isValidNumber(result)) { return result.toInt(); } - } else { - return -1; - } - return -1; - } + // NOTE: Use modem TINYGSMSIM7000SSL for a secure client! /* * Constructor */ public: - explicit TinyGsmSim7000(Stream& stream) : stream(stream), certificates() { + explicit TinyGsmSim7000(Stream& stream) + : TinyGsmSim70xx(stream) { memset(sockets, 0, sizeof(sockets)); } @@ -235,165 +138,23 @@ class TinyGsmSim7000 : public TinyGsmModem, } } - String getModemNameImpl() { - String name = "SIMCom SIM7000"; - - sendAT(GF("+GMM")); - String res2; - if (waitResponse(1000L, res2) != 1) { return name; } - res2.replace(GSM_NL "OK" GSM_NL, ""); - res2.replace("_", " "); - res2.trim(); - - name = res2; - return name; - } - - bool factoryDefaultImpl() { // these commands aren't supported - 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; - } - /* * Power functions */ protected: - bool restartImpl(const char* pin = NULL) { - if (!setPhoneFunctionality(0)) { return false; } - if (!setPhoneFunctionality(1, true)) { return false; } - waitResponse(10000L, GF("SMS Ready"), GF("RDY")); - return init(pin); - } - - bool powerOffImpl() { - sendAT(GF("+CPOWD=1")); - return waitResponse(GF("NORMAL POWER DOWN")) == 1; - } - - // During sleep, the SIM7000 module has its serial communication disabled. - // In order to reestablish communication pull the DRT-pin of the SIM7000 - // module LOW for at least 50ms. Then use this function to disable sleep mode. - // The DTR-pin can then be released again. - bool sleepEnableImpl(bool enable = true) { - sendAT(GF("+CSCLK="), enable); - return waitResponse() == 1; - } - - bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { - sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); - return waitResponse(10000L) == 1; - } + // Follows the SIM70xx template /* * Generic network functions */ - public: - RegStatus getRegistrationStatus() { - RegStatus epsStatus = (RegStatus)getRegistrationStatusXREG("CEREG"); - // If we're connected on EPS, great! - if (epsStatus == REG_OK_HOME || epsStatus == REG_OK_ROAMING) { - return epsStatus; - } else { - // Otherwise, check GPRS network status - // We could be using GPRS fall-back or the board could be being moody - return (RegStatus)getRegistrationStatusXREG("CGREG"); - } - } - protected: - bool setCertificate(const String& certificateName, const uint8_t mux = 0) { - if (mux >= TINY_GSM_MUX_COUNT) return false; - certificates[mux] = certificateName; - return true; - } - - - bool isNetworkConnectedImpl() { - RegStatus s = getRegistrationStatus(); - return (s == REG_OK_HOME || s == REG_OK_ROAMING); - } - - public: - String getNetworkModes() { - // Get the help string, not the setting value - sendAT(GF("+CNMP=?")); - if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return ""; } - String res = stream.readStringUntil('\n'); - waitResponse(); - return res; - } - - bool getNetworkMode(int16_t& mode) { - sendAT(GF("+CNMP?")); - if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return false; } - mode = streamGetIntBefore('\n'); - waitResponse(); - return true; - } - - String setNetworkMode(uint8_t mode) { - sendAT(GF("+CNMP="), mode); - if (waitResponse() != 1) return ""; - return "OK"; - } - - String getPreferredModes() { - // Get the help string, not the setting value - sendAT(GF("+CMNB=?")); - if (waitResponse(GF(GSM_NL "+CMNB:")) != 1) { return ""; } - String res = stream.readStringUntil('\n'); - waitResponse(); - return res; - } - - bool getPreferredMode(int16_t& mode) { - sendAT(GF("+CMNB?")); - if (waitResponse(GF(GSM_NL "+CMNB:")) != 1) { return false; } - mode = streamGetIntBefore('\n'); - waitResponse(); - return true; - } - - String setPreferredMode(uint8_t mode) { - sendAT(GF("+CMNB="), mode); - if (waitResponse() != 1) return ""; - return "OK"; - } - - bool getNetworkSystemMode(bool& n, int16_t& stat) { - // n: whether to automatically report the system mode info - // stat: the current service. 0 if it not connected - sendAT(GF("+CNSMOD?")); - if (waitResponse(GF(GSM_NL "+CNSMOD:")) != 1) { return false; } - n = streamGetIntBefore(',') != 0; - stat = streamGetIntBefore('\n'); - waitResponse(); - return true; - } - - String setNetworkSystemMode(bool n) { - // n: whether to automatically report the system mode info - sendAT(GF("+CNSMOD="), int8_t(n)); - if (waitResponse() != 1) return ""; - return "OK"; - } - String getLocalIPImpl() { - sendAT(GF("+CNACT?")); - if (waitResponse(GF(GSM_NL "+CNACT:")) != 1) { return ""; } - streamSkipUntil('\"'); - String res = stream.readStringUntil('\"'); - waitResponse(); + 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; } @@ -405,24 +166,23 @@ class TinyGsmSim7000 : public TinyGsmModem, const char* pwd = NULL) { gprsDisconnect(); - // Open data connection - sendAT(GF("+CNACT=1,\""), apn, GF("\"")); - if (waitResponse(60000L) != 1) { return false; } - - // Set the Bearer for the IP - sendAT(GF( - "+SAPBR=3,1,\"Contype\",\"GPRS\"")); // Set the connection type to GPRS + // Bearer settings for applications based on IP + // Set the connection type to GPRS + sendAT(GF("+SAPBR=3,1,\"Contype\",\"GPRS\"")); waitResponse(); - sendAT(GF("+SAPBR=3,1,\"APN\",\""), apn, '"'); // Set the APN + // Set the APN + sendAT(GF("+SAPBR=3,1,\"APN\",\""), apn, '"'); waitResponse(); + // Set the user name if (user && strlen(user) > 0) { - sendAT(GF("+SAPBR=3,1,\"USER\",\""), user, '"'); // Set the user name + sendAT(GF("+SAPBR=3,1,\"USER\",\""), user, '"'); waitResponse(); } + // Set the password if (pwd && strlen(pwd) > 0) { - sendAT(GF("+SAPBR=3,1,\"PWD\",\""), pwd, '"'); // Set the password + sendAT(GF("+SAPBR=3,1,\"PWD\",\""), pwd, '"'); waitResponse(); } @@ -430,6 +190,10 @@ class TinyGsmSim7000 : public TinyGsmModem, sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); waitResponse(); + // Attach to GPRS + sendAT(GF("+CGATT=1")); + if (waitResponse(60000L) != 1) { return false; } + // Activate the PDP context sendAT(GF("+CGACT=1,1")); waitResponse(60000L); @@ -441,24 +205,39 @@ class TinyGsmSim7000 : public TinyGsmModem, sendAT(GF("+SAPBR=2,1")); if (waitResponse(30000L) != 1) { return false; } - // Attach to GPRS - sendAT(GF("+CGATT=1")); + // Set the TCP application toolkit to multi-IP + sendAT(GF("+CIPMUX=1")); + if (waitResponse() != 1) { return false; } + + // Put the TCP application toolkit in "quick send" mode + // (thus no extra "Send OK") + sendAT(GF("+CIPQSEND=1")); + if (waitResponse() != 1) { return false; } + + // Set the TCP application toolkit to get data manually + sendAT(GF("+CIPRXGET=1")); + if (waitResponse() != 1) { return false; } + + // Start the TCP application toolkit task and set APN, USER NAME, PASSWORD + sendAT(GF("+CSTT=\""), apn, GF("\",\""), user, GF("\",\""), pwd, GF("\"")); if (waitResponse(60000L) != 1) { return false; } - // Check data connection + // Bring up the TCP application toolkit wireless connection with GPRS or CSD + sendAT(GF("+CIICR")); + if (waitResponse(60000L) != 1) { return false; } - sendAT(GF("+CNACT?")); - if (waitResponse(GF(GSM_NL "+CNACT:")) != 1) { return false; } - int res = streamGetIntBefore(','); - waitResponse(); + // Get local IP address for the TCP application toolkit + // only assigned after connection + sendAT(GF("+CIFSR;E0")); + if (waitResponse(10000L) != 1) { return false; } - return res == 1; + return true; } bool gprsDisconnectImpl() { - // Shut the TCP/IP connection - // CNACT will close *all* open connections - sendAT(GF("+CNACT=0")); + // Shut the TCP application toolkit connection + // CIPSHUT will close *all* open TCP application toolkit connections + sendAT(GF("+CIPSHUT")); if (waitResponse(60000L) != 1) { return false; } sendAT(GF("+CGATT=0")); // Deactivate the bearer context @@ -471,15 +250,7 @@ class TinyGsmSim7000 : public TinyGsmModem, * SIM card functions */ protected: - // Doesn't return the "+CCID" before the number - String getSimCCIDImpl() { - sendAT(GF("+CCID")); - if (waitResponse(GF(GSM_NL)) != 1) { return ""; } - String res = stream.readStringUntil('\n'); - waitResponse(); - res.trim(); - return res; - } + // Follows the SIM70xx template /* * Messaging functions @@ -491,113 +262,18 @@ class TinyGsmSim7000 : public TinyGsmModem, * GPS/GNSS/GLONASS location functions */ protected: - // enable GPS - bool enableGPSImpl() { - sendAT(GF("+CGNSPWR=1")); - if (waitResponse() != 1) { return false; } - return true; - } - - bool disableGPSImpl() { - sendAT(GF("+CGNSPWR=0")); - if (waitResponse() != 1) { return false; } - return true; - } - - // get the RAW GPS output - String getGPSrawImpl() { - sendAT(GF("+CGNSINF")); - if (waitResponse(10000L, GF(GSM_NL "+CGNSINF:")) != 1) { return ""; } - String res = stream.readStringUntil('\n'); - waitResponse(); - res.trim(); - return res; - } - - // get GPS informations - bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0, - int* vsat = 0, int* usat = 0, float* accuracy = 0, - int* year = 0, int* month = 0, int* day = 0, int* hour = 0, - int* minute = 0, int* second = 0) { - sendAT(GF("+CGNSINF")); - if (waitResponse(10000L, GF(GSM_NL "+CGNSINF:")) != 1) { return false; } - - streamSkipUntil(','); // GNSS run status - if (streamGetIntBefore(',') == 1) { // fix status - // init variables - float ilat = 0; - float ilon = 0; - float ispeed = 0; - float ialt = 0; - int ivsat = 0; - int iusat = 0; - float iaccuracy = 0; - int iyear = 0; - int imonth = 0; - int iday = 0; - int ihour = 0; - int imin = 0; - float secondWithSS = 0; - - // UTC date & Time - iyear = streamGetIntLength(4); // Four digit year - imonth = streamGetIntLength(2); // Two digit month - iday = streamGetIntLength(2); // Two digit day - ihour = streamGetIntLength(2); // Two digit hour - imin = streamGetIntLength(2); // Two digit minute - secondWithSS = - streamGetFloatBefore(','); // 6 digit second with subseconds - - ilat = streamGetFloatBefore(','); // Latitude - ilon = streamGetFloatBefore(','); // Longitude - ialt = streamGetFloatBefore(','); // MSL Altitude. Unit is meters - ispeed = streamGetFloatBefore(','); // Speed Over Ground. Unit is knots. - streamSkipUntil(','); // Course Over Ground. Degrees. - streamSkipUntil(','); // Fix Mode - streamSkipUntil(','); // Reserved1 - iaccuracy = - streamGetFloatBefore(','); // Horizontal Dilution Of Precision - streamSkipUntil(','); // Position Dilution Of Precision - streamSkipUntil(','); // Vertical Dilution Of Precision - streamSkipUntil(','); // Reserved2 - ivsat = streamGetIntBefore(','); // GNSS Satellites in View - iusat = streamGetIntBefore(','); // GNSS Satellites Used - streamSkipUntil(','); // GLONASS Satellites Used - streamSkipUntil(','); // Reserved3 - streamSkipUntil(','); // C/N0 max - streamSkipUntil(','); // HPA - streamSkipUntil('\n'); // VPA - - // Set pointers - if (lat != NULL) *lat = ilat; - if (lon != NULL) *lon = ilon; - if (speed != NULL) *speed = ispeed; - if (alt != NULL) *alt = ialt; - if (vsat != NULL) *vsat = ivsat; - if (usat != NULL) *usat = iusat; - if (accuracy != NULL) *accuracy = iaccuracy; - if (iyear < 2000) iyear += 2000; - if (year != NULL) *year = iyear; - if (month != NULL) *month = imonth; - if (day != NULL) *day = iday; - if (hour != NULL) *hour = ihour; - if (minute != NULL) *minute = imin; - if (second != NULL) *second = static_cast(secondWithSS); - - waitResponse(); - return true; - } - - streamSkipUntil('\n'); // toss the row of commas - waitResponse(); - return false; - } + // Follows the SIM70xx template /* * Time functions */ // Can follow CCLK as per template + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + /* * Battery functions */ @@ -610,161 +286,106 @@ class TinyGsmSim7000 : public TinyGsmModem, protected: bool modemConnect(const char* host, uint16_t port, uint8_t mux, bool ssl = false, int timeout_s = 75) { + if (ssl) { DBG("SSL only supported using application on SIM7000!"); } uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; - sendAT(GF("+CACID="), mux); - if (waitResponse(timeout_ms) != 1) return false; - - if (ssl) { - sendAT(GF("+CSSLCFG=\"sslversion\",0,3")); // TLS 1.2 - if (waitResponse() != 1) return false; - - sendAT(GF("+CSSLCFG=\"ctxindex\",0")); - if (waitResponse() != 1) return false; - - if (certificates[mux] != "") { - sendAT(GF("+CASSLCFG="), mux, ",CACERT,\"", certificates[mux].c_str(), - "\""); - if (waitResponse() != 1) return false; - } - } - - sendAT(GF("+CASSLCFG="), mux, ',', GF("ssl,"), ssl); - waitResponse(); - - sendAT(GF("+CASSLCFG="), mux, ',', GF("protocol,0")); - waitResponse(); - - sendAT(GF("+CSSLCFG=\"sni\","), mux, ',', GF("\""), host, GF("\"")); - waitResponse(); - - sendAT(GF("+CAOPEN="), mux, ',', GF("\""), host, GF("\","), port); - - if (waitResponse(timeout_ms, GF(GSM_NL "+CAOPEN:")) != 1) { return 0; } - streamSkipUntil(','); // Skip mux - - int8_t res = streamGetIntBefore('\n'); - waitResponse(); - - return 0 == res; + // when not using SSL, the TCP application toolkit is more stable + sendAT(GF("+CIPSTART="), mux, ',', GF("\"TCP"), GF("\",\""), host, + GF("\","), port); + return (1 == + waitResponse(timeout_ms, GF("CONNECT OK" GSM_NL), + GF("CONNECT FAIL" GSM_NL), + GF("ALREADY CONNECT" GSM_NL), GF("ERROR" GSM_NL), + GF("CLOSE OK" GSM_NL))); } int16_t modemSend(const void* buff, size_t len, uint8_t mux) { - sendAT(GF("+CASEND="), mux, ',', (uint16_t)len); + sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len); if (waitResponse(GF(">")) != 1) { return 0; } stream.write(reinterpret_cast(buff), len); stream.flush(); - if (waitResponse(GF(GSM_NL "+CASEND:")) != 1) { return 0; } - streamSkipUntil(','); // Skip mux - if (streamGetIntBefore(',') != 0) { return 0; } // If result != success + if (waitResponse(GF(GSM_NL "DATA ACCEPT:")) != 1) { return 0; } + streamSkipUntil(','); // Skip mux return streamGetIntBefore('\n'); } size_t modemRead(size_t size, uint8_t mux) { if (!sockets[mux]) return 0; - sendAT(GF("+CARECV="), mux, ',', (uint16_t)size); - - if (waitResponse(GF("+CARECV:")) != 1) { - sockets[mux]->sock_available = 0; - return 0; - } - - stream.read(); - if (stream.peek() == '0') { - waitResponse(); - sockets[mux]->sock_available = 0; - return 0; - } - - const int16_t len_confirmed = streamGetIntBefore(','); - if (len_confirmed <= 0) { - sockets[mux]->sock_available = 0; - waitResponse(); - return 0; - } - - for (int i = 0; i < len_confirmed; i++) { +#ifdef TINY_GSM_USE_HEX + sendAT(GF("+CIPRXGET=3,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } +#else + sendAT(GF("+CIPRXGET=2,"), mux, ',', (uint16_t)size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; } +#endif + streamSkipUntil(','); // Skip Rx mode 2/normal or 3/HEX + streamSkipUntil(','); // Skip mux + int16_t len_requested = streamGetIntBefore(','); + // ^^ Requested number of data bytes (1-1460 bytes)to be read + int16_t len_confirmed = streamGetIntBefore('\n'); + // ^^ Confirmed number of data bytes to be read, which may be less than + // requested. 0 indicates that no data can be read. + // SRGD NOTE: Contrary to above (which is copied from AT command manual) + // this is actually be the number of bytes that will be remaining in the + // buffer after the read. + for (int i = 0; i < len_requested; i++) { uint32_t startMillis = millis(); +#ifdef TINY_GSM_USE_HEX + while (stream.available() < 2 && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char buf[4] = { + 0, + }; + buf[0] = stream.read(); + buf[1] = stream.read(); + char c = strtol(buf, NULL, 16); +#else while (!stream.available() && (millis() - startMillis < sockets[mux]->_timeout)) { TINY_GSM_YIELD(); } char c = stream.read(); +#endif sockets[mux]->rx.put(c); } // DBG("### READ:", len_requested, "from", mux); // sockets[mux]->sock_available = modemGetAvailable(mux); - auto diff = int64_t(size) - int64_t(len_confirmed); - if (diff < 0) diff = 0; - sockets[mux]->sock_available = diff; + sockets[mux]->sock_available = len_confirmed; waitResponse(); - return len_confirmed; + return len_requested; } size_t modemGetAvailable(uint8_t mux) { if (!sockets[mux]) return 0; - if (!sockets[mux]->sock_connected) { - sockets[mux]->sock_connected = modemGetConnected(mux); - } - if (!sockets[mux]->sock_connected) return 0; - - sendAT(GF("+CARECV?")); - - int8_t readMux = -1; - size_t result = 0; - while (readMux != mux) { - if (waitResponse(GF("+CARECV:")) != 1) { - sockets[mux]->sock_connected = modemGetConnected(mux); - return 0; - }; - readMux = streamGetIntBefore(','); - result = streamGetIntBefore('\n'); + sendAT(GF("+CIPRXGET=4,"), mux); + size_t result = 0; + if (waitResponse(GF("+CIPRXGET:")) == 1) { + streamSkipUntil(','); // Skip mode 4 + streamSkipUntil(','); // Skip mux + result = streamGetIntBefore('\n'); + waitResponse(); } - waitResponse(); - + // DBG("### Available:", result, "on", mux); + if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); } return result; } bool modemGetConnected(uint8_t mux) { - sendAT(GF("+CASTATE?")); - int8_t readMux = -1; - while (readMux != mux) { - if (waitResponse(3000, GF("+CASTATE:"), GFP(GSM_OK)) != 1) { return 0; } - readMux = streamGetIntBefore(','); - } - int8_t res = streamGetIntBefore('\n'); + sendAT(GF("+CIPSTATUS="), mux); + waitResponse(GF("+CIPSTATUS")); + int8_t res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""), + GF(",\"CLOSING\""), GF(",\"REMOTE CLOSING\""), + GF(",\"INITIAL\"")); waitResponse(); return 1 == res; } - public: - bool modemGetConnected(const char* host, uint16_t port, uint8_t mux) { - sendAT(GF("+CAOPEN?")); - int8_t readMux = -1; - while (readMux != mux) { - if (waitResponse(GF("+CAOPEN:")) != 1) return 0; - readMux = streamGetIntBefore(','); - } - streamSkipUntil('\"'); - - size_t hostLen = strlen(host); - - char buffer[hostLen]; - stream.readBytesUntil('\"', buffer, hostLen); - streamSkipUntil(','); - uint16_t connectedPort = streamGetIntBefore('\n'); - waitResponse(); - bool samePort = connectedPort == port; - bool sameHost = memcmp(buffer, host, hostLen) == 0; - sockets[mux]->sock_connected = sameHost && samePort; - - return sockets[mux]->sock_connected; - } - /* * Utilities */ @@ -828,30 +449,6 @@ class TinyGsmSim7000 : public TinyGsmModem, } else { data += mode; } - } else if (data.endsWith(GF(GSM_NL "+CARECV:"))) { - int8_t mux = streamGetIntBefore(','); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->got_data = true; - } - data = ""; - // DBG("### Got Data:", mux); - } else if (data.endsWith(GF(GSM_NL "+CADATAIND:"))) { - int8_t mux = streamGetIntBefore('\n'); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - sockets[mux]->got_data = true; - } - data = ""; - // DBG("### Got Data:", mux); - } else if (data.endsWith(GF(GSM_NL "+CASTATE:"))) { - int8_t mux = streamGetIntBefore(','); - int8_t state = streamGetIntBefore('\n'); - if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { - if (state != 1) { - sockets[mux]->sock_connected = false; - DBG("### Closed: ", mux); - } - } - data = ""; } else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) { int8_t mux = streamGetIntBefore(','); int16_t len = streamGetIntBefore('\n'); @@ -887,6 +484,10 @@ class TinyGsmSim7000 : public TinyGsmModem, '\n'); // Refresh Network Daylight Saving Time by network data = ""; DBG("### Daylight savings time state updated."); + } else if (data.endsWith(GF(GSM_NL "SMS Ready" GSM_NL))) { + data = ""; + DBG("### Unexpected module reset!"); + init(); } } } while (millis() - startMillis < timeout_ms); @@ -926,13 +527,8 @@ class TinyGsmSim7000 : public TinyGsmModem, return waitResponse(1000, r1, r2, r3, r4, r5); } - public: - Stream& stream; - protected: GsmClientSim7000* sockets[TINY_GSM_MUX_COUNT]; - String certificates[TINY_GSM_MUX_COUNT]; - const char* gsmNL = GSM_NL; }; #endif // SRC_TINYGSMCLIENTSIM7000_H_ diff --git a/src/TinyGsmClientSIM7000SSL.h b/src/TinyGsmClientSIM7000SSL.h new file mode 100644 index 0000000..0202671 --- /dev/null +++ b/src/TinyGsmClientSIM7000SSL.h @@ -0,0 +1,646 @@ +/** + * @file TinyGsmClientSim7000SSL.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTSIM7000SSL_H_ +#define SRC_TINYGSMCLIENTSIM7000SSL_H_ + +// #define TINY_GSM_DEBUG Serial +// #define TINY_GSM_USE_HEX + +#define TINY_GSM_MUX_COUNT 2 +#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE + +#include "TinyGsmClientSIM70xx.h" +#include "TinyGsmTCP.tpp" +#include "TinyGsmSSL.tpp" + +class TinyGsmSim7000SSL + : public TinyGsmSim70xx, + public TinyGsmTCP, + public TinyGsmSSL { + friend class TinyGsmSim70xx; + friend class TinyGsmTCP; + friend class TinyGsmSSL; + + /* + * Inner Client + */ + public: + class GsmClientSim7000SSL : public GsmClient { + friend class TinyGsmSim7000SSL; + + public: + GsmClientSim7000SSL() {} + + explicit GsmClientSim7000SSL(TinyGsmSim7000SSL& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmSim7000SSL* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+CACLOSE="), mux); + sock_connected = false; + at->waitResponse(3000); + } + void stop() override { + stop(15000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + + class GsmClientSecureSIM7000SSL : public GsmClientSim7000SSL { + public: + GsmClientSecureSIM7000SSL() {} + + GsmClientSecureSIM7000SSL(TinyGsmSim7000SSL& modem, uint8_t mux = 0) + : GsmClientSim7000SSL(modem, mux) {} + + public: + bool setCertificate(const String& certificateName) { + return at->setCertificate(certificateName, mux); + } + + int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + + /* + * Constructor + */ + public: + explicit TinyGsmSim7000SSL(Stream& stream) + : TinyGsmSim70xx(stream), + certificates() { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM7000SSL")); + + if (!testAT()) { return false; } + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Enable Local Time Stamp for getting network time + sendAT(GF("+CLTS=1")); + if (waitResponse(10000L) != 1) { return false; } + + // Enable battery checks + sendAT(GF("+CBATCHK=1")); + if (waitResponse() != 1) { return false; } + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + /* + * Power functions + */ + protected: + // Follows the SIM70xx template + + /* + * Generic network functions + */ + protected: + String getLocalIPImpl() { + sendAT(GF("+CNACT?")); + if (waitResponse(GF(GSM_NL "+CNACT:")) != 1) { return ""; } + streamSkipUntil('\"'); + String res = stream.readStringUntil('\"'); + waitResponse(); + return res; + } + + /* + * Secure socket layer functions + */ + protected: + bool setCertificate(const String& certificateName, const uint8_t mux = 0) { + if (mux >= TINY_GSM_MUX_COUNT) return false; + certificates[mux] = certificateName; + return true; + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); + + // Bearer settings for applications based on IP + // Set the connection type to GPRS + sendAT(GF("+SAPBR=3,1,\"Contype\",\"GPRS\"")); + waitResponse(); + + // Set the APN + sendAT(GF("+SAPBR=3,1,\"APN\",\""), apn, '"'); + waitResponse(); + + // Set the user name + if (user && strlen(user) > 0) { + sendAT(GF("+SAPBR=3,1,\"USER\",\""), user, '"'); + waitResponse(); + } + // Set the password + if (pwd && strlen(pwd) > 0) { + sendAT(GF("+SAPBR=3,1,\"PWD\",\""), pwd, '"'); + waitResponse(); + } + + // Define the PDP context + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); + waitResponse(); + + // Attach to GPRS + sendAT(GF("+CGATT=1")); + if (waitResponse(60000L) != 1) { return false; } + + // 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; } + + // Bearer settings for applications based on IP + // Set the user name and password + // AT+CNCFG=[,[,,[,]]] + // 0: Dual PDN Stack + // 1: Internet Protocol Version 4 + // 2: Internet Protocol Version 6 + // 0: NONE + // 1: PAP + // 2: CHAP + // 3: PAP or CHAP + if (pwd && strlen(pwd) > 0 && user && strlen(user) > 0) { + sendAT(GF("+CNCFG=1,\""), apn, "\",\"", "\",\"", user, pwd, '"'); + waitResponse(); + } else if (user && strlen(user) > 0) { + // Set the user name only + sendAT(GF("+CNCFG=1,\""), apn, "\",\"", user, '"'); + waitResponse(); + } else { + // Set the APN only + sendAT(GF("+CNCFG=1,\""), apn, '"'); + waitResponse(); + } + + // Activate application network connection + // This is for most other supported applications outside of the + // TCP application toolkit (ie, SSL) + // AT+CNACT=, + // 0: Deactive + // 1: Active + // 2: Auto Active + int res = 0; + int ntries = 0; + while (res != 1 && ntries < 5) { + sendAT(GF("+CNACT=1,\""), apn, GF("\"")); + res = waitResponse(60000L, GF(GSM_NL "+APP PDP: ACTIVE"), + GF(GSM_NL "+APP PDP: DEACTIVE")); + waitResponse(); + ntries++; + } + + // return res == 1; + return true; + } + + bool gprsDisconnectImpl() { + // Shut down the general application TCP/IP connection + // CNACT will close *all* open application connections + sendAT(GF("+CNACT=0")); + if (waitResponse(60000L) != 1) { return false; } + + sendAT(GF("+CGATT=0")); // Deactivate the bearer context + if (waitResponse(60000L) != 1) { return false; } + + return true; + } + + /* + * SIM card functions + */ + protected: + // Follows the SIM70xx template + + /* + * Messaging functions + */ + protected: + // Follows all messaging functions per template + + /* + * GPS/GNSS/GLONASS location functions + */ + protected: + // Follows the SIM70xx template + + /* + * Time functions + */ + // Can follow CCLK as per template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Battery functions + */ + protected: + // Follows all battery functions per template + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 75) { + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + + // set the connection (mux) identifier to use + sendAT(GF("+CACID="), mux); + if (waitResponse(timeout_ms) != 1) return false; + + + if (ssl) { + // set the ssl version + sendAT(GF("+CSSLCFG=\"sslversion\",0,3")); // TLS 1.2 + if (waitResponse(5000L) != 1) return false; + + // set the PDP context to apply SSL to + sendAT(GF("+CSSLCFG=\"ctxindex\",0")); + if (waitResponse(5000L, GF("+CSSLCFG:")) != 1) return false; + streamSkipUntil('\n'); // read out the certificate information + waitResponse(); + + if (certificates[mux] != "") { + // apply the correct certificate to the connection + sendAT(GF("+CASSLCFG="), mux, ",CACERT,\"", certificates[mux].c_str(), + "\""); + if (waitResponse(5000L) != 1) return false; + } + } + + // enable or disable ssl + sendAT(GF("+CASSLCFG="), mux, ',', GF("ssl,"), ssl); + waitResponse(); + + // set the protocol + // 0: TCP; 1: UDP + sendAT(GF("+CASSLCFG="), mux, ',', GF("protocol,0")); + waitResponse(); + + // set the SSL SNI (server name indication) + sendAT(GF("+CSSLCFG=\"sni\","), mux, ',', GF("\""), host, GF("\"")); + waitResponse(); + + // actually open the connection + // AT+CAOPEN=[,],, + // TCP/UDP identifier + // "TCP" or "UDP" + sendAT(GF("+CAOPEN="), mux, GF(",\"TCP\",\""), host, GF("\","), port); + if (waitResponse(timeout_ms, GF(GSM_NL "+CAOPEN:")) != 1) { return 0; } + // returns OK/r/n/r/n+CAOPEN: , + // 0: Success + // 1: Socket error + // 2: No memory + // 3: Connection limit + // 4: Parameter invalid + // 6: Invalid IP address + // 7: Not support the function + // 12: Can’t bind the port + // 13: Can’t listen the port + // 20: Can’t resolv the host + // 21: Network not active + // 23: Remote refuse + // 24: Certificate’s time expired + // 25: Certificate’s common name does not match + // 26: Certificate’s common name does not match and time expired + // 27: Connect failed + streamSkipUntil(','); // Skip mux + + // make sure the connection really opened + int8_t res = streamGetIntBefore('\n'); + waitResponse(); + + return 0 == res; + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+CASEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + + stream.write(reinterpret_cast(buff), len); + stream.flush(); + + if (waitResponse(GF(GSM_NL "+CASEND:")) != 1) { return 0; } + streamSkipUntil(','); // Skip mux + if (streamGetIntBefore(',') != 0) { return 0; } // If result != success + return streamGetIntBefore('\n'); + } + + size_t modemRead(size_t size, uint8_t mux) { + if (!sockets[mux]) return 0; + + sendAT(GF("+CARECV="), mux, ',', (uint16_t)size); + + if (waitResponse(GF("+CARECV:")) != 1) { + sockets[mux]->sock_available = 0; + return 0; + } + + stream.read(); + if (stream.peek() == '0') { + waitResponse(); + sockets[mux]->sock_available = 0; + return 0; + } + + const int16_t len_confirmed = streamGetIntBefore(','); + if (len_confirmed <= 0) { + sockets[mux]->sock_available = 0; + waitResponse(); + return 0; + } + + for (int i = 0; i < len_confirmed; i++) { + uint32_t startMillis = millis(); + while (!stream.available() && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char c = stream.read(); + sockets[mux]->rx.put(c); + } + // DBG("### READ:", len_requested, "from", mux); + // sockets[mux]->sock_available = modemGetAvailable(mux); + auto diff = int64_t(size) - int64_t(len_confirmed); + if (diff < 0) diff = 0; + sockets[mux]->sock_available = diff; + waitResponse(); + return len_confirmed; + } + + size_t modemGetAvailable(uint8_t mux) { + if (!sockets[mux]) return 0; + + sendAT(GF("+CARECV?")); + + int8_t readMux = -1; + size_t result = 0; + while (readMux != mux) { + if (waitResponse(GF("+CARECV:")) != 1) { + sockets[mux]->sock_connected = modemGetConnected(mux); + return 0; + }; + readMux = streamGetIntBefore(','); + result = streamGetIntBefore('\n'); + } + waitResponse(); + // DBG("### Available:", result, "on", mux); + if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); } + return result; + } + + bool modemGetConnected(uint8_t mux) { + sendAT(GF("+CASTATE?")); + int8_t readMux = -1; + while (readMux != mux) { + if (waitResponse(3000, GF("+CASTATE:"), GFP(GSM_OK)) != 1) { return 0; } + readMux = streamGetIntBefore(','); + } + int8_t res = streamGetIntBefore('\n'); + waitResponse(); + return 1 == res; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + 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); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(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)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + 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 "+CARECV:"))) { + int8_t mux = streamGetIntBefore(','); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + DBG("### Got Data:", mux); + } else if (data.endsWith(GF(GSM_NL "+CADATAIND:"))) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + DBG("### Got Data:", mux); + } else if (data.endsWith(GF(GSM_NL "+CASTATE:"))) { + int8_t mux = streamGetIntBefore(','); + int8_t state = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + if (state != 1) { + sockets[mux]->sock_connected = false; + DBG("### Closed: ", mux); + } + } + data = ""; + } else if (data.endsWith(GF("CLOSED" GSM_NL))) { + int8_t nl = data.lastIndexOf(GSM_NL, data.length() - 8); + int8_t coma = data.indexOf(',', nl + 2); + int8_t 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); + } else if (data.endsWith(GF("*PSNWID:"))) { + streamSkipUntil('\n'); // Refresh network name by network + data = ""; + DBG("### Network name updated."); + } else if (data.endsWith(GF("*PSUTTZ:"))) { + streamSkipUntil('\n'); // Refresh time and time zone by network + data = ""; + DBG("### Network time and time zone updated."); + } else if (data.endsWith(GF("+CTZV:"))) { + streamSkipUntil('\n'); // Refresh network time zone by network + data = ""; + DBG("### Network time zone updated."); + } else if (data.endsWith(GF("DST: "))) { + streamSkipUntil( + '\n'); // Refresh Network Daylight Saving Time by network + data = ""; + DBG("### Daylight savings time state updated."); + } else if (data.endsWith(GF(GSM_NL "SMS Ready" GSM_NL))) { + data = ""; + DBG("### Unexpected module reset!"); + init(); + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + protected: + GsmClientSim7000SSL* sockets[TINY_GSM_MUX_COUNT]; + String certificates[TINY_GSM_MUX_COUNT]; +}; + +#endif // SRC_TINYGSMCLIENTSIM7000SSL_H_ diff --git a/src/TinyGsmClientSIM70x0.h b/src/TinyGsmClientSIM70x0.h new file mode 100644 index 0000000..1fc8a63 --- /dev/null +++ b/src/TinyGsmClientSIM70x0.h @@ -0,0 +1,676 @@ +/** + * @file TinyGsmClientSim70x0.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTSIM70X0_H_ +#define SRC_TINYGSMCLIENTSIM70X0_H_ + +// #define TINY_GSM_DEBUG Serial +// #define TINY_GSM_USE_HEX + +#define TINY_GSM_MUX_COUNT 12 +#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE + +#include "TinyGsmClientSIM70xx.h" +#include "TinyGsmTCP.tpp" +#include "TinyGsmSSL.tpp" + +class TinyGsmSim70x0 : public TinyGsmSim70xx, + public TinyGsmTCP, + public TinyGsmSSL { + friend class TinyGsmSim70xx; + friend class TinyGsmTCP; + friend class TinyGsmSSL; + + /* + * Inner Client + */ + public: + class GsmClientSim70x0 : public GsmClient { + friend class TinyGsmSim70x0; + + public: + GsmClientSim70x0() {} + + explicit GsmClientSim70x0(TinyGsmSim70x0& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmSim70x0* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, false, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+CACLOSE="), mux); + sock_connected = false; + at->waitResponse(3000); + } + void stop() override { + stop(15000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + + class GsmClientSecureSIM70x0 : public GsmClientSim70x0 { + public: + GsmClientSecureSIM70x0() {} + + GsmClientSecureSIM70x0(TinyGsmSim70x0& modem, uint8_t mux = 0) + : GsmClientSim70x0(modem, mux) {} + + public: + bool setCertificate(const String& certificateName) { + return at->setCertificate(certificateName, mux); + } + + int connect(const char* host, uint16_t port, int timeout_s) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true, timeout_s); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + + /* + * Constructor + */ + public: + explicit TinyGsmSim70x0(Stream& stream) + : TinyGsmSim70xx(stream), + certificates() { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM70x0")); + + if (!testAT()) { return false; } + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Enable Local Time Stamp for getting network time + sendAT(GF("+CLTS=1")); + if (waitResponse(10000L) != 1) { return false; } + + // Enable battery checks + sendAT(GF("+CBATCHK=1")); + if (waitResponse() != 1) { return false; } + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + /* + * Power functions + */ + protected: + // Follows the SIM70xx template + + /* + * Generic network functions + */ + protected: + String getLocalIPImpl() { + sendAT(GF("+CNACT?")); + if (waitResponse(GF(GSM_NL "+CNACT:")) != 1) { return ""; } + streamSkipUntil('\"'); + String res = stream.readStringUntil('\"'); + waitResponse(); + return res; + } + + /* + * Secure socket layer functions + */ + protected: + bool setCertificate(const String& certificateName, const uint8_t mux = 0) { + if (mux >= TINY_GSM_MUX_COUNT) return false; + certificates[mux] = certificateName; + return true; + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + gprsDisconnect(); + + // Bearer settings for applications based on IP + // Set the connection type to GPRS + sendAT(GF("+SAPBR=3,1,\"Contype\",\"GPRS\"")); + waitResponse(); + + // Set the APN + sendAT(GF("+SAPBR=3,1,\"APN\",\""), apn, '"'); + waitResponse(); + + // Set the user name + if (user && strlen(user) > 0) { + sendAT(GF("+SAPBR=3,1,\"USER\",\""), user, '"'); + waitResponse(); + } + // Set the password + if (pwd && strlen(pwd) > 0) { + sendAT(GF("+SAPBR=3,1,\"PWD\",\""), pwd, '"'); + waitResponse(); + } + + // Define the PDP context + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); + waitResponse(); + + // Attach to GPRS + sendAT(GF("+CGATT=1")); + if (waitResponse(60000L) != 1) { return false; } + + // 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; } + + // Bearer settings for applications based on IP + // Set the user name and password + // AT+CNCFG=,,[,[,,[]]] + // PDP Context Identifier (1 is setup above) + // 0: Dual PDN Stack + // 1: Internet Protocol Version 4 + // 2: Internet Protocol Version 6 + // 0: NONE + // 1: PAP + // 2: CHAP + // 3: PAP or CHAP + if (pwd && strlen(pwd) > 0 && user && strlen(user) > 0) { + sendAT(GF("+CNCFG=1,1,\""), apn, "\",\"", "\",\"", user, pwd, '"'); + waitResponse(); + } else if (user && strlen(user) > 0) { + // Set the user name only + sendAT(GF("+CNCFG=1,1,\""), apn, "\",\"", user, '"'); + waitResponse(); + } else { + // Set the APN only + sendAT(GF("+CNCFG=1,1,\""), apn, '"'); + waitResponse(); + } + + // Activate application network connection + // This is for most other supported applications outside of the + // TCP application toolkit (ie, SSL) + // AT+CNACT=, + // PDP Context Identifier (1 is setup above) + // 0: Deactive + // 1: Active + // 2: Auto Active + int res = 0; + int ntries = 0; + while (res != 1 && ntries < 5) { + sendAT(GF("+CNACT=1,1,\""), apn, GF("\"")); + res = waitResponse(60000L, GF(GSM_NL "+APP PDP: ACTIVE"), + GF(GSM_NL "+APP PDP: DEACTIVE")); + waitResponse(); + ntries++; + } + + // return res == 1; + return true; + } + + bool gprsDisconnectImpl() { + // Shut down the general application TCP/IP connection + // CNACT will close *all* open application connections + sendAT(GF("+CNACT=1,0")); + if (waitResponse(60000L) != 1) { return false; } + + sendAT(GF("+CGATT=0")); // Deactivate the bearer context + if (waitResponse(60000L) != 1) { return false; } + + return true; + } + + /* + * SIM card functions + */ + protected: + // Follows the SIM70xx template + + /* + * Messaging functions + */ + protected: + // Follows all messaging functions per template + + /* + * GPS/GNSS/GLONASS location functions + */ + protected: + // Follows the SIM70xx template + + /* + * Time functions + */ + // Can follow CCLK as per template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Battery functions + */ + protected: + // Follows all battery functions per template + + /* + * Client related functions + */ + protected: + bool modemConnect(const char* host, uint16_t port, uint8_t mux, + bool ssl = false, int timeout_s = 75) { + uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000; + + // set the connection (mux) identifier to use + sendAT(GF("+CACID="), mux); + if (waitResponse(timeout_ms) != 1) return false; + + + if (ssl) { + // set the ssl version + // AT+CSSLCFG="SSLVERSION",, + // PDP context identifier + // 0: QAPI_NET_SSL_PROTOCOL_UNKNOWN + // 1: QAPI_NET_SSL_PROTOCOL_TLS_1_0 + // 2: QAPI_NET_SSL_PROTOCOL_TLS_1_1 + // 3: QAPI_NET_SSL_PROTOCOL_TLS_1_2 + // 4: QAPI_NET_SSL_PROTOCOL_DTLS_1_0 + // 5: QAPI_NET_SSL_PROTOCOL_DTLS_1_2 + sendAT(GF("+CSSLCFG=\"SSLVERSION\",0,3")); // TLS 1.2 + if (waitResponse(5000L) != 1) return false; + + // set the PDP context to apply SSL to + // AT+CSSLCFG="CTXINDEX", + // PDP context identifier + sendAT(GF("+CSSLCFG=\"CTXINDEX\",0")); + if (waitResponse(5000L, GF("+CSSLCFG:")) != 1) return false; + streamSkipUntil('\n'); // read out the certificate information + waitResponse(); + + if (certificates[mux] != "") { + // apply the correct certificate to the connection + // AT+CASSLCFG=,"CACERT", + // Application connection ID (set with AT+CACID above) + // certificate name + sendAT(GF("+CASSLCFG="), mux, ",CACERT,\"", certificates[mux].c_str(), + "\""); + if (waitResponse(5000L) != 1) return false; + } + } + + // enable or disable ssl + // AT+CASSLCFG=,"SSL", + // Application connection ID (set with AT+CACID above) + // 0: Not support SSL + // 1: Support SSL + sendAT(GF("+CASSLCFG="), mux, ',', GF("SSL,"), ssl); + waitResponse(); + + // set the SSL SNI (server name indication) + sendAT(GF("+CSSLCFG=\"SNI\","), mux, ',', GF("\""), host, GF("\"")); + waitResponse(); + + // actually open the connection + // AT+CAOPEN=,,,,[,] + // TCP/UDP identifier + // Index of PDP connection; we set up PCP context 1 above + // "TCP" or "UDP" + // 0: The received data can only be read manually using + // AT+CARECV= + // 1: After receiving the data, it will automatically report + // URC: + // +CAURC: + // "recv",,,, + sendAT(GF("+CAOPEN="), mux, GF(",0,\"TCP\",\""), host, GF("\","), port, ',', + 0); + if (waitResponse(timeout_ms, GF(GSM_NL "+CAOPEN:")) != 1) { return 0; } + // returns OK/r/n/r/n+CAOPEN: , + // 0: Success + // 1: Socket error + // 2: No memory + // 3: Connection limit + // 4: Parameter invalid + // 6: Invalid IP address + // 7: Not support the function + // 12: Can’t bind the port + // 13: Can’t listen the port + // 20: Can’t resolve the host + // 21: Network not active + // 23: Remote refuse + // 24: Certificate’s time expired + // 25: Certificate’s common name does not match + // 26: Certificate’s common name does not match and time expired + // 27: Connect failed + streamSkipUntil(','); // Skip mux + + // make sure the connection really opened + int8_t res = streamGetIntBefore('\n'); + waitResponse(); + + return 0 == res; + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+CASEND="), mux, ',', (uint16_t)len); + if (waitResponse(GF(">")) != 1) { return 0; } + + stream.write(reinterpret_cast(buff), len); + stream.flush(); + + if (waitResponse(GF(GSM_NL "+CASEND:")) != 1) { return 0; } + streamSkipUntil(','); // Skip mux + if (streamGetIntBefore(',') != 0) { return 0; } // If result != success + return streamGetIntBefore('\n'); + } + + size_t modemRead(size_t size, uint8_t mux) { + if (!sockets[mux]) return 0; + + sendAT(GF("+CARECV="), mux, ',', (uint16_t)size); + + if (waitResponse(GF("+CARECV:")) != 1) { + sockets[mux]->sock_available = 0; + return 0; + } + + stream.read(); + if (stream.peek() == '0') { + waitResponse(); + sockets[mux]->sock_available = 0; + return 0; + } + + const int16_t len_confirmed = streamGetIntBefore(','); + if (len_confirmed <= 0) { + sockets[mux]->sock_available = 0; + waitResponse(); + return 0; + } + + for (int i = 0; i < len_confirmed; i++) { + uint32_t startMillis = millis(); + while (!stream.available() && + (millis() - startMillis < sockets[mux]->_timeout)) { + TINY_GSM_YIELD(); + } + char c = stream.read(); + sockets[mux]->rx.put(c); + } + // DBG("### READ:", len_requested, "from", mux); + // sockets[mux]->sock_available = modemGetAvailable(mux); + auto diff = int64_t(size) - int64_t(len_confirmed); + if (diff < 0) diff = 0; + sockets[mux]->sock_available = diff; + waitResponse(); + return len_confirmed; + } + + size_t modemGetAvailable(uint8_t mux) { + // NOTE: This gets how many characters are available on all connections + sendAT(GF("+CARECV?")); + for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) { + if (waitResponse(3000, GF(GSM_NL "+CARECV: ")) != 1) { break; } + size_t result = 0; + // if (streamGetIntBefore(',') != muxNo) { // check the mux no + // DBG("### Warning: misaligned mux numbers!"); + // } + streamSkipUntil(','); // skip mux [use muxNo] + result = streamGetIntBefore('\n'); + GsmClientSim70x0* sock = sockets[mux]; + if (sock && muxNo == mux) { sock->sock_available = result; } + } + waitResponse(); // Should be an OK at the end + modemGetConnected(mux); + if (!sockets[mux]) return 0; + return sockets[mux]->sock_available; + } + + bool modemGetConnected(uint8_t mux) { + // NOTE: This gets the state of all connections + sendAT(GF("+CASTATE?")); + + for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) { + if (waitResponse(3000, GF(GSM_NL "+CASTATE: ")) != 1) { break; } + uint8_t status = 0; + // if (streamGetIntBefore(',') != muxNo) { // check the mux no + // DBG("### Warning: misaligned mux numbers!"); + // } + streamSkipUntil(','); // skip mux [use muxNo] + status = stream.parseInt(); // Read the status + // 0: Closed by remote server or internal error + // 1: Connected to remote server + // 2: Listening (server mode) + GsmClientSim70x0* sock = sockets[mux]; + if (sock && muxNo == mux) { sock->sock_connected = (status == 1); } + } + waitResponse(); // Should be an OK at the end + return sockets[mux]->sock_connected; + } + + /* + * Utilities + */ + public: + // TODO(vshymanskyy): Optimize this! + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + 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); + uint8_t index = 0; + uint32_t startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + TINY_GSM_YIELD(); + int8_t a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += static_cast(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)) { +#if defined TINY_GSM_DEBUG + if (r3 == GFP(GSM_CME_ERROR)) { + streamSkipUntil('\n'); // Read out the error + } +#endif + 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 "+CARECV:"))) { + int8_t mux = streamGetIntBefore(','); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + DBG("### Got Data:", mux); + } else if (data.endsWith(GF(GSM_NL "+CADATAIND:"))) { + int8_t mux = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + DBG("### Got Data:", mux); + } else if (data.endsWith(GF(GSM_NL "+CASTATE:"))) { + int8_t mux = streamGetIntBefore(','); + int8_t state = streamGetIntBefore('\n'); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + if (state != 1) { + sockets[mux]->sock_connected = false; + DBG("### Closed: ", mux); + } + } + data = ""; + } else if (data.endsWith(GF("CLOSED" GSM_NL))) { + int8_t nl = data.lastIndexOf(GSM_NL, data.length() - 8); + int8_t coma = data.indexOf(',', nl + 2); + int8_t 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); + } else if (data.endsWith(GF("*PSNWID:"))) { + streamSkipUntil('\n'); // Refresh network name by network + data = ""; + DBG("### Network name updated."); + } else if (data.endsWith(GF("*PSUTTZ:"))) { + streamSkipUntil('\n'); // Refresh time and time zone by network + data = ""; + DBG("### Network time and time zone updated."); + } else if (data.endsWith(GF("+CTZV:"))) { + streamSkipUntil('\n'); // Refresh network time zone by network + data = ""; + DBG("### Network time zone updated."); + } else if (data.endsWith(GF("DST: "))) { + streamSkipUntil( + '\n'); // Refresh Network Daylight Saving Time by network + data = ""; + DBG("### Daylight savings time state updated."); + } else if (data.endsWith(GF(GSM_NL "SMS Ready" GSM_NL))) { + data = ""; + DBG("### Unexpected module reset!"); + init(); + } + } + } while (millis() - startMillis < timeout_ms); + finish: + if (!index) { + data.trim(); + if (data.length()) { DBG("### Unhandled:", data); } + data = ""; + } + // data.replace(GSM_NL, "/"); + // DBG('<', index, '>', data); + return index; + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + + protected: + GsmClientSim70x0* sockets[TINY_GSM_MUX_COUNT]; + String certificates[TINY_GSM_MUX_COUNT]; +}; + +#endif // SRC_TINYGSMCLIENTSIM70X0_H_ diff --git a/src/TinyGsmClientSIM70xx.h b/src/TinyGsmClientSIM70xx.h new file mode 100644 index 0000000..fe043d5 --- /dev/null +++ b/src/TinyGsmClientSIM70xx.h @@ -0,0 +1,456 @@ +/** + * @file TinyGsmClientSIM70xx.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMCLIENTSIM70XX_H_ +#define SRC_TINYGSMCLIENTSIM70XX_H_ + +// #define TINY_GSM_DEBUG Serial +// #define TINY_GSM_USE_HEX + +#include "TinyGsmBattery.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmGPS.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmTime.tpp" +#include "TinyGsmNTP.tpp" + +#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; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; + +template +class TinyGsmSim70xx : public TinyGsmModem>, + public TinyGsmGPRS>, + public TinyGsmSMS>, + public TinyGsmGPS>, + public TinyGsmTime>, + public TinyGsmNTP>, + public TinyGsmBattery> { + friend class TinyGsmModem>; + friend class TinyGsmGPRS>; + friend class TinyGsmSMS>; + friend class TinyGsmGPS>; + friend class TinyGsmTime>; + friend class TinyGsmNTP>; + friend class TinyGsmBattery>; + + /* + * CRTP Helper + */ + protected: + inline const modemType& thisModem() const { + return static_cast(*this); + } + inline modemType& thisModem() { + return static_cast(*this); + } + + /* + * Constructor + */ + public: + explicit TinyGsmSim70xx(Stream& stream) : stream(stream) {} + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + return thisModem().initImpl(pin); + } + + String getModemNameImpl() { + String name = "SIMCom SIM7000"; + + thisModem().sendAT(GF("+GMM")); + String res2; + if (thisModem().waitResponse(5000L, res2) != 1) { return name; } + res2.replace(GSM_NL "OK" GSM_NL, ""); + res2.replace("_", " "); + res2.trim(); + + name = res2; + return name; + } + + bool factoryDefaultImpl() { // these commands aren't supported + thisModem().sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write + thisModem().waitResponse(); + thisModem().sendAT(GF("+IPR=0")); // Auto-baud + thisModem().waitResponse(); + thisModem().sendAT(GF("+IFC=0,0")); // No Flow Control + thisModem().waitResponse(); + thisModem().sendAT(GF("+ICF=3,3")); // 8 data 0 parity 1 stop + thisModem().waitResponse(); + thisModem().sendAT(GF("+CSCLK=0")); // Disable Slow Clock + thisModem().waitResponse(); + thisModem().sendAT(GF("&W")); // Write configuration + return thisModem().waitResponse() == 1; + } + + /* + * Power functions + */ + protected: + bool restartImpl(const char* pin = NULL) { + thisModem().sendAT(GF("E0")); // Echo Off + thisModem().waitResponse(); + if (!thisModem().setPhoneFunctionality(0)) { return false; } + if (!thisModem().setPhoneFunctionality(1, true)) { return false; } + thisModem().waitResponse(30000L, GF("SMS Ready")); + return thisModem().initImpl(pin); + } + + bool powerOffImpl() { + thisModem().sendAT(GF("+CPOWD=1")); + return thisModem().waitResponse(GF("NORMAL POWER DOWN")) == 1; + } + + // During sleep, the SIM70xx module has its serial communication disabled. + // In order to reestablish communication pull the DRT-pin of the SIM70xx + // module LOW for at least 50ms. Then use this function to disable sleep mode. + // The DTR-pin can then be released again. + bool sleepEnableImpl(bool enable = true) { + thisModem().sendAT(GF("+CSCLK="), enable); + return thisModem().waitResponse() == 1; + } + + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { + thisModem().sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); + return thisModem().waitResponse(10000L) == 1; + } + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + RegStatus epsStatus = + (RegStatus)thisModem().getRegistrationStatusXREG("CEREG"); + // If we're connected on EPS, great! + if (epsStatus == REG_OK_HOME || epsStatus == REG_OK_ROAMING) { + return epsStatus; + } else { + // Otherwise, check GPRS network status + // We could be using GPRS fall-back or the board could be being moody + return (RegStatus)thisModem().getRegistrationStatusXREG("CGREG"); + } + } + + protected: + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + public: + String getNetworkModes() { + // Get the help string, not the setting value + thisModem().sendAT(GF("+CNMP=?")); + if (thisModem().waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + thisModem().waitResponse(); + return res; + } + + int16_t getNetworkMode() { + thisModem().sendAT(GF("+CNMP?")); + if (thisModem().waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return false; } + int16_t mode = thisModem().streamGetIntBefore('\n'); + thisModem().waitResponse(); + return mode; + } + + bool setNetworkMode(uint8_t mode) { + // 2 Automatic + // 13 GSM only + // 38 LTE only + // 51 GSM and LTE only + thisModem().sendAT(GF("+CNMP="), mode); + return thisModem().waitResponse() == 1; + } + + String getPreferredModes() { + // Get the help string, not the setting value + thisModem().sendAT(GF("+CMNB=?")); + if (thisModem().waitResponse(GF(GSM_NL "+CMNB:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + thisModem().waitResponse(); + return res; + } + + int16_t getPreferredMode() { + thisModem().sendAT(GF("+CMNB?")); + if (thisModem().waitResponse(GF(GSM_NL "+CMNB:")) != 1) { return false; } + int16_t mode = thisModem().streamGetIntBefore('\n'); + thisModem().waitResponse(); + return mode; + } + + bool setPreferredMode(uint8_t mode) { + // 1 CAT-M + // 2 NB-IoT + // 3 CAT-M and NB-IoT + thisModem().sendAT(GF("+CMNB="), mode); + return thisModem().waitResponse() == 1; + } + + bool getNetworkSystemMode(bool& n, int16_t& stat) { + // n: whether to automatically report the system mode info + // stat: the current service. 0 if it not connected + thisModem().sendAT(GF("+CNSMOD?")); + if (thisModem().waitResponse(GF(GSM_NL "+CNSMOD:")) != 1) { return false; } + n = thisModem().streamGetIntBefore(',') != 0; + stat = thisModem().streamGetIntBefore('\n'); + thisModem().waitResponse(); + return true; + } + + bool setNetworkSystemMode(bool n) { + // n: whether to automatically report the system mode info + thisModem().sendAT(GF("+CNSMOD="), int8_t(n)); + return thisModem().waitResponse() == 1; + } + + String getLocalIPImpl() { + return thisModem().getLocalIPImpl(); + } + + /* + * GPRS functions + */ + protected: + // should implement in sub-classes + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + return thisModem().gprsConnectImpl(apn, user, pwd); + } + + bool gprsDisconnectImpl() { + return thisModem().gprsDisconnectImpl(); + } + + /* + * SIM card functions + */ + protected: + // Doesn't return the "+CCID" before the number + String getSimCCIDImpl() { + thisModem().sendAT(GF("+CCID")); + if (thisModem().waitResponse(GF(GSM_NL)) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + thisModem().waitResponse(); + res.trim(); + return res; + } + + /* + * Messaging functions + */ + protected: + // Follows all messaging functions per template + + /* + * GPS/GNSS/GLONASS location functions + */ + protected: + // enable GPS + bool enableGPSImpl() { + thisModem().sendAT(GF("+CGNSPWR=1")); + if (thisModem().waitResponse() != 1) { return false; } + return true; + } + + bool disableGPSImpl() { + thisModem().sendAT(GF("+CGNSPWR=0")); + if (thisModem().waitResponse() != 1) { return false; } + return true; + } + + // get the RAW GPS output + String getGPSrawImpl() { + thisModem().sendAT(GF("+CGNSINF")); + if (thisModem().waitResponse(10000L, GF(GSM_NL "+CGNSINF:")) != 1) { + return ""; + } + String res = stream.readStringUntil('\n'); + thisModem().waitResponse(); + res.trim(); + return res; + } + + // get GPS informations + bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0, + int* vsat = 0, int* usat = 0, float* accuracy = 0, + int* year = 0, int* month = 0, int* day = 0, int* hour = 0, + int* minute = 0, int* second = 0) { + thisModem().sendAT(GF("+CGNSINF")); + if (thisModem().waitResponse(10000L, GF(GSM_NL "+CGNSINF:")) != 1) { + return false; + } + + thisModem().streamSkipUntil(','); // GNSS run status + if (thisModem().streamGetIntBefore(',') == 1) { // fix status + // init variables + float ilat = 0; + float ilon = 0; + float ispeed = 0; + float ialt = 0; + int ivsat = 0; + int iusat = 0; + float iaccuracy = 0; + int iyear = 0; + int imonth = 0; + int iday = 0; + int ihour = 0; + int imin = 0; + float secondWithSS = 0; + + // UTC date & Time + iyear = thisModem().streamGetIntLength(4); // Four digit year + imonth = thisModem().streamGetIntLength(2); // Two digit month + iday = thisModem().streamGetIntLength(2); // Two digit day + ihour = thisModem().streamGetIntLength(2); // Two digit hour + imin = thisModem().streamGetIntLength(2); // Two digit minute + secondWithSS = thisModem().streamGetFloatBefore( + ','); // 6 digit second with subseconds + + ilat = thisModem().streamGetFloatBefore(','); // Latitude + ilon = thisModem().streamGetFloatBefore(','); // Longitude + ialt = thisModem().streamGetFloatBefore( + ','); // MSL Altitude. Unit is meters + ispeed = thisModem().streamGetFloatBefore( + ','); // Speed Over Ground. Unit is knots. + thisModem().streamSkipUntil(','); // Course Over Ground. Degrees. + thisModem().streamSkipUntil(','); // Fix Mode + thisModem().streamSkipUntil(','); // Reserved1 + iaccuracy = thisModem().streamGetFloatBefore( + ','); // Horizontal Dilution Of Precision + thisModem().streamSkipUntil(','); // Position Dilution Of Precision + thisModem().streamSkipUntil(','); // Vertical Dilution Of Precision + thisModem().streamSkipUntil(','); // Reserved2 + ivsat = thisModem().streamGetIntBefore(','); // GNSS Satellites in View + iusat = thisModem().streamGetIntBefore(','); // GNSS Satellites Used + thisModem().streamSkipUntil(','); // GLONASS Satellites Used + thisModem().streamSkipUntil(','); // Reserved3 + thisModem().streamSkipUntil(','); // C/N0 max + thisModem().streamSkipUntil(','); // HPA + thisModem().streamSkipUntil('\n'); // VPA + + // Set pointers + if (lat != NULL) *lat = ilat; + if (lon != NULL) *lon = ilon; + if (speed != NULL) *speed = ispeed; + if (alt != NULL) *alt = ialt; + if (vsat != NULL) *vsat = ivsat; + if (usat != NULL) *usat = iusat; + if (accuracy != NULL) *accuracy = iaccuracy; + if (iyear < 2000) iyear += 2000; + if (year != NULL) *year = iyear; + if (month != NULL) *month = imonth; + if (day != NULL) *day = iday; + if (hour != NULL) *hour = ihour; + if (minute != NULL) *minute = imin; + if (second != NULL) *second = static_cast(secondWithSS); + + thisModem().waitResponse(); + return true; + } + + thisModem().streamSkipUntil('\n'); // toss the row of commas + thisModem().waitResponse(); + return false; + } + + /* + * Time functions + */ + // Can follow CCLK as per template + + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + + /* + * Battery functions + */ + protected: + // Follows all battery functions per template + + /* + * Client related functions + */ + // should implement in sub-classes + + /* + * Utilities + */ + public: + // should implement in sub-classes + int8_t waitResponse(uint32_t timeout_ms, String& data, + GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return thisModem().waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + String data; + return thisModem().waitResponse(timeout_ms, data, r1, r2, r3, r4, r5); + } + + int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK), + GsmConstStr r2 = GFP(GSM_ERROR), +#if defined TINY_GSM_DEBUG + GsmConstStr r3 = GFP(GSM_CME_ERROR), + GsmConstStr r4 = GFP(GSM_CMS_ERROR), +#else + GsmConstStr r3 = NULL, GsmConstStr r4 = NULL, +#endif + GsmConstStr r5 = NULL) { + return thisModem().waitResponse(1000, r1, r2, r3, r4, r5); + } + + public: + Stream& stream; + + protected: + const char* gsmNL = GSM_NL; +}; + +#endif // SRC_TINYGSMCLIENTSIM70XX_H_ diff --git a/src/TinyGsmClientSIM7600.h b/src/TinyGsmClientSIM7600.h index 9d980f2..55f50a0 100644 --- a/src/TinyGsmClientSIM7600.h +++ b/src/TinyGsmClientSIM7600.h @@ -24,6 +24,8 @@ #include "TinyGsmTCP.tpp" #include "TinyGsmTemperature.tpp" #include "TinyGsmTime.tpp" +#include "TinyGsmNTP.tpp" + #define GSM_NL "\r\n" static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; @@ -50,6 +52,7 @@ class TinyGsmSim7600 : public TinyGsmModem, public TinyGsmGSMLocation, public TinyGsmGPS, public TinyGsmTime, + public TinyGsmNTP, public TinyGsmBattery, public TinyGsmTemperature { friend class TinyGsmModem; @@ -59,6 +62,7 @@ class TinyGsmSim7600 : public TinyGsmModem, friend class TinyGsmGPS; friend class TinyGsmGSMLocation; friend class TinyGsmTime; + friend class TinyGsmNTP; friend class TinyGsmBattery; friend class TinyGsmTemperature; @@ -271,12 +275,17 @@ class TinyGsmSim7600 : public TinyGsmModem, return res; } - String setNetworkMode(uint8_t mode) { - sendAT(GF("+CNMP="), mode); - if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return "OK"; } - String res = stream.readStringUntil('\n'); + int16_t getNetworkMode() { + sendAT(GF("+CNMP?")); + if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return false; } + int16_t mode = streamGetIntBefore('\n'); waitResponse(); - return res; + return mode; + } + + bool setNetworkMode(uint8_t mode) { + sendAT(GF("+CNMP="), mode); + return waitResponse() == 1; } String getLocalIPImpl() { @@ -558,6 +567,11 @@ class TinyGsmSim7600 : public TinyGsmModem, protected: // Can follow the standard CCLK function in the template + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + /* * Battery functions */ diff --git a/src/TinyGsmClientSIM800.h b/src/TinyGsmClientSIM800.h index 3754eb5..48857ce 100644 --- a/src/TinyGsmClientSIM800.h +++ b/src/TinyGsmClientSIM800.h @@ -25,6 +25,7 @@ #include "TinyGsmSSL.tpp" #include "TinyGsmTCP.tpp" #include "TinyGsmTime.tpp" +#include "TinyGsmNTP.tpp" #define GSM_NL "\r\n" static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; @@ -51,6 +52,7 @@ class TinyGsmSim800 : public TinyGsmModem, public TinyGsmSMS, public TinyGsmGSMLocation, public TinyGsmTime, + public TinyGsmNTP, public TinyGsmBattery { friend class TinyGsmModem; friend class TinyGsmGPRS; @@ -60,6 +62,7 @@ class TinyGsmSim800 : public TinyGsmModem, friend class TinyGsmSMS; friend class TinyGsmGSMLocation; friend class TinyGsmTime; + friend class TinyGsmNTP; friend class TinyGsmBattery; /* @@ -315,20 +318,23 @@ class TinyGsmSim800 : public TinyGsmModem, const char* pwd = NULL) { gprsDisconnect(); - // Set the Bearer for the IP - sendAT(GF( - "+SAPBR=3,1,\"Contype\",\"GPRS\"")); // Set the connection type to GPRS + // Bearer settings for applications based on IP + // Set the connection type to GPRS + sendAT(GF("+SAPBR=3,1,\"Contype\",\"GPRS\"")); waitResponse(); - sendAT(GF("+SAPBR=3,1,\"APN\",\""), apn, '"'); // Set the APN + // Set the APN + sendAT(GF("+SAPBR=3,1,\"APN\",\""), apn, '"'); waitResponse(); + // Set the user name if (user && strlen(user) > 0) { - sendAT(GF("+SAPBR=3,1,\"USER\",\""), user, '"'); // Set the user name + sendAT(GF("+SAPBR=3,1,\"USER\",\""), user, '"'); waitResponse(); } + // Set the password if (pwd && strlen(pwd) > 0) { - sendAT(GF("+SAPBR=3,1,\"PWD\",\""), pwd, '"'); // Set the password + sendAT(GF("+SAPBR=3,1,\"PWD\",\""), pwd, '"'); waitResponse(); } @@ -351,8 +357,6 @@ class TinyGsmSim800 : public TinyGsmModem, 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; } @@ -447,6 +451,11 @@ class TinyGsmSim800 : public TinyGsmModem, protected: // Can follow the standard CCLK function in the template + /* + * NTP server functions + */ + // Can sync with server using CNTP as per template + /* * Battery functions */ @@ -456,50 +465,7 @@ class TinyGsmSim800 : public TinyGsmModem, /* * NTP server functions */ - public: - boolean isValidNumber(String str) { - if (!(str.charAt(0) == '+' || str.charAt(0) == '-' || - isDigit(str.charAt(0)))) - return false; - - for (byte i = 1; i < str.length(); i++) { - if (!(isDigit(str.charAt(i)) || str.charAt(i) == '.')) { return false; } - } - return true; - } - - String ShowNTPError(byte error) { - switch (error) { - case 1: return "Network time synchronization is successful"; - case 61: return "Network error"; - case 62: return "DNS resolution error"; - case 63: return "Connection error"; - case 64: return "Service response error"; - case 65: return "Service response timeout"; - default: return "Unknown error: " + String(error); - } - } - - byte NTPServerSync(String server = "pool.ntp.org", byte TimeZone = 3) { - // Set GPRS bearer profile to associate with NTP sync - sendAT(GF("+CNTPCID=1")); - if (waitResponse(10000L) != 1) { return -1; } - - // Set NTP server and timezone - sendAT(GF("+CNTP="), server, ',', String(TimeZone)); - if (waitResponse(10000L) != 1) { return -1; } - - // Request network synchronization - sendAT(GF("+CNTP")); - if (waitResponse(10000L, GF(GSM_NL "+CNTP:"))) { - String result = stream.readStringUntil('\n'); - result.trim(); - if (isValidNumber(result)) { return result.toInt(); } - } else { - return -1; - } - return -1; - } + // Can sync with server using CNTP as per template /* * Client related functions diff --git a/src/TinyGsmNTP.tpp b/src/TinyGsmNTP.tpp new file mode 100644 index 0000000..502c88b --- /dev/null +++ b/src/TinyGsmNTP.tpp @@ -0,0 +1,92 @@ +/** + * @file TinyGsmNTP.tpp + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef SRC_TINYGSMNTP_H_ +#define SRC_TINYGSMNTP_H_ + +#include "TinyGsmCommon.h" + +#define TINY_GSM_MODEM_HAS_NTP + +template +class TinyGsmNTP { + public: + /* + * NTP server functions + */ + + public: + boolean TinyGsmIsValidNumber(String str) { + if (!(str.charAt(0) == '+' || str.charAt(0) == '-' || + isDigit(str.charAt(0)))) + return false; + + for (byte i = 1; i < str.length(); i++) { + if (!(isDigit(str.charAt(i)) || str.charAt(i) == '.')) { return false; } + } + return true; + } + + byte NTPServerSync(String server = "pool.ntp.org", byte TimeZone = 3) { + return thisModem().NTPServerSyncImpl(server, TimeZone); + } + String ShowNTPError(byte error) { + return thisModem().ShowNTPErrorImpl(error); + } + + /* + * CRTP Helper + */ + protected: + inline const modemType& thisModem() const { + return static_cast(*this); + } + inline modemType& thisModem() { + return static_cast(*this); + } + + /* + * NTP server functions + */ + protected: + byte NTPServerSyncImpl(String server = "pool.ntp.org", byte TimeZone = 3) { + // Set GPRS bearer profile to associate with NTP sync + // this may fail, it's not supported by all modules + thisModem().sendAT(GF("+CNTPCID=1")); + thisModem().waitResponse(10000L); + + // Set NTP server and timezone + thisModem().sendAT(GF("+CNTP="), server, ',', String(TimeZone)); + if (thisModem().waitResponse(10000L) != 1) { return -1; } + + // Request network synchronization + thisModem().sendAT(GF("+CNTP")); + if (thisModem().waitResponse(10000L, GF("+CNTP:"))) { + String result = thisModem().stream.readStringUntil('\n'); + result.trim(); + if (TinyGsmIsValidNumber(result)) { return result.toInt(); } + } else { + return -1; + } + return -1; + } + + String ShowNTPErrorImpl(byte error) { + switch (error) { + case 1: return "Network time synchronization is successful"; + case 61: return "Network error"; + case 62: return "DNS resolution error"; + case 63: return "Connection error"; + case 64: return "Service response error"; + case 65: return "Service response timeout"; + default: return "Unknown error: " + String(error); + } + } +}; + +#endif // SRC_TINYGSMNTP_H_