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.

1386 lines
40 KiB

7 years ago
7 years ago
5 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
5 years ago
  1. /**
  2. * @file TinyGsmClientXBee.h
  3. * @author Volodymyr Shymanskyy
  4. * @license LGPL-3.0
  5. * @copyright Copyright (c) 2016 Volodymyr Shymanskyy, XBee module by Sara Damiano
  6. * @date Nov 2016
  7. */
  8. #ifndef TinyGsmClientXBee_h
  9. #define TinyGsmClientXBee_h
  10. //#pragma message("TinyGSM: TinyGsmClientXBee")
  11. //#define TINY_GSM_DEBUG Serial
  12. // XBee's do not support multi-plexing in transparent/command mode
  13. // The much more complicated API mode is needed for multi-plexing
  14. #define TINY_GSM_MUX_COUNT 1
  15. // XBee's have a default guard time of 1 second (1000ms, 10 extra for safety here)
  16. #define TINY_GSM_XBEE_GUARD_TIME 1010
  17. #include <TinyGsmCommon.h>
  18. #define GSM_NL "\r"
  19. static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
  20. static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
  21. // Use this to avoid too many entrances and exits from command mode.
  22. // The cellular Bee's often freeze up and won't respond when attempting
  23. // to enter command mode too many times.
  24. #define XBEE_COMMAND_START_DECORATOR(nAttempts, failureReturn) \
  25. bool wasInCommandMode = inCommandMode; \
  26. if (!wasInCommandMode) { /* don't re-enter command mode if already in it */ \
  27. if (!commandMode(nAttempts)) return failureReturn; /* Return immediately if fails */ \
  28. }
  29. #define XBEE_COMMAND_END_DECORATOR \
  30. if (!wasInCommandMode) { /* only exit if we weren't in command mode */ \
  31. exitCommand(); \
  32. }
  33. enum SimStatus {
  34. SIM_ERROR = 0,
  35. SIM_READY = 1,
  36. SIM_LOCKED = 2,
  37. };
  38. enum RegStatus {
  39. REG_OK = 0,
  40. REG_UNREGISTERED = 1,
  41. REG_SEARCHING = 2,
  42. REG_DENIED = 3,
  43. REG_UNKNOWN = 4,
  44. };
  45. // These are responses to the HS command to get "hardware series"
  46. enum XBeeType {
  47. XBEE_UNKNOWN = 0,
  48. XBEE_S6B_WIFI = 0x601, // Digi XBee® Wi-Fi
  49. XBEE_LTE1_VZN = 0xB01, // Digi XBee® Cellular LTE Cat 1
  50. XBEE_3G = 0xB02, // Digi XBee® Cellular 3G
  51. XBEE3_LTE1_ATT = 0xB06, // Digi XBee3™ Cellular LTE CAT 1
  52. XBEE3_LTEM_ATT = 0xB08, // Digi XBee3™ Cellular LTE-M
  53. };
  54. class TinyGsmXBee
  55. {
  56. public:
  57. class GsmClient : public Client
  58. {
  59. friend class TinyGsmXBee;
  60. // typedef TinyGsmFifo<uint8_t, TINY_GSM_RX_BUFFER> RxFifo;
  61. public:
  62. GsmClient() {}
  63. GsmClient(TinyGsmXBee& modem, uint8_t mux = 0) {
  64. init(&modem, mux);
  65. }
  66. virtual ~GsmClient(){}
  67. bool init(TinyGsmXBee* modem, uint8_t mux = 0) {
  68. this->at = modem;
  69. this->mux = mux;
  70. sock_connected = false;
  71. at->sockets[mux] = this;
  72. return true;
  73. }
  74. public:
  75. // NOTE: The XBee saves all connection information (ssid/pwd or apn AND last used IP address)
  76. // in flash (NVM). When you turn it on it immediately prepares to re-connect to whatever was
  77. // last set. The TCP connection itself is not opened until you attempt to send data.
  78. // Because all settings are saved to flash, it is possible (or likely) that
  79. // you could send data even if you haven't "made" any connection.
  80. virtual int connect(const char *host, uint16_t port, int timeout_s) {
  81. // NOTE: Not caling stop() or yeild() here
  82. at->streamClear(); // Empty anything in the buffer before starting
  83. sock_connected = at->modemConnect(host, port, mux, false, timeout_s);
  84. return sock_connected;
  85. }
  86. virtual int connect(const char *host, uint16_t port) {
  87. return connect(host, port, 75);
  88. }
  89. virtual int connect(IPAddress ip, uint16_t port, int timeout_s) {
  90. if (timeout_s != 0) {
  91. DBG("Timeout [", timeout_s, "] doesn't apply here.");
  92. }
  93. // NOTE: Not caling stop() or yeild() here
  94. at->streamClear(); // Empty anything in the buffer before starting
  95. sock_connected = at->modemConnect(ip, port, mux, false);
  96. return sock_connected;
  97. }
  98. virtual int connect(IPAddress ip, uint16_t port) {
  99. return connect(ip, port, 0);
  100. }
  101. virtual void stop(uint32_t maxWaitMs) {
  102. at->streamClear(); // Empty anything in the buffer
  103. // empty the saved currently-in-use destination address
  104. at->modemStop(maxWaitMs);
  105. at->streamClear(); // Empty anything in the buffer
  106. sock_connected = false;
  107. // Note: because settings are saved in flash, the XBEE will attempt to
  108. // reconnect to the previous socket if it receives any outgoing data.
  109. // Setting sock_connected to false after the stop ensures that connected()
  110. // will return false after a stop has been ordered. This makes it play
  111. // much more nicely with libraries like PubSubClient.
  112. }
  113. virtual void stop() { stop(5000L); }
  114. virtual size_t write(const uint8_t *buf, size_t size) {
  115. TINY_GSM_YIELD();
  116. return at->modemSend(buf, size, mux);
  117. }
  118. virtual size_t write(uint8_t c) {
  119. return write(&c, 1);
  120. }
  121. virtual size_t write(const char *str) {
  122. if (str == NULL) return 0;
  123. return write((const uint8_t *)str, strlen(str));
  124. }
  125. virtual int available() {
  126. TINY_GSM_YIELD();
  127. return at->stream.available();
  128. /*
  129. if (!rx.size() || at->stream.available()) {
  130. at->maintain();
  131. }
  132. return at->stream.available() + rx.size();
  133. */
  134. }
  135. virtual int read(uint8_t *buf, size_t size) {
  136. TINY_GSM_YIELD();
  137. return at->stream.readBytes((char *)buf, size);
  138. /*
  139. size_t cnt = 0;
  140. uint32_t _startMillis = millis();
  141. while (cnt < size && millis() - _startMillis < _timeout) {
  142. size_t chunk = TinyGsmMin(size-cnt, rx.size());
  143. if (chunk > 0) {
  144. rx.get(buf, chunk);
  145. buf += chunk;
  146. cnt += chunk;
  147. continue;
  148. }
  149. // TODO: Read directly into user buffer?
  150. if (!rx.size() || at->stream.available()) {
  151. at->maintain();
  152. }
  153. }
  154. return cnt;
  155. */
  156. }
  157. virtual int read() {
  158. TINY_GSM_YIELD();
  159. return at->stream.read();
  160. /*
  161. uint8_t c;
  162. if (read(&c, 1) == 1) {
  163. return c;
  164. }
  165. return -1;
  166. */
  167. }
  168. virtual int peek() { return at->stream.peek(); }
  169. virtual void flush() { at->stream.flush(); }
  170. virtual uint8_t connected() {
  171. if (available()) {
  172. return true;
  173. }
  174. return sock_connected;
  175. // NOTE: We dont't check or return
  176. // modemGetConnected() because we don't
  177. // want to go into command mode.
  178. // return at->modemGetConnected();
  179. }
  180. virtual operator bool() { return connected(); }
  181. /*
  182. * Extended API
  183. */
  184. String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
  185. private:
  186. TinyGsmXBee* at;
  187. uint8_t mux;
  188. bool sock_connected;
  189. // RxFifo rx;
  190. };
  191. class GsmClientSecure : public GsmClient
  192. {
  193. public:
  194. GsmClientSecure() {}
  195. GsmClientSecure(TinyGsmXBee& modem, uint8_t mux = 0)
  196. : GsmClient(modem, mux)
  197. {}
  198. virtual ~GsmClientSecure(){}
  199. public:
  200. virtual int connect(const char *host, uint16_t port, int timeout_s) {
  201. // NOTE: Not caling stop() or yeild() here
  202. at->streamClear(); // Empty anything in the buffer before starting
  203. sock_connected = at->modemConnect(host, port, mux, true, timeout_s);
  204. return sock_connected;
  205. }
  206. virtual int connect(IPAddress ip, uint16_t port, int timeout_s) {
  207. if (timeout_s != 0) {
  208. DBG("Timeout [", timeout_s, "] doesn't apply here.");
  209. }
  210. // NOTE: Not caling stop() or yeild() here
  211. at->streamClear(); // Empty anything in the buffer before starting
  212. sock_connected = at->modemConnect(ip, port, mux, true);
  213. return sock_connected;
  214. }
  215. };
  216. public:
  217. TinyGsmXBee(Stream& stream)
  218. : stream(stream)
  219. {
  220. beeType = XBEE_UNKNOWN; // Start not knowing what kind of bee it is
  221. guardTime = TINY_GSM_XBEE_GUARD_TIME; // Start with the default guard time of 1 second
  222. resetPin = -1;
  223. savedIP = IPAddress(0,0,0,0);
  224. savedHost = "";
  225. savedHostIP = IPAddress(0,0,0,0);
  226. inCommandMode = false;
  227. memset(sockets, 0, sizeof(sockets));
  228. }
  229. TinyGsmXBee(Stream& stream, int8_t resetPin)
  230. : stream(stream)
  231. {
  232. beeType = XBEE_UNKNOWN; // Start not knowing what kind of bee it is
  233. guardTime = TINY_GSM_XBEE_GUARD_TIME; // Start with the default guard time of 1 second
  234. this->resetPin = resetPin;
  235. savedIP = IPAddress(0,0,0,0);
  236. savedHost = "";
  237. savedHostIP = IPAddress(0,0,0,0);
  238. inCommandMode = false;
  239. memset(sockets, 0, sizeof(sockets));
  240. }
  241. virtual ~TinyGsmXBee() {}
  242. /*
  243. * Basic functions
  244. */
  245. bool begin(const char* pin = NULL) {
  246. return init(pin);
  247. }
  248. bool init(const char* pin = NULL) {
  249. DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
  250. if (resetPin >= 0) {
  251. pinMode(resetPin, OUTPUT);
  252. digitalWrite(resetPin, HIGH);
  253. }
  254. if (pin && strlen(pin) > 0) {
  255. DBG("XBee's do not support SIMs that require an unlock pin!");
  256. }
  257. XBEE_COMMAND_START_DECORATOR(10, false)
  258. sendAT(GF("AP0")); // Put in transparent mode
  259. bool ret_val = waitResponse() == 1;
  260. sendAT(GF("GT64")); // shorten the guard time to 100ms
  261. ret_val &= waitResponse() == 1;
  262. if (ret_val) guardTime = 110;
  263. // Make sure the command mode drop-out time is long enough that we won't fall
  264. // out of command mode without intentionally leaving it. This is the default
  265. // drop out time of 0x64 x 100ms (10 seconds)
  266. sendAT(GF("CT64"));
  267. ret_val &= waitResponse() == 1;
  268. ret_val &= writeChanges();
  269. getSeries(); // Get the "Hardware Series";
  270. XBEE_COMMAND_END_DECORATOR
  271. return ret_val;
  272. }
  273. String getModemName() {
  274. return getBeeName();
  275. }
  276. void setBaud(unsigned long baud) {
  277. XBEE_COMMAND_START_DECORATOR(5, )
  278. switch(baud)
  279. {
  280. case 2400: sendAT(GF("BD1")); break;
  281. case 4800: sendAT(GF("BD2")); break;
  282. case 9600: sendAT(GF("BD3")); break;
  283. case 19200: sendAT(GF("BD4")); break;
  284. case 38400: sendAT(GF("BD5")); break;
  285. case 57600: sendAT(GF("BD6")); break;
  286. case 115200: sendAT(GF("BD7")); break;
  287. case 230400: sendAT(GF("BD8")); break;
  288. case 460800: sendAT(GF("BD9")); break;
  289. case 921600: sendAT(GF("BDA")); break;
  290. default: {
  291. DBG(GF("Specified baud rate is unsupported! Setting to 9600 baud."));
  292. sendAT(GF("BD3")); // Set to default of 9600
  293. break;
  294. }
  295. }
  296. waitResponse();
  297. writeChanges();
  298. XBEE_COMMAND_END_DECORATOR
  299. }
  300. bool testAT(unsigned long timeout_ms = 10000L) {
  301. unsigned long start = millis();
  302. bool success = false;
  303. while (!success && millis() - start < timeout_ms) {
  304. if (!inCommandMode) {
  305. success = commandMode();
  306. if (success) exitCommand();
  307. }
  308. else {
  309. sendAT();
  310. if (waitResponse(200) == 1) {
  311. success = true;
  312. }
  313. // if we didn't respond to the AT, assume we're not in command mode
  314. else inCommandMode = false;
  315. }
  316. delay(250);
  317. }
  318. return success;
  319. }
  320. void maintain() {
  321. // this only happens OUTSIDE command mode, so if we're getting characters
  322. // they should be data received from the TCP connection
  323. // TINY_GSM_YIELD();
  324. // if (!inCommandMode) {
  325. // while (stream.available()) {
  326. // char c = stream.read();
  327. // if (c > 0) sockets[0]->rx.put(c);
  328. // }
  329. // }
  330. }
  331. bool factoryDefault() {
  332. XBEE_COMMAND_START_DECORATOR(5, false)
  333. sendAT(GF("RE"));
  334. bool ret_val = waitResponse() == 1;
  335. ret_val &= writeChanges();
  336. XBEE_COMMAND_END_DECORATOR
  337. // Make sure the guard time for the modem object is set back to default
  338. // otherwise communication would fail after the reset
  339. guardTime = 1010;
  340. return ret_val;
  341. }
  342. String getModemInfo() {
  343. return sendATGetString(GF("HS"));
  344. }
  345. bool hasSSL() {
  346. if (beeType == XBEE_S6B_WIFI) return false;
  347. else return true;
  348. }
  349. bool hasWifi() {
  350. if (beeType == XBEE_S6B_WIFI) return true;
  351. else return false;
  352. }
  353. bool hasGPRS() {
  354. if (beeType == XBEE_S6B_WIFI) return false;
  355. else return true;
  356. }
  357. XBeeType getBeeType() {
  358. return beeType;
  359. }
  360. String getBeeName() {
  361. switch (beeType){
  362. case XBEE_S6B_WIFI: return "Digi XBee Wi-Fi";
  363. case XBEE_LTE1_VZN: return "Digi XBee Cellular LTE Cat 1";
  364. case XBEE_3G: return "Digi XBee Cellular 3G";
  365. case XBEE3_LTE1_ATT: return "Digi XBee3 Cellular LTE CAT 1";
  366. case XBEE3_LTEM_ATT: return "Digi XBee3 Cellular LTE-M";
  367. default: return "Digi XBee";
  368. }
  369. }
  370. /*
  371. * Power functions
  372. */
  373. // The XBee's have a bad habit of getting into an unresponsive funk
  374. // This uses the board's hardware reset pin to force it to reset
  375. void pinReset() {
  376. if (resetPin >= 0) {
  377. DBG("### Forcing a modem reset!\r\n");
  378. digitalWrite(resetPin, LOW);
  379. delay(1);
  380. digitalWrite(resetPin, HIGH);
  381. }
  382. }
  383. bool restart() {
  384. if (!commandMode()) return false; // Return immediately
  385. if (beeType == XBEE_UNKNOWN) getSeries(); // how we restart depends on this
  386. if (beeType != XBEE_S6B_WIFI) {
  387. sendAT(GF("AM1")); // Digi suggests putting cellular modules into airplane mode before restarting
  388. // This allows the sockets and connections to close cleanly
  389. if (waitResponse() != 1) return exitAndFail();
  390. if (!writeChanges()) return exitAndFail();
  391. }
  392. sendAT(GF("FR"));
  393. if (waitResponse() != 1) return exitAndFail();
  394. else inCommandMode = false; // Reset effectively exits command mode
  395. if (beeType == XBEE_S6B_WIFI) delay(2000); // Wifi module actually resets about 2 seconds later
  396. else delay(100); // cellular modules wait 100ms before reset happens
  397. // Wait until reboot complete and responds to command mode call again
  398. for (unsigned long start = millis(); millis() - start < 60000L; ) {
  399. if (commandMode(1)) break;
  400. delay(250); // wait a litle before trying again
  401. }
  402. if (beeType != XBEE_S6B_WIFI) {
  403. sendAT(GF("AM0")); // Turn off airplane mode
  404. if (waitResponse() != 1) return exitAndFail();
  405. if (!writeChanges()) return exitAndFail();
  406. }
  407. exitCommand();
  408. return init();
  409. }
  410. void setupPinSleep(bool maintainAssociation = false) {
  411. XBEE_COMMAND_START_DECORATOR(5, )
  412. if (beeType == XBEE_UNKNOWN) getSeries(); // Command depends on series
  413. sendAT(GF("SM"),1); // Pin sleep
  414. waitResponse();
  415. if (beeType == XBEE_S6B_WIFI && !maintainAssociation) {
  416. sendAT(GF("SO"),200); // For lowest power, dissassociated deep sleep
  417. waitResponse();
  418. }
  419. else if (!maintainAssociation){
  420. sendAT(GF("SO"),1); // For supported cellular modules, maintain association
  421. // Not supported by all modules, will return "ERROR"
  422. waitResponse();
  423. }
  424. writeChanges();
  425. XBEE_COMMAND_END_DECORATOR
  426. }
  427. bool poweroff() { // NOTE: Not supported for WiFi or older cellular firmware
  428. XBEE_COMMAND_START_DECORATOR(5, false)
  429. sendAT(GF("SD"));
  430. bool ret_val = waitResponse(120000L) == 1;
  431. if (ret_val) {
  432. ret_val &= (sendATGetString(GF("AI")) == "2D");
  433. }
  434. XBEE_COMMAND_END_DECORATOR
  435. return ret_val;
  436. }
  437. bool radioOff() TINY_GSM_ATTR_NOT_IMPLEMENTED;
  438. bool sleepEnable(bool enable = true) TINY_GSM_ATTR_NOT_IMPLEMENTED;
  439. /*
  440. * SIM card functions
  441. */
  442. bool simUnlock(const char *pin) { // Not supported
  443. if (pin && strlen(pin) > 0) {
  444. DBG("XBee's do not support SIMs that require an unlock pin!");
  445. }
  446. return false;
  447. }
  448. String getSimCCID() {
  449. return sendATGetString(GF("S#"));
  450. }
  451. String getIMEI() {
  452. return sendATGetString(GF("IM"));
  453. }
  454. SimStatus getSimStatus() {
  455. return SIM_READY; // unsupported
  456. }
  457. RegStatus getRegistrationStatus() {
  458. XBEE_COMMAND_START_DECORATOR(5, REG_UNKNOWN)
  459. if (!inCommandMode) return REG_UNKNOWN; // Return immediately
  460. if (beeType == XBEE_UNKNOWN) getSeries(); // Need to know the bee type to interpret response
  461. sendAT(GF("AI"));
  462. int16_t intRes = readResponseInt(10000L);
  463. RegStatus stat = REG_UNKNOWN;
  464. switch (beeType){
  465. case XBEE_S6B_WIFI: {
  466. switch (intRes) {
  467. case 0x00: // 0x00 Successfully joined an access point, established IP addresses and IP listening sockets
  468. stat = REG_OK;
  469. break;
  470. case 0x01: // 0x01 Wi-Fi transceiver initialization in progress.
  471. case 0x02: // 0x02 Wi-Fi transceiver initialized, but not yet scanning for access point.
  472. case 0x40: // 0x40 Waiting for WPA or WPA2 Authentication.
  473. case 0x41: // 0x41 Device joined a network and is waiting for IP configuration to complete
  474. case 0x42: // 0x42 Device is joined, IP is configured, and listening sockets are being set up.
  475. case 0xFF: // 0xFF Device is currently scanning for the configured SSID.
  476. stat = REG_SEARCHING;
  477. break;
  478. case 0x13: // 0x13 Disconnecting from access point.
  479. restart(); // Restart the device; the S6B tends to get stuck "disconnecting"
  480. stat = REG_UNREGISTERED;
  481. break;
  482. case 0x23: // 0x23 SSID not configured.
  483. stat = REG_UNREGISTERED;
  484. break;
  485. case 0x24: // 0x24 Encryption key invalid (either NULL or invalid length for WEP).
  486. case 0x27: // 0x27 SSID was found, but join failed.
  487. stat = REG_DENIED;
  488. break;
  489. default:
  490. stat = REG_UNKNOWN;
  491. break;
  492. }
  493. break;
  494. }
  495. default: { // Cellular XBee's
  496. switch (intRes) {
  497. case 0x00: // 0x00 Connected to the Internet.
  498. stat = REG_OK;
  499. break;
  500. case 0x22: // 0x22 Registering to cellular network.
  501. case 0x23: // 0x23 Connecting to the Internet.
  502. case 0xFF: // 0xFF Initializing.
  503. stat = REG_SEARCHING;
  504. break;
  505. case 0x24: // 0x24 The cellular component is missing, corrupt, or otherwise in error.
  506. case 0x2B: // 0x2B USB Direct active.
  507. case 0x2C: // 0x2C Cellular component is in PSM (power save mode).
  508. stat = REG_UNKNOWN;
  509. break;
  510. case 0x25: // 0x25 Cellular network registration denied.
  511. stat = REG_DENIED;
  512. break;
  513. case 0x2A: // 0x2A Airplane mode.
  514. sendAT(GF("AM0")); // Turn off airplane mode
  515. waitResponse();
  516. writeChanges();
  517. stat = REG_UNKNOWN;
  518. break;
  519. case 0x2F: // 0x2F Bypass mode active.
  520. sendAT(GF("AP0")); // Set back to transparent mode
  521. waitResponse();
  522. writeChanges();
  523. stat = REG_UNKNOWN;
  524. break;
  525. default:
  526. stat = REG_UNKNOWN;
  527. break;
  528. }
  529. break;
  530. }
  531. }
  532. XBEE_COMMAND_END_DECORATOR
  533. return stat;
  534. }
  535. String getOperator() {
  536. return sendATGetString(GF("MN"));
  537. }
  538. /*
  539. * Generic network functions
  540. */
  541. int16_t getSignalQuality() {
  542. XBEE_COMMAND_START_DECORATOR(5, 0);
  543. if (beeType == XBEE_UNKNOWN) getSeries(); // Need to know what type of bee so we know how to ask
  544. if (beeType == XBEE_S6B_WIFI) sendAT(GF("LM")); // ask for the "link margin" - the dB above sensitivity
  545. else sendAT(GF("DB")); // ask for the cell strength in dBm
  546. int16_t intRes = readResponseInt();
  547. XBEE_COMMAND_END_DECORATOR
  548. if (beeType == XBEE3_LTEM_ATT && intRes == 105) intRes = 0; // tends to reply with "69" when signal is unknown
  549. if (beeType == XBEE_S6B_WIFI) return -93 + intRes; // the maximum sensitivity is -93dBm
  550. else return -1*intRes; // need to convert to negative number
  551. }
  552. bool isNetworkConnected() {
  553. RegStatus s = getRegistrationStatus();
  554. return (s == REG_OK);
  555. }
  556. bool waitForNetwork(unsigned long timeout_ms = 60000L) {
  557. bool retVal = false;
  558. XBEE_COMMAND_START_DECORATOR(5, false)
  559. for (unsigned long start = millis(); millis() - start < timeout_ms; ) {
  560. if (isNetworkConnected()) {
  561. retVal = true;
  562. break;
  563. }
  564. delay(250); // per Neil H. - more stable with delay
  565. }
  566. XBEE_COMMAND_END_DECORATOR
  567. return retVal;
  568. }
  569. /*
  570. * WiFi functions
  571. */
  572. bool networkConnect(const char* ssid, const char* pwd) {
  573. bool retVal = true;
  574. XBEE_COMMAND_START_DECORATOR(5, false)
  575. //nh For no pwd don't set setscurity or pwd
  576. if (ssid == NULL) retVal = false;;
  577. if (pwd && strlen(pwd) > 0)
  578. {
  579. sendAT(GF("EE"), 2); // Set security to WPA2
  580. if (waitResponse() != 1) retVal = false;
  581. sendAT(GF("PK"), pwd);
  582. } else {
  583. sendAT(GF("EE"), 0); // Set No security
  584. }
  585. if (waitResponse() != 1) retVal = false;
  586. sendAT(GF("ID"), ssid);
  587. if (waitResponse() != 1) retVal = false;
  588. if (!writeChanges()) retVal = false;
  589. XBEE_COMMAND_END_DECORATOR
  590. return retVal;
  591. }
  592. bool networkDisconnect() {
  593. XBEE_COMMAND_START_DECORATOR(5, false)
  594. sendAT(GF("NR0")); // Do a network reset in order to disconnect
  595. // WARNING: On wifi modules, using a network reset will not
  596. // allow the same ssid to re-join without rebooting the module.
  597. int8_t res = (1 == waitResponse(5000));
  598. writeChanges();
  599. XBEE_COMMAND_END_DECORATOR
  600. return res;
  601. }
  602. /*
  603. * IP Address functions
  604. */
  605. String getLocalIP() {
  606. XBEE_COMMAND_START_DECORATOR(5, "")
  607. sendAT(GF("MY"));
  608. String IPaddr; IPaddr.reserve(16);
  609. // wait for the response - this response can be very slow
  610. IPaddr = readResponseString(30000);
  611. XBEE_COMMAND_END_DECORATOR
  612. IPaddr.trim();
  613. return IPaddr;
  614. }
  615. IPAddress localIP() {
  616. return TinyGsmIpFromString(getLocalIP());
  617. }
  618. /*
  619. * GPRS functions
  620. */
  621. bool gprsConnect(const char* apn, const char* user = NULL,
  622. const char* pwd = NULL) {
  623. if (user && strlen(user) > 0) {
  624. DBG("XBee's do not support SIMs that a user name/password!");
  625. }
  626. if (pwd && strlen(pwd) > 0) {
  627. DBG("XBee's do not support SIMs that a user name/password!");
  628. }
  629. XBEE_COMMAND_START_DECORATOR(5, false)
  630. sendAT(GF("AN"), apn); // Set the APN
  631. bool success = waitResponse() == 1;
  632. sendAT(GF("AM0")); // Airplane mode off
  633. waitResponse(5000);
  634. writeChanges();
  635. XBEE_COMMAND_END_DECORATOR
  636. return success;
  637. }
  638. bool gprsDisconnect() {
  639. XBEE_COMMAND_START_DECORATOR(5, false)
  640. sendAT(GF("AM1")); // Cheating and disconnecting by turning on airplane mode
  641. int8_t res = (1 == waitResponse(5000));
  642. writeChanges();
  643. // sendAT(GF("AM0")); // Airplane mode off
  644. // waitResponse(5000);
  645. // writeChanges();
  646. XBEE_COMMAND_END_DECORATOR
  647. return res;
  648. }
  649. bool isGprsConnected() {
  650. return isNetworkConnected();
  651. }
  652. /*
  653. * Messaging functions
  654. */
  655. String sendUSSD(const String& code) TINY_GSM_ATTR_NOT_IMPLEMENTED;
  656. bool sendSMS(const String& number, const String& text) {
  657. if (!commandMode()) return false; // Return immediately
  658. sendAT(GF("IP"), 2); // Put in text messaging mode
  659. if (waitResponse() !=1) return exitAndFail();
  660. sendAT(GF("PH"), number); // Set the phone number
  661. if (waitResponse() !=1) return exitAndFail();
  662. sendAT(GF("TDD")); // Set the text delimiter to the standard 0x0D (carriage return)
  663. if (waitResponse() !=1) return exitAndFail();
  664. if (!writeChanges()) return exitAndFail();
  665. // Get out of command mode to actually send the text
  666. exitCommand();
  667. streamWrite(text);
  668. stream.write((char)0x0D); // close off with the carriage return
  669. return true;
  670. }
  671. /*
  672. * Location functions
  673. */
  674. String getGsmLocation() TINY_GSM_ATTR_NOT_AVAILABLE;
  675. /*
  676. * Battery & temperature functions
  677. */
  678. // Use: float vBatt = modem.getBattVoltage() / 1000.0;
  679. uint16_t getBattVoltage() {
  680. int16_t intRes = 0;
  681. XBEE_COMMAND_START_DECORATOR(5, false)
  682. if (beeType == XBEE_UNKNOWN) getSeries();
  683. if (beeType == XBEE_S6B_WIFI) {
  684. sendAT(GF("%V"));
  685. intRes = readResponseInt();
  686. }
  687. XBEE_COMMAND_END_DECORATOR
  688. return intRes;
  689. }
  690. int8_t getBattPercent() TINY_GSM_ATTR_NOT_AVAILABLE;
  691. uint8_t getBattChargeState() TINY_GSM_ATTR_NOT_AVAILABLE;
  692. bool getBattStats(uint8_t &chargeState, int8_t &percent, uint16_t &milliVolts) {
  693. chargeState = 0;
  694. percent = 0;
  695. milliVolts = getBattVoltage();
  696. return true;
  697. }
  698. float getTemperature() {
  699. XBEE_COMMAND_START_DECORATOR(5, (float)-9999)
  700. String res = sendATGetString(GF("TP"));
  701. if (res == "") {
  702. return (float)-9999;
  703. }
  704. char buf[5] = {0,};
  705. res.toCharArray(buf, 5);
  706. int8_t intRes = (int8_t)strtol(buf, 0, 16); // degrees Celsius displayed in 8-bit two's complement format.
  707. XBEE_COMMAND_END_DECORATOR
  708. return (float)intRes;
  709. }
  710. /*
  711. * Client related functions
  712. */
  713. protected:
  714. int16_t getConnectionIndicator() {
  715. XBEE_COMMAND_START_DECORATOR(5, false)
  716. sendAT(GF("CI"));
  717. int16_t intRes = readResponseInt();
  718. XBEE_COMMAND_END_DECORATOR
  719. return intRes;
  720. }
  721. IPAddress getOperatingIP() {
  722. String strIP;
  723. strIP.reserve(16);
  724. XBEE_COMMAND_START_DECORATOR(5, IPAddress(0, 0, 0, 0))
  725. sendAT(GF("OD"));
  726. strIP = stream.readStringUntil('\r'); // read result
  727. strIP.trim();
  728. XBEE_COMMAND_END_DECORATOR
  729. if (strIP != "" && strIP != GF("ERROR")) {
  730. return TinyGsmIpFromString(strIP);
  731. } else
  732. return IPAddress(0, 0, 0, 0);
  733. }
  734. IPAddress lookupHostIP(const char* host, int timeout_s = 45) {
  735. String strIP;
  736. strIP.reserve(16);
  737. unsigned long startMillis = millis();
  738. uint32_t timeout_ms = ((uint32_t)timeout_s)*1000;
  739. bool gotIP = false;
  740. XBEE_COMMAND_START_DECORATOR(5, IPAddress(0,0,0,0))
  741. // XBee's require a numeric IP address for connection, but do provide the
  742. // functionality to look up the IP address from a fully qualified domain name
  743. while ((millis() - startMillis) < timeout_ms) // the lookup can take a while
  744. {
  745. sendAT(GF("LA"), host);
  746. while (stream.available() < 4 && (millis() - startMillis < timeout_ms)) {TINY_GSM_YIELD()};
  747. strIP = stream.readStringUntil('\r'); // read result
  748. strIP.trim();
  749. if (strIP != "" && strIP != GF("ERROR")) {
  750. gotIP = true;
  751. break;
  752. }
  753. delay(2500); // wait a bit before trying again
  754. }
  755. XBEE_COMMAND_END_DECORATOR
  756. if (gotIP) {
  757. return TinyGsmIpFromString(strIP);
  758. }
  759. else return IPAddress(0,0,0,0);
  760. }
  761. bool modemConnect(const char* host, uint16_t port, uint8_t mux = 0,
  762. bool ssl = false, int timeout_s = 75)
  763. {
  764. bool retVal = false;
  765. XBEE_COMMAND_START_DECORATOR(5, false)
  766. // If this is a new host name, replace the saved host and wipe out the saved host IP
  767. if (this->savedHost != String(host)) {
  768. this->savedHost = String(host);
  769. savedHostIP = IPAddress(0,0,0,0);
  770. }
  771. // If we don't have a good IP for the host, we need to do a DNS search
  772. if (savedHostIP == IPAddress(0,0,0,0)) {
  773. savedHostIP = lookupHostIP(host, timeout_s); // This will return 0.0.0.0 if lookup fails
  774. }
  775. // If we now have a valid IP address, use it to connect
  776. if (savedHostIP != IPAddress(0,0,0,0)) { // Only re-set connection information if we have an IP address
  777. retVal = modemConnect(savedHostIP, port, mux, ssl);
  778. }
  779. XBEE_COMMAND_END_DECORATOR
  780. return retVal;
  781. }
  782. bool modemConnect(IPAddress ip, uint16_t port, uint8_t mux = 0,
  783. bool ssl = false) {
  784. bool success = true;
  785. if (mux != 0) {
  786. DBG("XBee only supports 1 IP channel in transparent mode!");
  787. }
  788. // empty the saved currelty-in-use destination address
  789. savedOperatingIP = IPAddress(0, 0, 0, 0);
  790. XBEE_COMMAND_START_DECORATOR(5, false)
  791. if (ip != savedIP) { // Can skip almost everything if there's no
  792. // change in the IP address
  793. savedIP = ip; // Set the newly requested IP address
  794. String host; host.reserve(16);
  795. host += ip[0];
  796. host += ".";
  797. host += ip[1];
  798. host += ".";
  799. host += ip[2];
  800. host += ".";
  801. host += ip[3];
  802. if (ssl) {
  803. sendAT(GF("IP"), 4); // Put in SSL over TCP communication mode
  804. success &= (1 == waitResponse());
  805. } else {
  806. sendAT(GF("IP"), 1); // Put in TCP mode
  807. success &= (1 == waitResponse());
  808. }
  809. sendAT(GF("DL"), host); // Set the "Destination Address Low"
  810. success &= (1 == waitResponse());
  811. sendAT(GF("DE"), String(port, HEX)); // Set the destination port
  812. success &= (1 == waitResponse());
  813. success &= writeChanges();
  814. }
  815. // we'll accept either unknown or connected
  816. if (beeType != XBEE_S6B_WIFI) {
  817. uint16_t ci = getConnectionIndicator();
  818. success &= (ci == 0x00 || ci == 0xFF || ci == 0x28);
  819. }
  820. if (success) {
  821. sockets[mux]->sock_connected = true;
  822. }
  823. XBEE_COMMAND_END_DECORATOR
  824. return success;
  825. }
  826. bool modemStop(uint32_t maxWaitMs) {
  827. streamClear(); // Empty anything in the buffer
  828. // empty the saved currently-in-use destination address
  829. savedOperatingIP = IPAddress(0, 0, 0, 0);
  830. XBEE_COMMAND_START_DECORATOR(5, false)
  831. // Get the current socket timeout
  832. sendAT(GF("TM"));
  833. String timeoutUsed = readResponseString(5000L);
  834. // For WiFi models, there's no direct way to close the socket. This is a
  835. // hack to shut the socket by setting the timeout to zero.
  836. if (beeType == XBEE_S6B_WIFI) {
  837. sendAT(GF("TM0")); // Set socket timeout to 0
  838. waitResponse(maxWaitMs); // This response can be slow
  839. writeChanges();
  840. }
  841. // For cellular models, per documentation: If you write the TM (socket
  842. // timeout) value while in Transparent Mode, the current connection is
  843. // immediately closed - this works even if the TM values is unchanged
  844. sendAT(GF("TM"), timeoutUsed); // Re-set socket timeout
  845. waitResponse(maxWaitMs); // This response can be slow
  846. writeChanges();
  847. XBEE_COMMAND_END_DECORATOR
  848. return true;
  849. }
  850. int16_t modemSend(const void* buff, size_t len, uint8_t mux = 0) {
  851. if (mux != 0) {
  852. DBG("XBee only supports 1 IP channel in transparent mode!");
  853. }
  854. stream.write((uint8_t*)buff, len);
  855. stream.flush();
  856. if (beeType != XBEE_S6B_WIFI) {
  857. // After a send, verify the outgoing ip if it isn't set
  858. if (savedOperatingIP == IPAddress(0, 0, 0, 0)) {
  859. modemGetConnected();
  860. }
  861. // After sending several characters, also re-check
  862. // NOTE: I'm intentionally not checking after every single character!
  863. else if (len > 5) {
  864. modemGetConnected();
  865. }
  866. }
  867. return len;
  868. }
  869. // NOTE: The CI command returns the status of the TCP connection as open only
  870. // after data has been sent on the socket. If it returns 0xFF the socket may
  871. // really be open, but no data has yet been sent. We return this unknown value
  872. // as true so there's a possibility it's wrong.
  873. bool modemGetConnected() {
  874. // If the IP address is 0, it's not valid so we can't be connected
  875. if (savedIP == IPAddress(0,0,0,0)) return false;
  876. XBEE_COMMAND_START_DECORATOR(5, false)
  877. if (beeType == XBEE_UNKNOWN) getSeries(); // Need to know the bee type to interpret response
  878. switch (beeType){
  879. // The wifi be can only say if it's connected to the netowrk
  880. case XBEE_S6B_WIFI: {
  881. RegStatus s = getRegistrationStatus();
  882. XBEE_COMMAND_END_DECORATOR
  883. if (s != REG_OK) {
  884. sockets[0]->sock_connected = false; // no multiplex
  885. }
  886. return (s == REG_OK); // if it's connected, we hope the sockets are too
  887. }
  888. // Cellular XBee's
  889. default: {
  890. int16_t ci = getConnectionIndicator();
  891. // Get the operating destination address
  892. IPAddress od = getOperatingIP();
  893. XBEE_COMMAND_END_DECORATOR
  894. switch(ci) {
  895. // 0x00 = The socket is definitely open
  896. case 0x00: {
  897. savedOperatingIP = od;
  898. // but it's possible the socket is set to the wrong place
  899. if (od != IPAddress(0, 0, 0, 0) && od != savedIP) {
  900. sockets[0]->stop();
  901. return false;
  902. }
  903. return true;
  904. }
  905. // 0x28 = "Unknown."
  906. // 0xFF = No known status - always returned prior to sending data
  907. case 0x28:
  908. case 0xFF: {
  909. // If we previously had an operating destination and we no longer do,
  910. // the socket must have closed
  911. if (od == IPAddress(0, 0, 0, 0) && savedOperatingIP != IPAddress(0, 0, 0, 0)) {
  912. savedOperatingIP = od;
  913. sockets[0]->sock_connected = false;
  914. return false;
  915. }
  916. // else if the operating destination exists, but is wrong
  917. // we need to close and re-open
  918. else if (od != IPAddress(0, 0, 0, 0) && od != savedIP) {
  919. sockets[0]->stop();
  920. return false;
  921. }
  922. // else if the operating destination exists and matches, we're
  923. // good to go
  924. else if (od != IPAddress(0, 0, 0, 0) && od == savedIP) {
  925. savedOperatingIP = od;
  926. return true;
  927. }
  928. // If we never had an operating destination, then sock may be open
  929. // but data never sent - this is the dreaded "we don't know"
  930. else {
  931. savedOperatingIP = od;
  932. return true;
  933. }
  934. // // Ask for information about any open sockets
  935. // sendAT(GF("SI"));
  936. // String open_socks = stream.readStringUntil('\r');
  937. // open_socks.replace(GSM_NL, "");
  938. // open_socks.trim();
  939. // if (open_socks != "") {
  940. // // In transparent mode, only socket 0 should be possible
  941. // sendAT(GF("SI0"));
  942. // // read socket it
  943. // String sock_id = stream.readStringUntil('\r');
  944. // // read socket state
  945. // String sock_state = stream.readStringUntil('\r');
  946. // // read socket protocol (TCP/UDP)
  947. // String sock_protocol = stream.readStringUntil('\r');
  948. // // read local port number
  949. // String local_port = stream.readStringUntil('\r');
  950. // // read remote port number
  951. // String remote_port = stream.readStringUntil('\r');
  952. // // read remote ip address
  953. // String remoted_address =
  954. // stream.readStringUntil('\r'); // read result
  955. // stream.readStringUntil('\r'); // final carriage return
  956. // }
  957. }
  958. // 0x21 = User closed
  959. // 0x27 = Connection lost
  960. // If the connection is lost or timed out on our side,
  961. // we force close so it can reopen
  962. case 0x21 :
  963. case 0x27 : {
  964. sendAT(GF("TM")); // Get socket timeout
  965. String timeoutUsed = readResponseString(5000L);
  966. sendAT(GF("TM"), timeoutUsed); // Re-set socket timeout
  967. waitResponse(5000L); // This response can be slow
  968. }
  969. // 0x02 = Invalid parameters (bad IP/host)
  970. // 0x12 = DNS query lookup failure
  971. // 0x25 = Unknown server - DNS lookup failed (0x22 for UDP socket!)
  972. case 0x02:
  973. case 0x12:
  974. case 0x25: {
  975. savedIP = IPAddress(0, 0, 0, 0); // force a lookup next time!
  976. }
  977. // If it's anything else (inc 0x02, 0x12, and 0x25)...
  978. // it's definitely NOT connected
  979. default: {
  980. sockets[0]->sock_connected = false;
  981. savedOperatingIP = od;
  982. return false;
  983. }
  984. }
  985. }
  986. }
  987. }
  988. public:
  989. /*
  990. Utilities
  991. */
  992. void streamClear(void) {
  993. while (stream.available()) {
  994. stream.read();
  995. TINY_GSM_YIELD();
  996. }
  997. }
  998. TINY_GSM_MODEM_STREAM_UTILITIES()
  999. // TODO: Optimize this!
  1000. // NOTE: This function is used while INSIDE command mode, so we're only
  1001. // waiting for requested responses. The XBee has no unsoliliced responses
  1002. // (URC's) when in command mode.
  1003. uint8_t waitResponse(uint32_t timeout_ms, String& data,
  1004. GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
  1005. GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
  1006. {
  1007. /*String r1s(r1); r1s.trim();
  1008. String r2s(r2); r2s.trim();
  1009. String r3s(r3); r3s.trim();
  1010. String r4s(r4); r4s.trim();
  1011. String r5s(r5); r5s.trim();
  1012. DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/
  1013. data.reserve(16); // Should never be getting much here for the XBee
  1014. int8_t index = 0;
  1015. unsigned long startMillis = millis();
  1016. do {
  1017. TINY_GSM_YIELD();
  1018. while (stream.available() > 0) {
  1019. TINY_GSM_YIELD();
  1020. int a = stream.read();
  1021. if (a <= 0) continue; // Skip 0x00 bytes, just in case
  1022. data += (char)a;
  1023. if (r1 && data.endsWith(r1)) {
  1024. index = 1;
  1025. goto finish;
  1026. } else if (r2 && data.endsWith(r2)) {
  1027. index = 2;
  1028. goto finish;
  1029. } else if (r3 && data.endsWith(r3)) {
  1030. index = 3;
  1031. goto finish;
  1032. } else if (r4 && data.endsWith(r4)) {
  1033. index = 4;
  1034. goto finish;
  1035. } else if (r5 && data.endsWith(r5)) {
  1036. index = 5;
  1037. goto finish;
  1038. }
  1039. }
  1040. } while (millis() - startMillis < timeout_ms);
  1041. finish:
  1042. if (!index) {
  1043. data.trim();
  1044. data.replace(GSM_NL GSM_NL, GSM_NL);
  1045. data.replace(GSM_NL, "\r\n ");
  1046. if (data.length()) {
  1047. DBG("### Unhandled:", data, "\r\n");
  1048. } else {
  1049. DBG("### NO RESPONSE FROM MODEM!\r\n");
  1050. }
  1051. } else {
  1052. data.trim();
  1053. data.replace(GSM_NL GSM_NL, GSM_NL);
  1054. data.replace(GSM_NL, "\r\n ");
  1055. if (data.length()) {
  1056. }
  1057. }
  1058. //data.replace(GSM_NL, "/");
  1059. //DBG('<', index, '>', data);
  1060. return index;
  1061. }
  1062. uint8_t waitResponse(uint32_t timeout_ms,
  1063. GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
  1064. GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
  1065. {
  1066. String data;
  1067. return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5);
  1068. }
  1069. uint8_t waitResponse(GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR),
  1070. GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL)
  1071. {
  1072. return waitResponse(1000, r1, r2, r3, r4, r5);
  1073. }
  1074. bool commandMode(uint8_t retries = 5) {
  1075. // If we're already in command mode, move on
  1076. if (inCommandMode && (millis() - lastCommandModeMillis) < 10000L) return true;
  1077. uint8_t triesMade = 0;
  1078. uint8_t triesUntilReset = 4; // only reset after 4 failures
  1079. bool success = false;
  1080. streamClear(); // Empty everything in the buffer before starting
  1081. while (!success and triesMade < retries) {
  1082. // Cannot send anything for 1 "guard time" before entering command mode
  1083. // Default guard time is 1s, but the init fxn decreases it to 100 ms
  1084. delay(guardTime + 10);
  1085. streamWrite(GF("+++")); // enter command mode
  1086. int res = waitResponse(guardTime*2);
  1087. success = (1 == res);
  1088. if (0 == res) {
  1089. triesUntilReset--;
  1090. if (triesUntilReset == 0) {
  1091. triesUntilReset = 4;
  1092. pinReset(); // if it's unresponsive, reset
  1093. delay(250); // a short delay to allow it to come back up
  1094. // TODO-optimize this
  1095. }
  1096. }
  1097. triesMade ++;
  1098. }
  1099. if (success) {
  1100. inCommandMode = true;
  1101. lastCommandModeMillis = millis();
  1102. }
  1103. return success;
  1104. }
  1105. bool writeChanges(void) {
  1106. sendAT(GF("WR")); // Write changes to flash
  1107. if (1 != waitResponse()) return false;
  1108. sendAT(GF("AC")); // Apply changes
  1109. if (1 != waitResponse()) return false;
  1110. return true;
  1111. }
  1112. void exitCommand(void) {
  1113. // NOTE: Here we explicitely try to exit command mode
  1114. // even if the internal flag inCommandMode was already false
  1115. sendAT(GF("CN")); // Exit command mode
  1116. waitResponse();
  1117. inCommandMode = false;
  1118. }
  1119. bool exitAndFail(void) {
  1120. exitCommand(); // Exit command mode
  1121. return false;
  1122. }
  1123. void getSeries(void) {
  1124. sendAT(GF("HS")); // Get the "Hardware Series";
  1125. int16_t intRes = readResponseInt();
  1126. beeType = (XBeeType)intRes;
  1127. DBG(GF("### Modem: "), getModemName());
  1128. }
  1129. String readResponseString(uint32_t timeout_ms = 1000) {
  1130. TINY_GSM_YIELD();
  1131. unsigned long startMillis = millis();
  1132. while (!stream.available() && millis() - startMillis < timeout_ms) {};
  1133. String res = stream.readStringUntil('\r'); // lines end with carriage returns
  1134. res.trim();
  1135. return res;
  1136. }
  1137. int16_t readResponseInt(uint32_t timeout_ms = 1000) {
  1138. String res = readResponseString(timeout_ms); // it just works better reading a string first
  1139. if (res == "") res = "FF";
  1140. char buf[5] = {0,};
  1141. res.toCharArray(buf, 5);
  1142. int16_t intRes = strtol(buf, 0, 16);
  1143. return intRes;
  1144. }
  1145. String sendATGetString(GsmConstStr cmd) {
  1146. XBEE_COMMAND_START_DECORATOR(5, "")
  1147. sendAT(cmd);
  1148. String res = readResponseString();
  1149. XBEE_COMMAND_END_DECORATOR
  1150. return res;
  1151. }
  1152. bool gotIPforSavedHost() {
  1153. if (savedHost != "" && savedHostIP != IPAddress(0,0,0,0)) return true;
  1154. else return false;
  1155. }
  1156. public:
  1157. Stream& stream;
  1158. protected:
  1159. int16_t guardTime;
  1160. int8_t resetPin;
  1161. XBeeType beeType;
  1162. IPAddress savedIP;
  1163. String savedHost;
  1164. IPAddress savedHostIP;
  1165. IPAddress savedOperatingIP;
  1166. bool inCommandMode;
  1167. uint32_t lastCommandModeMillis;
  1168. GsmClient* sockets[TINY_GSM_MUX_COUNT];
  1169. };
  1170. #endif