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.

1217 lines
33 KiB

/**
* @file TinyGsmCommon.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCOMMON_H_
#define SRC_TINYGSMCOMMON_H_
// The current library version number
#define TINYGSM_VERSION "0.9.19"
#if defined(SPARK) || defined(PARTICLE)
#include "Particle.h"
#elif defined(ARDUINO)
#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#endif
#if defined(ARDUINO_DASH)
#include <ArduinoCompat/Client.h>
#else
#include <Client.h>
#endif
#include "TinyGsmFifo.h"
#ifndef TINY_GSM_YIELD_MS
#define TINY_GSM_YIELD_MS 0
#endif
#ifndef TINY_GSM_YIELD
#define TINY_GSM_YIELD() \
{ delay(TINY_GSM_YIELD_MS); }
#endif
#if !defined(TINY_GSM_RX_BUFFER)
#define TINY_GSM_RX_BUFFER 64
#endif
#define TINY_GSM_ATTR_NOT_AVAILABLE \
__attribute__((error("Not available on this modem type")))
#define TINY_GSM_ATTR_NOT_IMPLEMENTED __attribute__((error("Not implemented")))
#if defined(__AVR__)
#define TINY_GSM_PROGMEM PROGMEM
typedef const __FlashStringHelper* GsmConstStr;
#define GFP(x) (reinterpret_cast<GsmConstStr>(x))
#define GF(x) F(x)
#else
#define TINY_GSM_PROGMEM
typedef const char* GsmConstStr;
#define GFP(x) x
#define GF(x) x
#endif
#ifdef TINY_GSM_DEBUG
namespace {
template <typename T>
static void DBG_PLAIN(T last) {
TINY_GSM_DEBUG.println(last);
}
template <typename T, typename... Args>
static void DBG_PLAIN(T head, Args... tail) {
TINY_GSM_DEBUG.print(head);
TINY_GSM_DEBUG.print(' ');
DBG_PLAIN(tail...);
}
template <typename... Args>
static void DBG(Args... args) {
TINY_GSM_DEBUG.print(GF("["));
TINY_GSM_DEBUG.print(millis());
TINY_GSM_DEBUG.print(GF("] "));
DBG_PLAIN(args...);
}
} // namespace
#else
#define DBG_PLAIN(...)
#define DBG(...)
#endif
template <class T>
const T& TinyGsmMin(const T& a, const T& b) {
return (b < a) ? b : a;
}
template <class T>
const T& TinyGsmMax(const T& a, const T& b) {
return (b < a) ? a : b;
}
template <class T>
uint32_t TinyGsmAutoBaud(T& SerialAT, uint32_t minimum = 9600,
uint32_t maximum = 115200) {
static uint32_t rates[] = {115200, 57600, 38400, 19200, 9600, 74400, 74880,
230400, 460800, 2400, 4800, 14400, 28800};
for (unsigned i = 0; i < sizeof(rates) / sizeof(rates[0]); i++) {
uint32_t rate = rates[i];
if (rate < minimum || rate > maximum) continue;
DBG("Trying baud rate", rate, "...");
SerialAT.begin(rate);
delay(10);
for (int j = 0; j < 10; j++) {
SerialAT.print("AT\r\n");
String input = SerialAT.readString();
if (input.indexOf("OK") >= 0) {
DBG("Modem responded at rate", rate);
return rate;
}
}
}
return 0;
}
enum modemInternalBuffferType {
NO_MODEM_BUFFER =
0, // For modules that do not store incoming data in any sort of buffer
READ_NO_CHECK = 1, // Data is stored in a buffer, but we can only read from
// the buffer, not check how much data is stored in it
READ_AND_CHECK_SIZE = 2, // Data is stored in a buffer and we can both read
// and check the size of the buffer
};
enum SimStatus {
SIM_ERROR = 0,
SIM_READY = 1,
SIM_LOCKED = 2,
SIM_ANTITHEFT_LOCKED = 3,
};
enum TinyGSMDateTimeFormat { DATE_FULL = 0, DATE_TIME = 1, DATE_DATE = 2 };
template <class modemType, modemInternalBuffferType bufType, uint8_t muxCount>
class TinyGsmModem {
public:
/*
* Basic functions
*/
bool begin(const char* pin = NULL) {
return thisModem().initImpl(pin);
}
bool init(const char* pin = NULL) {
return thisModem().initImpl(pin);
}
template <typename... Args>
void sendAT(Args... cmd) {
thisModem().streamWrite("AT", cmd..., thisModem().gsmNL);
thisModem().stream.flush();
TINY_GSM_YIELD(); /* DBG("### AT:", cmd...); */
}
void setBaud(uint32_t baud) {
thisModem().setBaudImpl(baud);
}
// Test response to AT commands
bool testAT(uint32_t timeout_ms = 10000L) {
return thisModem().testATImpl(timeout_ms);
}
// Asks for modem information via the V.25TER standard ATI command
// NOTE: The actual value and style of the response is quite varied
String getModemInfo() {
return thisModem().getModemInfoImpl();
}
// Gets the modem name (as it calls itself)
String getModemName() {
return thisModem().getModemNameImpl();
}
void maintain() {
return thisModem().maintainImpl();
}
bool factoryDefault() {
return thisModem().factoryDefaultImpl();
}
bool hasSSL() {
return thisModem().thisHasSSL();
}
bool hasWifi() {
return thisModem().thisHasWifi();
}
bool hasGPRS() {
return thisModem().thisHasGPRS();
}
/*
* Power functions
*/
bool restart() {
return thisModem().restartImpl();
}
bool poweroff() {
return thisModem().powerOffImpl();
}
bool radioOff() {
return thisModem().radioOffImpl();
}
bool sleepEnable(bool enable = true) {
return thisModem().sleepEnableImpl(enable);
}
/*
* SIM card functions
*/
// Unlocks the SIM
bool simUnlock(const char* pin) {
return thisModem().simUnlockImpl(pin);
}
// Gets the CCID of a sim card via AT+CCID
String getSimCCID() {
return thisModem().getSimCCIDImpl();
}
// Asks for TA Serial Number Identification (IMEI)
String getIMEI() {
return thisModem().getIMEIImpl();
}
SimStatus getSimStatus(uint32_t timeout_ms = 10000L) {
return thisModem().getSimStatusImpl(timeout_ms);
}
/*
* Generic network functions
*/
// RegStatus getRegistrationStatus() {}
bool isNetworkConnected() {
return thisModem().isNetworkConnectedImpl();
}
// Waits for network attachment
bool waitForNetwork(uint32_t timeout_ms = 60000L) {
return thisModem().waitForNetworkImpl(timeout_ms);
}
// Gets signal quality report
int16_t getSignalQuality() {
return thisModem().getSignalQualityImpl();
}
/*
* GPRS functions
*/
bool gprsConnect(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
return thisModem().gprsConnectImpl(apn, user, pwd);
}
bool gprsDisconnect() {
return thisModem().gprsDisconnectImpl();
}
// Checks if current attached to GPRS/EPS service
bool isGprsConnected() {
return thisModem().isGprsConnectedImpl();
}
// Gets the current network operator
String getOperator() {
return thisModem().getOperatorImpl();
}
/*
* WiFi functions
*/
bool networkConnect(const char* ssid, const char* pwd) {
return thisModem().networkConnectImpl(ssid, pwd);
}
bool networkDisconnect() {
return thisModem().networkDisconnectImpl();
}
/*
* GPRS functions
*/
String getLocalIP() {
return thisModem().getLocalIPImpl();
}
IPAddress localIP() {
return thisModem().TinyGsmIpFromString(thisModem().getLocalIP());
}
/*
* Phone Call functions
*/
bool callAnswer() {
return thisModem().callAnswerImpl();
}
bool callNumber(const String& number) {
return thisModem().callNumberImpl(number);
}
bool callHangup() {
return thisModem().callHangupImpl();
}
bool dtmfSend(char cmd, int duration_ms = 100) {
return thisModem().dtmfSendImpl(cmd, duration_ms);
}
/*
* Messaging functions
*/
String sendUSSD(const String& code) {
return thisModem().sendUSSDImpl(code);
}
bool sendSMS(const String& number, const String& text) {
return thisModem().sendSMSImpl(number, text);
}
bool sendSMS_UTF16(const char* const number, const void* text, size_t len) {
return thisModem().sendSMS_UTF16Impl(number, text, len);
}
/*
* Location functions
*/
String getGsmLocation() {
return thisModem().getGsmLocationImpl();
}
/*
* GPS location functions
*/
// No template interface or implementation of these functions
/*
* Time functions
*/
String getGSMDateTime(TinyGSMDateTimeFormat format) {
return thisModem().getGSMDateTimeImpl(format);
}
/*
* Battery & temperature functions
*/
uint16_t getBattVoltage() {
return thisModem().getBattVoltageImpl();
}
int8_t getBattPercent() {
return thisModem().getBattPercentImpl();
}
uint8_t getBattChargeState() {
return thisModem().getBattChargeStateImpl();
}
bool
getBattStats(uint8_t& chargeState, int8_t& percent, uint16_t& milliVolts) {
return thisModem().getBattStatsImpl(chargeState, percent, milliVolts);
}
float getTemperature() {
return thisModem().getTemperatureImpl();
}
/*
* CRTP Helper
*/
protected:
const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
modemType& thisModem() {
return static_cast<modemType&>(*this);
}
/*
* Inner Client
*/
public:
class GsmClient : public Client {
// Make all classes created from the modem template friends
friend class TinyGsmModem<modemType, bufType, muxCount>;
typedef TinyGsmFifo<uint8_t, TINY_GSM_RX_BUFFER> RxFifo;
public:
// bool init(modemType* modem, uint8_t);
// int connect(const char* host, uint16_t port, int timeout_s);
// Connect to a IP address given as an IPAddress object by
// converting said IP address to text
// int connect(IPAddress ip, uint16_t port, int timeout_s) {
// return connect(TinyGsmStringFromIp(ip).c_str(), port,
// timeout_s);
// }
// int connect(const char* host, uint16_t port) override {
// return connect(host, port, 75);
// }
// int connect(IPAddress ip, uint16_t port) override {
// return connect(ip, port, 75);
// }
static String TinyGsmStringFromIp(IPAddress ip) {
String host;
host.reserve(16);
host += ip[0];
host += ".";
host += ip[1];
host += ".";
host += ip[2];
host += ".";
host += ip[3];
return host;
}
// void stop(uint32_t maxWaitMs);
// void stop() override {
// stop(15000L);
// }
// Writes data out on the client using the modem send functionality
size_t write(const uint8_t* buf, size_t size) override {
TINY_GSM_YIELD();
at->maintain();
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();
switch (bufType) {
// Returns the number of characters available in the TinyGSM fifo
case NO_MODEM_BUFFER:
if (!rx.size() && sock_connected) { at->maintain(); }
return rx.size();
// Returns the combined number of characters available in the TinyGSM
// fifo and the modem chips internal fifo.
case READ_NO_CHECK:
if (!rx.size()) { at->maintain(); }
return rx.size() + sock_available;
// Returns the combined number of characters available in the TinyGSM
// fifo and the modem chips internal fifo, doing an extra check-in
// with the modem to see if anything has arrived without a UURC.
case READ_AND_CHECK_SIZE:
if (!rx.size()) {
if (millis() - prev_check > 500) {
got_data = true;
prev_check = millis();
}
at->maintain();
}
return rx.size() + sock_available;
}
}
int read(uint8_t* buf, size_t size) override {
TINY_GSM_YIELD();
size_t cnt = 0;
uint32_t _startMillis = millis();
switch (bufType) {
// Reads characters out of the TinyGSM fifo, waiting for any URC's
// from the modem for new data if there's nothing in the fifo.
case NO_MODEM_BUFFER:
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: Read directly into user buffer? */
if (!rx.size() && sock_connected) { at->maintain(); }
}
return cnt;
// Reads characters out of the TinyGSM fifo, and from the modem chip's
// internal fifo if avaiable.
case READ_NO_CHECK:
at->maintain();
while (cnt < size) {
size_t chunk = TinyGsmMin(size - cnt, rx.size());
if (chunk > 0) {
rx.get(buf, chunk);
buf += chunk;
cnt += chunk;
continue;
} /* TODO: Read directly into user buffer? */
at->maintain();
if (sock_available > 0) {
int n = at->modemRead(
TinyGsmMin((uint16_t)rx.free(), sock_available), mux);
if (n == 0) break;
} else {
break;
}
}
return cnt;
// Reads characters out of the TinyGSM fifo, and from the modem chips
// internal fifo if avaiable, also double checking with the modem if
// data has arrived without issuing a UURC.
case READ_AND_CHECK_SIZE:
at->maintain();
while (cnt < size) {
size_t chunk = TinyGsmMin(size - cnt, rx.size());
if (chunk > 0) {
rx.get(buf, chunk);
buf += chunk;
cnt += chunk;
continue;
}
// Workaround: Some modules "forget" to notify about data arrival
if (millis() - prev_check > 500) {
got_data = true;
prev_check = millis();
}
// TODO(vshymanskyy): Read directly into user buffer?
at->maintain();
if (sock_available > 0) {
int n = at->modemRead(
TinyGsmMin((uint16_t)rx.free(), sock_available), mux);
if (n == 0) break;
} else {
break;
}
}
return cnt;
}
}
int read() override {
uint8_t c;
if (read(&c, 1) == 1) { return c; }
return -1;
}
// TODO(SRGDamia1): Implement peek
int peek() override {
return -1;
}
void flush() override {
at->stream.flush();
}
uint8_t connected() override {
if (available()) { return true; }
return sock_connected;
}
operator bool() override {
return connected();
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
protected:
// Read and dump anything remaining in the modem's internal buffer.
// Using this in the client stop() function.
// The socket will appear open in response to connected() even after it
// closes until all data is read from the buffer.
// Doing it this way allows the external mcu to find and get all of the
// data that it wants from the socket even if it was closed externally.
void dumpModemBuffer(uint32_t maxWaitMs) {
TINY_GSM_YIELD();
rx.clear();
at->maintain();
uint32_t startMillis = millis();
while (sock_available > 0 && (millis() - startMillis < maxWaitMs)) {
at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux);
rx.clear();
at->maintain();
}
}
modemType* at;
uint8_t mux;
uint16_t sock_available;
uint32_t prev_check;
bool sock_connected;
bool got_data;
RxFifo rx;
};
/*
* Inner Secure Client
*/
/*
* Constructor
*/
protected:
/*
* Basic functions
*/
void setBaudImpl(uint32_t baud) {
thisModem().sendAT(GF("+IPR="), baud);
thisModem().waitResponse();
}
bool testATImpl(uint32_t timeout_ms = 10000L) {
for (uint32_t start = millis(); millis() - start < timeout_ms;) {
thisModem().sendAT(GF(""));
if (thisModem().waitResponse(200) == 1) { return true; }
delay(100);
}
return false;
}
String getModemInfoImpl() {
thisModem().sendAT(GF("I"));
String res;
if (thisModem().waitResponse(1000L, res) != 1) { return ""; }
// Do the replaces twice so we cover both \r and \r\n type endings
res.replace("\r\nOK\r\n", "");
res.replace("\rOK\r", "");
res.replace("\r\n", " ");
res.replace("\r", " ");
res.trim();
return res;
}
String getModemNameImpl() {
thisModem().sendAT(GF("+CGMI"));
String res1;
if (thisModem().waitResponse(1000L, res1) != 1) { return "unknown"; }
res1.replace("\r\nOK\r\n", "");
res1.replace("\rOK\r", "");
res1.trim();
thisModem().sendAT(GF("+GMM"));
String res2;
if (thisModem().waitResponse(1000L, res2) != 1) { return "unknown"; }
res1.replace("\r\nOK\r\n", "");
res1.replace("\rOK\r", "");
res2.trim();
String name = res1 + String(' ') + res2;
DBG("### Modem:", name);
return name;
}
void maintainImpl() {
switch (bufType) {
case READ_AND_CHECK_SIZE:
// Keep listening for modem URC's and proactively iterate through
// sockets asking if any data is avaiable
for (int mux = 0; mux < muxCount; mux++) {
GsmClient* sock = thisModem().sockets[mux];
if (sock && sock->got_data) {
sock->got_data = false;
sock->sock_available = thisModem().modemGetAvailable(mux);
}
}
while (thisModem().stream.available()) {
thisModem().waitResponse(15, NULL, NULL);
}
break;
default:
// Just listen for any URC's
thisModem().waitResponse(100, NULL, NULL);
break;
}
}
bool factoryDefaultImpl() {
thisModem().sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write
thisModem().waitResponse();
thisModem().sendAT(GF("+IPR=0")); // Auto-baud
thisModem().waitResponse();
thisModem().sendAT(GF("&W")); // Write configuration
return thisModem().waitResponse() == 1;
}
/*
* Power functions
*/
protected:
bool radioOffImpl() {
thisModem().sendAT(GF("+CFUN=0"));
if (thisModem().waitResponse(10000L) != 1) { return false; }
delay(3000);
return true;
}
bool sleepEnableImpl(bool enable = true) TINY_GSM_ATTR_NOT_IMPLEMENTED;
/*
* SIM card functions
*/
protected:
// Unlocks a sim via the 3GPP TS command AT+CPIN
bool simUnlockImpl(const char* pin) {
if (pin && strlen(pin) > 0) {
thisModem().sendAT(GF("+CPIN=\""), pin, GF("\""));
return thisModem().waitResponse() == 1;
}
return true;
}
// Gets the CCID of a sim card via AT+CCID
String getSimCCIDImpl() {
thisModem().sendAT(GF("+CCID"));
if (thisModem().waitResponse(GF("+CCID:")) != 1) { return ""; }
String res = thisModem().stream.readStringUntil('\n');
thisModem().waitResponse();
res.trim();
return res;
}
// Asks for TA Serial Number Identification (IMEI) via the V.25TER standard
// AT+GSN command
String getIMEIImpl() {
thisModem().sendAT(GF("+GSN"));
String res = thisModem().stream.readStringUntil('\n');
thisModem().waitResponse();
res.trim();
return res;
}
SimStatus getSimStatusImpl(uint32_t timeout_ms = 10000L) {
for (uint32_t start = millis(); millis() - start < timeout_ms;) {
thisModem().sendAT(GF("+CPIN?"));
if (thisModem().waitResponse(GF("+CPIN:")) != 1) {
delay(1000);
continue;
}
int status =
thisModem().waitResponse(GF("READY"), GF("SIM PIN"), GF("SIM PUK"),
GF("NOT INSERTED"), GF("NOT READY"));
thisModem().waitResponse();
switch (status) {
case 2:
case 3: return SIM_LOCKED;
case 1: return SIM_READY;
default: return SIM_ERROR;
}
}
return SIM_ERROR;
}
/*
* Generic network functions
*/
protected:
// Gets the modem's registration status via CREG/CGREG/CEREG
// CREG = Generic network registration
// CGREG = GPRS service registration
// CEREG = EPS registration for LTE modules
int getRegistrationStatusXREG(const char* regCommand) {
thisModem().sendAT('+', regCommand, '?');
// check for any of the three for simplicity
int resp =
thisModem().waitResponse(GF("+CREG:"), GF("+CGREG:"), GF("+CEREG:"));
if (resp != 1 && resp != 2 && resp != 3) { return -1; }
thisModem().streamSkipUntil(','); /* Skip format (0) */
int status = thisModem().stream.readStringUntil('\n').toInt();
thisModem().waitResponse();
return status;
}
bool waitForNetworkImpl(uint32_t timeout_ms = 60000L) {
for (uint32_t start = millis(); millis() - start < timeout_ms;) {
if (thisModem().isNetworkConnected()) { return true; }
delay(250);
}
return false;
}
// Gets signal quality report according to 3GPP TS command AT+CSQ
int16_t getSignalQualityImpl() {
thisModem().sendAT(GF("+CSQ"));
if (thisModem().waitResponse(GF("+CSQ:")) != 1) { return 99; }
int res = thisModem().stream.readStringUntil(',').toInt();
thisModem().waitResponse();
return res;
}
/*
* GPRS functions
*/
protected:
// Checks if current attached to GPRS/EPS service
bool isGprsConnectedImpl() {
thisModem().sendAT(GF("+CGATT?"));
if (thisModem().waitResponse(GF("+CGATT:")) != 1) { return false; }
int res = thisModem().stream.readStringUntil('\n').toInt();
thisModem().waitResponse();
if (res != 1) { return false; }
return thisModem().localIP() != IPAddress(0, 0, 0, 0);
}
// Gets the current network operator via the 3GPP TS command AT+COPS
String getOperatorImpl() {
thisModem().sendAT(GF("+COPS?"));
if (thisModem().waitResponse(GF("+COPS:")) != 1) { return ""; }
thisModem().streamSkipUntil('"'); /* Skip mode and format */
String res = thisModem().stream.readStringUntil('"');
thisModem().waitResponse();
return res;
}
/*
* WiFi functions
*/
bool networkConnectImpl(const char* ssid, const char* pwd) {
return false;
}
bool networkDisconnectImpl() {
return thisModem().gprsConnectImpl();
}
/*
* IP Address functions
*/
protected:
String getLocalIPImpl() {
thisModem().sendAT(GF("+CGPADDR=1"));
if (thisModem().waitResponse(GF("+CGPADDR:")) != 1) { return ""; }
thisModem().streamSkipUntil(','); // Skip context id
String res = thisModem().stream.readStringUntil('\r');
if (thisModem().waitResponse() != 1) { return ""; }
return res;
}
static IPAddress TinyGsmIpFromString(const String& strIP) {
int Parts[4] = {
0,
};
int Part = 0;
for (uint8_t i = 0; i < strIP.length(); i++) {
char c = strIP[i];
if (c == '.') {
Part++;
if (Part > 3) { return IPAddress(0, 0, 0, 0); }
continue;
} else if (c >= '0' && c <= '9') {
Parts[Part] *= 10;
Parts[Part] += c - '0';
} else {
if (Part == 3) break;
}
}
return IPAddress(Parts[0], Parts[1], Parts[2], Parts[3]);
}
/*
* Phone Call functions
*/
protected:
bool callAnswerImpl() {
thisModem().sendAT(GF("A"));
return thisModem().waitResponse() == 1;
}
// Returns true on pick-up, false on error/busy
bool callNumberImpl(const String& number) {
if (number == GF("last")) {
thisModem().sendAT(GF("DL"));
} else {
thisModem().sendAT(GF("D"), number, ";");
}
int status = thisModem().waitResponse(60000L, GF("OK"), GF("BUSY"),
GF("NO ANSWER"), GF("NO CARRIER"));
switch (status) {
case 1: return true;
case 2:
case 3: return false;
default: return false;
}
}
bool callHangupImpl() {
thisModem().sendAT(GF("H"));
return thisModem().waitResponse() == 1;
}
// 0-9,*,#,A,B,C,D
bool dtmfSendImpl(char cmd, int duration_ms = 100) {
duration_ms = constrain(duration_ms, 100, 1000);
thisModem().sendAT(GF("+VTD="),
duration_ms / 100); // VTD accepts in 1/10 of a second
thisModem().waitResponse();
thisModem().sendAT(GF("+VTS="), cmd);
return thisModem().waitResponse(10000L) == 1;
}
/*
* Messaging functions
*/
protected:
static inline String TinyGsmDecodeHex7bit(String& instr) {
String result;
byte reminder = 0;
int bitstate = 7;
for (unsigned i = 0; i < instr.length(); i += 2) {
char buf[4] = {
0,
};
buf[0] = instr[i];
buf[1] = instr[i + 1];
byte b = strtol(buf, NULL, 16);
byte bb = b << (7 - bitstate);
char c = (bb + reminder) & 0x7F;
result += c;
reminder = b >> bitstate;
bitstate--;
if (bitstate == 0) {
char cc = reminder;
result += cc;
reminder = 0;
bitstate = 7;
}
}
return result;
}
static inline String TinyGsmDecodeHex8bit(String& instr) {
String result;
for (unsigned i = 0; i < instr.length(); i += 2) {
char buf[4] = {
0,
};
buf[0] = instr[i];
buf[1] = instr[i + 1];
char b = strtol(buf, NULL, 16);
result += b;
}
return result;
}
static inline String TinyGsmDecodeHex16bit(String& instr) {
String result;
for (unsigned i = 0; i < instr.length(); i += 4) {
char buf[4] = {
0,
};
buf[0] = instr[i];
buf[1] = instr[i + 1];
char b = strtol(buf, NULL, 16);
if (b) { // If high byte is non-zero, we can't handle it ;(
#if defined(TINY_GSM_UNICODE_TO_HEX)
result += "\\x";
result += instr.substring(i, i + 4);
#else
result += "?";
#endif
} else {
buf[0] = instr[i + 2];
buf[1] = instr[i + 3];
b = strtol(buf, NULL, 16);
result += b;
}
}
return result;
}
String sendUSSDImpl(const String& code) {
// Set preferred message format to text mode
thisModem().sendAT(GF("+CMGF=1"));
thisModem().waitResponse();
// Set 8-bit hexadecimal alphabet (3GPP TS 23.038)
thisModem().sendAT(GF("+CSCS=\"HEX\""));
thisModem().waitResponse();
// Send the message
thisModem().sendAT(GF("+CUSD=1,\""), code, GF("\""));
if (thisModem().waitResponse() != 1) { return ""; }
if (thisModem().waitResponse(10000L, GF("+CUSD:")) != 1) { return ""; }
thisModem().stream.readStringUntil('"');
String hex = thisModem().stream.readStringUntil('"');
thisModem().stream.readStringUntil(',');
int dcs = thisModem().stream.readStringUntil('\n').toInt();
if (dcs == 15) {
return TinyGsmDecodeHex8bit(hex);
} else if (dcs == 72) {
return TinyGsmDecodeHex16bit(hex);
} else {
return hex;
}
}
bool sendSMSImpl(const String& number, const String& text) {
// Set preferred message format to text mode
thisModem().sendAT(GF("+CMGF=1"));
thisModem().waitResponse();
// Set GSM 7 bit default alphabet (3GPP TS 23.038)
thisModem().sendAT(GF("+CSCS=\"GSM\""));
thisModem().waitResponse();
thisModem().sendAT(GF("+CMGS=\""), number, GF("\""));
if (thisModem().waitResponse(GF(">")) != 1) { return false; }
thisModem().stream.print(text); // Actually send the message
thisModem().stream.write(static_cast<char>(0x1A)); // Terminate the message
thisModem().stream.flush();
return thisModem().waitResponse(60000L) == 1;
}
// Common methods for UTF8/UTF16 SMS.
// Supported by: BG96, M95, MC60, SIM5360, SIM7000, SIM7600, SIM800
class UTF8Print : public Print {
public:
explicit UTF8Print(Print& p) : p(p) {}
size_t write(const uint8_t c) override {
if (prv < 0xC0) {
if (c < 0xC0) printHex(c);
prv = c;
} else {
uint16_t v = uint16_t(prv) << 8 | c;
v -= (v >> 8 == 0xD0) ? 0xCC80 : 0xCD40;
printHex(v);
prv = 0;
}
return 1;
}
private:
Print& p;
uint8_t prv = 0;
void printHex(const uint16_t v) {
uint8_t c = v >> 8;
if (c < 0x10) p.print('0');
p.print(c, HEX);
c = v & 0xFF;
if (c < 0x10) p.print('0');
p.print(c, HEX);
}
};
bool sendSMS_UTF8_begin(const char* const number) {
thisModem().sendAT(GF("+CMGF=1"));
thisModem().waitResponse();
thisModem().sendAT(GF("+CSCS=\"HEX\""));
thisModem().waitResponse();
thisModem().sendAT(GF("+CSMP=17,167,0,8"));
thisModem().waitResponse();
thisModem().sendAT(GF("+CMGS=\""), number, GF("\""));
return thisModem().waitResponse(GF(">")) == 1;
}
bool sendSMS_UTF8_end() {
thisModem().stream.write(static_cast<char>(0x1A));
thisModem().stream.flush();
return thisModem().waitResponse(60000L) == 1;
}
UTF8Print sendSMS_UTF8_stream() {
return UTF8Print(thisModem().stream);
}
bool
sendSMS_UTF16Impl(const char* const number, const void* text, size_t len) {
if (!sendSMS_UTF8_begin(number)) { return false; }
uint16_t* t = reinterpret_cast<uint16_t*>(text);
for (size_t i = 0; i < len; i++) {
uint8_t c = t[i] >> 8;
if (c < 0x10) { thisModem().stream.print('0'); }
thisModem().stream.print(c, HEX);
c = t[i] & 0xFF;
if (c < 0x10) { thisModem().stream.print('0'); }
thisModem().stream.print(c, HEX);
}
return sendSMS_UTF8_end();
}
/*
* Location functions
*/
protected:
String getGsmLocationImpl() {
thisModem().sendAT(GF("+CIPGSMLOC=1,1"));
if (thisModem().waitResponse(10000L, GF("+CIPGSMLOC:")) != 1) { return ""; }
String res = thisModem().stream.readStringUntil('\n');
thisModem().waitResponse();
res.trim();
return res;
}
/*
* GPS location functions
*/
public:
/*
* Time functions
*/
protected:
String getGSMDateTimeImpl(TinyGSMDateTimeFormat format) {
thisModem().sendAT(GF("+CCLK?"));
if (thisModem().waitResponse(2000L, GF("+CCLK: \"")) != 1) { return ""; }
String res;
switch (format) {
case DATE_FULL: res = thisModem().stream.readStringUntil('"'); break;
case DATE_TIME:
thisModem().streamSkipUntil(',');
res = thisModem().stream.readStringUntil('"');
break;
case DATE_DATE: res = thisModem().stream.readStringUntil(','); break;
}
return res;
}
/*
* Battery & temperature functions
*/
protected:
// Use: float vBatt = modem.getBattVoltage() / 1000.0;
uint16_t getBattVoltageImpl() {
thisModem().sendAT(GF("+CBC"));
if (thisModem().waitResponse(GF("+CBC:")) != 1) { return 0; }
thisModem().streamSkipUntil(','); // Skip battery charge status
thisModem().streamSkipUntil(','); // Skip battery charge level
// return voltage in mV
uint16_t res = thisModem().stream.readStringUntil(',').toInt();
// Wait for final OK
thisModem().waitResponse();
return res;
}
int8_t getBattPercentImpl() {
thisModem().sendAT(GF("+CBC"));
if (thisModem().waitResponse(GF("+CBC:")) != 1) { return false; }
thisModem().streamSkipUntil(','); // Skip battery charge status
// Read battery charge level
int res = thisModem().stream.readStringUntil(',').toInt();
// Wait for final OK
thisModem().waitResponse();
return res;
}
uint8_t getBattChargeStateImpl() {
thisModem().sendAT(GF("+CBC?"));
if (thisModem().waitResponse(GF("+CBC:")) != 1) { return false; }
// Read battery charge status
int res = thisModem().stream.readStringUntil(',').toInt();
// Wait for final OK
thisModem().waitResponse();
return res;
}
bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent,
uint16_t& milliVolts) {
thisModem().sendAT(GF("+CBC?"));
if (thisModem().waitResponse(GF("+CBC:")) != 1) { return false; }
chargeState = thisModem().stream.readStringUntil(',').toInt();
percent = thisModem().stream.readStringUntil(',').toInt();
milliVolts = thisModem().stream.readStringUntil('\n').toInt();
// Wait for final OK
thisModem().waitResponse();
return true;
}
float getTemperatureImpl() TINY_GSM_ATTR_NOT_AVAILABLE;
/*
* Client related functions
*/
protected:
/*
Utilities
*/
// Utility templates for writing/skipping characters on a stream
template <typename T>
void streamWrite(T last) {
thisModem().stream.print(last);
}
template <typename T, typename... Args>
void streamWrite(T head, Args... tail) {
thisModem().stream.print(head);
thisModem().streamWrite(tail...);
}
// template <typename... Args> void sendAT(Args... cmd) {
// thisModem().streamWrite("AT", cmd..., thisModem().gsmNL);
// thisModem().stream.flush();
// TINY_GSM_YIELD(); /* DBG("### AT:", cmd...); */
// }
bool streamSkipUntil(const char c, const uint32_t timeout_ms = 1000L) {
uint32_t startMillis = millis();
while (millis() - startMillis < timeout_ms) {
while (millis() - startMillis < timeout_ms &&
!thisModem().stream.available()) {
TINY_GSM_YIELD();
}
if (thisModem().stream.read() == c) { return true; }
}
return false;
}
// Yields up to a time-out period and then reads a character from the stream
// into the mux FIFO
// TODO(SRGDamia1): Do we need to wait two _timeout periods for no
// character return? Will wait once in the first "while
// !stream.available()" and then will wait again in the stream.read()
// function.
void moveCharFromStreamToFifo(uint8_t mux) {
uint32_t startMillis = millis();
while (!thisModem().stream.available() &&
(millis() - startMillis < thisModem().sockets[mux]->_timeout)) {
TINY_GSM_YIELD();
}
char c = thisModem().stream.read();
thisModem().sockets[mux]->rx.put(c);
}
};
#endif // SRC_TINYGSMCOMMON_H_