Smart-Home am Beispiel der Präsenzerkennung im Raum Projektarbeit Lennart Heimbs, Johannes Krug, Sebastian Dohle und Kevin Holzschuh bei Prof. Oliver Hofmann SS2019
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TinyGsmClientM590.h 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. /**
  2. * file TinyGsmClientM590.h
  3. * author Volodymyr Shymanskyy
  4. * license LGPL-3.0
  5. * copyright Copyright (c) 2016 Volodymyr Shymanskyy
  6. * date Nov 2016
  7. */
  8. #ifndef TinyGsmClientM590_h
  9. #define TinyGsmClientM590_h
  10. //#define TINY_GSM_DEBUG Serial
  11. #if !defined(TINY_GSM_RX_BUFFER)
  12. #define TINY_GSM_RX_BUFFER 256
  13. #endif
  14. #define TINY_GSM_MUX_COUNT 2
  15. #include "TinyGsmCommon.h"
  16. #define GSM_NL "\r\n"
  17. static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
  18. static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
  19. enum SimStatus {
  20. SIM_ERROR = 0,
  21. SIM_READY = 1,
  22. SIM_LOCKED = 2,
  23. };
  24. enum RegStatus {
  25. REG_UNREGISTERED = 0,
  26. REG_SEARCHING = 3,
  27. REG_DENIED = 2,
  28. REG_OK_HOME = 1,
  29. REG_OK_ROAMING = 5,
  30. REG_UNKNOWN = 4,
  31. };
  32. class TinyGsm
  33. {
  34. public:
  35. class GsmClient : public Client
  36. {
  37. friend class TinyGsm;
  38. typedef TinyGsmFifo<uint8_t, TINY_GSM_RX_BUFFER> RxFifo;
  39. public:
  40. GsmClient() {}
  41. GsmClient(TinyGsm& modem, uint8_t mux = 1)
  42. {
  43. init(&modem, mux);
  44. }
  45. bool init(TinyGsm* modem, uint8_t mux = 1)
  46. {
  47. this->at = modem;
  48. this->mux = mux;
  49. sock_connected = false;
  50. at->sockets[mux] = this;
  51. return true;
  52. }
  53. public:
  54. virtual int connect(const char *host, uint16_t port)
  55. {
  56. stop();
  57. TINY_GSM_YIELD();
  58. rx.clear();
  59. sock_connected = at->modemConnect(host, port, mux);
  60. return sock_connected;
  61. }
  62. virtual int connect(IPAddress ip, uint16_t port)
  63. {
  64. String host;
  65. host.reserve(16);
  66. host += ip[0];
  67. host += ".";
  68. host += ip[1];
  69. host += ".";
  70. host += ip[2];
  71. host += ".";
  72. host += ip[3];
  73. return connect(host.c_str(), port);
  74. }
  75. virtual void stop()
  76. {
  77. TINY_GSM_YIELD();
  78. at->sendAT(GF("+TCPCLOSE="), mux);
  79. sock_connected = false;
  80. at->waitResponse();
  81. rx.clear();
  82. }
  83. virtual size_t write(const uint8_t *buf, size_t size)
  84. {
  85. TINY_GSM_YIELD();
  86. //at->maintain();
  87. return at->modemSend(buf, size, mux);
  88. }
  89. virtual size_t write(uint8_t c)
  90. {
  91. return write(&c, 1);
  92. }
  93. virtual int available()
  94. {
  95. TINY_GSM_YIELD();
  96. if (!rx.size() && sock_connected) {
  97. at->maintain();
  98. }
  99. return rx.size();
  100. }
  101. virtual int read(uint8_t *buf, size_t size)
  102. {
  103. TINY_GSM_YIELD();
  104. size_t cnt = 0;
  105. while (cnt < size) {
  106. size_t chunk = TinyGsmMin(size-cnt, rx.size());
  107. if (chunk > 0) {
  108. rx.get(buf, chunk);
  109. buf += chunk;
  110. cnt += chunk;
  111. continue;
  112. }
  113. // TODO: Read directly into user buffer?
  114. if (!rx.size() && sock_connected) {
  115. at->maintain();
  116. //break;
  117. }
  118. }
  119. return cnt;
  120. }
  121. virtual int read()
  122. {
  123. uint8_t c;
  124. if (read(&c, 1) == 1) {
  125. return c;
  126. }
  127. return -1;
  128. }
  129. virtual int peek()
  130. {
  131. return -1; //TODO
  132. }
  133. virtual void flush()
  134. {
  135. at->stream.flush();
  136. }
  137. virtual uint8_t connected()
  138. {
  139. if (available()) {
  140. return true;
  141. }
  142. return sock_connected;
  143. }
  144. virtual operator bool()
  145. {
  146. return connected();
  147. }
  148. /*
  149. * Extended API
  150. */
  151. String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
  152. private:
  153. TinyGsm* at;
  154. uint8_t mux;
  155. bool sock_connected;
  156. RxFifo rx;
  157. };
  158. public:
  159. explicit TinyGsm(Stream& stream)
  160. : stream(stream)
  161. {
  162. memset(sockets, 0, sizeof(sockets));
  163. }
  164. /*
  165. * Basic functions
  166. */
  167. bool begin()
  168. {
  169. return init();
  170. }
  171. bool init()
  172. {
  173. if (!testAT()) {
  174. return false;
  175. }
  176. sendAT(GF("&FZE0")); // Factory + Reset + Echo Off
  177. if (waitResponse() != 1) {
  178. return false;
  179. }
  180. #ifdef TINY_GSM_DEBUG
  181. sendAT(GF("+CMEE=2"));
  182. waitResponse();
  183. #endif
  184. getSimStatus();
  185. return true;
  186. }
  187. void setBaud(unsigned long baud)
  188. {
  189. sendAT(GF("+IPR="), baud);
  190. }
  191. bool testAT(unsigned long timeout = 10000L)
  192. {
  193. for (unsigned long start = millis(); millis() - start < timeout; ) {
  194. sendAT(GF(""));
  195. if (waitResponse(200) == 1) {
  196. delay(100);
  197. return true;
  198. }
  199. delay(100);
  200. }
  201. return false;
  202. }
  203. void maintain()
  204. {
  205. //while (stream.available()) {
  206. waitResponse(10, NULL, NULL);
  207. //}
  208. }
  209. bool factoryDefault()
  210. {
  211. sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write
  212. waitResponse();
  213. sendAT(GF("+ICF=3,1")); // 8 data 0 parity 1 stop
  214. waitResponse();
  215. sendAT(GF("+ENPWRSAVE=0")); // Disable PWR save
  216. waitResponse();
  217. sendAT(GF("+XISP=0")); // Use internal stack
  218. waitResponse();
  219. sendAT(GF("&W")); // Write configuration
  220. return waitResponse() == 1;
  221. }
  222. String getModemInfo()
  223. {
  224. sendAT(GF("I"));
  225. String res;
  226. if (waitResponse(1000L, res) != 1) {
  227. return "";
  228. }
  229. res.replace(GSM_NL "OK" GSM_NL, "");
  230. res.replace(GSM_NL, " ");
  231. res.trim();
  232. return res;
  233. }
  234. bool hasSSL()
  235. {
  236. return false;
  237. }
  238. /*
  239. * Power functions
  240. */
  241. bool restart()
  242. {
  243. if (!testAT()) {
  244. return false;
  245. }
  246. sendAT(GF("+CFUN=15"));
  247. if (waitResponse(10000L) != 1) {
  248. return false;
  249. }
  250. //MODEM:STARTUP
  251. waitResponse(60000L, GF(GSM_NL "+PBREADY" GSM_NL));
  252. return init();
  253. }
  254. bool poweroff()
  255. {
  256. sendAT(GF("+CPWROFF"));
  257. return waitResponse(3000L) == 1;
  258. }
  259. bool radioOff() TINY_GSM_ATTR_NOT_IMPLEMENTED;
  260. bool sleepEnable(bool enable = true)
  261. {
  262. sendAT(GF("+ENPWRSAVE="), enable);
  263. return waitResponse() == 1;
  264. }
  265. /*
  266. * SIM card functions
  267. */
  268. bool simUnlock(const char *pin)
  269. {
  270. sendAT(GF("+CPIN=\""), pin, GF("\""));
  271. return waitResponse() == 1;
  272. }
  273. String getSimCCID()
  274. {
  275. sendAT(GF("+CCID"));
  276. if (waitResponse(GF(GSM_NL "+CCID:")) != 1) {
  277. return "";
  278. }
  279. String res = stream.readStringUntil('\n');
  280. waitResponse();
  281. res.trim();
  282. return res;
  283. }
  284. String getIMEI()
  285. {
  286. sendAT(GF("+GSN"));
  287. if (waitResponse(GF(GSM_NL)) != 1) {
  288. return "";
  289. }
  290. String res = stream.readStringUntil('\n');
  291. waitResponse();
  292. res.trim();
  293. return res;
  294. }
  295. SimStatus getSimStatus(unsigned long timeout = 10000L)
  296. {
  297. for (unsigned long start = millis(); millis() - start < timeout; ) {
  298. sendAT(GF("+CPIN?"));
  299. if (waitResponse(GF(GSM_NL "+CPIN:")) != 1) {
  300. delay(1000);
  301. continue;
  302. }
  303. int status = waitResponse(GF("READY"), GF("SIM PIN"), GF("SIM PUK"));
  304. waitResponse();
  305. switch (status) {
  306. case 2:
  307. case 3:
  308. return SIM_LOCKED;
  309. case 1:
  310. return SIM_READY;
  311. default:
  312. return SIM_ERROR;
  313. }
  314. }
  315. return SIM_ERROR;
  316. }
  317. RegStatus getRegistrationStatus()
  318. {
  319. sendAT(GF("+CREG?"));
  320. if (waitResponse(GF(GSM_NL "+CREG:")) != 1) {
  321. return REG_UNKNOWN;
  322. }
  323. streamSkipUntil(','); // Skip format (0)
  324. int status = stream.readStringUntil('\n').toInt();
  325. waitResponse();
  326. return (RegStatus)status;
  327. }
  328. String getOperator()
  329. {
  330. sendAT(GF("+COPS?"));
  331. if (waitResponse(GF(GSM_NL "+COPS:")) != 1) {
  332. return "";
  333. }
  334. streamSkipUntil('"'); // Skip mode and format
  335. String res = stream.readStringUntil('"');
  336. waitResponse();
  337. return res;
  338. }
  339. /*
  340. * Generic network functions
  341. */
  342. int getSignalQuality()
  343. {
  344. sendAT(GF("+CSQ"));
  345. if (waitResponse(GF(GSM_NL "+CSQ:")) != 1) {
  346. return 99;
  347. }
  348. int res = stream.readStringUntil(',').toInt();
  349. waitResponse();
  350. return res;
  351. }
  352. bool isNetworkConnected()
  353. {
  354. RegStatus s = getRegistrationStatus();
  355. return (s == REG_OK_HOME || s == REG_OK_ROAMING);
  356. }
  357. bool waitForNetwork(unsigned long timeout = 60000L)
  358. {
  359. for (unsigned long start = millis(); millis() - start < timeout; ) {
  360. if (isNetworkConnected()) {
  361. return true;
  362. }
  363. delay(250);
  364. }
  365. return false;
  366. }
  367. /*
  368. * GPRS functions
  369. */
  370. bool gprsConnect(const char* apn, const char* user = NULL, const char* pwd = NULL)
  371. {
  372. gprsDisconnect();
  373. sendAT(GF("+XISP=0"));
  374. waitResponse();
  375. sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"');
  376. waitResponse();
  377. if (!user) {
  378. user = "";
  379. }
  380. if (!pwd) {
  381. pwd = "";
  382. }
  383. sendAT(GF("+XGAUTH=1,1,\""), user, GF("\",\""), pwd, GF("\""));
  384. waitResponse();
  385. sendAT(GF("+XIIC=1"));
  386. waitResponse();
  387. const unsigned long timeout = 60000L;
  388. for (unsigned long start = millis(); millis() - start < timeout; ) {
  389. if (isGprsConnected()) {
  390. //goto set_dns; // TODO
  391. return true;
  392. }
  393. delay(500);
  394. }
  395. return false;
  396. set_dns:
  397. sendAT(GF("+DNSSERVER=1,8.8.8.8"));
  398. waitResponse();
  399. sendAT(GF("+DNSSERVER=2,8.8.4.4"));
  400. waitResponse();
  401. return true;
  402. }
  403. bool gprsDisconnect()
  404. {
  405. // TODO: There is no command in AT command set
  406. // XIIC=0 does not work
  407. return true;
  408. }
  409. bool isGprsConnected()
  410. {
  411. sendAT(GF("+XIIC?"));
  412. if (waitResponse(GF(GSM_NL "+XIIC:")) != 1) {
  413. return false;
  414. }
  415. int res = stream.readStringUntil(',').toInt();
  416. waitResponse();
  417. return res == 1;
  418. }
  419. String getLocalIP()
  420. {
  421. sendAT(GF("+XIIC?"));
  422. if (waitResponse(GF(GSM_NL "+XIIC:")) != 1) {
  423. return "";
  424. }
  425. stream.readStringUntil(',');
  426. String res = stream.readStringUntil('\n');
  427. waitResponse();
  428. res.trim();
  429. return res;
  430. }
  431. IPAddress localIP()
  432. {
  433. return TinyGsmIpFromString(getLocalIP());
  434. }
  435. /*
  436. * Phone Call functions
  437. */
  438. bool setGsmBusy(bool busy = true) TINY_GSM_ATTR_NOT_AVAILABLE;
  439. bool callAnswer() TINY_GSM_ATTR_NOT_AVAILABLE;
  440. bool callNumber(const String& number) TINY_GSM_ATTR_NOT_AVAILABLE;
  441. bool callHangup() TINY_GSM_ATTR_NOT_AVAILABLE;
  442. /*
  443. * Messaging functions
  444. */
  445. String sendUSSD(const String& code)
  446. {
  447. sendAT(GF("+CMGF=1"));
  448. waitResponse();
  449. sendAT(GF("+CSCS=\"HEX\""));
  450. waitResponse();
  451. sendAT(GF("D"), code);
  452. if (waitResponse(10000L, GF(GSM_NL "+CUSD:")) != 1) {
  453. return "";
  454. }
  455. stream.readStringUntil('"');
  456. String hex = stream.readStringUntil('"');
  457. stream.readStringUntil(',');
  458. int dcs = stream.readStringUntil('\n').toInt();
  459. if (waitResponse() != 1) {
  460. return "";
  461. }
  462. if (dcs == 15) {
  463. return TinyGsmDecodeHex8bit(hex);
  464. } else if (dcs == 72) {
  465. return TinyGsmDecodeHex16bit(hex);
  466. } else {
  467. return hex;
  468. }
  469. }
  470. bool sendSMS(const String& number, const String& text)
  471. {
  472. sendAT(GF("+CSCS=\"GSM\""));
  473. waitResponse();
  474. sendAT(GF("+CMGF=1"));
  475. waitResponse();
  476. sendAT(GF("+CMGS=\""), number, GF("\""));
  477. if (waitResponse(GF(">")) != 1) {
  478. return false;
  479. }
  480. stream.print(text);
  481. stream.write((char)0x1A);
  482. stream.flush();
  483. return waitResponse(60000L) == 1;
  484. }
  485. bool sendSMS_UTF16(const String& number, const void* text, size_t len)
  486. TINY_GSM_ATTR_NOT_AVAILABLE;
  487. /*
  488. * Location functions
  489. */
  490. String getGsmLocation() TINY_GSM_ATTR_NOT_AVAILABLE;
  491. /*
  492. * Battery functions
  493. */
  494. uint16_t getBattVoltage() TINY_GSM_ATTR_NOT_AVAILABLE;
  495. int getBattPercent() TINY_GSM_ATTR_NOT_AVAILABLE;
  496. protected:
  497. bool modemConnect(const char* host, uint16_t port, uint8_t mux)
  498. {
  499. for (int i=0; i<3; i++) { // TODO: no need for loop?
  500. String ip = dnsIpQuery(host);
  501. sendAT(GF("+TCPSETUP="), mux, GF(","), ip, GF(","), port);
  502. int rsp = waitResponse(75000L,
  503. GF(",OK" GSM_NL),
  504. GF(",FAIL" GSM_NL),
  505. GF("+TCPSETUP:Error" GSM_NL));
  506. if (1 == rsp) {
  507. return true;
  508. } else if (3 == rsp) {
  509. sendAT(GF("+TCPCLOSE="), mux);
  510. waitResponse();
  511. }
  512. delay(1000);
  513. }
  514. return false;
  515. }
  516. int modemSend(const void* buff, size_t len, uint8_t mux)
  517. {
  518. sendAT(GF("+TCPSEND="), mux, ',', len);
  519. if (waitResponse(GF(">")) != 1) {
  520. return 0;
  521. }
  522. stream.write((uint8_t*)buff, len);
  523. stream.write((char)0x0D);
  524. stream.flush();
  525. if (waitResponse(30000L, GF(GSM_NL "+TCPSEND:")) != 1) {
  526. return 0;
  527. }
  528. stream.readStringUntil('\n');
  529. return len;
  530. }
  531. bool modemGetConnected(uint8_t mux)
  532. {
  533. sendAT(GF("+CIPSTATUS="), mux);
  534. int res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""), GF(",\"CLOSING\""),
  535. GF(",\"INITIAL\""));
  536. waitResponse();
  537. return 1 == res;
  538. }
  539. String dnsIpQuery(const char* host)
  540. {
  541. sendAT(GF("+DNS=\""), host, GF("\""));
  542. if (waitResponse(10000L, GF(GSM_NL "+DNS:")) != 1) {
  543. return "";
  544. }
  545. String res = stream.readStringUntil('\n');
  546. waitResponse(GF("+DNS:OK" GSM_NL));
  547. res.trim();
  548. return res;
  549. }
  550. public:
  551. /* Utilities */
  552. template<typename T>
  553. void streamWrite(T last)
  554. {
  555. stream.print(last);
  556. }
  557. template<typename T, typename... Args>
  558. void streamWrite(T head, Args... tail)
  559. {
  560. stream.print(head);
  561. streamWrite(tail...);
  562. }
  563. bool streamSkipUntil(char c) //TODO: timeout
  564. {
  565. while (true) {
  566. while (!stream.available()) {
  567. TINY_GSM_YIELD();
  568. }
  569. if (stream.read() == c) {
  570. return true;
  571. }
  572. }
  573. return false;
  574. }
  575. template<typename... Args>
  576. void sendAT(Args... cmd)
  577. {
  578. streamWrite("AT", cmd..., GSM_NL);
  579. stream.flush();
  580. TINY_GSM_YIELD();
  581. //DBG("### AT:", cmd...);
  582. }
  583. // TODO: Optimize this!
  584. uint8_t waitResponse(uint32_t timeout, String& data,
  585. GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
  586. GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
  587. {
  588. /*String r1s(r1); r1s.trim();
  589. String r2s(r2); r2s.trim();
  590. String r3s(r3); r3s.trim();
  591. String r4s(r4); r4s.trim();
  592. String r5s(r5); r5s.trim();
  593. DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/
  594. data.reserve(64);
  595. int index = 0;
  596. unsigned long startMillis = millis();
  597. do {
  598. TINY_GSM_YIELD();
  599. while (stream.available() > 0) {
  600. int a = stream.read();
  601. if (a <= 0) {
  602. continue; // Skip 0x00 bytes, just in case
  603. }
  604. data += (char)a;
  605. if (r1 && data.endsWith(r1)) {
  606. index = 1;
  607. goto finish;
  608. } else if (r2 && data.endsWith(r2)) {
  609. index = 2;
  610. goto finish;
  611. } else if (r3 && data.endsWith(r3)) {
  612. index = 3;
  613. goto finish;
  614. } else if (r4 && data.endsWith(r4)) {
  615. index = 4;
  616. goto finish;
  617. } else if (r5 && data.endsWith(r5)) {
  618. index = 5;
  619. goto finish;
  620. } else if (data.endsWith(GF("+TCPRECV:"))) {
  621. int mux = stream.readStringUntil(',').toInt();
  622. int len = stream.readStringUntil(',').toInt();
  623. int len_orig = len;
  624. if (len > sockets[mux]->rx.free()) {
  625. DBG("### Buffer overflow: ", len, "->", sockets[mux]->rx.free());
  626. } else {
  627. DBG("### Got: ", len, "->", sockets[mux]->rx.free());
  628. }
  629. while (len--) {
  630. while (!stream.available()) {
  631. TINY_GSM_YIELD();
  632. }
  633. sockets[mux]->rx.put(stream.read());
  634. }
  635. if (len_orig > sockets[mux]->available()) { // TODO
  636. DBG("### Fewer characters received than expected: ", sockets[mux]->available(), " vs ", len_orig);
  637. }
  638. data = "";
  639. } else if (data.endsWith(GF("+TCPCLOSE:"))) {
  640. int mux = stream.readStringUntil(',').toInt();
  641. stream.readStringUntil('\n');
  642. if (mux >= 0 && mux < TINY_GSM_MUX_COUNT) {
  643. sockets[mux]->sock_connected = false;
  644. }
  645. data = "";
  646. DBG("### Closed: ", mux);
  647. }
  648. }
  649. } while (millis() - startMillis < timeout);
  650. finish:
  651. if (!index) {
  652. data.trim();
  653. if (data.length()) {
  654. DBG("### Unhandled:", data);
  655. }
  656. data = "";
  657. }
  658. return index;
  659. }
  660. uint8_t waitResponse(uint32_t timeout,
  661. GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
  662. GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
  663. {
  664. String data;
  665. return waitResponse(timeout, data, r1, r2, r3, r4, r5);
  666. }
  667. uint8_t waitResponse(GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
  668. GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
  669. {
  670. return waitResponse(1000, r1, r2, r3, r4, r5);
  671. }
  672. public:
  673. Stream& stream;
  674. protected:
  675. GsmClient* sockets[TINY_GSM_MUX_COUNT];
  676. };
  677. #endif