This commit is contained in:
xin
2025-07-08 08:54:35 +08:00
parent bc81bd41ac
commit 6de3458dfc
376 changed files with 68605 additions and 246 deletions

View File

@ -0,0 +1,730 @@
/**
* @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<TinyGsmSim7000SSL>,
public TinyGsmTCP<TinyGsmSim7000SSL, TINY_GSM_MUX_COUNT>,
public TinyGsmSSL<TinyGsmSim7000SSL> {
friend class TinyGsmSim70xx<TinyGsmSim7000SSL>;
friend class TinyGsmTCP<TinyGsmSim7000SSL, TINY_GSM_MUX_COUNT>;
friend class TinyGsmSSL<TinyGsmSim7000SSL>;
/*
* 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() {}
explicit GsmClientSecureSIM7000SSL(TinyGsmSim7000SSL& modem,
uint8_t mux = 0)
: GsmClientSim7000SSL(modem, mux) {}
public:
bool setCertificate(const String& certificateName) {
return at->setCertificate(certificateName, mux);
}
virtual 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
};
/*
* Constructor
*/
public:
explicit TinyGsmSim7000SSL(Stream& stream)
: TinyGsmSim70xx<TinyGsmSim7000SSL>(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);
}
}
void maintainImpl() {
// Keep listening for modem URC's and proactively iterate through
// sockets asking if any data is avaiable
bool check_socks = false;
for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) {
GsmClientSim7000SSL* sock = sockets[mux];
if (sock && sock->got_data) {
sock->got_data = false;
check_socks = true;
}
}
// modemGetAvailable checks all socks, so we only want to do it once
// modemGetAvailable calls modemGetConnected(), which also checks allf
if (check_socks) { modemGetAvailable(0); }
while (stream.available()) { waitResponse(15, NULL, NULL); }
}
/*
* 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();
// Define the PDP context
sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"');
waitResponse();
// Attach to GPRS
sendAT(GF("+CGATT=1"));
if (waitResponse(60000L) != 1) { return false; }
// NOTE: **DO NOT** activate the PDP context
// For who only knows what reason, doing so screws up the rest of the
// process
// Bearer settings for applications based on IP
// Set the user name and password
// AT+CNCFG=<ip_type>[,<APN>[,<usename>,<password>[,<authentication>]]]
//<ip_type> 0: Dual PDN Stack
// 1: Internet Protocol Version 4
// 2: Internet Protocol Version 6
//<authentication> 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=<mode>,<action>
// <mode> 0: Deactive
// 1: Active
// 2: Auto Active
bool res = false;
int ntries = 0;
while (!res && ntries < 5) {
sendAT(GF("+CNACT=1,\""), apn, GF("\""));
res = waitResponse(60000L, GF(GSM_NL "+APP PDP: ACTIVE"),
GF(GSM_NL "+APP PDP: DEACTIVE")) == 1;
waitResponse();
ntries++;
}
return res;
}
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
// AT+CSSLCFG="SSLVERSION",<ctxindex>,<sslversion>
// <ctxindex> PDP context identifier
// <sslversion> 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
// NOTE: despite docs using caps, "sslversion" must be in lower case
sendAT(GF("+CSSLCFG=\"sslversion\",0,3")); // TLS 1.2
if (waitResponse(5000L) != 1) return false;
}
// enable or disable ssl
// AT+CASSLCFG=<cid>,"SSL",<sslFlag>
// <cid> Application connection ID (set with AT+CACID above)
// <sslFlag> 0: Not support SSL
// 1: Support SSL
sendAT(GF("+CASSLCFG="), mux, ',', GF("ssl,"), ssl);
waitResponse();
if (ssl) {
// set the PDP context to apply SSL to
// AT+CSSLCFG="CTXINDEX",<ctxindex>
// <ctxindex> PDP context identifier
// NOTE: despite docs using caps, "ctxindex" must be in lower case
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=<cid>,"CACERT",<caname>
// <cid> Application connection ID (set with AT+CACID above)
// <certname> certificate name
sendAT(GF("+CASSLCFG="), mux, ",CACERT,\"", certificates[mux].c_str(),
"\"");
if (waitResponse(5000L) != 1) return false;
}
// set the protocol
// 0: TCP; 1: UDP
sendAT(GF("+CASSLCFG="), mux, ',', GF("protocol,0"));
waitResponse();
// set the SSL SNI (server name indication)
// NOTE: despite docs using caps, "sni" must be in lower case
sendAT(GF("+CSSLCFG=\"sni\","), mux, ',', GF("\""), host, GF("\""));
waitResponse();
}
// actually open the connection
// AT+CAOPEN=<cid>[,<conn_type>],<server>,<port>
// <cid> TCP/UDP identifier
// <conn_type> "TCP" or "UDP"
// NOTE: the "TCP" can't be included
sendAT(GF("+CAOPEN="), mux, GF(",\""), host, GF("\","), port);
if (waitResponse(timeout_ms, GF(GSM_NL "+CAOPEN:")) != 1) { return 0; }
// returns OK/r/n/r/n+CAOPEN: <cid>,<result>
// <result> 0: Success
// 1: Socket error
// 2: No memory
// 3: Connection limit
// 4: Parameter invalid
// 6: Invalid IP address
// 7: Not support the function
// 12: Cant bind the port
// 13: Cant listen the port
// 20: Cant resolve the host
// 21: Network not active
// 23: Remote refuse
// 24: Certificates time expired
// 25: Certificates common name does not match
// 26: Certificates 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) {
// send data on prompt
sendAT(GF("+CASEND="), mux, ',', (uint16_t)len);
if (waitResponse(GF(">")) != 1) { return 0; }
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
// after posting data, module responds with:
//+CASEND: <cid>,<result>,<sendlen>
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) { return 0; }
// uint8_t ret_mux = stream.parseInt();
// streamSkipUntil(',');
// const int16_t len_confirmed = streamGetIntBefore('\n');
// DBG("### READING:", len_confirmed, "from", ret_mux);
// if (ret_mux != mux) {
// DBG("### Data from wrong mux! Got", ret_mux, "expected", mux);
// waitResponse();
// sockets[mux]->sock_available = modemGetAvailable(mux);
// return 0;
// }
// NOTE: manual says the mux number is returned before the number of
// characters available, but in tests only the number is returned
int16_t len_confirmed = stream.parseInt();
streamSkipUntil(','); // skip the comma
if (len_confirmed <= 0) {
waitResponse();
sockets[mux]->sock_available = modemGetAvailable(mux);
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);
}
waitResponse();
// DBG("### READ:", len_confirmed, "from", mux);
// make sure the sock available number is accurate again
// the module is **EXTREMELY** testy about being asked to read more from
// the buffer than exits; it will freeze until a hard reset or power cycle!
sockets[mux]->sock_available = modemGetAvailable(mux);
return len_confirmed;
}
size_t modemGetAvailable(uint8_t mux) {
// If the socket doesn't exist, just return
if (!sockets[mux]) { return 0; }
// We need to check if there are any connections open *before* checking for
// available characters. The SIM7000 *will crash* if you ask about data
// when there are no open connections.
if (!modemGetConnected(mux)) { return 0; }
// NOTE: This gets how many characters are available on all connections that
// have data. It does not return all the connections, just those with data.
sendAT(GF("+CARECV?"));
for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) {
// after the last connection, there's an ok, so we catch it right away
int res = waitResponse(3000, GF("+CARECV:"), GFP(GSM_OK), GFP(GSM_ERROR));
// if we get the +CARECV: response, read the mux number and the number of
// characters available
if (res == 1) {
int ret_mux = streamGetIntBefore(',');
size_t result = streamGetIntBefore('\n');
GsmClientSim7000SSL* sock = sockets[ret_mux];
if (sock) { sock->sock_available = result; }
// if the first returned mux isn't 0 (or is higher than expected)
// we need to fill in the missing muxes
if (ret_mux > muxNo) {
for (int extra_mux = muxNo; extra_mux < ret_mux; extra_mux++) {
GsmClientSim7000SSL* isock = sockets[extra_mux];
if (isock) { isock->sock_available = 0; }
}
muxNo = ret_mux;
}
} else if (res == 2) {
// if we get an OK, we've reached the last socket with available data
// so we set any we haven't gotten to yet to 0
for (int extra_mux = muxNo; extra_mux < TINY_GSM_MUX_COUNT;
extra_mux++) {
GsmClientSim7000SSL* isock = sockets[extra_mux];
if (isock) { isock->sock_available = 0; }
}
break;
} else {
// if we got an error, give up
break;
}
// Should be a final OK at the end.
// If every connection was returned, catch the OK here.
// If only a portion were returned, catch it above.
if (muxNo == TINY_GSM_MUX_COUNT - 1) { waitResponse(); }
}
modemGetConnected(mux); // check the state of all connections
if (!sockets[mux]) { return 0; }
return sockets[mux]->sock_available;
}
bool modemGetConnected(uint8_t mux) {
// NOTE: This gets the state of all connections that have been opened
// since the last connection
sendAT(GF("+CASTATE?"));
for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) {
// after the last connection, there's an ok, so we catch it right away
int res = waitResponse(3000, GF("+CASTATE:"), GFP(GSM_OK),
GFP(GSM_ERROR));
// if we get the +CASTATE: response, read the mux number and the status
if (res == 1) {
int ret_mux = streamGetIntBefore(',');
size_t status = streamGetIntBefore('\n');
// 0: Closed by remote server or internal error
// 1: Connected to remote server
// 2: Listening (server mode)
GsmClientSim7000SSL* sock = sockets[ret_mux];
if (sock) { sock->sock_connected = (status == 1); }
// if the first returned mux isn't 0 (or is higher than expected)
// we need to fill in the missing muxes
if (ret_mux > muxNo) {
for (int extra_mux = muxNo; extra_mux < ret_mux; extra_mux++) {
GsmClientSim7000SSL* isock = sockets[extra_mux];
if (isock) { isock->sock_connected = false; }
}
muxNo = ret_mux;
}
} else if (res == 2) {
// if we get an OK, we've reached the last socket with available data
// so we set any we haven't gotten to yet to 0
for (int extra_mux = muxNo; extra_mux < TINY_GSM_MUX_COUNT;
extra_mux++) {
GsmClientSim7000SSL* isock = sockets[extra_mux];
if (isock) { isock->sock_connected = false; }
}
break;
} else {
// if we got an error, give up
break;
}
// Should be a final OK at the end.
// If every connection was returned, catch the OK here.
// If only a portion were returned, catch it above.
if (muxNo == TINY_GSM_MUX_COUNT - 1) { waitResponse(); }
}
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<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)) {
#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("+CARECV:"))) {
int8_t mux = streamGetIntBefore(',');
int16_t len = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; }
}
data = "";
DBG("### Got Data:", len, "on", mux);
} else if (data.endsWith(GF("+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("+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("*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();
data = "";
}
}
} 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_