/**
|
|
* @file TinyGsmModem.tpp
|
|
* @author Volodymyr Shymanskyy
|
|
* @license LGPL-3.0
|
|
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
|
|
* @date Nov 2016
|
|
*/
|
|
|
|
#ifndef SRC_TINYGSMMODEM_H_
|
|
#define SRC_TINYGSMMODEM_H_
|
|
|
|
#include "TinyGsmCommon.h"
|
|
|
|
template <class modemType>
|
|
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>
|
|
inline 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();
|
|
}
|
|
bool factoryDefault() {
|
|
return thisModem().factoryDefaultImpl();
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
bool setPhoneFunctionality(uint8_t fun, bool reset = false) {
|
|
return thisModem().setPhoneFunctionalityImpl(fun, reset);
|
|
}
|
|
|
|
/*
|
|
* 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();
|
|
}
|
|
String getLocalIP() {
|
|
return thisModem().getLocalIPImpl();
|
|
}
|
|
IPAddress localIP() {
|
|
return thisModem().TinyGsmIpFromString(thisModem().getLocalIP());
|
|
}
|
|
|
|
/*
|
|
* CRTP Helper
|
|
*/
|
|
protected:
|
|
inline const modemType& thisModem() const {
|
|
return static_cast<const modemType&>(*this);
|
|
}
|
|
inline modemType& thisModem() {
|
|
return static_cast<modemType&>(*this);
|
|
}
|
|
|
|
/*
|
|
* Basic functions
|
|
*/
|
|
protected:
|
|
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"; }
|
|
res2.replace("\r\nOK\r\n", "");
|
|
res2.replace("\rOK\r", "");
|
|
res2.trim();
|
|
|
|
String name = res1 + String(' ') + res2;
|
|
DBG("### Modem:", name);
|
|
return name;
|
|
}
|
|
|
|
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() {
|
|
if (!thisModem().setPhoneFunctionality(0)) { return false; }
|
|
delay(3000);
|
|
return true;
|
|
}
|
|
|
|
bool sleepEnableImpl(bool enable = true) TINY_GSM_ATTR_NOT_IMPLEMENTED;
|
|
|
|
bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false)
|
|
TINY_GSM_ATTR_NOT_IMPLEMENTED;
|
|
|
|
/*
|
|
* 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
|
|
int8_t getRegistrationStatusXREG(const char* regCommand) {
|
|
thisModem().sendAT('+', regCommand, '?');
|
|
// check for any of the three for simplicity
|
|
int8_t 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().streamGetIntBefore('\n');
|
|
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
|
|
int8_t getSignalQualityImpl() {
|
|
thisModem().sendAT(GF("+CSQ"));
|
|
if (thisModem().waitResponse(GF("+CSQ:")) != 1) { return 99; }
|
|
int8_t res = thisModem().streamGetIntBefore(',');
|
|
thisModem().waitResponse();
|
|
return res;
|
|
}
|
|
|
|
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 inline 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]);
|
|
}
|
|
|
|
/*
|
|
Utilities
|
|
*/
|
|
public:
|
|
// Utility templates for writing/skipping characters on a stream
|
|
template <typename T>
|
|
inline void streamWrite(T last) {
|
|
thisModem().stream.print(last);
|
|
}
|
|
|
|
template <typename T, typename... Args>
|
|
inline void streamWrite(T head, Args... tail) {
|
|
thisModem().stream.print(head);
|
|
thisModem().streamWrite(tail...);
|
|
}
|
|
|
|
inline void streamClear() {
|
|
while (thisModem().stream.available()) {
|
|
thisModem().waitResponse(50, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
protected:
|
|
inline bool streamGetLength(char* buf, int8_t numChars,
|
|
const uint32_t timeout_ms = 1000L) {
|
|
if (!buf) { return false; }
|
|
|
|
int8_t numCharsReady = -1;
|
|
uint32_t startMillis = millis();
|
|
while (millis() - startMillis < timeout_ms &&
|
|
(numCharsReady = thisModem().stream.available()) < numChars) {
|
|
TINY_GSM_YIELD();
|
|
}
|
|
|
|
if (numCharsReady >= numChars) {
|
|
thisModem().stream.readBytes(buf, numChars);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
inline int16_t streamGetIntLength(int8_t numChars,
|
|
const uint32_t timeout_ms = 1000L) {
|
|
char buf[numChars + 1];
|
|
if (streamGetLength(buf, numChars, timeout_ms)) {
|
|
buf[numChars] = '\0';
|
|
return atoi(buf);
|
|
}
|
|
|
|
return -9999;
|
|
}
|
|
|
|
inline int16_t streamGetIntBefore(char lastChar) {
|
|
char buf[7];
|
|
size_t bytesRead = thisModem().stream.readBytesUntil(
|
|
lastChar, buf, static_cast<size_t>(7));
|
|
// if we read 7 or more bytes, it's an overflow
|
|
if (bytesRead && bytesRead < 7) {
|
|
buf[bytesRead] = '\0';
|
|
int16_t res = atoi(buf);
|
|
return res;
|
|
}
|
|
|
|
return -9999;
|
|
}
|
|
|
|
inline float streamGetFloatLength(int8_t numChars,
|
|
const uint32_t timeout_ms = 1000L) {
|
|
char buf[numChars + 1];
|
|
if (streamGetLength(buf, numChars, timeout_ms)) {
|
|
buf[numChars] = '\0';
|
|
return atof(buf);
|
|
}
|
|
|
|
return -9999.0F;
|
|
}
|
|
|
|
inline float streamGetFloatBefore(char lastChar) {
|
|
char buf[16];
|
|
size_t bytesRead = thisModem().stream.readBytesUntil(
|
|
lastChar, buf, static_cast<size_t>(16));
|
|
// if we read 16 or more bytes, it's an overflow
|
|
if (bytesRead && bytesRead < 16) {
|
|
buf[bytesRead] = '\0';
|
|
float res = atof(buf);
|
|
return res;
|
|
}
|
|
|
|
return -9999.0F;
|
|
}
|
|
|
|
inline 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;
|
|
}
|
|
};
|
|
|
|
#endif // SRC_TINYGSMMODEM_H_
|