Files
TinyGSM_EC25/src/TinyGsmClientSIM5360.h
2019-07-17 13:30:46 -04:00

815 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file TinyGsmClientSIM5360.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef TinyGsmClientSIM5360_h
#define TinyGsmClientSIM5360_h
// #define TINY_GSM_DEBUG Serial
//#define TINY_GSM_USE_HEX
#if !defined(TINY_GSM_RX_BUFFER)
#define TINY_GSM_RX_BUFFER 64
#endif
#define TINY_GSM_MUX_COUNT 10
#include <TinyGsmCommon.h>
#define GSM_NL "\r\n"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
enum SimStatus {
SIM_ERROR = 0,
SIM_READY = 1,
SIM_LOCKED = 2,
};
enum RegStatus {
REG_UNREGISTERED = 0,
REG_SEARCHING = 2,
REG_DENIED = 3,
REG_OK_HOME = 1,
REG_OK_ROAMING = 5,
REG_UNKNOWN = 4,
};
enum TinyGSMDateTimeFormat {
DATE_FULL = 0,
DATE_TIME = 1,
DATE_DATE = 2
};
class TinyGsmSim5360
{
public:
class GsmClient : public Client
{
friend class TinyGsmSim5360;
typedef TinyGsmFifo<uint8_t, TINY_GSM_RX_BUFFER> RxFifo;
public:
GsmClient() {}
GsmClient(TinyGsmSim5360& modem, uint8_t mux = 1) {
init(&modem, mux);
}
virtual ~GsmClient(){}
bool init(TinyGsmSim5360* modem, uint8_t mux = 1) {
this->at = modem;
this->mux = mux;
sock_available = 0;
prev_check = 0;
sock_connected = false;
got_data = false;
at->sockets[mux] = this;
return true;
}
public:
virtual int connect(const char *host, uint16_t port, 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_OVERLOADS()
virtual void stop(uint32_t maxWaitMs) {
TINY_GSM_CLIENT_DUMP_MODEM_BUFFER()
at->sendAT(GF("+CIPCLOSE="), mux);
sock_connected = false;
at->waitResponse();
}
virtual void stop() { stop(15000L); }
TINY_GSM_CLIENT_WRITE()
TINY_GSM_CLIENT_AVAILABLE_WITH_BUFFER_CHECK()
TINY_GSM_CLIENT_READ_WITH_BUFFER_CHECK()
TINY_GSM_CLIENT_PEEK_FLUSH_CONNECTED()
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
private:
TinyGsmSim5360* at;
uint8_t mux;
uint16_t sock_available;
uint32_t prev_check;
bool sock_connected;
bool got_data;
RxFifo rx;
};
public:
TinyGsmSim5360(Stream& stream)
: stream(stream)
{
memset(sockets, 0, sizeof(sockets));
}
virtual ~TinyGsmSim5360(){}
/*
* Basic functions
*/
bool begin(const char* pin = NULL) {
return init(pin);
}
bool init(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
if (!testAT()) {
return false;
}
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) {
return false;
}
DBG(GF("### Modem:"), getModemName());
getSimStatus();
return true;
}
String getModemName() {
String name = "SIMCom SIM5360";
sendAT(GF("+CGMM"));
String res2;
if (waitResponse(1000L, res2) != 1) {
return name;
}
res2.replace(GSM_NL "OK" GSM_NL, "");
res2.replace("_", " ");
res2.trim();
name = res2;
DBG("### Modem:", name);
return name;
}
TINY_GSM_MODEM_SET_BAUD_IPR()
TINY_GSM_MODEM_TEST_AT()
TINY_GSM_MODEM_MAINTAIN_CHECK_SOCKS()
bool factoryDefault() { // these commands aren't supported
return false;
}
TINY_GSM_MODEM_GET_INFO_ATI()
bool hasSSL() {
return false; // TODO: Module supports SSL, but not yet implemented
}
bool hasWifi() {
return false;
}
bool hasGPRS() {
return true;
}
/*
* Power functions
*/
bool restart() {
if (!testAT()) {
return false;
}
sendAT(GF("+REBOOT"));
if (waitResponse(10000L) != 1) {
return false;
}
waitResponse(10000L, GF(GSM_NL "PB DONE"));
return init();
}
bool poweroff() {
sendAT(GF("+CPOF"));
return waitResponse() == 1;
}
bool radioOff() {
sendAT(GF("+CFUN=4"));
if (waitResponse(10000L) != 1) {
return false;
}
delay(3000);
return true;
}
bool sleepEnable(bool enable = true) {
sendAT(GF("+CSCLK="), enable);
return waitResponse() == 1;
}
/*
* SIM card functions
*/
TINY_GSM_MODEM_SIM_UNLOCK_CPIN()
// Gets the CCID of a sim card via AT+CCID
String getSimCCID() {
sendAT(GF("+CICCID"));
if (waitResponse(GF(GSM_NL "+ICCID:")) != 1) {
return "";
}
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
TINY_GSM_MODEM_GET_IMEI_GSN()
SimStatus getSimStatus(unsigned long timeout_ms = 10000L) {
for (unsigned long start = millis(); millis() - start < timeout_ms; ) {
sendAT(GF("+CPIN?"));
if (waitResponse(GF(GSM_NL "+CPIN:")) != 1) {
delay(1000);
continue;
}
int status = waitResponse(GF("READY"), GF("SIM PIN"), GF("SIM PUK"));
waitResponse();
switch (status) {
case 2:
case 3: return SIM_LOCKED;
case 1: return SIM_READY;
default: return SIM_ERROR;
}
}
return SIM_ERROR;
}
TINY_GSM_MODEM_GET_REGISTRATION_XREG(CGREG)
TINY_GSM_MODEM_GET_OPERATOR_COPS()
/*
* Generic network functions
*/
TINY_GSM_MODEM_GET_CSQ()
bool isNetworkConnected() {
RegStatus s = getRegistrationStatus();
return (s == REG_OK_HOME || s == REG_OK_ROAMING);
}
String getNetworkModes() {
sendAT(GF("+CNMP=?"));
if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) {
return "";
}
String res = stream.readStringUntil('\n');
waitResponse();
return res;
}
TINY_GSM_MODEM_WAIT_FOR_NETWORK()
String setNetworkMode(uint8_t mode) {
sendAT(GF("+CNMP="), mode);
if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) {
return "OK";
}
String res = stream.readStringUntil('\n');
waitResponse();
return res;
}
/*
* GPRS functions
*/
bool gprsConnect(const char* apn, const char* user = NULL, const char* pwd = NULL) {
gprsDisconnect();
// Define the PDP context
// Using CGSOCKCONT commands defines a PDP context for Embedded TCP/IP application
// CGDCONT commands could be used for an external PDP context
sendAT(GF("+CGSOCKCONT=1,\"IP\",\""), apn, '"');
waitResponse();
// Set the user name and password
if (user && strlen(user) > 0) {
sendAT(GF("+CSOCKAUTH=1,1,\""), user, "\",\"", pwd, '"');
waitResponse();
}
// Set active PDP contexts profile number
sendAT(GF("+CSOCKSETPN=1"));
waitResponse();
// Configure TCP parameters
//AT+CIPCCFG= [<NmRetry>][,[<DelayTm>][,[<Ack>][,[<errMode>][,]<HeaderType>][,[[<AsyncMode>][,[<TimeoutVal>]]]]]]]]
// NmRetry = number of retransmission to be made for an IP packet = 10 (default)
// DelayTm = number of milliseconds to delay to output data of Receiving = 0 (default)
// Ack = sets whether reporting a string “Send ok” = 0 (don't report)
// errMode = mode of reporting error result code = 0 (numberic values)
// HeaderType = which data header of receiving data in multi-client mode = 1 (“+RECEIVE,<link num>,<data length>”)
// AsyncMode = sets mode of executing commands = 0 (synchronous command executing)
// TimeoutVal = minimum retransmission timeout in milliseconds = 75000
sendAT(GF("+CIPCCFG=10,0,0,0,1,0,75000"));
if (waitResponse() != 1) {
return false;
}
// Select TCP/IP application mode (command mode)
sendAT(GF("+CIPMODE=0"));
waitResponse();
// Configure timeouts for open and close socket
// AT+CIPTIMEOUT=[<netopen_timeout>][, [<cipopen_timeout>][, [<cipsend_timeout>]]]
sendAT(GF("+CIPTIMEOUT"), 75000, ',', 15000, ',', 15000);
waitResponse();
// Set to get data manually
sendAT(GF("+CIPRXGET=1"));
if (waitResponse() != 1) {
return false;
}
// Actually open the packet network
// NOTE: AT command manual hints this might be depricated or other options preferred
// but all application notes use it
sendAT(GF("+NETOPEN"));
if (waitResponse(60000L) != 1) {
return false;
}
/*
sendAT(GF("+CGATT=1")); // attach to GPRS
if (waitResponse(360000L) != 1) {
return false;
}
// Using CGDCONT sets up an "external" PCP context, i.e. a data connection
// using the external IP stack (e.g. Windows dial up) and PPP link over the
// serial interface. Is this preferred?
if (user && strlen(user) > 0) {
sendAT(GF("+CGAUTH=1,0,\""), user, GF("\",\""), pwd, '"'); // Set the authentication
waitResponse();
}
sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); // Define PDP context 1
waitResponse();
sendAT(GF("+CGACT=1,1")); // activate PDP profile/context 1
if (waitResponse(150000L) != 1) {
return false;
}
*/
return true;
}
bool gprsDisconnect() {
// Close the network (note, all sockets should be closed first)
sendAT(GF("+NETCLOSE"));
if (waitResponse(60000L) != 1)
return false;
return true;
}
bool isGprsConnected() {
sendAT(GF("+NETOPEN?"));
if (waitResponse(GF(GSM_NL "+NETOPEN:")) != 1) {
return false;
}
int res = stream.readStringUntil('\n').toInt();
waitResponse();
if (res != 1)
return false;
sendAT(GF("+IPADDR")); // Make sure we have a local IP address
if (waitResponse() != 1)
return false;
return true;
}
/*
* IP Address functions
*/
String getLocalIP() {
sendAT(GF("+IPADDR"));
String res;
if (waitResponse(10000L, res) != 1) {
return "";
}
res.replace(GSM_NL "OK" GSM_NL, "");
res.replace(GSM_NL, "");
res.trim();
return res;
}
IPAddress localIP() {
return TinyGsmIpFromString(getLocalIP());
}
/*
* Phone Call functions
*/
bool setGsmBusy() TINY_GSM_ATTR_NOT_IMPLEMENTED;
bool callAnswer() TINY_GSM_ATTR_NOT_IMPLEMENTED;
bool callNumber() TINY_GSM_ATTR_NOT_IMPLEMENTED;
bool callHangup() TINY_GSM_ATTR_NOT_IMPLEMENTED;
bool dtmfSend() TINY_GSM_ATTR_NOT_IMPLEMENTED;
/*
* Messaging functions
*/
String sendUSSD(const String& code) {
sendAT(GF("+CMGF=1"));
waitResponse();
sendAT(GF("+CSCS=\"HEX\""));
waitResponse();
sendAT(GF("+CUSD=1,\""), code, GF("\""));
if (waitResponse() != 1) {
return "";
}
if (waitResponse(10000L, GF(GSM_NL "+CUSD:")) != 1) {
return "";
}
stream.readStringUntil('"');
String hex = stream.readStringUntil('"');
stream.readStringUntil(',');
int dcs = stream.readStringUntil('\n').toInt();
if (dcs == 15) {
return TinyGsmDecodeHex8bit(hex);
} else if (dcs == 72) {
return TinyGsmDecodeHex16bit(hex);
} else {
return hex;
}
}
bool sendSMS(const String& number, const String& text) {
sendAT(GF("+AT+CSCA?"));
waitResponse();
sendAT(GF("+CMGF=1"));
waitResponse();
//Set GSM 7 bit default alphabet (3GPP TS 23.038)
sendAT(GF("+CSCS=\"GSM\""));
waitResponse();
sendAT(GF("+CMGS=\""), number, GF("\""));
if (waitResponse(GF(">")) != 1) {
return false;
}
stream.print(text);
stream.write((char)0x1A);
stream.flush();
return waitResponse(60000L) == 1;
}
bool sendSMS_UTF16(const String& number, const void* text, size_t len) {
sendAT(GF("+CMGF=1"));
waitResponse();
sendAT(GF("+CSCS=\"HEX\""));
waitResponse();
sendAT(GF("+CSMP=17,167,0,8"));
waitResponse();
sendAT(GF("+CMGS=\""), number, GF("\""));
if (waitResponse(GF(">")) != 1) {
return false;
}
uint16_t* t = (uint16_t*)text;
for (size_t i=0; i<len; i++) {
uint8_t c = t[i] >> 8;
if (c < 0x10) { stream.print('0'); }
stream.print(c, HEX);
c = t[i] & 0xFF;
if (c < 0x10) { stream.print('0'); }
stream.print(c, HEX);
}
stream.write((char)0x1A);
stream.flush();
return waitResponse(60000L) == 1;
}
/*
* Location functions
*/
String getGsmLocation() TINY_GSM_ATTR_NOT_IMPLEMENTED;
/*
* GPS location functions
*/
/*
* Time functions
*/
/*
* Battery & temperature functions
*/
// Use: float vBatt = modem.getBattVoltage() / 1000.0;
uint16_t getBattVoltage() {
sendAT(GF("+CBC"));
if (waitResponse(GF(GSM_NL "+CBC:")) != 1) {
return 0;
}
streamSkipUntil(','); // Skip battery charge status
streamSkipUntil(','); // Skip battery charge level
// return voltage in mV
uint16_t res = stream.readStringUntil(',').toInt();
// Wait for final OK
waitResponse();
return res;
}
int8_t getBattPercent() {
sendAT(GF("+CBC"));
if (waitResponse(GF(GSM_NL "+CBC:")) != 1) {
return false;
}
streamSkipUntil(','); // Skip battery charge status
// Read battery charge level
int res = stream.readStringUntil(',').toInt();
// Wait for final OK
waitResponse();
return res;
}
uint8_t getBattChargeState() {
sendAT(GF("+CBC?"));
if (waitResponse(GF(GSM_NL "+CBC:")) != 1) {
return false;
}
// Read battery charge status
int res = stream.readStringUntil(',').toInt();
// Wait for final OK
waitResponse();
return res;
}
bool getBattStats(uint8_t &chargeState, int8_t &percent, uint16_t &milliVolts) {
sendAT(GF("+CBC?"));
if (waitResponse(GF(GSM_NL "+CBC:")) != 1) {
return false;
}
chargeState = stream.readStringUntil(',').toInt();
percent = stream.readStringUntil(',').toInt();
milliVolts = stream.readStringUntil('\n').toInt();
// Wait for final OK
waitResponse();
return true;
}
float getTemperature() TINY_GSM_ATTR_NOT_AVAILABLE;
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t mux,
bool ssl = false, int timeout_s = 75)
{
// Establish connection in multi-socket mode
sendAT(GF("+CIOPEN="), mux, ',', GF("\"TCP"), GF("\",\""), host, GF("\","), port);
if (waitResponse(GF(GSM_NL "+CIOPEN:")) != 1) { // reply is +USOCR: ## of socket created
return false;
}
return waitResponse() == 1;
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
sendAT(GF("+CIPSEND="), mux, ',', len);
if (waitResponse(GF(">")) != 1) {
return 0;
}
stream.write((uint8_t*)buff, len);
stream.flush();
if (waitResponse(GF(GSM_NL "+CIPSEND:")) != 1) {
return 0;
}
streamSkipUntil(','); // Skip mux
streamSkipUntil(','); // Skip requested bytes to send
// TODO: make sure requested and confirmed bytes match
return stream.readStringUntil('\n').toInt();
}
size_t modemRead(size_t size, uint8_t mux) {
#ifdef TINY_GSM_USE_HEX
sendAT(GF("+CIPRXGET=3,"), mux, ',', size);
if (waitResponse(GF("+CIPRXGET:")) != 1) {
return 0;
}
#else
sendAT(GF("+CIPRXGET=2,"), mux, ',', size);
if (waitResponse(GF("+CIPRXGET:")) != 1) {
return 0;
}
#endif
streamSkipUntil(','); // Skip Rx mode 2/normal or 3/HEX
streamSkipUntil(','); // Skip mux/cid (connecion id)
size_t len_requested = stream.readStringUntil(',').toInt();
// ^^ Requested number of data bytes (1-1460 bytes)to be read
size_t len_confirmed = stream.readStringUntil('\n').toInt();
// ^^ The data length which not read in the buffer
for (size_t 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);
sockets[mux]->sock_available = len_confirmed;
waitResponse();
return len_requested;
}
size_t modemGetAvailable(uint8_t mux) {
sendAT(GF("+CIPRXGET=4,"), mux);
size_t result = 0;
if (waitResponse(GF("+CIPRXGET:")) == 1) {
streamSkipUntil(','); // Skip mode 4
streamSkipUntil(','); // Skip mux
result = stream.readStringUntil('\n').toInt();
waitResponse();
}
DBG("### Available:", result, "on", mux);
if (!result) {
sockets[mux]->sock_connected = modemGetConnected(mux);
}
return result;
}
bool modemGetConnected(uint8_t mux) {
// Read the status of all sockets at once
sendAT(GF("+CIPCLOSE?"), mux);
if (waitResponse(GFP(GSM_OK), GF(GSM_NL "+CIPCLOSE: ")) != 2)
return false;
for (int muxNo = 0; muxNo <= TINY_GSM_MUX_COUNT; muxNo++) {
// +CIPCLOSE:<link0_state>,<link1_state>,...,<link9_state>
sockets[muxNo]->sock_connected = stream.parseInt();
}
waitResponse(); // Should be an OK at the end
return sockets[mux]->sock_connected;
}
public:
/*
Utilities
*/
TINY_GSM_MODEM_STREAM_UTILITIES()
// TODO: Optimize this!
uint8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
{
/*String r1s(r1); r1s.trim();
String r2s(r2); r2s.trim();
String r3s(r3); r3s.trim();
String r4s(r4); r4s.trim();
String r5s(r5); r5s.trim();
DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/
data.reserve(64);
int index = 0;
unsigned long startMillis = millis();
do {
TINY_GSM_YIELD();
while (stream.available() > 0) {
TINY_GSM_YIELD();
int a = stream.read();
if (a <= 0) continue; // Skip 0x00 bytes, just in case
data += (char)a;
if (r1 && data.endsWith(r1)) {
index = 1;
goto finish;
} else if (r2 && data.endsWith(r2)) {
index = 2;
goto finish;
} else if (r3 && data.endsWith(r3)) {
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) {
String mode = stream.readStringUntil(',');
if (mode.toInt() == 1) {
int mux = stream.readStringUntil('\n').toInt();
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
}
data = "";
DBG("### Got Data:", mux);
} else {
data += mode;
}
} else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) {
int mux = stream.readStringUntil(',').toInt();
int len = stream.readStringUntil('\n').toInt();
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
sockets[mux]->sock_available = len;
}
data = "";
DBG("### Got Data:", len, "on", mux);
} else if (data.endsWith(GF("+IPCLOSE:"))) {
int mux = stream.readStringUntil(',').toInt();
streamSkipUntil('\n'); // Skip the reason code
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->sock_connected = false;
}
data = "";
DBG("### Closed: ", mux);
}
}
} 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;
}
uint8_t waitResponse(uint32_t timeout_ms,
GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
{
String data;
return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5);
}
uint8_t waitResponse(GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
{
return waitResponse(1000, r1, r2, r3, r4, r5);
}
public:
Stream& stream;
protected:
GsmClient* sockets[TINY_GSM_MUX_COUNT];
};
#endif