/** * @file TinyGsmTCP.tpp * @author Volodymyr Shymanskyy * @license LGPL-3.0 * @copyright Copyright (c) 2016 Volodymyr Shymanskyy * @date Nov 2016 */ #ifndef SRC_TINYGSMTCP_H_ #define SRC_TINYGSMTCP_H_ #include "TinyGsmCommon.h" #define TINY_GSM_MODEM_HAS_TCP #include "TinyGsmFifo.h" #if !defined(TINY_GSM_RX_BUFFER) #define TINY_GSM_RX_BUFFER 64 #endif // Because of the ordering of resolution of overrides in templates, these need // to be written out every time. This macro is to shorten that. #define TINY_GSM_CLIENT_CONNECT_OVERRIDES \ int connect(IPAddress ip, uint16_t port, int timeout_s) { \ return connect(TinyGsmStringFromIp(ip).c_str(), port, timeout_s); \ } \ int connect(const char* host, uint16_t port) override { \ return connect(host, port, 75); \ } \ int connect(IPAddress ip, uint16_t port) override { \ return connect(ip, port, 75); \ } // // For modules that do not store incoming data in any sort of buffer // #define TINY_GSM_NO_MODEM_BUFFER // // Data is stored in a buffer, but we can only read from the buffer, // // not check how much data is stored in it // #define TINY_GSM_BUFFER_READ_NO_CHECK // // Data is stored in a buffer and we can both read and check the size // // of the buffer // #define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE template class TinyGsmTCP { public: /* * Basic functions */ void maintain() { return thisModem().maintainImpl(); } /* * CRTP Helper */ protected: inline const modemType& thisModem() const { return static_cast(*this); } inline modemType& thisModem() { return static_cast(*this); } /* * Inner Client */ public: class GsmClient : public Client { // Make all classes created from the modem template friends friend class TinyGsmTCP; typedef TinyGsmFifo RxFifo; public: // bool init(modemType* modem, uint8_t); // int connect(const char* host, uint16_t port, int timeout_s); // Connect to a IP address given as an IPAddress object by // converting said IP address to text // virtual int connect(IPAddress ip,uint16_t port, int timeout_s) { // return connect(TinyGsmStringFromIp(ip).c_str(), port, // timeout_s); // } // int connect(const char* host, uint16_t port) override { // return connect(host, port, 75); // } // int connect(IPAddress ip,uint16_t port) override { // return connect(ip, port, 75); // } static inline String TinyGsmStringFromIp(IPAddress ip) { String host; host.reserve(16); host += ip[0]; host += "."; host += ip[1]; host += "."; host += ip[2]; host += "."; host += ip[3]; return host; } // void stop(uint32_t maxWaitMs); // void stop() override { // stop(15000L); // } // Writes data out on the client using the modem send functionality size_t write(const uint8_t* buf, size_t size) override { TINY_GSM_YIELD(); at->maintain(); return at->modemSend(buf, size, mux); } size_t write(uint8_t c) override { return write(&c, 1); } size_t write(const char* str) { if (str == NULL) return 0; return write((const uint8_t*)str, strlen(str)); } int available() override { TINY_GSM_YIELD(); #if defined TINY_GSM_NO_MODEM_BUFFER // Returns the number of characters available in the TinyGSM fifo if (!rx.size() && sock_connected) { at->maintain(); } return rx.size(); #elif defined TINY_GSM_BUFFER_READ_NO_CHECK // Returns the combined number of characters available in the TinyGSM // fifo and the modem chips internal fifo. if (!rx.size()) { at->maintain(); } return rx.size() + sock_available; #elif defined TINY_GSM_BUFFER_READ_AND_CHECK_SIZE // 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. if (!rx.size()) { if (millis() - prev_check > 500) { got_data = true; prev_check = millis(); } at->maintain(); } return rx.size() + sock_available; #else #error Modem client has been incorrectly created #endif } int read(uint8_t* buf, size_t size) override { TINY_GSM_YIELD(); size_t cnt = 0; #if defined TINY_GSM_NO_MODEM_BUFFER // 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. 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; #elif defined TINY_GSM_BUFFER_READ_NO_CHECK // Reads characters out of the TinyGSM fifo, and from the modem chip's // internal fifo if avaiable. at->maintain(); 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; #elif defined TINY_GSM_BUFFER_READ_AND_CHECK_SIZE // 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. at->maintain(); while (cnt < size) { size_t chunk = TinyGsmMin(size - cnt, rx.size()); if (chunk > 0) { rx.get(buf, chunk); buf += chunk; cnt += chunk; continue; } // Workaround: Some modules "forget" to notify about data arrival if (millis() - prev_check > 500) { got_data = true; prev_check = millis(); } // TODO(vshymanskyy): 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; #else #error Modem client has been incorrectly created #endif } int read() override { uint8_t c; if (read(&c, 1) == 1) { return c; } return -1; } // TODO(SRGDamia1): Implement peek int peek() override { return -1; } void flush() override { at->stream.flush(); } uint8_t connected() override { if (available()) { return true; } return sock_connected; } operator bool() override { return connected(); } /* * Extended API */ String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; protected: // 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. inline void dumpModemBuffer(uint32_t maxWaitMs) { #if defined TINY_GSM_BUFFER_READ_AND_CHECK_SIZE || \ defined TINY_GSM_BUFFER_READ_NO_CHECK TINY_GSM_YIELD(); uint32_t startMillis = millis(); while (sock_available > 0 && (millis() - startMillis < maxWaitMs)) { rx.clear(); DBG(TinyGsmMin((uint16_t)rx.free(), sock_available)); at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); } #elif defined TINY_GSM_NO_MODEM_BUFFER // Do nothing #else #error Modem client has been incorrectly created #endif } modemType* at; uint8_t mux; uint16_t sock_available; uint32_t prev_check; bool sock_connected; bool got_data; RxFifo rx; }; /* * Basic functions */ protected: void maintainImpl() { #if defined TINY_GSM_BUFFER_READ_AND_CHECK_SIZE // Keep listening for modem URC's and proactively iterate through // sockets asking if any data is avaiable for (int mux = 0; mux < muxCount; mux++) { GsmClient* sock = thisModem().sockets[mux]; if (sock && sock->got_data) { sock->got_data = false; sock->sock_available = thisModem().modemGetAvailable(mux); } } while (thisModem().stream.available()) { thisModem().waitResponse(15, NULL, NULL); } #elif defined TINY_GSM_NO_MODEM_BUFFER || defined TINY_GSM_BUFFER_READ_NO_CHECK // Just listen for any URC's thisModem().waitResponse(100, NULL, NULL); #else #error Modem client has been incorrectly created #endif } // Yields up to a time-out period and then reads a character from the stream // into the mux FIFO // TODO(SRGDamia1): 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. inline void moveCharFromStreamToFifo(uint8_t mux) { uint32_t startMillis = millis(); while (!thisModem().stream.available() && (millis() - startMillis < thisModem().sockets[mux]->_timeout)) { TINY_GSM_YIELD(); } char c = thisModem().stream.read(); thisModem().sockets[mux]->rx.put(c); } }; #endif // SRC_TINYGSMTCP_H_