/** * @file TinyGsmCommon.h * @author Volodymyr Shymanskyy * @license LGPL-3.0 * @copyright Copyright (c) 2016 Volodymyr Shymanskyy * @date Nov 2016 */ #ifndef TinyGsmCommon_h #define TinyGsmCommon_h // The current library version number #define TINYGSM_VERSION "0.9.7" #if defined(SPARK) || defined(PARTICLE) #include "Particle.h" #elif defined(ARDUINO) #if ARDUINO >= 100 #include "Arduino.h" #else #include "WProgram.h" #endif #endif #if defined(ARDUINO_DASH) #include #else #include #endif #include #ifndef TINY_GSM_YIELD_MS #define TINY_GSM_YIELD_MS 0 #endif #ifndef TINY_GSM_YIELD #define TINY_GSM_YIELD() { delay(TINY_GSM_YIELD_MS); } #endif #define TINY_GSM_ATTR_NOT_AVAILABLE __attribute__((error("Not available on this modem type"))) #define TINY_GSM_ATTR_NOT_IMPLEMENTED __attribute__((error("Not implemented"))) #if defined(__AVR__) #define TINY_GSM_PROGMEM PROGMEM typedef const __FlashStringHelper* GsmConstStr; #define GFP(x) (reinterpret_cast(x)) #define GF(x) F(x) #else #define TINY_GSM_PROGMEM typedef const char* GsmConstStr; #define GFP(x) x #define GF(x) x #endif #ifdef TINY_GSM_DEBUG namespace { template static void DBG_PLAIN(T last) { TINY_GSM_DEBUG.println(last); } template static void DBG_PLAIN(T head, Args... tail) { TINY_GSM_DEBUG.print(head); TINY_GSM_DEBUG.print(' '); DBG_PLAIN(tail...); } template static void DBG(Args... args) { TINY_GSM_DEBUG.print(GF("[")); TINY_GSM_DEBUG.print(millis()); TINY_GSM_DEBUG.print(GF("] ")); DBG_PLAIN(args...); } } #else #define DBG_PLAIN(...) #define DBG(...) #endif template const T& TinyGsmMin(const T& a, const T& b) { return (b < a) ? b : a; } template const T& TinyGsmMax(const T& a, const T& b) { return (b < a) ? a : b; } template uint32_t TinyGsmAutoBaud(T& SerialAT, uint32_t minimum = 9600, uint32_t maximum = 115200) { static uint32_t rates[] = { 115200, 57600, 38400, 19200, 9600, 74400, 74880, 230400, 460800, 2400, 4800, 14400, 28800 }; for (unsigned i = 0; i < sizeof(rates)/sizeof(rates[0]); i++) { uint32_t rate = rates[i]; if (rate < minimum || rate > maximum) continue; DBG("Trying baud rate", rate, "..."); SerialAT.begin(rate); delay(10); for (int i=0; i<10; i++) { SerialAT.print("AT\r\n"); String input = SerialAT.readString(); if (input.indexOf("OK") >= 0) { DBG("Modem responded at rate", rate); return rate; } } } return 0; } static inline IPAddress TinyGsmIpFromString(const String& strIP) { int Parts[4] = {0, }; int Part = 0; for (uint8_t i=0; i 3) { return IPAddress(0,0,0,0); } continue; } else if (c >= '0' && c <= '9') { Parts[Part] *= 10; Parts[Part] += c - '0'; } else { if (Part == 3) break; } } return IPAddress(Parts[0], Parts[1], Parts[2], Parts[3]); } static inline String TinyGsmDecodeHex7bit(String &instr) { String result; byte reminder = 0; int bitstate = 7; for (unsigned i=0; i> bitstate; bitstate--; if (bitstate == 0) { char c = reminder; result += c; reminder = 0; bitstate = 7; } } return result; } static inline String TinyGsmDecodeHex8bit(String &instr) { String result; for (unsigned i=0; imaintain(); \ return at->modemSend(buf, size, mux); \ } \ \ virtual size_t write(uint8_t c) {\ return write(&c, 1); \ }\ \ virtual size_t write(const char *str) { \ if (str == NULL) return 0; \ return write((const uint8_t *)str, strlen(str)); \ } // Returns the combined number of characters available in the TinyGSM fifo // and the modem chips internal fifo, doing an extra check-in with the // modem to see if anything has arrived without a UURC. #define TINY_GSM_CLIENT_AVAILABLE_WITH_BUFFER_CHECK() \ virtual int available() { \ TINY_GSM_YIELD(); \ if (!rx.size()) { \ /* Workaround: sometimes module forgets to notify about data arrival. TODO: Currently we ping the module periodically, but maybe there's a better indicator that we need to poll */ \ if (millis() - prev_check > 500) { \ got_data = true; \ prev_check = millis(); \ } \ at->maintain(); \ } \ return rx.size() + sock_available; \ } // Returns the combined number of characters available in the TinyGSM fifo and // the modem chips internal fifo. Use this if you don't expect to miss any URC's. #define TINY_GSM_CLIENT_AVAILABLE_NO_BUFFER_CHECK() \ virtual int available() { \ TINY_GSM_YIELD(); \ if (!rx.size()) { \ at->maintain(); \ } \ return rx.size() + sock_available; \ } // Returns the number of characters available in the TinyGSM fifo // Assumes the modem chip has no internal fifo #define TINY_GSM_CLIENT_AVAILABLE_NO_MODEM_FIFO() \ virtual int available() { \ TINY_GSM_YIELD(); \ if (!rx.size() && sock_connected) { \ at->maintain(); \ } \ return rx.size(); \ } #define TINY_GSM_CLIENT_READ_OVERLOAD() \ virtual int read() { \ uint8_t c; \ if (read(&c, 1) == 1) { \ return c; \ } \ return -1; \ } // Reads characters out of the TinyGSM fifo, and from the modem chips internal // fifo if avaiable, also double checking with the modem if data has arrived // without issuing a UURC. #define TINY_GSM_CLIENT_READ_WITH_BUFFER_CHECK() \ virtual int read(uint8_t *buf, size_t size) { \ TINY_GSM_YIELD(); \ at->maintain(); \ size_t cnt = 0; \ while (cnt < size) { \ size_t chunk = TinyGsmMin(size-cnt, rx.size()); \ if (chunk > 0) { \ rx.get(buf, chunk); \ buf += chunk; \ cnt += chunk; \ continue; \ } \ /* Workaround: sometimes module forgets to notify about data arrival. TODO: Currently we ping the module periodically, but maybe there's a better indicator that we need to poll */ \ if (millis() - prev_check > 500) { \ got_data = true; \ prev_check = millis(); \ } \ /* TODO: Read directly into user buffer? */ \ at->maintain(); \ if (sock_available > 0) { \ int n = at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); \ if (n == 0) break; \ } else { \ break; \ } \ } \ return cnt; \ } \ TINY_GSM_CLIENT_READ_OVERLOAD() // Reads characters out of the TinyGSM fifo, and from the modem chips internal // fifo if avaiable. Use this if you don't expect to miss any URC's. #define TINY_GSM_CLIENT_READ_NO_BUFFER_CHECK() \ virtual int read(uint8_t *buf, size_t size) { \ TINY_GSM_YIELD(); \ at->maintain(); \ size_t cnt = 0; \ while (cnt < size) { \ size_t chunk = TinyGsmMin(size-cnt, rx.size()); \ if (chunk > 0) { \ rx.get(buf, chunk); \ buf += chunk; \ cnt += chunk; \ continue; \ } \ /* TODO: Read directly into user buffer? */ \ at->maintain(); \ if (sock_available > 0) { \ int n = at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); \ if (n == 0) break; \ } else { \ break; \ } \ } \ return cnt; \ } \ TINY_GSM_CLIENT_READ_OVERLOAD() // Reads characters out of the TinyGSM fifo, waiting for any URC's from the // modem for new data if there's nothing in the fifo. This assumes the //modem chip itself has no fifo. #define TINY_GSM_CLIENT_READ_NO_MODEM_FIFO() \ virtual int read(uint8_t *buf, size_t size) { \ TINY_GSM_YIELD(); \ size_t cnt = 0; \ uint32_t _startMillis = millis(); \ while (cnt < size && millis() - _startMillis < _timeout) { \ size_t chunk = TinyGsmMin(size-cnt, rx.size()); \ if (chunk > 0) { \ rx.get(buf, chunk); \ buf += chunk; \ cnt += chunk; \ continue; \ } \ /* TODO: Read directly into user buffer? */ \ if (!rx.size() && sock_connected) { \ at->maintain(); \ } \ } \ return cnt; \ } \ \ virtual int read() { \ uint8_t c; \ if (read(&c, 1) == 1) { \ return c; \ } \ return -1; \ } // Read and dump anything remaining in the modem's internal buffer. // Using this in the client stop() function. // The socket will appear open in response to connected() even after it // closes until all data is read from the buffer. // Doing it this way allows the external mcu to find and get all of the data // that it wants from the socket even if it was closed externally. #define TINY_GSM_CLIENT_DUMP_MODEM_BUFFER() \ TINY_GSM_YIELD(); \ rx.clear(); \ at->maintain(); \ unsigned long startMillis = millis(); \ while (sock_available > 0 && (millis() - startMillis < maxWaitMs)) { \ at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); \ rx.clear(); \ at->maintain(); \ } // The peek, flush, and connected functions #define TINY_GSM_CLIENT_PEEK_FLUSH_CONNECTED() \ virtual int peek() { return -1; } /* TODO */ \ \ virtual void flush() { at->stream.flush(); } \ \ virtual uint8_t connected() { \ if (available()) { \ return true; \ } \ return sock_connected; \ } \ virtual operator bool() { return connected(); } // Set baud rate via the V.25TER standard IPR command #define TINY_GSM_MODEM_SET_BAUD_IPR() \ void setBaud(unsigned long baud) { \ sendAT(GF("+IPR="), baud); \ } // Test response to AT commands #define TINY_GSM_MODEM_TEST_AT() \ bool testAT(unsigned long timeout_ms = 10000L) { \ for (unsigned long start = millis(); millis() - start < timeout_ms; ) { \ sendAT(GF("")); \ if (waitResponse(200) == 1) return true; \ delay(100); \ } \ return false; \ } // Keeps listening for modem URC's and iterates through sockets // to see if any data is avaiable #define TINY_GSM_MODEM_MAINTAIN_CHECK_SOCKS() \ void maintain() { \ for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) { \ GsmClient* sock = sockets[mux]; \ if (sock && sock->got_data) { \ sock->got_data = false; \ sock->sock_available = modemGetAvailable(mux); \ } \ } \ while (stream.available()) { \ waitResponse(15, NULL, NULL); \ } \ } // Keeps listening for modem URC's - doesn't check socks because // modem has no internal fifo #define TINY_GSM_MODEM_MAINTAIN_LISTEN() \ void maintain() { \ waitResponse(100, NULL, NULL); \ } // Asks for modem information via the V.25TER standard ATI command // NOTE: The actual value and style of the response is quite varied #define TINY_GSM_MODEM_GET_INFO_ATI() \ String getModemInfo() { \ sendAT(GF("I")); \ String res; \ if (waitResponse(1000L, res) != 1) { \ return ""; \ } \ res.replace(GSM_NL "OK" GSM_NL, ""); \ res.replace(GSM_NL, " "); \ res.trim(); \ return res; \ } // Unlocks a sim via the 3GPP TS command AT+CPIN #define TINY_GSM_MODEM_SIM_UNLOCK_CPIN() \ bool simUnlock(const char *pin) { \ sendAT(GF("+CPIN=\""), pin, GF("\"")); \ return waitResponse() == 1; \ } // Gets the CCID of a sim card via AT+CCID #define TINY_GSM_MODEM_GET_SIMCCID_CCID() \ String getSimCCID() { \ sendAT(GF("+CCID")); \ if (waitResponse(GF(GSM_NL "+CCID:")) != 1) { \ return ""; \ } \ String res = stream.readStringUntil('\n'); \ waitResponse(); \ res.trim(); \ return res; \ } // Asks for TA Serial Number Identification (IMEI) via the V.25TER standard AT+GSN command #define TINY_GSM_MODEM_GET_IMEI_GSN() \ String getIMEI() { \ sendAT(GF("+GSN")); \ if (waitResponse(GF(GSM_NL)) != 1) { \ return ""; \ } \ String res = stream.readStringUntil('\n'); \ waitResponse(); \ res.trim(); \ return res; \ } // Gets the modem's registration status via CREG/CGREG/CEREG // CREG = Generic network registration // CGREG = GPRS service registration // CEREG = EPS registration for LTE modules #define TINY_GSM_MODEM_GET_REGISTRATION_XREG(regCommand) \ RegStatus getRegistrationStatus() { \ sendAT(GF("+" #regCommand "?")); \ if (waitResponse(GF(GSM_NL "+" #regCommand ":")) != 1) { \ return REG_UNKNOWN; \ } \ streamSkipUntil(','); /* Skip format (0) */ \ int status = stream.readStringUntil('\n').toInt(); \ waitResponse(); \ return (RegStatus)status; \ } // Gets the current network operator via the 3GPP TS command AT+COPS #define TINY_GSM_MODEM_GET_OPERATOR_COPS() \ String getOperator() { \ sendAT(GF("+COPS?")); \ if (waitResponse(GF(GSM_NL "+COPS:")) != 1) { \ return ""; \ } \ streamSkipUntil('"'); /* Skip mode and format */ \ String res = stream.readStringUntil('"'); \ waitResponse(); \ return res; \ } // Waits for network attachment #define TINY_GSM_MODEM_WAIT_FOR_NETWORK() \ bool waitForNetwork(unsigned long timeout_ms = 60000L) { \ for (unsigned long start = millis(); millis() - start < timeout_ms; ) { \ if (isNetworkConnected()) { \ return true; \ } \ delay(250); \ } \ return false; \ } // Checks if current attached to GPRS/EPS service #define TINY_GSM_MODEM_GET_GPRS_IP_CONNECTED() \ bool isGprsConnected() { \ sendAT(GF("+CGATT?")); \ if (waitResponse(GF(GSM_NL "+CGATT:")) != 1) { \ return false; \ } \ int res = stream.readStringUntil('\n').toInt(); \ waitResponse(); \ if (res != 1) \ return false; \ \ return localIP() != IPAddress(0,0,0,0); \ } // Gets signal quality report according to 3GPP TS command AT+CSQ #define TINY_GSM_MODEM_GET_CSQ() \ int16_t getSignalQuality() { \ sendAT(GF("+CSQ")); \ if (waitResponse(GF(GSM_NL "+CSQ:")) != 1) { \ return 99; \ } \ int res = stream.readStringUntil(',').toInt(); \ waitResponse(); \ return res; \ } // Yields up to a time-out period and then reads a character from the stream into the mux FIFO // TODO: Do we need to wait two _timeout periods for no character return? Will wait once in the first // "while !stream.available()" and then will wait again in the stream.read() function. #define TINY_GSM_MODEM_STREAM_TO_MUX_FIFO_WITH_DOUBLE_TIMEOUT \ uint32_t startMillis = millis(); \ while (!stream.available() && (millis() - startMillis < sockets[mux]->_timeout)) { TINY_GSM_YIELD(); } \ char c = stream.read(); \ sockets[mux]->rx.put(c); // Utility templates for writing/skipping characters on a stream #define TINY_GSM_MODEM_STREAM_UTILITIES() \ template \ void streamWrite(T last) { \ stream.print(last); \ } \ \ template \ void streamWrite(T head, Args... tail) { \ stream.print(head); \ streamWrite(tail...); \ } \ \ template \ void sendAT(Args... cmd) { \ streamWrite("AT", cmd..., GSM_NL); \ stream.flush(); \ TINY_GSM_YIELD(); \ /* DBG("### AT:", cmd...); */ \ } \ \ bool streamSkipUntil(const char c, const unsigned long timeout_ms = 1000L) { \ unsigned long startMillis = millis(); \ while (millis() - startMillis < timeout_ms) { \ while (millis() - startMillis < timeout_ms && !stream.available()) { \ TINY_GSM_YIELD(); \ } \ if (stream.read() == c) { \ return true; \ } \ } \ return false; \ } #endif