/**
|
|
* @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 modemType, uint8_t muxCount>
|
|
class TinyGsmTCP {
|
|
public:
|
|
/*
|
|
* Basic functions
|
|
*/
|
|
void maintain() {
|
|
return thisModem().maintainImpl();
|
|
}
|
|
|
|
/*
|
|
* CRTP Helper
|
|
*/
|
|
protected:
|
|
inline const modemType& thisModem() const {
|
|
return static_cast<const modemType&>(*this);
|
|
}
|
|
inline modemType& thisModem() {
|
|
return static_cast<modemType&>(*this);
|
|
}
|
|
|
|
/*
|
|
* Inner Client
|
|
*/
|
|
public:
|
|
class GsmClient : public Client {
|
|
// Make all classes created from the modem template friends
|
|
friend class TinyGsmTCP<modemType, muxCount>;
|
|
typedef TinyGsmFifo<uint8_t, TINY_GSM_RX_BUFFER> 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_
|