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.

1460 lines
44 KiB

/**
* @file TinyGsmClientXBee.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy, XBee module by Sara
* Damiano
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTXBEE_H_
#define SRC_TINYGSMCLIENTXBEE_H_
// #pragma message("TinyGSM: TinyGsmClientXBee")
// #define TINY_GSM_DEBUG Serial
// XBee's do not support multi-plexing in transparent/command mode
// The much more complicated API mode is needed for multi-plexing
#define TINY_GSM_MUX_COUNT 1
// XBee's have a default guard time of 1 second (1000ms, 10 extra for safety
// here)
#define TINY_GSM_XBEE_GUARD_TIME 1010
#include "TinyGsmBattery.tpp"
#include "TinyGsmGPRS.tpp"
#include "TinyGsmModem.tpp"
#include "TinyGsmSMS.tpp"
#include "TinyGsmSSL.tpp"
#include "TinyGsmTCP.tpp"
#include "TinyGsmTemperature.tpp"
#include "TinyGsmWifi.tpp"
#define GSM_NL "\r"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
// Use this to avoid too many entrances and exits from command mode.
// The cellular Bee's often freeze up and won't respond when attempting
// to enter command mode too many times.
#define XBEE_COMMAND_START_DECORATOR(nAttempts, failureReturn) \
bool wasInCommandMode = inCommandMode; \
if (!wasInCommandMode) { /* don't re-enter command mode if already in it */ \
if (!commandMode(nAttempts)) \
return failureReturn; /* Return immediately if fails */ \
}
#define XBEE_COMMAND_END_DECORATOR \
if (!wasInCommandMode) { /* only exit if we weren't in command mode */ \
exitCommand(); \
}
enum RegStatus {
REG_OK = 0,
REG_UNREGISTERED = 1,
REG_SEARCHING = 2,
REG_DENIED = 3,
REG_UNKNOWN = 4,
};
// These are responses to the HS command to get "hardware series"
enum XBeeType {
XBEE_UNKNOWN = 0,
XBEE_S6B_WIFI = 0x601, // Digi XBee Wi-Fi
XBEE_LTE1_VZN = 0xB01, // Digi XBee Cellular LTE Cat 1
XBEE_3G = 0xB02, // Digi XBee Cellular 3G
XBEE3_LTE1_ATT = 0xB06, // Digi XBee3 Cellular LTE CAT 1
XBEE3_LTEM_ATT = 0xB08, // Digi XBee3 Cellular LTE-M
};
class TinyGsmXBee
: public TinyGsmModem<TinyGsmXBee>,
public TinyGsmGPRS<TinyGsmXBee>,
public TinyGsmWifi<TinyGsmXBee>,
public TinyGsmTCP<TinyGsmXBee, NO_MODEM_BUFFER, TINY_GSM_MUX_COUNT>,
public TinyGsmSSL<TinyGsmXBee>,
public TinyGsmSMS<TinyGsmXBee>,
public TinyGsmBattery<TinyGsmXBee>,
public TinyGsmTemperature<TinyGsmXBee> {
friend class TinyGsmModem<TinyGsmXBee>;
friend class TinyGsmGPRS<TinyGsmXBee>;
friend class TinyGsmWifi<TinyGsmXBee>;
friend class TinyGsmTCP<TinyGsmXBee, NO_MODEM_BUFFER, TINY_GSM_MUX_COUNT>;
friend class TinyGsmSSL<TinyGsmXBee>;
friend class TinyGsmSMS<TinyGsmXBee>;
friend class TinyGsmBattery<TinyGsmXBee>;
friend class TinyGsmTemperature<TinyGsmXBee>;
/*
* Inner Client
*/
public:
class GsmClientXBee : public GsmClient {
friend class TinyGsmXBee;
public:
GsmClientXBee() {}
explicit GsmClientXBee(TinyGsmXBee& modem, uint8_t mux = 0) {
init(&modem, mux);
}
bool init(TinyGsmXBee* modem, uint8_t mux = 0) {
this->at = modem;
this->mux = mux;
sock_connected = false;
at->sockets[mux] = this;
return true;
}
public:
// NOTE: The XBee saves all connection information (ssid/pwd or apn AND
// last used IP address) in flash (NVM). When you turn it on it immediately
// prepares to re-connect to whatever was last set. The TCP connection
// itself is not opened until you attempt to send data. Because all settings
// are saved to flash, it is possible (or likely) that you could send data
// even if you haven't "made" any connection.
virtual int connect(const char* host, uint16_t port, int timeout_s) {
// NOTE: Not caling stop() or yeild() here
at->streamClear(); // Empty anything in the buffer before starting
sock_connected = at->modemConnect(host, port, mux, false, timeout_s);
return sock_connected;
}
int connect(const char* host, uint16_t port) override {
return connect(host, port, 75);
}
virtual int connect(IPAddress ip, uint16_t port, int timeout_s) {
if (timeout_s != 0) {
DBG("Timeout [", timeout_s, "] doesn't apply here.");
}
// NOTE: Not caling stop() or yeild() here
at->streamClear(); // Empty anything in the buffer before starting
sock_connected = at->modemConnect(ip, port, mux, false);
return sock_connected;
}
int connect(IPAddress ip, uint16_t port) override {
return connect(ip, port, 0);
}
virtual void stop(uint32_t maxWaitMs) {
at->streamClear(); // Empty anything in the buffer
// empty the saved currently-in-use destination address
at->modemStop(maxWaitMs);
at->streamClear(); // Empty anything in the buffer
sock_connected = false;
// Note: because settings are saved in flash, the XBEE will attempt to
// reconnect to the previous socket if it receives any outgoing data.
// Setting sock_connected to false after the stop ensures that connected()
// will return false after a stop has been ordered. This makes it play
// much more nicely with libraries like PubSubClient.
}
void stop() override {
stop(5000L);
}
size_t write(const uint8_t* buf, size_t size) override {
TINY_GSM_YIELD();
return at->modemSend(buf, size, mux);
}
size_t write(uint8_t c) override {
return write(&c, 1);
}
size_t write(const char* str) {
if (str == NULL) return 0;
return write((const uint8_t*)str, strlen(str));
}
int available() override {
TINY_GSM_YIELD();
return at->stream.available();
/*
if (!rx.size() || at->stream.available()) {
at->maintain();
}
return at->stream.available() + rx.size();
*/
}
int read(uint8_t* buf, size_t size) override {
TINY_GSM_YIELD();
return at->stream.readBytes(reinterpret_cast<char*>(buf), size);
/*
size_t cnt = 0;
uint32_t _startMillis = millis();
while (cnt < size && millis() - _startMillis < _timeout) {
size_t chunk = TinyGsmMin(size-cnt, rx.size());
if (chunk > 0) {
rx.get(buf, chunk);
buf += chunk;
cnt += chunk;
continue;
}
// TODO(vshymanskyy): Read directly into user buffer?
if (!rx.size() || at->stream.available()) {
at->maintain();
}
}
return cnt;
*/
}
int read() override {
TINY_GSM_YIELD();
return at->stream.read();
/*
uint8_t c;
if (read(&c, 1) == 1) {
return c;
}
return -1;
*/
}
int peek() override {
return at->stream.peek();
}
void flush() override {
at->stream.flush();
}
uint8_t connected() override {
if (available()) {
return true;
// if we never got an IP, it can't be connected
} else if (at->savedIP == IPAddress(0, 0, 0, 0)) {
return false;
}
return sock_connected;
// NOTE: We don't check or return
// modemGetConnected() because we don't
// want to go into command mode.
// return at->modemGetConnected();
}
operator bool() override {
return connected();
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
public:
class GsmClientSecureXBee : public GsmClientXBee {
public:
GsmClientSecureXBee() {}
explicit GsmClientSecureXBee(TinyGsmXBee& modem, uint8_t mux = 0)
: GsmClientXBee(modem, mux) {}
public:
int connect(const char* host, uint16_t port, int timeout_s) override {
// NOTE: Not caling stop() or yeild() here
at->streamClear(); // Empty anything in the buffer before starting
sock_connected = at->modemConnect(host, port, mux, true, timeout_s);
return sock_connected;
}
int connect(const char* host, uint16_t port) override {
return connect(host, port, 75);
}
int connect(IPAddress ip, uint16_t port, int timeout_s) override {
if (timeout_s != 0) {
DBG("Timeout [", timeout_s, "] doesn't apply here.");
}
// NOTE: Not caling stop() or yeild() here
at->streamClear(); // Empty anything in the buffer before starting
sock_connected = at->modemConnect(ip, port, mux, true);
return sock_connected;
}
int connect(IPAddress ip, uint16_t port) override {
return connect(ip, port, 0);
}
};
/*
* Constructor
*/
public:
explicit TinyGsmXBee(Stream& stream)
: stream(stream),
guardTime(TINY_GSM_XBEE_GUARD_TIME),
beeType(XBEE_UNKNOWN),
resetPin(-1),
savedIP(IPAddress(0, 0, 0, 0)),
savedHost(""),
savedHostIP(IPAddress(0, 0, 0, 0)),
savedOperatingIP(IPAddress(0, 0, 0, 0)),
inCommandMode(false),
lastCommandModeMillis(0) {
// Start not knowing what kind of bee it is
// Start with the default guard time of 1 second
memset(sockets, 0, sizeof(sockets));
}
TinyGsmXBee(Stream& stream, int8_t resetPin)
: stream(stream),
guardTime(TINY_GSM_XBEE_GUARD_TIME),
beeType(XBEE_UNKNOWN),
resetPin(resetPin),
savedIP(IPAddress(0, 0, 0, 0)),
savedHost(""),
savedHostIP(IPAddress(0, 0, 0, 0)),
savedOperatingIP(IPAddress(0, 0, 0, 0)),
inCommandMode(false),
lastCommandModeMillis(0) {
// Start not knowing what kind of bee it is
// Start with the default guard time of 1 second
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
if (resetPin >= 0) {
pinMode(resetPin, OUTPUT);
digitalWrite(resetPin, HIGH);
}
if (pin && strlen(pin) > 0) {
DBG("XBee's do not support SIMs that require an unlock pin!");
}
XBEE_COMMAND_START_DECORATOR(10, false)
sendAT(GF("AP0")); // Put in transparent mode
bool ret_val = waitResponse() == 1;
sendAT(GF("GT64")); // shorten the guard time to 100ms
ret_val &= waitResponse() == 1;
if (ret_val) guardTime = 110;
// Make sure the command mode drop-out time is long enough that we won't
// fall out of command mode without intentionally leaving it. This is the
// default drop out time of 0x64 x 100ms (10 seconds)
sendAT(GF("CT64"));
ret_val &= waitResponse() == 1;
ret_val &= writeChanges();
getSeries(); // Get the "Hardware Series";
XBEE_COMMAND_END_DECORATOR
return ret_val;
}
String getModemNameImpl() {
return getBeeName();
}
void setBaudImpl(uint32_t baud) {
XBEE_COMMAND_START_DECORATOR(5, )
switch (baud) {
case 2400: sendAT(GF("BD1")); break;
case 4800: sendAT(GF("BD2")); break;
case 9600: sendAT(GF("BD3")); break;
case 19200: sendAT(GF("BD4")); break;
case 38400: sendAT(GF("BD5")); break;
case 57600: sendAT(GF("BD6")); break;
case 115200: sendAT(GF("BD7")); break;
case 230400: sendAT(GF("BD8")); break;
case 460800: sendAT(GF("BD9")); break;
case 921600: sendAT(GF("BDA")); break;
default: {
DBG(GF("Specified baud rate is unsupported! Setting to 9600 baud."));
sendAT(GF("BD3")); // Set to default of 9600
break;
}
}
waitResponse();
writeChanges();
XBEE_COMMAND_END_DECORATOR
}
bool testATImpl(uint32_t timeout_ms = 10000L) {
uint32_t start = millis();
bool success = false;
while (!success && millis() - start < timeout_ms) {
if (!inCommandMode) {
success = commandMode();
if (success) exitCommand();
} else {
sendAT();
if (waitResponse(200) == 1) {
success = true;
} else {
// if we didn't respond to the AT, assume we're not in command mode
inCommandMode = false;
}
}
delay(250);
}
return success;
}
void maintainImpl() {
// this only happens OUTSIDE command mode, so if we're getting characters
// they should be data received from the TCP connection
// TINY_GSM_YIELD();
// if (!inCommandMode) {
// while (stream.available()) {
// char c = stream.read();
// if (c > 0) sockets[0]->rx.put(c);
// }
// }
}
bool factoryDefaultImpl() {
XBEE_COMMAND_START_DECORATOR(5, false)
sendAT(GF("RE"));
bool ret_val = waitResponse() == 1;
ret_val &= writeChanges();
XBEE_COMMAND_END_DECORATOR
// Make sure the guard time for the modem object is set back to default
// otherwise communication would fail after the reset
guardTime = 1010;
return ret_val;
}
String getModemInfoImpl() {
return sendATGetString(GF("HS"));
}
/*
bool thisHasSSL() {
if (beeType == XBEE_S6B_WIFI)
return false;
else
return true;
}
bool thisHasWifi() {
if (beeType == XBEE_S6B_WIFI)
return true;
else
return false;
}
bool thisHasGPRS() {
if (beeType == XBEE_S6B_WIFI)
return false;
else
return true;
}
*/
public:
XBeeType getBeeType() {
return beeType;
}
String getBeeName() {
switch (beeType) {
case XBEE_S6B_WIFI: return "Digi XBee Wi-Fi";
case XBEE_LTE1_VZN: return "Digi XBee Cellular LTE Cat 1";
case XBEE_3G: return "Digi XBee Cellular 3G";
case XBEE3_LTE1_ATT: return "Digi XBee3 Cellular LTE CAT 1";
case XBEE3_LTEM_ATT: return "Digi XBee3 Cellular LTE-M";
default: return "Digi XBee";
}
}
/*
* Power functions
*/
protected:
// The XBee's have a bad habit of getting into an unresponsive funk
// This uses the board's hardware reset pin to force it to reset
void pinReset() {
if (resetPin >= 0) {
DBG("### Forcing a modem reset!\r\n");
digitalWrite(resetPin, LOW);
delay(1);
digitalWrite(resetPin, HIGH);
}
}
bool restartImpl() {
if (!commandMode()) { return false; } // Return immediately
if (beeType == XBEE_UNKNOWN) getSeries(); // how we restart depends on this
if (beeType != XBEE_S6B_WIFI) {
sendAT(GF("AM1")); // Digi suggests putting cellular modules into
// airplane mode before restarting This allows the
// sockets and connections to close cleanly
if (waitResponse() != 1) return exitAndFail();
if (!writeChanges()) return exitAndFail();
}
sendAT(GF("FR"));
if (waitResponse() != 1)
return exitAndFail();
else
inCommandMode = false; // Reset effectively exits command mode
if (beeType == XBEE_S6B_WIFI)
delay(2000); // Wifi module actually resets about 2 seconds later
else
delay(100); // cellular modules wait 100ms before reset happens
// Wait until reboot completes and XBee responds to command mode call again
for (uint32_t start = millis(); millis() - start < 60000L;) {
if (commandMode(1)) break;
delay(250); // wait a litle before trying again
}
if (beeType != XBEE_S6B_WIFI) {
sendAT(GF("AM0")); // Turn off airplane mode
if (waitResponse() != 1) return exitAndFail();
if (!writeChanges()) return exitAndFail();
}
exitCommand();
return init();
}
void setupPinSleep(bool maintainAssociation = false) {
XBEE_COMMAND_START_DECORATOR(5, )
if (beeType == XBEE_UNKNOWN) getSeries(); // Command depends on series
sendAT(GF("SM"), 1); // Pin sleep
waitResponse();
if (beeType == XBEE_S6B_WIFI && !maintainAssociation) {
sendAT(GF("SO"), 200); // For lowest power, dissassociated deep sleep
waitResponse();
} else if (!maintainAssociation) {
sendAT(GF("SO"),
1); // For supported cellular modules, maintain association
// Not supported by all modules, will return "ERROR"
waitResponse();
}
writeChanges();
XBEE_COMMAND_END_DECORATOR
}
bool
powerOffImpl() { // NOTE: Not supported for WiFi or older cellular firmware
XBEE_COMMAND_START_DECORATOR(5, false)
sendAT(GF("SD"));
bool ret_val = waitResponse(120000L) == 1;
if (ret_val) { ret_val &= (sendATGetString(GF("AI")) == "2D"); }
XBEE_COMMAND_END_DECORATOR
return ret_val;
}
// Enable airplane mode
bool radioOffImpl() {
XBEE_COMMAND_START_DECORATOR(5, false)
sendAT(GF("AM1"));
int8_t res = (1 == waitResponse(5000));
writeChanges();
XBEE_COMMAND_END_DECORATOR
return res;
}
bool sleepEnableImpl(bool enable = true) TINY_GSM_ATTR_NOT_IMPLEMENTED;
/*
* Generic network functions
*/
public:
RegStatus getRegistrationStatus() {
XBEE_COMMAND_START_DECORATOR(5, REG_UNKNOWN)
if (!inCommandMode) return REG_UNKNOWN; // Return immediately
if (beeType == XBEE_UNKNOWN)
getSeries(); // Need to know the bee type to interpret response
sendAT(GF("AI"));
int16_t intRes = readResponseInt(10000L);
RegStatus stat = REG_UNKNOWN;
switch (beeType) {
case XBEE_S6B_WIFI: {
switch (intRes) {
case 0x00: // 0x00 Successfully joined an access point, established
// IP addresses and IP listening sockets
stat = REG_OK;
break;
case 0x01: // 0x01 Wi-Fi transceiver initialization in progress.
case 0x02: // 0x02 Wi-Fi transceiver initialized, but not yet
// scanning for access point.
case 0x40: // 0x40 Waiting for WPA or WPA2 Authentication.
case 0x41: // 0x41 Device joined a network and is waiting for IP
// configuration to complete
case 0x42: // 0x42 Device is joined, IP is configured, and listening
// sockets are being set up.
case 0xFF: // 0xFF Device is currently scanning for the configured
// SSID.
stat = REG_SEARCHING;
break;
case 0x13: // 0x13 Disconnecting from access point.
restart(); // Restart the device; the S6B tends to get stuck
// "disconnecting"
stat = REG_UNREGISTERED;
break;
case 0x23: // 0x23 SSID not configured.
stat = REG_UNREGISTERED;
break;
case 0x24: // 0x24 Encryption key invalid (either NULL or invalid
// length for WEP).
case 0x27: // 0x27 SSID was found, but join failed.
stat = REG_DENIED;
break;
default: stat = REG_UNKNOWN; break;
}
break;
}
default: { // Cellular XBee's
switch (intRes) {
case 0x00: // 0x00 Connected to the Internet.
stat = REG_OK;
break;
case 0x22: // 0x22 Registering to cellular network.
case 0x23: // 0x23 Connecting to the Internet.
case 0xFF: // 0xFF Initializing.
stat = REG_SEARCHING;
break;
case 0x24: // 0x24 The cellular component is missing, corrupt, or
// otherwise in error.
case 0x2B: // 0x2B USB Direct active.
case 0x2C: // 0x2C Cellular component is in PSM (power save mode).
stat = REG_UNKNOWN;
break;
case 0x25: // 0x25 Cellular network registration denied.
stat = REG_DENIED;
break;
case 0x2A: // 0x2A Airplane mode.
sendAT(GF("AM0")); // Turn off airplane mode
waitResponse();
writeChanges();
stat = REG_UNKNOWN;
break;
case 0x2F: // 0x2F Bypass mode active.
sendAT(GF("AP0")); // Set back to transparent mode
waitResponse();
writeChanges();
stat = REG_UNKNOWN;
break;
default: stat = REG_UNKNOWN; break;
}
break;
}
}
XBEE_COMMAND_END_DECORATOR
return stat;
}
protected:
int8_t getSignalQualityImpl() {
XBEE_COMMAND_START_DECORATOR(5, 0);
if (beeType == XBEE_UNKNOWN)
getSeries(); // Need to know what type of bee so we know how to ask
if (beeType == XBEE_S6B_WIFI)
sendAT(GF("LM")); // ask for the "link margin" - the dB above sensitivity
else
sendAT(GF("DB")); // ask for the cell strength in dBm
int16_t intRes = readResponseInt();
XBEE_COMMAND_END_DECORATOR
if (beeType == XBEE3_LTEM_ATT && intRes == 105)
intRes = 0; // tends to reply with "69" when signal is unknown
if (beeType == XBEE_S6B_WIFI) {
if (intRes == 0xFF) {
return 0; // 0xFF returned for unknown
} else {
return -93 + intRes; // the maximum sensitivity is -93dBm
}
} else {
return -1 * intRes; // need to convert to negative number
}
}
bool isNetworkConnectedImpl() {
RegStatus s = getRegistrationStatus();
if (s == REG_OK) {
IPAddress ip = localIP();
if (ip != IPAddress(0, 0, 0, 0)) {
return true;
} else {
return false;
}
} else {
return false;
}
}
bool waitForNetworkImpl(uint32_t timeout_ms = 60000L) {
bool retVal = false;
XBEE_COMMAND_START_DECORATOR(5, false)
for (uint32_t start = millis(); millis() - start < timeout_ms;) {
if (isNetworkConnected()) {
retVal = true;
break;
}
delay(250); // per Neil H. - more stable with delay
}
XBEE_COMMAND_END_DECORATOR
return retVal;
}
String getLocalIPImpl() {
XBEE_COMMAND_START_DECORATOR(5, "")
sendAT(GF("MY"));
String IPaddr;
IPaddr.reserve(16);
// wait for the response - this response can be very slow
IPaddr = readResponseString(30000);
XBEE_COMMAND_END_DECORATOR
IPaddr.trim();
return IPaddr;
}
/*
* WiFi functions
*/
protected:
bool networkConnectImpl(const char* ssid, const char* pwd) {
bool retVal = true;
XBEE_COMMAND_START_DECORATOR(5, false)
// nh For no pwd don't set set security or pwd
if (ssid == NULL) retVal = false;
if (pwd && strlen(pwd) > 0) {
sendAT(GF("EE"), 2); // Set security to WPA2
if (waitResponse() != 1) retVal = false;
sendAT(GF("PK"), pwd);
} else {
sendAT(GF("EE"), 0); // Set No security
}
if (waitResponse() != 1) retVal = false;
sendAT(GF("ID"), ssid);
if (waitResponse() != 1) retVal = false;
if (!writeChanges()) retVal = false;
XBEE_COMMAND_END_DECORATOR
return retVal;
}
bool networkDisconnectImpl() {
XBEE_COMMAND_START_DECORATOR(5, false)
sendAT(GF("NR0")); // Do a network reset in order to disconnect
// WARNING: On wifi modules, using a network reset will not
// allow the same ssid to re-join without rebooting the module.
int8_t res = (1 == waitResponse(5000));
writeChanges();
XBEE_COMMAND_END_DECORATOR
return res;
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
if (user && strlen(user) > 0) {
DBG("XBee's do not support SIMs that a user name/password!");
}
if (pwd && strlen(pwd) > 0) {
DBG("XBee's do not support SIMs that a user name/password!");
}
XBEE_COMMAND_START_DECORATOR(5, false)
sendAT(GF("AN"), apn); // Set the APN
bool success = waitResponse() == 1;
sendAT(GF("AM0")); // Airplane mode off
waitResponse(5000);
writeChanges();
XBEE_COMMAND_END_DECORATOR
return success;
}
bool gprsDisconnectImpl() {
XBEE_COMMAND_START_DECORATOR(5, false)
sendAT(
GF("AM1")); // Cheating and disconnecting by turning on airplane mode
int8_t res = (1 == waitResponse(5000));
writeChanges();
// sendAT(GF("AM0")); // Airplane mode off
// waitResponse(5000);
// writeChanges();
XBEE_COMMAND_END_DECORATOR
return res;
}
bool isGprsConnectedImpl() {
return isNetworkConnected();
}
String getOperatorImpl() {
return sendATGetString(GF("MN"));
}
/*
* SIM card functions
*/
protected:
bool simUnlockImpl(const char* pin) { // Not supported
if (pin && strlen(pin) > 0) {
DBG("XBee's do not support SIMs that require an unlock pin!");
}
return false;
}
String getSimCCIDImpl() {
return sendATGetString(GF("S#"));
}
String getIMEIImpl() {
return sendATGetString(GF("IM"));
}
String getIMSIImpl() {
return sendATGetString(GF("II"));
}
SimStatus getSimStatusImpl(uint32_t) {
return SIM_READY; // unsupported
}
/*
* Messaging functions
*/
protected:
String sendUSSDImpl(const String& code) TINY_GSM_ATTR_NOT_AVAILABLE;
bool sendSMSImpl(const String& number, const String& text) {
if (!commandMode()) { return false; } // Return immediately
sendAT(GF("IP"), 2); // Put in text messaging mode
if (waitResponse() != 1) return exitAndFail();
sendAT(GF("PH"), number); // Set the phone number
if (waitResponse() != 1) return exitAndFail();
sendAT(GF("TDD")); // Set the text delimiter to the standard 0x0D (carriage
// return)
if (waitResponse() != 1) return exitAndFail();
if (!writeChanges()) return exitAndFail();
// Get out of command mode to actually send the text
exitCommand();
streamWrite(text);
stream.write(
static_cast<char>(0x0D)); // close off with the carriage return
return true;
}
/*
* Battery functions
*/
protected:
// Use: float vBatt = modem.getBattVoltage() / 1000.0;
uint16_t getBattVoltageImpl() {
int16_t intRes = 0;
XBEE_COMMAND_START_DECORATOR(5, false)
if (beeType == XBEE_UNKNOWN) getSeries();
if (beeType == XBEE_S6B_WIFI) {
sendAT(GF("%V"));
intRes = readResponseInt();
}
XBEE_COMMAND_END_DECORATOR
return intRes;
}
int8_t getBattPercentImpl() TINY_GSM_ATTR_NOT_AVAILABLE;
uint8_t getBattChargeStateImpl() TINY_GSM_ATTR_NOT_AVAILABLE;
bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent,
uint16_t& milliVolts) {
chargeState = 0;
percent = 0;
milliVolts = getBattVoltage();
return true;
}
/*
* Temperature functions
*/
float getTemperatureImpl() {
XBEE_COMMAND_START_DECORATOR(5, static_cast<float>(-9999))
String res = sendATGetString(GF("TP"));
if (res == "") { return static_cast<float>(-9999); }
char buf[5] = {
0,
};
res.toCharArray(buf, 5);
int8_t intRes = (int8_t)strtol(
buf, 0,
16); // degrees Celsius displayed in 8-bit two's complement format.
XBEE_COMMAND_END_DECORATOR
return static_cast<float>(intRes);
}
/*
* Client related functions
*/
protected:
int16_t getConnectionIndicator() {
XBEE_COMMAND_START_DECORATOR(5, false)
sendAT(GF("CI"));
int16_t intRes = readResponseInt();
XBEE_COMMAND_END_DECORATOR
return intRes;
}
IPAddress getOperatingIP() {
String strIP;
strIP.reserve(16);
XBEE_COMMAND_START_DECORATOR(5, IPAddress(0, 0, 0, 0))
sendAT(GF("OD"));
strIP = stream.readStringUntil('\r'); // read result
strIP.trim();
XBEE_COMMAND_END_DECORATOR
if (strIP != "" && strIP != GF("ERROR")) {
return TinyGsmIpFromString(strIP);
} else {
return IPAddress(0, 0, 0, 0);
}
}
IPAddress lookupHostIP(const char* host, int timeout_s = 45) {
String strIP;
strIP.reserve(16);
uint32_t startMillis = millis();
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
bool gotIP = false;
XBEE_COMMAND_START_DECORATOR(5, IPAddress(0, 0, 0, 0))
// XBee's require a numeric IP address for connection, but do provide the
// functionality to look up the IP address from a fully qualified domain
// name
// NOTE: the lookup can take a while
while ((millis() - startMillis) < timeout_ms) {
sendAT(GF("LA"), host);
while (stream.available() < 4 && (millis() - startMillis < timeout_ms)) {
TINY_GSM_YIELD()
}
strIP = stream.readStringUntil('\r'); // read result
strIP.trim();
if (strIP != "" && strIP != GF("ERROR")) {
gotIP = true;
break;
}
delay(2500); // wait a bit before trying again
}
XBEE_COMMAND_END_DECORATOR
if (gotIP) {
return TinyGsmIpFromString(strIP);
} else {
return IPAddress(0, 0, 0, 0);
}
}
bool modemConnect(const char* host, uint16_t port, uint8_t mux = 0,
bool ssl = false, int timeout_s = 75) {
bool retVal = false;
XBEE_COMMAND_START_DECORATOR(5, false)
// If this is a new host name, replace the saved host and wipe out the saved
// host IP
if (this->savedHost != String(host)) {
this->savedHost = String(host);
savedHostIP = IPAddress(0, 0, 0, 0);
}
// If we don't have a good IP for the host, we need to do a DNS search
if (savedHostIP == IPAddress(0, 0, 0, 0)) {
// This will return 0.0.0.0 if lookup fails
savedHostIP = lookupHostIP(host, timeout_s);
}
// If we now have a valid IP address, use it to connect
if (savedHostIP != IPAddress(0, 0, 0, 0)) {
// Only re-set connection information if we have an IP address
retVal = modemConnect(savedHostIP, port, mux, ssl);
}
XBEE_COMMAND_END_DECORATOR
return retVal;
}
bool modemConnect(IPAddress ip, uint16_t port, uint8_t mux = 0,
bool ssl = false) {
bool success = true;
if (mux != 0) {
DBG("XBee only supports 1 IP channel in transparent mode!");
}
// empty the saved currelty-in-use destination address
savedOperatingIP = IPAddress(0, 0, 0, 0);
XBEE_COMMAND_START_DECORATOR(5, false)
if (ip != savedIP) { // Can skip almost everything if there's no
// change in the IP address
savedIP = ip; // Set the newly requested IP address
String host;
host.reserve(16);
host += ip[0];
host += ".";
host += ip[1];
host += ".";
host += ip[2];
host += ".";
host += ip[3];
if (ssl) {
sendAT(GF("IP"), 4); // Put in SSL over TCP communication mode
success &= (1 == waitResponse());
} else {
sendAT(GF("IP"), 1); // Put in TCP mode
success &= (1 == waitResponse());
}
sendAT(GF("DL"), host); // Set the "Destination Address Low"
success &= (1 == waitResponse());
sendAT(GF("DE"), String(port, HEX)); // Set the destination port
success &= (1 == waitResponse());
success &= writeChanges();
}
// we'll accept either unknown or connected
if (beeType != XBEE_S6B_WIFI) {
uint16_t ci = getConnectionIndicator();
success &= (ci == 0x00 || ci == 0xFF || ci == 0x28);
}
if (success) { sockets[mux]->sock_connected = true; }
XBEE_COMMAND_END_DECORATOR
return success;
}
bool modemStop(uint32_t maxWaitMs) {
streamClear(); // Empty anything in the buffer
// empty the saved currently-in-use destination address
savedOperatingIP = IPAddress(0, 0, 0, 0);
XBEE_COMMAND_START_DECORATOR(5, false)
// Get the current socket timeout
sendAT(GF("TM"));
String timeoutUsed = readResponseString(5000L);
// For WiFi models, there's no direct way to close the socket. This is a
// hack to shut the socket by setting the timeout to zero.
if (beeType == XBEE_S6B_WIFI) {
sendAT(GF("TM0")); // Set socket timeout to 0
waitResponse(maxWaitMs); // This response can be slow
writeChanges();
}
// For cellular models, per documentation: If you write the TM (socket
// timeout) value while in Transparent Mode, the current connection is
// immediately closed - this works even if the TM values is unchanged
sendAT(GF("TM"), timeoutUsed); // Re-set socket timeout
waitResponse(maxWaitMs); // This response can be slow
writeChanges();
XBEE_COMMAND_END_DECORATOR
return true;
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux = 0) {
if (mux != 0) {
DBG("XBee only supports 1 IP channel in transparent mode!");
}
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
if (beeType != XBEE_S6B_WIFI) {
// After a send, verify the outgoing ip if it isn't set
if (savedOperatingIP == IPAddress(0, 0, 0, 0)) {
modemGetConnected();
} else if (len > 5) {
// After sending several characters, also re-check
// NOTE: I'm intentionally not checking after every single character!
modemGetConnected();
}
}
return len;
}
size_t modemRead(size_t, uint8_t) {
return 0;
}
size_t modemGetAvailable(uint8_t) {
return 0;
}
// NOTE: The CI command returns the status of the TCP connection as open only
// after data has been sent on the socket. If it returns 0xFF the socket may
// really be open, but no data has yet been sent. We return this unknown
// value as true so there's a possibility it's wrong.
bool modemGetConnected() {
// If the IP address is 0, it's not valid so we can't be connected
if (savedIP == IPAddress(0, 0, 0, 0)) { return false; }
XBEE_COMMAND_START_DECORATOR(5, false)
if (beeType == XBEE_UNKNOWN)
getSeries(); // Need to know the bee type to interpret response
switch (beeType) {
// The wifi be can only say if it's connected to the netowrk
case XBEE_S6B_WIFI: {
RegStatus s = getRegistrationStatus();
XBEE_COMMAND_END_DECORATOR
if (s != REG_OK) {
sockets[0]->sock_connected = false; // no multiplex
}
return (s == REG_OK); // if it's connected, we hope the sockets are too
}
// Cellular XBee's
default: {
int16_t ci = getConnectionIndicator();
// Get the operating destination address
IPAddress od = getOperatingIP();
XBEE_COMMAND_END_DECORATOR
switch (ci) {
// 0x00 = The socket is definitely open
case 0x00: {
savedOperatingIP = od;
// but it's possible the socket is set to the wrong place
if (od != IPAddress(0, 0, 0, 0) && od != savedIP) {
sockets[0]->stop();
return false;
}
return true;
}
// 0x28 = "Unknown."
// 0xFF = No known status - always returned prior to sending data
case 0x28:
case 0xFF: {
// If we previously had an operating destination and we no longer
// do, the socket must have closed
if (od == IPAddress(0, 0, 0, 0) &&
savedOperatingIP != IPAddress(0, 0, 0, 0)) {
savedOperatingIP = od;
sockets[0]->sock_connected = false;
return false;
} else if (od != IPAddress(0, 0, 0, 0) && od != savedIP) {
// else if the operating destination exists, but is wrong
// we need to close and re-open
sockets[0]->stop();
return false;
} else if (od != IPAddress(0, 0, 0, 0) && od == savedIP) {
// else if the operating destination exists and matches, we're
// good to go
savedOperatingIP = od;
return true;
} else {
// If we never had an operating destination, then sock may be open
// but data never sent - this is the dreaded "we don't know"
savedOperatingIP = od;
return true;
}
// // Ask for information about any open sockets
// sendAT(GF("SI"));
// String open_socks = stream.readStringUntil('\r');
// open_socks.replace(GSM_NL, "");
// open_socks.trim();
// if (open_socks != "") {
// // In transparent mode, only socket 0 should be possible
// sendAT(GF("SI0"));
// // read socket it
// String sock_id = stream.readStringUntil('\r');
// // read socket state
// String sock_state = stream.readStringUntil('\r');
// // read socket protocol (TCP/UDP)
// String sock_protocol = stream.readStringUntil('\r');
// // read local port number
// String local_port = stream.readStringUntil('\r');
// // read remote port number
// String remote_port = stream.readStringUntil('\r');
// // read remote ip address
// String remoted_address =
// stream.readStringUntil('\r'); // read result
// streamSkipUntil('\r'); // final carriage return
// }
}
// 0x21 = User closed
// 0x27 = Connection lost
// If the connection is lost or timed out on our side,
// we force close so it can reopen
case 0x21:
case 0x27: {
sendAT(GF("TM")); // Get socket timeout
String timeoutUsed = readResponseString(5000L);
sendAT(GF("TM"), timeoutUsed); // Re-set socket timeout
waitResponse(5000L); // This response can be slow
}
// 0x02 = Invalid parameters (bad IP/host)
// 0x12 = DNS query lookup failure
// 0x25 = Unknown server - DNS lookup failed (0x22 for UDP socket!)
// fall through
case 0x02:
case 0x12:
case 0x25: {
savedIP = IPAddress(0, 0, 0, 0); // force a lookup next time!
}
// If it's anything else (inc 0x02, 0x12, and 0x25)...
// it's definitely NOT connected
// fall through
default: {
sockets[0]->sock_connected = false;
savedOperatingIP = od;
return false;
}
}
}
}
}
/*
* Utilities
*/
public:
void streamClear(void) {
while (stream.available()) {
stream.read();
TINY_GSM_YIELD();
}
}
// TODO(vshymanskyy): Optimize this!
// NOTE: This function is used while INSIDE command mode, so we're only
// waiting for requested responses. The XBee has no unsoliliced responses
// (URC's) when in command mode.
int8_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(16); // Should never be getting much here for the XBee
int8_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)) {
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
data.replace(GSM_NL GSM_NL, GSM_NL);
data.replace(GSM_NL, "\r\n ");
if (data.length()) {
DBG("### Unhandled:", data, "\r\n");
} else {
DBG("### NO RESPONSE FROM MODEM!\r\n");
}
} else {
data.trim();
data.replace(GSM_NL GSM_NL, GSM_NL);
data.replace(GSM_NL, "\r\n ");
}
// 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), GsmConstStr r3 = NULL,
GsmConstStr r4 = NULL, 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), GsmConstStr r3 = NULL,
GsmConstStr r4 = NULL, GsmConstStr r5 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5);
}
bool commandMode(uint8_t retries = 5) {
// If we're already in command mode, move on
if (inCommandMode && (millis() - lastCommandModeMillis) < 10000L)
return true;
uint8_t triesMade = 0;
uint8_t triesUntilReset = 4; // only reset after 4 failures
bool success = false;
streamClear(); // Empty everything in the buffer before starting
while (!success && triesMade < retries) {
// Cannot send anything for 1 "guard time" before entering command mode
// Default guard time is 1s, but the init fxn decreases it to 100 ms
delay(guardTime + 10);
streamWrite(GF("+++")); // enter command mode
int8_t res = waitResponse(guardTime * 2);
success = (1 == res);
if (0 == res) {
triesUntilReset--;
if (triesUntilReset == 0) {
triesUntilReset = 4;
pinReset(); // if it's unresponsive, reset
delay(250); // a short delay to allow it to come back up
// TODO(SRGDamia1) optimize this
}
}
triesMade++;
}
if (success) {
inCommandMode = true;
lastCommandModeMillis = millis();
}
return success;
}
bool writeChanges(void) {
sendAT(GF("WR")); // Write changes to flash
if (1 != waitResponse()) { return false; }
sendAT(GF("AC")); // Apply changes
if (1 != waitResponse()) { return false; }
return true;
}
void exitCommand(void) {
// NOTE: Here we explicitely try to exit command mode
// even if the internal flag inCommandMode was already false
sendAT(GF("CN")); // Exit command mode
waitResponse();
inCommandMode = false;
}
bool exitAndFail(void) {
exitCommand(); // Exit command mode
return false;
}
void getSeries(void) {
sendAT(GF("HS")); // Get the "Hardware Series";
int16_t intRes = readResponseInt();
beeType = (XBeeType)intRes;
DBG(GF("### Modem: "), getModemName());
}
String readResponseString(uint32_t timeout_ms = 1000) {
TINY_GSM_YIELD();
uint32_t startMillis = millis();
while (!stream.available() && millis() - startMillis < timeout_ms) {}
String res =
stream.readStringUntil('\r'); // lines end with carriage returns
res.trim();
return res;
}
int16_t readResponseInt(uint32_t timeout_ms = 1000) {
String res = readResponseString(
timeout_ms); // it just works better reading a string first
if (res == "") res = "FF";
char buf[5] = {
0,
};
res.toCharArray(buf, 5);
int16_t intRes = strtol(buf, 0, 16);
return intRes;
}
String sendATGetString(GsmConstStr cmd) {
XBEE_COMMAND_START_DECORATOR(5, "")
sendAT(cmd);
String res = readResponseString();
XBEE_COMMAND_END_DECORATOR
return res;
}
bool gotIPforSavedHost() {
if (savedHost != "" && savedHostIP != IPAddress(0, 0, 0, 0))
return true;
else
return false;
}
protected:
Stream& stream;
GsmClientXBee* sockets[TINY_GSM_MUX_COUNT];
const char* gsmNL = GSM_NL;
int16_t guardTime;
XBeeType beeType;
int8_t resetPin;
IPAddress savedIP;
String savedHost;
IPAddress savedHostIP;
IPAddress savedOperatingIP;
bool inCommandMode;
uint32_t lastCommandModeMillis;
};
#endif // SRC_TINYGSMCLIENTXBEE_H_