This commit is contained in:
xin
2025-07-08 08:54:35 +08:00
parent bc81bd41ac
commit 6de3458dfc
376 changed files with 68605 additions and 246 deletions

View File

@ -0,0 +1,47 @@
/*
Client.h - Base class that provides Client
Copyright (c) 2011 Adrian McEwen. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef client_h
#define client_h
#include "Print.h"
#include "Stream.h"
#include "ArduinoCompat/IPAddress.h"
class Client : public Stream {
public:
virtual int connect(IPAddress ip, uint16_t port) = 0;
virtual int connect(const char* host, uint16_t port) = 0;
virtual size_t write(uint8_t) = 0;
virtual size_t write(const uint8_t* buf, size_t size) = 0;
virtual int available() = 0;
virtual int read() = 0;
virtual int read(uint8_t* buf, size_t size) = 0;
virtual int peek() = 0;
virtual void flush() = 0;
virtual void stop() = 0;
virtual uint8_t connected() = 0;
virtual operator bool() = 0;
protected:
uint8_t* rawIPAddress(IPAddress& addr) {
return addr.raw_address();
};
};
#endif

View File

@ -0,0 +1,146 @@
/*
IPAddress.h - Base class that provides IPAddress
Copyright (c) 2011 Adrian McEwen. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef IPAddress_h
#define IPAddress_h
#include <stdint.h>
#include "Printable.h"
#include "WString.h"
// A class to make it easier to handle and pass around IP addresses
class IPAddress : public Printable {
private:
union {
uint8_t bytes[4]; // IPv4 address
uint32_t dword;
} _address;
// Access the raw byte array containing the address. Because this returns a pointer
// to the internal structure rather than a copy of the address this function should only
// be used when you know that the usage of the returned uint8_t* will be transient and not
// stored.
uint8_t* raw_address() { return _address.bytes; };
public:
// Constructors
IPAddress() {
_address.dword = 0;
}
IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) {
_address.bytes[0] = first_octet;
_address.bytes[1] = second_octet;
_address.bytes[2] = third_octet;
_address.bytes[3] = fourth_octet;
}
IPAddress(uint32_t address) {
_address.dword = address;
}
IPAddress(const uint8_t *address) {
memcpy(_address.bytes, address, sizeof(_address.bytes));
}
bool fromString(const char *address) {
uint16_t acc = 0; // Accumulator
uint8_t dots = 0;
while (*address)
{
char c = *address++;
if (c >= '0' && c <= '9')
{
acc = acc * 10 + (c - '0');
if (acc > 255) {
// Value out of [0..255] range
return false;
}
}
else if (c == '.')
{
if (dots == 3) {
// Too much dots (there must be 3 dots)
return false;
}
_address.bytes[dots++] = acc;
acc = 0;
}
else
{
// Invalid char
return false;
}
}
if (dots != 3) {
// Too few dots (there must be 3 dots)
return false;
}
_address.bytes[3] = acc;
return true;
}
bool fromString(const String &address) { return fromString(address.c_str()); }
// Overloaded cast operator to allow IPAddress objects to be used where a pointer
// to a four-byte uint8_t array is expected
operator uint32_t() const { return _address.dword; };
bool operator==(const IPAddress& addr) const { return _address.dword == addr._address.dword; };
bool operator==(const uint8_t* addr) const {
return memcmp(addr, _address.bytes, sizeof(_address.bytes)) == 0;
}
// Overloaded index operator to allow getting and setting individual octets of the address
uint8_t operator[](int index) const { return _address.bytes[index]; };
uint8_t& operator[](int index) { return _address.bytes[index]; };
// Overloaded copy operators to allow initialisation of IPAddress objects from other types
IPAddress& operator=(const uint8_t *address) {
memcpy(_address.bytes, address, sizeof(_address.bytes));
return *this;
}
IPAddress& operator=(uint32_t address) {
_address.dword = address;
return *this;
}
virtual size_t printTo(Print& p) const {
size_t n = 0;
for (int i =0; i < 3; i++)
{
n += p.print(_address.bytes[i], DEC);
n += p.print('.');
}
n += p.print(_address.bytes[3], DEC);
return n;
}
friend class EthernetClass;
friend class UDP;
friend class Client;
friend class Server;
friend class DhcpClass;
friend class DNSClient;
};
const IPAddress INADDR_NONE(0,0,0,0);
#endif

View File

@ -0,0 +1,6 @@
#ifndef TINYGSM_H
#define TINYGSM_H
#include "TinyGsmClient.h"
#endif

View File

@ -0,0 +1,98 @@
/**
* @file TinyGsmBattery.tpp
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMBATTERY_H_
#define SRC_TINYGSMBATTERY_H_
#include "TinyGsmCommon.h"
#define TINY_GSM_MODEM_HAS_BATTERY
template <class modemType>
class TinyGsmBattery {
public:
/*
* Battery 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);
}
/*
* CRTP Helper
*/
protected:
inline const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
inline modemType& thisModem() {
return static_cast<modemType&>(*this);
}
/*
* Battery 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().streamGetIntBefore('\n');
// 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
int8_t res = thisModem().streamGetIntBefore(',');
// 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
int8_t res = thisModem().streamGetIntBefore(',');
// 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().streamGetIntBefore(',');
percent = thisModem().streamGetIntBefore(',');
milliVolts = thisModem().streamGetIntBefore('\n');
// Wait for final OK
thisModem().waitResponse();
return true;
}
};
#endif // SRC_TINYGSMBATTERY_H_

View File

@ -0,0 +1,57 @@
/**
* @file TinyGsmGPS.tpp
* @author Adrian Cervera Andes
* @license LGPL-3.0
* @copyright Copyright (c) 2021 Adrian Cervera Andes
* @date Jan 2021
*/
#ifndef SRC_TINYGSMBLUETOOTH_H_
#define SRC_TINYGSMBLUETOOTH_H_
#include "TinyGsmCommon.h"
#define TINY_GSM_MODEM_HAS_BLUETOOTH
template <class modemType>
class TinyGsmBluetooth {
public:
/*
* Bluetooth functions
*/
bool enableBluetooth() {
return thisModem().enableBluetoothImpl();
}
bool disableBluetooth() {
return thisModem().disableBluetoothImpl();
}
bool setBluetoothVisibility(bool visible) {
return thisModem().setBluetoothVisibilityImpl(visible);
}
bool setBluetoothHostName(const char* name) {
return thisModem().setBluetoothHostNameImpl(name);
}
/*
* CRTP Helper
*/
protected:
inline const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
inline modemType& thisModem() {
return static_cast<modemType&>(*this);
}
/*
* Bluetooth functions
*/
bool enableBluetoothImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED;
bool disableBluetoothImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED;
bool setBluetoothVisibilityImpl(bool visible) TINY_GSM_ATTR_NOT_IMPLEMENTED;
bool setBluetoothHostNameImpl(const char* name) TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
#endif // SRC_TINYGSMBLUETOOTH_H_

View File

@ -0,0 +1,90 @@
/**
* @file TinyGsmCalling.tpp
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCALLING_H_
#define SRC_TINYGSMCALLING_H_
#include "TinyGsmCommon.h"
#define TINY_GSM_MODEM_HAS_CALLING
template <class modemType>
class TinyGsmCalling {
public:
/*
* 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);
}
/*
* CRTP Helper
*/
protected:
inline const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
inline modemType& thisModem() {
return static_cast<modemType&>(*this);
}
/*
* 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, ";");
}
int8_t 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;
}
};
#endif // SRC_TINYGSMCALLING_H_

View File

@ -0,0 +1,121 @@
/**
* @file TinyGsmClient.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENT_H_
#define SRC_TINYGSMCLIENT_H_
#if defined(TINY_GSM_MODEM_SIM800)
#include "TinyGsmClientSIM800.h"
typedef TinyGsmSim800 TinyGsm;
typedef TinyGsmSim800::GsmClientSim800 TinyGsmClient;
typedef TinyGsmSim800::GsmClientSecureSim800 TinyGsmClientSecure;
#elif defined(TINY_GSM_MODEM_SIM808) || defined(TINY_GSM_MODEM_SIM868)
#include "TinyGsmClientSIM808.h"
typedef TinyGsmSim808 TinyGsm;
typedef TinyGsmSim808::GsmClientSim800 TinyGsmClient;
typedef TinyGsmSim808::GsmClientSecureSim800 TinyGsmClientSecure;
#elif defined(TINY_GSM_MODEM_SIM900)
#include "TinyGsmClientSIM800.h"
typedef TinyGsmSim800 TinyGsm;
typedef TinyGsmSim800::GsmClientSim800 TinyGsmClient;
#elif defined(TINY_GSM_MODEM_SIM7000)
#include "TinyGsmClientSIM7000.h"
typedef TinyGsmSim7000 TinyGsm;
typedef TinyGsmSim7000::GsmClientSim7000 TinyGsmClient;
#elif defined(TINY_GSM_MODEM_SIM7000SSL)
#include "TinyGsmClientSIM7000SSL.h"
typedef TinyGsmSim7000SSL TinyGsm;
typedef TinyGsmSim7000SSL::GsmClientSim7000SSL TinyGsmClient;
typedef TinyGsmSim7000SSL::GsmClientSecureSIM7000SSL TinyGsmClientSecure;
#elif defined(TINY_GSM_MODEM_SIM7070) || defined(TINY_GSM_MODEM_SIM7080) || \
defined(TINY_GSM_MODEM_SIM7090)
#include "TinyGsmClientSIM7080.h"
typedef TinyGsmSim7080 TinyGsm;
typedef TinyGsmSim7080::GsmClientSim7080 TinyGsmClient;
typedef TinyGsmSim7080::GsmClientSecureSIM7080 TinyGsmClientSecure;
#elif defined(TINY_GSM_MODEM_SIM5320) || defined(TINY_GSM_MODEM_SIM5360) || \
defined(TINY_GSM_MODEM_SIM5300) || defined(TINY_GSM_MODEM_SIM7100)
#include "TinyGsmClientSIM5360.h"
typedef TinyGsmSim5360 TinyGsm;
typedef TinyGsmSim5360::GsmClientSim5360 TinyGsmClient;
#elif defined(TINY_GSM_MODEM_SIM7600) || defined(TINY_GSM_MODEM_SIM7800) || \
defined(TINY_GSM_MODEM_SIM7500)
#include "TinyGsmClientSIM7600.h"
typedef TinyGsmSim7600 TinyGsm;
typedef TinyGsmSim7600::GsmClientSim7600 TinyGsmClient;
#elif defined(TINY_GSM_MODEM_UBLOX)
#include "TinyGsmClientUBLOX.h"
typedef TinyGsmUBLOX TinyGsm;
typedef TinyGsmUBLOX::GsmClientUBLOX TinyGsmClient;
typedef TinyGsmUBLOX::GsmClientSecureUBLOX TinyGsmClientSecure;
#elif defined(TINY_GSM_MODEM_SARAR4)
#include "TinyGsmClientSaraR4.h"
typedef TinyGsmSaraR4 TinyGsm;
typedef TinyGsmSaraR4::GsmClientSaraR4 TinyGsmClient;
typedef TinyGsmSaraR4::GsmClientSecureR4 TinyGsmClientSecure;
#elif defined(TINY_GSM_MODEM_M95)
#include "TinyGsmClientM95.h"
typedef TinyGsmM95 TinyGsm;
typedef TinyGsmM95::GsmClientM95 TinyGsmClient;
#elif defined(TINY_GSM_MODEM_BG96)
#include "TinyGsmClientBG96.h"
typedef TinyGsmBG96 TinyGsm;
typedef TinyGsmBG96::GsmClientBG96 TinyGsmClient;
#elif defined(TINY_GSM_MODEM_A6) || defined(TINY_GSM_MODEM_A7)
#include "TinyGsmClientA6.h"
typedef TinyGsmA6 TinyGsm;
typedef TinyGsmA6::GsmClientA6 TinyGsmClient;
#elif defined(TINY_GSM_MODEM_M590)
#include "TinyGsmClientM590.h"
typedef TinyGsmM590 TinyGsm;
typedef TinyGsmM590::GsmClientM590 TinyGsmClient;
#elif defined(TINY_GSM_MODEM_MC60) || defined(TINY_GSM_MODEM_MC60E)
#include "TinyGsmClientMC60.h"
typedef TinyGsmMC60 TinyGsm;
typedef TinyGsmMC60::GsmClientMC60 TinyGsmClient;
#elif defined(TINY_GSM_MODEM_ESP8266)
#define TINY_GSM_MODEM_HAS_WIFI
#include "TinyGsmClientESP8266.h"
typedef TinyGsmESP8266 TinyGsm;
typedef TinyGsmESP8266::GsmClientESP8266 TinyGsmClient;
typedef TinyGsmESP8266::GsmClientSecureESP8266 TinyGsmClientSecure;
#elif defined(TINY_GSM_MODEM_XBEE)
#define TINY_GSM_MODEM_HAS_WIFI
#include "TinyGsmClientXBee.h"
typedef TinyGsmXBee TinyGsm;
typedef TinyGsmXBee::GsmClientXBee TinyGsmClient;
typedef TinyGsmXBee::GsmClientSecureXBee TinyGsmClientSecure;
#elif defined(TINY_GSM_MODEM_SEQUANS_MONARCH)
#include "TinyGsmClientSequansMonarch.h"
typedef TinyGsmSequansMonarch TinyGsm;
typedef TinyGsmSequansMonarch::GsmClientSequansMonarch TinyGsmClient;
typedef TinyGsmSequansMonarch::GsmClientSecureSequansMonarch
TinyGsmClientSecure;
#else
#error "Please define GSM modem model"
#endif
#endif // SRC_TINYGSMCLIENT_H_

View File

@ -0,0 +1,580 @@
/**
* @file TinyGsmClientA6.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTA6_H_
#define SRC_TINYGSMCLIENTA6_H_
// #pragma message("TinyGSM: TinyGsmClientA6")
// #define TINY_GSM_DEBUG Serial
#define TINY_GSM_MUX_COUNT 8
#define TINY_GSM_NO_MODEM_BUFFER
#include "TinyGsmBattery.tpp"
#include "TinyGsmCalling.tpp"
#include "TinyGsmGPRS.tpp"
#include "TinyGsmModem.tpp"
#include "TinyGsmSMS.tpp"
#include "TinyGsmTCP.tpp"
#include "TinyGsmTime.tpp"
#define GSM_NL "\r\n"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
#if defined TINY_GSM_DEBUG
static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:";
static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:";
#endif
enum RegStatus {
REG_NO_RESULT = -1,
REG_UNREGISTERED = 0,
REG_SEARCHING = 2,
REG_DENIED = 3,
REG_OK_HOME = 1,
REG_OK_ROAMING = 5,
REG_UNKNOWN = 4,
};
class TinyGsmA6 : public TinyGsmModem<TinyGsmA6>,
public TinyGsmGPRS<TinyGsmA6>,
public TinyGsmTCP<TinyGsmA6, TINY_GSM_MUX_COUNT>,
public TinyGsmCalling<TinyGsmA6>,
public TinyGsmSMS<TinyGsmA6>,
public TinyGsmTime<TinyGsmA6>,
public TinyGsmBattery<TinyGsmA6> {
friend class TinyGsmModem<TinyGsmA6>;
friend class TinyGsmGPRS<TinyGsmA6>;
friend class TinyGsmTCP<TinyGsmA6, TINY_GSM_MUX_COUNT>;
friend class TinyGsmCalling<TinyGsmA6>;
friend class TinyGsmSMS<TinyGsmA6>;
friend class TinyGsmTime<TinyGsmA6>;
friend class TinyGsmBattery<TinyGsmA6>;
/*
* Inner Client
*/
public:
class GsmClientA6 : public GsmClient {
friend class TinyGsmA6;
public:
GsmClientA6() {}
explicit GsmClientA6(TinyGsmA6& modem, uint8_t = 0) {
init(&modem, -1);
}
bool init(TinyGsmA6* modem, uint8_t = 0) {
this->at = modem;
this->mux = -1;
sock_connected = false;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
stop();
TINY_GSM_YIELD();
rx.clear();
uint8_t newMux = -1;
sock_connected = at->modemConnect(host, port, &newMux, timeout_s);
if (sock_connected) {
mux = newMux;
at->sockets[mux] = this;
}
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void stop(uint32_t maxWaitMs) {
TINY_GSM_YIELD();
at->sendAT(GF("+CIPCLOSE="), mux);
sock_connected = false;
at->waitResponse(maxWaitMs);
rx.clear();
}
void stop() override {
stop(1000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
// Doesn't support SSL
/*
* Constructor
*/
public:
explicit TinyGsmA6(Stream& stream) : stream(stream) {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientA6"));
if (!testAT()) { return false; }
// sendAT(GF("&FZ")); // Factory + Reset
// waitResponse();
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
#ifdef TINY_GSM_DEBUG
sendAT(GF("+CMEE=2")); // turn on verbose error codes
#else
sendAT(GF("+CMEE=0")); // turn off error codes
#endif
waitResponse();
sendAT(
GF("+CMER=3,0,0,2")); // Set unsolicited result code output destination
waitResponse();
DBG(GF("### Modem:"), getModemName());
SimStatus ret = getSimStatus();
// if the sim isn't ready and a pin has been provided, try to unlock the sim
if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
simUnlock(pin);
return (getSimStatus() == SIM_READY);
} else {
// if the sim is ready, or it's locked but no pin has been provided,
// return true
return (ret == SIM_READY || ret == SIM_LOCKED);
}
}
bool factoryDefaultImpl() {
sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write
waitResponse();
sendAT(GF("&W")); // Write configuration
return waitResponse() == 1;
}
/*
* Power functions
*/
protected:
bool restartImpl(const char* pin = NULL) {
if (!testAT()) { return false; }
sendAT(GF("+RST=1"));
delay(3000);
return init(pin);
}
bool powerOffImpl() {
sendAT(GF("+CPOF"));
// +CPOF: MS OFF OK
return waitResponse() == 1;
}
bool sleepEnableImpl(bool enable = true) TINY_GSM_ATTR_NOT_AVAILABLE;
bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false)
TINY_GSM_ATTR_NOT_IMPLEMENTED;
/*
* Generic network functions
*/
public:
RegStatus getRegistrationStatus() {
return (RegStatus)getRegistrationStatusXREG("CREG");
}
protected:
bool isNetworkConnectedImpl() {
RegStatus s = getRegistrationStatus();
return (s == REG_OK_HOME || s == REG_OK_ROAMING);
}
String getLocalIPImpl() {
sendAT(GF("+CIFSR"));
String res;
if (waitResponse(10000L, res) != 1) { return ""; }
res.replace(GSM_NL "OK" GSM_NL, "");
res.replace(GSM_NL, "");
res.trim();
return res;
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
gprsDisconnect();
sendAT(GF("+CGATT=1"));
if (waitResponse(60000L) != 1) { return false; }
// TODO(?): wait AT+CGATT?
sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"');
waitResponse();
if (!user) user = "";
if (!pwd) pwd = "";
sendAT(GF("+CSTT=\""), apn, GF("\",\""), user, GF("\",\""), pwd, GF("\""));
if (waitResponse(60000L) != 1) { return false; }
sendAT(GF("+CGACT=1,1"));
waitResponse(60000L);
sendAT(GF("+CIPMUX=1"));
if (waitResponse() != 1) { return false; }
return true;
}
bool gprsDisconnectImpl() {
// Shut the TCP/IP connection
sendAT(GF("+CIPSHUT"));
if (waitResponse(60000L) != 1) { return false; }
for (int i = 0; i < 3; i++) {
sendAT(GF("+CGATT=0"));
if (waitResponse(5000L) == 1) { return true; }
}
return false;
}
String getOperatorImpl() {
sendAT(GF("+COPS=3,0")); // Set format
waitResponse();
sendAT(GF("+COPS?"));
if (waitResponse(GF(GSM_NL "+COPS:")) != 1) { return ""; }
streamSkipUntil('"'); // Skip mode and format
String res = stream.readStringUntil('"');
waitResponse();
return res;
}
/*
* SIM card functions
*/
protected:
String getSimCCIDImpl() {
sendAT(GF("+CCID"));
if (waitResponse(GF(GSM_NL "+SCID: SIM Card ID:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
/*
* Phone Call functions
*/
protected:
// Returns true on pick-up, false on error/busy
bool callNumberImpl(const String& number) {
if (number == GF("last")) {
sendAT(GF("DLST"));
} else {
sendAT(GF("D\""), number, "\";");
}
if (waitResponse(5000L) != 1) { return false; }
if (waitResponse(60000L, GF(GSM_NL "+CIEV: \"CALL\",1"),
GF(GSM_NL "+CIEV: \"CALL\",0"), GFP(GSM_ERROR)) != 1) {
return false;
}
int8_t rsp = waitResponse(60000L, GF(GSM_NL "+CIEV: \"SOUNDER\",0"),
GF(GSM_NL "+CIEV: \"CALL\",0"));
int8_t rsp2 = waitResponse(300L, GF(GSM_NL "BUSY" GSM_NL),
GF(GSM_NL "NO ANSWER" GSM_NL));
return rsp == 1 && rsp2 == 0;
}
// 0-9,*,#,A,B,C,D
bool dtmfSendImpl(char cmd, uint8_t duration_ms = 100) {
duration_ms = constrain(duration_ms, 100, 1000);
// The duration parameter is not working, so we simulate it using delay..
// TODO(?): Maybe there's another way...
// sendAT(GF("+VTD="), duration_ms / 100);
// waitResponse();
sendAT(GF("+VTS="), cmd);
if (waitResponse(10000L) == 1) {
delay(duration_ms);
return true;
}
return false;
}
/*
* Audio functions
*/
public:
bool audioSetHeadphones() {
sendAT(GF("+SNFS=0"));
return waitResponse() == 1;
}
bool audioSetSpeaker() {
sendAT(GF("+SNFS=1"));
return waitResponse() == 1;
}
bool audioMuteMic(bool mute) {
sendAT(GF("+CMUT="), mute);
return waitResponse() == 1;
}
/*
* Messaging functions
*/
protected:
String sendUSSDImpl(const String& code) {
sendAT(GF("+CMGF=1"));
waitResponse();
sendAT(GF("+CSCS=\"HEX\""));
waitResponse();
sendAT(GF("+CUSD=1,\""), code, GF("\",15"));
if (waitResponse(10000L) != 1) { return ""; }
if (waitResponse(GF(GSM_NL "+CUSD:")) != 1) { return ""; }
streamSkipUntil('"');
String hex = stream.readStringUntil('"');
streamSkipUntil(',');
int8_t dcs = streamGetIntBefore('\n');
if (dcs == 15) {
return TinyGsmDecodeHex7bit(hex);
} else if (dcs == 72) {
return TinyGsmDecodeHex16bit(hex);
} else {
return hex;
}
}
/*
* Time functions
*/
protected:
// Can follow the standard CCLK function in the template
// Note - the clock probably has to be set manaually first
/*
* Battery functions
*/
protected:
uint16_t getBattVoltageImpl() TINY_GSM_ATTR_NOT_AVAILABLE;
// Needs a '?' after CBC, unlike most
int8_t getBattPercentImpl() {
sendAT(GF("+CBC?"));
if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return false; }
streamSkipUntil(','); // Skip battery charge status
// Read battery charge level
int8_t res = streamGetIntBefore('\n');
// Wait for final OK
waitResponse();
return res;
}
// Needs a '?' after CBC, unlike most
bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent,
uint16_t& milliVolts) {
sendAT(GF("+CBC?"));
if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return false; }
chargeState = streamGetIntBefore(',');
percent = streamGetIntBefore('\n');
milliVolts = 0;
// Wait for final OK
waitResponse();
return true;
}
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t* mux,
int timeout_s = 75) {
uint32_t startMillis = millis();
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
sendAT(GF("+CIPSTART="), GF("\"TCP"), GF("\",\""), host, GF("\","), port);
if (waitResponse(timeout_ms, GF(GSM_NL "+CIPNUM:")) != 1) { return false; }
int8_t newMux = streamGetIntBefore('\n');
int8_t rsp = waitResponse(
(timeout_ms - (millis() - startMillis)), GF("CONNECT OK" GSM_NL),
GF("CONNECT FAIL" GSM_NL), GF("ALREADY CONNECT" GSM_NL));
if (waitResponse() != 1) { return false; }
*mux = newMux;
return (1 == rsp);
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len);
if (waitResponse(2000L, GF(GSM_NL ">")) != 1) { return 0; }
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
if (waitResponse(10000L, GFP(GSM_OK), GF(GSM_NL "FAIL")) != 1) { return 0; }
return len;
}
bool modemGetConnected(uint8_t) {
sendAT(GF("+CIPSTATUS")); // TODO(?) mux?
int8_t res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""),
GF(",\"CLOSING\""), GF(",\"INITIAL\""));
waitResponse();
return 1 == res;
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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(64);
uint8_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)) {
#if defined TINY_GSM_DEBUG
if (r3 == GFP(GSM_CME_ERROR)) {
streamSkipUntil('\n'); // Read out the error
}
#endif
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF("+CIPRCV:"))) {
int8_t mux = streamGetIntBefore(',');
int16_t len = streamGetIntBefore(',');
int16_t len_orig = len;
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
if (len > sockets[mux]->rx.free()) {
DBG("### Buffer overflow: ", len, "->", sockets[mux]->rx.free());
} else {
DBG("### Got: ", len, "->", sockets[mux]->rx.free());
}
while (len--) { moveCharFromStreamToFifo(mux); }
// TODO(?) Deal with missing characters
if (len_orig > sockets[mux]->available()) {
DBG("### Fewer characters received than expected: ",
sockets[mux]->available(), " vs ", len_orig);
}
}
data = "";
} else if (data.endsWith(GF("+TCPCLOSED:"))) {
int8_t mux = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->sock_connected = false;
}
data = "";
DBG("### Closed: ", mux);
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5);
}
public:
Stream& stream;
protected:
GsmClientA6* sockets[TINY_GSM_MUX_COUNT];
const char* gsmNL = GSM_NL;
};
#endif // SRC_TINYGSMCLIENTA6_H_

View File

@ -0,0 +1,727 @@
/**
* @file TinyGsmClientBG96.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Apr 2018
*/
#ifndef SRC_TINYGSMCLIENTBG96_H_
#define SRC_TINYGSMCLIENTBG96_H_
// #pragma message("TinyGSM: TinyGsmClientBG96")
// #define TINY_GSM_DEBUG Serial
#define TINY_GSM_MUX_COUNT 12
#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE
#include "TinyGsmBattery.tpp"
#include "TinyGsmCalling.tpp"
#include "TinyGsmGPRS.tpp"
#include "TinyGsmGPS.tpp"
#include "TinyGsmModem.tpp"
#include "TinyGsmSMS.tpp"
#include "TinyGsmTCP.tpp"
#include "TinyGsmTemperature.tpp"
#include "TinyGsmTime.tpp"
#include "TinyGsmNTP.tpp"
#define GSM_NL "\r\n"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
#if defined TINY_GSM_DEBUG
static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:";
static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:";
#endif
enum RegStatus {
REG_NO_RESULT = -1,
REG_UNREGISTERED = 0,
REG_SEARCHING = 2,
REG_DENIED = 3,
REG_OK_HOME = 1,
REG_OK_ROAMING = 5,
REG_UNKNOWN = 4,
};
class TinyGsmBG96 : public TinyGsmModem<TinyGsmBG96>,
public TinyGsmGPRS<TinyGsmBG96>,
public TinyGsmTCP<TinyGsmBG96, TINY_GSM_MUX_COUNT>,
public TinyGsmCalling<TinyGsmBG96>,
public TinyGsmSMS<TinyGsmBG96>,
public TinyGsmTime<TinyGsmBG96>,
public TinyGsmNTP<TinyGsmBG96>,
public TinyGsmGPS<TinyGsmBG96>,
public TinyGsmBattery<TinyGsmBG96>,
public TinyGsmTemperature<TinyGsmBG96> {
friend class TinyGsmModem<TinyGsmBG96>;
friend class TinyGsmGPRS<TinyGsmBG96>;
friend class TinyGsmTCP<TinyGsmBG96, TINY_GSM_MUX_COUNT>;
friend class TinyGsmCalling<TinyGsmBG96>;
friend class TinyGsmSMS<TinyGsmBG96>;
friend class TinyGsmTime<TinyGsmBG96>;
friend class TinyGsmNTP<TinyGsmBG96>;
friend class TinyGsmGPS<TinyGsmBG96>;
friend class TinyGsmBattery<TinyGsmBG96>;
friend class TinyGsmTemperature<TinyGsmBG96>;
/*
* Inner Client
*/
public:
class GsmClientBG96 : public GsmClient {
friend class TinyGsmBG96;
public:
GsmClientBG96() {}
explicit GsmClientBG96(TinyGsmBG96& modem, uint8_t mux = 0) {
init(&modem, mux);
}
bool init(TinyGsmBG96* modem, uint8_t mux = 0) {
this->at = modem;
sock_available = 0;
prev_check = 0;
sock_connected = false;
got_data = false;
if (mux < TINY_GSM_MUX_COUNT) {
this->mux = mux;
} else {
this->mux = (mux % TINY_GSM_MUX_COUNT);
}
at->sockets[this->mux] = this;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, false, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void stop(uint32_t maxWaitMs) {
uint32_t startMillis = millis();
dumpModemBuffer(maxWaitMs);
at->sendAT(GF("+QICLOSE="), mux);
sock_connected = false;
at->waitResponse((maxWaitMs - (millis() - startMillis)));
}
void stop() override {
stop(15000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
/*
class GsmClientSecureBG96 : public GsmClientBG96
{
public:
GsmClientSecure() {}
GsmClientSecure(TinyGsmBG96& modem, uint8_t mux = 0)
: public GsmClient(modem, mux)
{}
public:
int connect(const char* host, uint16_t port, int timeout_s) override {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, true, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
};
*/
/*
* Constructor
*/
public:
explicit TinyGsmBG96(Stream& stream) : stream(stream) {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientBG96"));
if (!testAT()) { return false; }
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
#ifdef TINY_GSM_DEBUG
sendAT(GF("+CMEE=2")); // turn on verbose error codes
#else
sendAT(GF("+CMEE=0")); // turn off error codes
#endif
waitResponse();
DBG(GF("### Modem:"), getModemName());
// Disable time and time zone URC's
sendAT(GF("+CTZR=0"));
if (waitResponse(10000L) != 1) { return false; }
// Enable automatic time zone update
sendAT(GF("+CTZU=1"));
if (waitResponse(10000L) != 1) { return false; }
SimStatus ret = getSimStatus();
// if the sim isn't ready and a pin has been provided, try to unlock the sim
if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
simUnlock(pin);
return (getSimStatus() == SIM_READY);
} else {
// if the sim is ready, or it's locked but no pin has been provided,
// return true
return (ret == SIM_READY || ret == SIM_LOCKED);
}
}
/*
* Power functions
*/
protected:
bool restartImpl(const char* pin = NULL) {
if (!testAT()) { return false; }
if (!setPhoneFunctionality(1, true)) { return false; }
waitResponse(10000L, GF("APP RDY"));
return init(pin);
}
bool powerOffImpl() {
sendAT(GF("+QPOWD=1"));
waitResponse(300); // returns OK first
return waitResponse(300, GF("POWERED DOWN")) == 1;
}
// When entering into sleep mode is enabled, DTR is pulled up, and WAKEUP_IN
// is pulled up, the module can directly enter into sleep mode.If entering
// into sleep mode is enabled, DTR is pulled down, and WAKEUP_IN is pulled
// down, there is a need to pull the DTR pin and the WAKEUP_IN pin up first,
// and then the module can enter into sleep mode.
bool sleepEnableImpl(bool enable = true) {
sendAT(GF("+QSCLK="), enable);
return waitResponse() == 1;
}
bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) {
sendAT(GF("+CFUN="), fun, reset ? ",1" : "");
return waitResponse(10000L, GF("OK")) == 1;
}
/*
* Generic network functions
*/
public:
RegStatus getRegistrationStatus() {
// Check first for EPS registration
RegStatus epsStatus = (RegStatus)getRegistrationStatusXREG("CEREG");
// If we're connected on EPS, great!
if (epsStatus == REG_OK_HOME || epsStatus == REG_OK_ROAMING) {
return epsStatus;
} else {
// Otherwise, check generic network status
return (RegStatus)getRegistrationStatusXREG("CREG");
}
}
protected:
bool isNetworkConnectedImpl() {
RegStatus s = getRegistrationStatus();
return (s == REG_OK_HOME || s == REG_OK_ROAMING);
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
gprsDisconnect();
// Configure the TCPIP Context
sendAT(GF("+QICSGP=1,1,\""), apn, GF("\",\""), user, GF("\",\""), pwd,
GF("\""));
if (waitResponse() != 1) { return false; }
// Activate GPRS/CSD Context
sendAT(GF("+QIACT=1"));
if (waitResponse(150000L) != 1) { return false; }
// Attach to Packet Domain service - is this necessary?
sendAT(GF("+CGATT=1"));
if (waitResponse(60000L) != 1) { return false; }
return true;
}
bool gprsDisconnectImpl() {
sendAT(GF("+QIDEACT=1")); // Deactivate the bearer context
if (waitResponse(40000L) != 1) { return false; }
return true;
}
/*
* SIM card functions
*/
protected:
String getSimCCIDImpl() {
sendAT(GF("+QCCID"));
if (waitResponse(GF(GSM_NL "+QCCID:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
/*
* Phone Call functions
*/
protected:
// Can follow all of the phone call functions from the template
/*
* Messaging functions
*/
protected:
// Follows all messaging functions per template
/*
* GSM Location functions
*/
protected:
// NOTE: As of application firmware version 01.016.01.016 triangulated
// locations can be obtained via the QuecLocator service and accompanying AT
// commands. As this is a separate paid service which I do not have access
// to, I am not implementing it here.
/*
* GPS/GNSS/GLONASS location functions
*/
protected:
// enable GPS
bool enableGPSImpl() {
sendAT(GF("+QGPS=1"));
if (waitResponse() != 1) { return false; }
return true;
}
bool disableGPSImpl() {
sendAT(GF("+QGPSEND"));
if (waitResponse() != 1) { return false; }
return true;
}
// get the RAW GPS output
String getGPSrawImpl() {
sendAT(GF("+QGPSLOC=2"));
if (waitResponse(10000L, GF(GSM_NL "+QGPSLOC:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
// get GPS informations
bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0,
int* vsat = 0, int* usat = 0, float* accuracy = 0,
int* year = 0, int* month = 0, int* day = 0, int* hour = 0,
int* minute = 0, int* second = 0) {
sendAT(GF("+QGPSLOC=2"));
if (waitResponse(10000L, GF(GSM_NL "+QGPSLOC:")) != 1) {
// NOTE: Will return an error if the position isn't fixed
return false;
}
// init variables
float ilat = 0;
float ilon = 0;
float ispeed = 0;
float ialt = 0;
int iusat = 0;
float iaccuracy = 0;
int iyear = 0;
int imonth = 0;
int iday = 0;
int ihour = 0;
int imin = 0;
float secondWithSS = 0;
// UTC date & Time
ihour = streamGetIntLength(2); // Two digit hour
imin = streamGetIntLength(2); // Two digit minute
secondWithSS = streamGetFloatBefore(','); // 6 digit second with subseconds
ilat = streamGetFloatBefore(','); // Latitude
ilon = streamGetFloatBefore(','); // Longitude
iaccuracy = streamGetFloatBefore(','); // Horizontal precision
ialt = streamGetFloatBefore(','); // Altitude from sea level
streamSkipUntil(','); // GNSS positioning mode
streamSkipUntil(','); // Course Over Ground based on true north
streamSkipUntil(','); // Speed Over Ground in Km/h
ispeed = streamGetFloatBefore(','); // Speed Over Ground in knots
iday = streamGetIntLength(2); // Two digit day
imonth = streamGetIntLength(2); // Two digit month
iyear = streamGetIntBefore(','); // Two digit year
iusat = streamGetIntBefore(','); // Number of satellites,
streamSkipUntil('\n'); // The error code of the operation. If it is not
// 0, it is the type of error.
// Set pointers
if (lat != NULL) *lat = ilat;
if (lon != NULL) *lon = ilon;
if (speed != NULL) *speed = ispeed;
if (alt != NULL) *alt = ialt;
if (vsat != NULL) *vsat = 0;
if (usat != NULL) *usat = iusat;
if (accuracy != NULL) *accuracy = iaccuracy;
if (iyear < 2000) iyear += 2000;
if (year != NULL) *year = iyear;
if (month != NULL) *month = imonth;
if (day != NULL) *day = iday;
if (hour != NULL) *hour = ihour;
if (minute != NULL) *minute = imin;
if (second != NULL) *second = static_cast<int>(secondWithSS);
waitResponse(); // Final OK
return true;
}
/*
* Time functions
*/
protected:
String getGSMDateTimeImpl(TinyGSMDateTimeFormat format) {
sendAT(GF("+QLTS=2"));
if (waitResponse(2000L, GF("+QLTS: \"")) != 1) { return ""; }
String res;
switch (format) {
case DATE_FULL: res = stream.readStringUntil('"'); break;
case DATE_TIME:
streamSkipUntil(',');
res = stream.readStringUntil('"');
break;
case DATE_DATE: res = stream.readStringUntil(','); break;
}
waitResponse(); // Ends with OK
return res;
}
// The BG96 returns UTC time instead of local time as other modules do in
// response to CCLK, so we're using QLTS where we can specifically request
// local time.
bool getNetworkTimeImpl(int* year, int* month, int* day, int* hour,
int* minute, int* second, float* timezone) {
sendAT(GF("+QLTS=2"));
if (waitResponse(2000L, GF("+QLTS: \"")) != 1) { return false; }
int iyear = 0;
int imonth = 0;
int iday = 0;
int ihour = 0;
int imin = 0;
int isec = 0;
int itimezone = 0;
// Date & Time
iyear = streamGetIntBefore('/');
imonth = streamGetIntBefore('/');
iday = streamGetIntBefore(',');
ihour = streamGetIntBefore(':');
imin = streamGetIntBefore(':');
isec = streamGetIntLength(2);
char tzSign = stream.read();
itimezone = streamGetIntBefore(',');
if (tzSign == '-') { itimezone = itimezone * -1; }
streamSkipUntil('\n'); // DST flag
// Set pointers
if (iyear < 2000) iyear += 2000;
if (year != NULL) *year = iyear;
if (month != NULL) *month = imonth;
if (day != NULL) *day = iday;
if (hour != NULL) *hour = ihour;
if (minute != NULL) *minute = imin;
if (second != NULL) *second = isec;
if (timezone != NULL) *timezone = static_cast<float>(itimezone) / 4.0;
// Final OK
waitResponse(); // Ends with OK
return true;
}
/*
* NTP server functions
*/
byte NTPServerSyncImpl(String server = "pool.ntp.org", byte = -5) {
// Request network synchronization
// AT+QNTP=<contextID>,<server>[,<port>][,<autosettime>]
sendAT(GF("+QNTP=1,\""), server, '"');
if (waitResponse(10000L, GF("+QNTP:"))) {
String result = stream.readStringUntil(',');
streamSkipUntil('\n');
result.trim();
if (TinyGsmIsValidNumber(result)) { return result.toInt(); }
} else {
return -1;
}
return -1;
}
String ShowNTPErrorImpl(byte error) TINY_GSM_ATTR_NOT_IMPLEMENTED;
/*
* Battery functions
*/
protected:
// Can follow CBC as in the template
/*
* Temperature functions
*/
protected:
// get temperature in degree celsius
uint16_t getTemperatureImpl() {
sendAT(GF("+QTEMP"));
if (waitResponse(GF(GSM_NL "+QTEMP:")) != 1) { return 0; }
// return temperature in C
uint16_t res =
streamGetIntBefore(','); // read PMIC (primary ic) temperature
streamSkipUntil(','); // skip XO temperature ??
streamSkipUntil('\n'); // skip PA temperature ??
// Wait for final OK
waitResponse();
return res;
}
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t mux,
bool ssl = false, int timeout_s = 150) {
if (ssl) { DBG("SSL not yet supported on this module!"); }
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
// <PDPcontextID>(1-16), <connectID>(0-11),
// "TCP/UDP/TCP LISTENER/UDPSERVICE", "<IP_address>/<domain_name>",
// <remote_port>,<local_port>,<access_mode>(0-2; 0=buffer)
sendAT(GF("+QIOPEN=1,"), mux, GF(",\""), GF("TCP"), GF("\",\""), host,
GF("\","), port, GF(",0,0"));
waitResponse();
if (waitResponse(timeout_ms, GF(GSM_NL "+QIOPEN:")) != 1) { return false; }
if (streamGetIntBefore(',') != mux) { return false; }
// Read status
return (0 == streamGetIntBefore('\n'));
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
sendAT(GF("+QISEND="), mux, ',', (uint16_t)len);
if (waitResponse(GF(">")) != 1) { return 0; }
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
if (waitResponse(GF(GSM_NL "SEND OK")) != 1) { return 0; }
// TODO(?): Wait for ACK? AT+QISEND=id,0
return len;
}
size_t modemRead(size_t size, uint8_t mux) {
if (!sockets[mux]) return 0;
sendAT(GF("+QIRD="), mux, ',', (uint16_t)size);
if (waitResponse(GF("+QIRD:")) != 1) { return 0; }
int16_t len = streamGetIntBefore('\n');
for (int i = 0; i < len; i++) { moveCharFromStreamToFifo(mux); }
waitResponse();
// DBG("### READ:", len, "from", mux);
sockets[mux]->sock_available = modemGetAvailable(mux);
return len;
}
size_t modemGetAvailable(uint8_t mux) {
if (!sockets[mux]) return 0;
sendAT(GF("+QIRD="), mux, GF(",0"));
size_t result = 0;
if (waitResponse(GF("+QIRD:")) == 1) {
streamSkipUntil(','); // Skip total received
streamSkipUntil(','); // Skip have read
result = streamGetIntBefore('\n');
if (result) { DBG("### DATA AVAILABLE:", result, "on", mux); }
waitResponse();
}
if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); }
return result;
}
bool modemGetConnected(uint8_t mux) {
sendAT(GF("+QISTATE=1,"), mux);
// +QISTATE: 0,"TCP","151.139.237.11",80,5087,4,1,0,0,"uart1"
if (waitResponse(GF("+QISTATE:")) != 1) { return false; }
streamSkipUntil(','); // Skip mux
streamSkipUntil(','); // Skip socket type
streamSkipUntil(','); // Skip remote ip
streamSkipUntil(','); // Skip remote port
streamSkipUntil(','); // Skip local port
int8_t res = streamGetIntBefore(','); // socket state
waitResponse();
// 0 Initial, 1 Opening, 2 Connected, 3 Listening, 4 Closing
return 2 == res;
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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(64);
uint8_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)) {
#if defined TINY_GSM_DEBUG
if (r3 == GFP(GSM_CME_ERROR)) {
streamSkipUntil('\n'); // Read out the error
}
#endif
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF(GSM_NL "+QIURC:"))) {
streamSkipUntil('\"');
String urc = stream.readStringUntil('\"');
streamSkipUntil(',');
if (urc == "recv") {
int8_t mux = streamGetIntBefore('\n');
DBG("### URC RECV:", mux);
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
}
} else if (urc == "closed") {
int8_t mux = streamGetIntBefore('\n');
DBG("### URC CLOSE:", mux);
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->sock_connected = false;
}
} else {
streamSkipUntil('\n');
}
data = "";
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5);
}
public:
Stream& stream;
protected:
GsmClientBG96* sockets[TINY_GSM_MUX_COUNT];
const char* gsmNL = GSM_NL;
};
#endif // SRC_TINYGSMCLIENTBG96_H_

View File

@ -0,0 +1,485 @@
/**
* @file TinyGsmClientESP8266.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTESP8266_H_
#define SRC_TINYGSMCLIENTESP8266_H_
// #pragma message("TinyGSM: TinyGsmClientESP8266")
// #define TINY_GSM_DEBUG Serial
#define TINY_GSM_MUX_COUNT 5
#define TINY_GSM_NO_MODEM_BUFFER
#include "TinyGsmModem.tpp"
#include "TinyGsmSSL.tpp"
#include "TinyGsmTCP.tpp"
#include "TinyGsmWifi.tpp"
#define GSM_NL "\r\n"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
static uint8_t TINY_GSM_TCP_KEEP_ALIVE = 120;
// <stat> status of ESP8266 station interface
// 2 : ESP8266 station connected to an AP and has obtained IP
// 3 : ESP8266 station created a TCP or UDP transmission
// 4 : the TCP or UDP transmission of ESP8266 station disconnected
// 5 : ESP8266 station did NOT connect to an AP
enum RegStatus {
REG_OK_IP = 2,
REG_OK_TCP = 3,
REG_OK_NO_TCP = 4,
REG_DENIED = 5,
REG_UNKNOWN = 6,
};
class TinyGsmESP8266 : public TinyGsmModem<TinyGsmESP8266>,
public TinyGsmWifi<TinyGsmESP8266>,
public TinyGsmTCP<TinyGsmESP8266, TINY_GSM_MUX_COUNT>,
public TinyGsmSSL<TinyGsmESP8266> {
friend class TinyGsmModem<TinyGsmESP8266>;
friend class TinyGsmWifi<TinyGsmESP8266>;
friend class TinyGsmTCP<TinyGsmESP8266, TINY_GSM_MUX_COUNT>;
friend class TinyGsmSSL<TinyGsmESP8266>;
/*
* Inner Client
*/
public:
class GsmClientESP8266 : public GsmClient {
friend class TinyGsmESP8266;
public:
GsmClientESP8266() {}
explicit GsmClientESP8266(TinyGsmESP8266& modem, uint8_t mux = 0) {
init(&modem, mux);
}
bool init(TinyGsmESP8266* modem, uint8_t mux = 0) {
this->at = modem;
sock_connected = false;
if (mux < TINY_GSM_MUX_COUNT) {
this->mux = mux;
} else {
this->mux = (mux % TINY_GSM_MUX_COUNT);
}
at->sockets[this->mux] = this;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, false, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void stop(uint32_t maxWaitMs) {
TINY_GSM_YIELD();
at->sendAT(GF("+CIPCLOSE="), mux);
sock_connected = false;
at->waitResponse(maxWaitMs);
rx.clear();
}
void stop() override {
stop(5000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
public:
class GsmClientSecureESP8266 : public GsmClientESP8266 {
public:
GsmClientSecureESP8266() {}
explicit GsmClientSecureESP8266(TinyGsmESP8266& modem, uint8_t mux = 0)
: GsmClientESP8266(modem, mux) {}
public:
int connect(const char* host, uint16_t port, int timeout_s) override {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, true, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
};
/*
* Constructor
*/
public:
explicit TinyGsmESP8266(Stream& stream) : stream(stream) {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientESP8266"));
if (!testAT()) { return false; }
if (pin && strlen(pin) > 0) {
DBG("ESP8266 modules do not use an unlock pin!");
}
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
sendAT(GF("+CIPMUX=1")); // Enable Multiple Connections
if (waitResponse() != 1) { return false; }
sendAT(GF("+CWMODE=1")); // Put into "station" mode
if (waitResponse() != 1) {
sendAT(GF("+CWMODE_CUR=1")); // Attempt "current" station mode command
// for some firmware variants if needed
if (waitResponse() != 1) { return false; }
}
DBG(GF("### Modem:"), getModemName());
return true;
}
String getModemNameImpl() {
return "ESP8266";
}
void setBaudImpl(uint32_t baud) {
sendAT(GF("+UART_CUR="), baud, "8,1,0,0");
if (waitResponse() != 1) {
sendAT(GF("+UART="), baud,
"8,1,0,0"); // Really old firmwares might need this
// if (waitResponse() != 1) {
// sendAT(GF("+IPR="), baud); // First release firmwares might need
// this
waitResponse();
// }
}
}
bool factoryDefaultImpl() {
sendAT(GF("+RESTORE"));
return waitResponse() == 1;
}
String getModemInfoImpl() {
sendAT(GF("+GMR"));
String res;
if (waitResponse(1000L, res) != 1) { return ""; }
res.replace(GSM_NL "OK" GSM_NL, "");
res.replace(GSM_NL, " ");
res.trim();
return res;
}
/*
* Power functions
*/
protected:
bool restartImpl(const char* pin = NULL) {
if (!testAT()) { return false; }
sendAT(GF("+RST"));
if (waitResponse(10000L) != 1) { return false; }
if (waitResponse(10000L, GF(GSM_NL "ready" GSM_NL)) != 1) { return false; }
delay(500);
return init(pin);
}
bool powerOffImpl() {
sendAT(GF("+GSLP=0")); // Power down indefinitely - until manually reset!
return waitResponse() == 1;
}
bool radioOffImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED;
bool sleepEnableImpl(bool enable = true) TINY_GSM_ATTR_NOT_AVAILABLE;
bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false)
TINY_GSM_ATTR_NOT_IMPLEMENTED;
/*
* Generic network functions
*/
public:
RegStatus getRegistrationStatus() {
sendAT(GF("+CIPSTATUS"));
if (waitResponse(3000, GF("STATUS:")) != 1) return REG_UNKNOWN;
int8_t status = waitResponse(GFP(GSM_ERROR), GF("2"), GF("3"), GF("4"),
GF("5"));
waitResponse(); // Returns an OK after the status
return (RegStatus)status;
}
protected:
int8_t getSignalQualityImpl() {
sendAT(GF("+CWJAP?"));
int8_t res1 = waitResponse(GF("No AP"), GF("+CWJAP:"));
if (res1 != 2) {
waitResponse();
sendAT(GF("+CWJAP_CUR?")); // attempt "current" as used by some firmware
// versions
int8_t res1 = waitResponse(GF("No AP"), GF("+CWJAP_CUR:"));
if (res1 != 2) {
waitResponse();
return 0;
}
}
streamSkipUntil(','); // Skip SSID
streamSkipUntil(','); // Skip BSSID/MAC address
streamSkipUntil(','); // Skip Chanel number
int8_t res2 = stream.parseInt(); // Read RSSI
waitResponse(); // Returns an OK after the value
return res2;
}
bool isNetworkConnectedImpl() {
RegStatus s = getRegistrationStatus();
if (s == REG_OK_IP || s == REG_OK_TCP) {
// with these, we're definitely connected
return true;
} else if (s == REG_OK_NO_TCP) {
// with this, we may or may not be connected
if (getLocalIP() == "") {
return false;
} else {
return true;
}
} else {
return false;
}
}
String getLocalIPImpl() {
// attempt with and without 'current' flag
sendAT(GF("+CIPSTA?"));
int8_t res1 = waitResponse(GF("ERROR"), GF("+CIPSTA:"));
if (res1 != 2) {
sendAT(GF("+CIPSTA_CUR?"));
res1 = waitResponse(GF("ERROR"), GF("+CIPSTA_CUR:"));
if (res1 != 2) { return ""; }
}
String res2 = stream.readStringUntil('\n');
res2.replace("ip:", ""); // newer firmwares have this
res2.replace("\"", "");
res2.trim();
waitResponse();
return res2;
}
/*
* WiFi functions
*/
protected:
bool networkConnectImpl(const char* ssid, const char* pwd) {
// attempt first without than with the 'current' flag used in some firmware
// versions
sendAT(GF("+CWJAP=\""), ssid, GF("\",\""), pwd, GF("\""));
if (waitResponse(30000L, GFP(GSM_OK), GF(GSM_NL "FAIL" GSM_NL)) != 1) {
sendAT(GF("+CWJAP_CUR=\""), ssid, GF("\",\""), pwd, GF("\""));
if (waitResponse(30000L, GFP(GSM_OK), GF(GSM_NL "FAIL" GSM_NL)) != 1) {
return false;
}
}
return true;
}
bool networkDisconnectImpl() {
sendAT(GF("+CWQAP"));
bool retVal = waitResponse(10000L) == 1;
waitResponse(GF("WIFI DISCONNECT"));
return retVal;
}
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t mux,
bool ssl = false, int timeout_s = 75) {
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
if (ssl) {
sendAT(GF("+CIPSSLSIZE=4096"));
waitResponse();
}
sendAT(GF("+CIPSTART="), mux, ',', ssl ? GF("\"SSL") : GF("\"TCP"),
GF("\",\""), host, GF("\","), port, GF(","),
TINY_GSM_TCP_KEEP_ALIVE);
// TODO(?): Check mux
int8_t rsp = waitResponse(timeout_ms, GFP(GSM_OK), GFP(GSM_ERROR),
GF("ALREADY CONNECT"));
// if (rsp == 3) waitResponse();
// May return "ERROR" after the "ALREADY CONNECT"
return (1 == rsp);
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len);
if (waitResponse(GF(">")) != 1) { return 0; }
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
if (waitResponse(10000L, GF(GSM_NL "SEND OK" GSM_NL)) != 1) { return 0; }
return len;
}
bool modemGetConnected(uint8_t mux) {
sendAT(GF("+CIPSTATUS"));
if (waitResponse(3000, GF("STATUS:")) != 1) { return false; }
int8_t status = waitResponse(GFP(GSM_ERROR), GF("2"), GF("3"), GF("4"),
GF("5"));
if (status != 3) {
// if the status is anything but 3, there are no connections open
waitResponse(); // Returns an OK after the status
for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) {
if (sockets[muxNo]) { sockets[muxNo]->sock_connected = false; }
}
return false;
}
bool verified_connections[TINY_GSM_MUX_COUNT] = {0, 0, 0, 0, 0};
for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) {
uint8_t has_status = waitResponse(GF("+CIPSTATUS:"), GFP(GSM_OK),
GFP(GSM_ERROR));
if (has_status == 1) {
int8_t returned_mux = streamGetIntBefore(',');
streamSkipUntil(','); // Skip mux
streamSkipUntil(','); // Skip type
streamSkipUntil(','); // Skip remote IP
streamSkipUntil(','); // Skip remote port
streamSkipUntil(','); // Skip local port
streamSkipUntil('\n'); // Skip client/server type
verified_connections[returned_mux] = 1;
}
if (has_status == 2) break; // once we get to the ok, stop
}
for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) {
if (sockets[muxNo]) {
sockets[muxNo]->sock_connected = verified_connections[muxNo];
}
}
return verified_connections[mux];
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
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(64);
uint8_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;
} else if (data.endsWith(GF("+IPD,"))) {
int8_t mux = streamGetIntBefore(',');
int16_t len = streamGetIntBefore(':');
int16_t len_orig = len;
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
if (len > sockets[mux]->rx.free()) {
DBG("### Buffer overflow: ", len, "received vs",
sockets[mux]->rx.free(), "available");
} else {
// DBG("### Got Data: ", len, "on", mux);
}
while (len--) { moveCharFromStreamToFifo(mux); }
// TODO(SRGDamia1): deal with buffer overflow/missed characters
if (len_orig > sockets[mux]->available()) {
DBG("### Fewer characters received than expected: ",
sockets[mux]->available(), " vs ", len_orig);
}
}
data = "";
} else if (data.endsWith(GF("CLOSED"))) {
int8_t muxStart =
TinyGsmMax(0, data.lastIndexOf(GSM_NL, data.length() - 8));
int8_t coma = data.indexOf(',', muxStart);
int8_t mux = data.substring(muxStart, coma).toInt();
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->sock_connected = false;
}
data = "";
DBG("### Closed: ", mux);
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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);
}
public:
Stream& stream;
protected:
GsmClientESP8266* sockets[TINY_GSM_MUX_COUNT];
const char* gsmNL = GSM_NL;
};
#endif // SRC_TINYGSMCLIENTESP8266_H_

View File

@ -0,0 +1,473 @@
/**
* @file TinyGsmClientM590.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTM590_H_
#define SRC_TINYGSMCLIENTM590_H_
// #pragma message("TinyGSM: TinyGsmClientM590")
// #define TINY_GSM_DEBUG Serial
#define TINY_GSM_MUX_COUNT 2
#define TINY_GSM_NO_MODEM_BUFFER
#include "TinyGsmGPRS.tpp"
#include "TinyGsmModem.tpp"
#include "TinyGsmSMS.tpp"
#include "TinyGsmTCP.tpp"
#include "TinyGsmTime.tpp"
#define GSM_NL "\r\n"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
#if defined TINY_GSM_DEBUG
static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:";
static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:";
#endif
enum RegStatus {
REG_NO_RESULT = -1,
REG_UNREGISTERED = 0,
REG_SEARCHING = 3,
REG_DENIED = 2,
REG_OK_HOME = 1,
REG_OK_ROAMING = 5,
REG_UNKNOWN = 4,
};
class TinyGsmM590 : public TinyGsmModem<TinyGsmM590>,
public TinyGsmGPRS<TinyGsmM590>,
public TinyGsmTCP<TinyGsmM590, TINY_GSM_MUX_COUNT>,
public TinyGsmSMS<TinyGsmM590>,
public TinyGsmTime<TinyGsmM590> {
friend class TinyGsmModem<TinyGsmM590>;
friend class TinyGsmGPRS<TinyGsmM590>;
friend class TinyGsmTCP<TinyGsmM590, TINY_GSM_MUX_COUNT>;
friend class TinyGsmSMS<TinyGsmM590>;
friend class TinyGsmTime<TinyGsmM590>;
/*
* Inner Client
*/
public:
class GsmClientM590 : public GsmClient {
friend class TinyGsmM590;
public:
GsmClientM590() {}
explicit GsmClientM590(TinyGsmM590& modem, uint8_t mux = 0) {
init(&modem, mux);
}
bool init(TinyGsmM590* modem, uint8_t mux = 0) {
this->at = modem;
sock_connected = false;
if (mux < TINY_GSM_MUX_COUNT) {
this->mux = mux;
} else {
this->mux = (mux % TINY_GSM_MUX_COUNT);
}
at->sockets[this->mux] = this;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, false, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void stop(uint32_t maxWaitMs) {
TINY_GSM_YIELD();
at->sendAT(GF("+TCPCLOSE="), mux);
sock_connected = false;
at->waitResponse(maxWaitMs);
rx.clear();
}
void stop() override {
stop(1000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Constructor
*/
public:
explicit TinyGsmM590(Stream& stream) : stream(stream) {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientM590"));
if (!testAT()) { return false; }
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
#ifdef TINY_GSM_DEBUG
sendAT(GF("+CMEE=2")); // turn on verbose error codes
#else
sendAT(GF("+CMEE=0")); // turn off error codes
#endif
waitResponse();
DBG(GF("### Modem:"), getModemName());
SimStatus ret = getSimStatus();
// if the sim isn't ready and a pin has been provided, try to unlock the sim
if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
simUnlock(pin);
return (getSimStatus() == SIM_READY);
} else {
// if the sim is ready, or it's locked but no pin has been provided,
// return true
return (ret == SIM_READY || ret == SIM_LOCKED);
}
}
// Doesn't support CGMI
String getModemNameImpl() {
return "Neoway M590";
}
// Extra stuff here - pwr save, internal stack
bool factoryDefaultImpl() {
sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write
waitResponse();
sendAT(GF("+ICF=3,1")); // 8 data 0 parity 1 stop
waitResponse();
sendAT(GF("+ENPWRSAVE=0")); // Disable PWR save
waitResponse();
sendAT(GF("+XISP=0")); // Use internal stack
waitResponse();
sendAT(GF("&W")); // Write configuration
return waitResponse() == 1;
}
/*
* Power functions
*/
protected:
bool restartImpl(const char* pin = NULL) {
if (!testAT()) { return false; }
if (!setPhoneFunctionality(15)) { return false; }
// MODEM:STARTUP
waitResponse(60000L, GF(GSM_NL "+PBREADY" GSM_NL));
return init(pin);
}
bool powerOffImpl() {
sendAT(GF("+CPWROFF"));
return waitResponse(3000L) == 1;
}
bool sleepEnableImpl(bool enable = true) {
sendAT(GF("+ENPWRSAVE="), enable);
return waitResponse() == 1;
}
bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) {
sendAT(GF("+CFUN="), fun, reset ? ",1" : "");
return waitResponse(10000L) == 1;
}
/*
* Generic network functions
*/
public:
RegStatus getRegistrationStatus() {
return (RegStatus)getRegistrationStatusXREG("CREG");
}
protected:
bool isNetworkConnectedImpl() {
RegStatus s = getRegistrationStatus();
return (s == REG_OK_HOME || s == REG_OK_ROAMING);
}
String getLocalIPImpl() {
sendAT(GF("+XIIC?"));
if (waitResponse(GF(GSM_NL "+XIIC:")) != 1) { return ""; }
streamSkipUntil(',');
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
gprsDisconnect();
sendAT(GF("+XISP=0"));
waitResponse();
sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"');
waitResponse();
if (!user) user = "";
if (!pwd) pwd = "";
sendAT(GF("+XGAUTH=1,1,\""), user, GF("\",\""), pwd, GF("\""));
waitResponse();
sendAT(GF("+XIIC=1"));
waitResponse();
const uint32_t timeout_ms = 60000L;
for (uint32_t start = millis(); millis() - start < timeout_ms;) {
if (isGprsConnected()) {
// goto set_dns; // TODO
return true;
}
delay(500);
}
return false;
// set_dns: // TODO
// sendAT(GF("+DNSSERVER=1,8.8.8.8"));
// waitResponse();
//
// sendAT(GF("+DNSSERVER=2,8.8.4.4"));
// waitResponse();
return true;
}
bool gprsDisconnectImpl() {
// TODO(?): There is no command in AT command set
// XIIC=0 does not work
return true;
}
bool isGprsConnectedImpl() {
sendAT(GF("+XIIC?"));
if (waitResponse(GF(GSM_NL "+XIIC:")) != 1) { return false; }
int8_t res = streamGetIntBefore(',');
waitResponse();
return res == 1;
}
/*
* SIM card functions
*/
protected:
// Able to follow all SIM card functions as inherited from the template
/*
* Messaging functions
*/
protected:
bool sendSMS_UTF16Impl(const String& number, const void* text,
size_t len) TINY_GSM_ATTR_NOT_AVAILABLE;
/*
* Time functions
*/
protected:
// Can follow the standard CCLK function in the template
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t mux, bool,
int timeout_s = 75) {
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
for (int i = 0; i < 3; i++) { // TODO(?): no need for loop?
String ip = dnsIpQuery(host);
sendAT(GF("+TCPSETUP="), mux, GF(","), ip, GF(","), port);
int8_t rsp = waitResponse(timeout_ms, GF(",OK" GSM_NL),
GF(",FAIL" GSM_NL),
GF("+TCPSETUP:Error" GSM_NL));
if (1 == rsp) {
return true;
} else if (3 == rsp) {
sendAT(GF("+TCPCLOSE="), mux);
waitResponse();
}
delay(1000);
}
return false;
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
sendAT(GF("+TCPSEND="), mux, ',', (uint16_t)len);
if (waitResponse(GF(">")) != 1) { return 0; }
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.write(static_cast<char>(0x0D));
stream.flush();
if (waitResponse(30000L, GF(GSM_NL "+TCPSEND:")) != 1) { return 0; }
streamSkipUntil('\n');
return len;
}
bool modemGetConnected(uint8_t mux) {
sendAT(GF("+CIPSTATUS="), mux);
int8_t res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""),
GF(",\"CLOSING\""), GF(",\"INITIAL\""));
waitResponse();
return 1 == res;
}
String dnsIpQuery(const char* host) {
sendAT(GF("+DNS=\""), host, GF("\""));
if (waitResponse(10000L, GF(GSM_NL "+DNS:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse(GF("+DNS:OK" GSM_NL));
res.trim();
return res;
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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(64);
uint8_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)) {
#if defined TINY_GSM_DEBUG
if (r3 == GFP(GSM_CME_ERROR)) {
streamSkipUntil('\n'); // Read out the error
}
#endif
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF("+TCPRECV:"))) {
int8_t mux = streamGetIntBefore(',');
int16_t len = streamGetIntBefore(',');
int16_t len_orig = len;
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
if (len > sockets[mux]->rx.free()) {
DBG("### Buffer overflow: ", len, "->", sockets[mux]->rx.free());
} else {
DBG("### Got: ", len, "->", sockets[mux]->rx.free());
}
while (len--) { moveCharFromStreamToFifo(mux); }
// TODO(?): Handle lost characters
if (len_orig > sockets[mux]->available()) {
DBG("### Fewer characters received than expected: ",
sockets[mux]->available(), " vs ", len_orig);
}
}
data = "";
} else if (data.endsWith(GF("+TCPCLOSE:"))) {
int8_t mux = streamGetIntBefore(',');
streamSkipUntil('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->sock_connected = false;
}
data = "";
DBG("### Closed: ", mux);
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5);
}
public:
Stream& stream;
protected:
GsmClientM590* sockets[TINY_GSM_MUX_COUNT];
const char* gsmNL = GSM_NL;
};
#endif // SRC_TINYGSMCLIENTM590_H_

View File

@ -0,0 +1,636 @@
/**
* @file TinyGsmClientM95.h
* @author Volodymyr Shymanskyy, Pacman Pereira, and Replicade Ltd.
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy, (c)2017 Replicade Ltd.
* <http://www.replicade.com>
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTM95_H_
#define SRC_TINYGSMCLIENTM95_H_
// #pragma message("TinyGSM: TinyGsmClientM95")
// #define TINY_GSM_DEBUG Serial
#define TINY_GSM_MUX_COUNT 6
#define TINY_GSM_BUFFER_READ_NO_CHECK
#include "TinyGsmBattery.tpp"
#include "TinyGsmCalling.tpp"
#include "TinyGsmGPRS.tpp"
#include "TinyGsmModem.tpp"
#include "TinyGsmSMS.tpp"
#include "TinyGsmTCP.tpp"
#include "TinyGsmTemperature.tpp"
#include "TinyGsmTime.tpp"
#define GSM_NL "\r\n"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
#if defined TINY_GSM_DEBUG
static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:";
static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:";
#endif
enum RegStatus {
REG_NO_RESULT = -1,
REG_UNREGISTERED = 0,
REG_SEARCHING = 2,
REG_DENIED = 3,
REG_OK_HOME = 1,
REG_OK_ROAMING = 5,
REG_UNKNOWN = 4,
};
class TinyGsmM95 : public TinyGsmModem<TinyGsmM95>,
public TinyGsmGPRS<TinyGsmM95>,
public TinyGsmTCP<TinyGsmM95, TINY_GSM_MUX_COUNT>,
public TinyGsmCalling<TinyGsmM95>,
public TinyGsmSMS<TinyGsmM95>,
public TinyGsmTime<TinyGsmM95>,
public TinyGsmBattery<TinyGsmM95>,
public TinyGsmTemperature<TinyGsmM95> {
friend class TinyGsmModem<TinyGsmM95>;
friend class TinyGsmGPRS<TinyGsmM95>;
friend class TinyGsmTCP<TinyGsmM95, TINY_GSM_MUX_COUNT>;
friend class TinyGsmCalling<TinyGsmM95>;
friend class TinyGsmSMS<TinyGsmM95>;
friend class TinyGsmTime<TinyGsmM95>;
friend class TinyGsmBattery<TinyGsmM95>;
friend class TinyGsmTemperature<TinyGsmM95>;
/*
* Inner Client
*/
public:
class GsmClientM95 : public GsmClient {
friend class TinyGsmM95;
public:
GsmClientM95() {}
explicit GsmClientM95(TinyGsmM95& modem, uint8_t mux = 0) {
init(&modem, mux);
}
bool init(TinyGsmM95* modem, uint8_t mux = 0) {
this->at = modem;
sock_available = 0;
sock_connected = false;
if (mux < TINY_GSM_MUX_COUNT) {
this->mux = mux;
} else {
this->mux = (mux % TINY_GSM_MUX_COUNT);
}
at->sockets[this->mux] = this;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, false, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void stop(uint32_t maxWaitMs) {
uint32_t startMillis = millis();
dumpModemBuffer(maxWaitMs);
at->sendAT(GF("+QICLOSE="), mux);
sock_connected = false;
at->waitResponse((maxWaitMs - (millis() - startMillis)), GF("CLOSED"),
GF("CLOSE OK"), GF("ERROR"));
}
void stop() override {
stop(75000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
/*
class GsmClientSecureM95 : public GsmClientM95
{
public:
GsmClientSecure() {}
GsmClientSecure(TinyGsmm95& modem, uint8_t mux = 0)
: GsmClient(modem, mux)
{}
public:
int connect(const char* host, uint16_t port, int timeout_s) override {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, true, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
};
*/
/*
* Constructor
*/
public:
explicit TinyGsmM95(Stream& stream) : stream(stream) {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientM95"));
if (!testAT()) { return false; }
// sendAT(GF("&FZ")); // Factory + Reset
// waitResponse();
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
#ifdef TINY_GSM_DEBUG
sendAT(GF("+CMEE=2")); // turn on verbose error codes
#else
sendAT(GF("+CMEE=0")); // turn off error codes
#endif
waitResponse();
DBG(GF("### Modem:"), getModemName());
// Enable network time synchronization
sendAT(GF("+QNITZ=1"));
if (waitResponse(10000L) != 1) { return false; }
SimStatus ret = getSimStatus();
// if the sim isn't ready and a pin has been provided, try to unlock the sim
if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
simUnlock(pin);
return (getSimStatus() == SIM_READY);
} else {
// if the sim is ready, or it's locked but no pin has been provided,
// return true
return (ret == SIM_READY || ret == SIM_LOCKED);
}
}
/*
* Power functions
*/
protected:
bool restartImpl(const char* pin = NULL) {
if (!testAT()) { return false; }
sendAT(GF("+CFUN=0"));
if (waitResponse(10000L, GF("NORMAL POWER DOWN"), GF("OK"), GF("FAIL")) ==
3) {
return false;
}
sendAT(GF("+CFUN=1"));
if (waitResponse(10000L, GF("Call Ready"), GF("OK"), GF("FAIL")) == 3) {
return false;
}
return init(pin);
}
bool powerOffImpl() {
sendAT(GF("+QPOWD=1"));
return waitResponse(300, GF("NORMAL POWER DOWN")) == 1;
}
// When entering into sleep mode is enabled, DTR is pulled up, and WAKEUP_IN
// is pulled up, the module can directly enter into sleep mode.If entering
// into sleep mode is enabled, DTR is pulled down, and WAKEUP_IN is pulled
// down, there is a need to pull the DTR pin and the WAKEUP_IN pin up first,
// and then the module can enter into sleep mode.
bool sleepEnableImpl(bool enable = true) {
sendAT(GF("+QSCLK="), enable);
return waitResponse() == 1;
}
bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false)
TINY_GSM_ATTR_NOT_IMPLEMENTED;
/*
* Generic network functions
*/
public:
RegStatus getRegistrationStatus() {
return (RegStatus)getRegistrationStatusXREG("CREG");
}
protected:
bool isNetworkConnectedImpl() {
RegStatus s = getRegistrationStatus();
return (s == REG_OK_HOME || s == REG_OK_ROAMING);
}
void setHostFormat(bool useDottedQuad) {
if (useDottedQuad) {
sendAT(GF("+QIDNSIP=0"));
} else {
sendAT(GF("+QIDNSIP=1"));
}
waitResponse();
}
String getLocalIPImpl() {
sendAT(GF("+QILOCIP"));
streamSkipUntil('\n');
String res = stream.readStringUntil('\n');
res.trim();
return res;
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
gprsDisconnect();
// select foreground context 0 = VIRTUAL_UART_1
sendAT(GF("+QIFGCNT=0"));
if (waitResponse() != 1) { return false; }
// Select GPRS (=1) as the Bearer
sendAT(GF("+QICSGP=1,\""), apn, GF("\",\""), user, GF("\",\""), pwd,
GF("\""));
if (waitResponse() != 1) { return false; }
// Select TCP/IP transfer mode - NOT transparent mode
sendAT(GF("+QIMODE=0"));
if (waitResponse() != 1) { return false; }
// Enable multiple TCP/IP connections
sendAT(GF("+QIMUX=1"));
if (waitResponse() != 1) { return false; }
// Start TCPIP Task and Set APN, User Name and Password
sendAT("+QIREGAPP=\"", apn, "\",\"", user, "\",\"", pwd, "\"");
if (waitResponse() != 1) { return false; }
// Activate GPRS/CSD Context
sendAT(GF("+QIACT"));
if (waitResponse(60000L) != 1) { return false; }
// Check that we have a local IP address
if (localIP() == IPAddress(0, 0, 0, 0)) { return false; }
// Set Method to Handle Received TCP/IP Data
// Mode = 1 - Output a notification when data is received
// +QIRDI: <id>,<sc>,<sid>
sendAT(GF("+QINDI=1"));
if (waitResponse() != 1) { return false; }
// // Request an IP header for received data
// // "IPD(data length):"
// sendAT(GF("+QIHEAD=1"));
// if (waitResponse() != 1) {
// return false;
// }
//
// // Do NOT show the IP address of the sender when receiving data
// // The format to show the address is: RECV FROM: <IP ADDRESS>:<PORT>
// sendAT(GF("+QISHOWRA=0"));
// if (waitResponse() != 1) {
// return false;
// }
//
// // Do NOT show the protocol type at the end of the header for received
// data
// // IPD(data length)(TCP/UDP):
// sendAT(GF("+QISHOWPT=0"));
// if (waitResponse() != 1) {
// return false;
// }
//
// // Do NOT show the destination address before receiving data
// // The format to show the address is: TO:<IP ADDRESS>
// sendAT(GF("+QISHOWLA=0"));
// if (waitResponse() != 1) {
// return false;
// }
return true;
}
bool gprsDisconnectImpl() {
sendAT(GF("+QIDEACT")); // Deactivate the bearer context
return waitResponse(60000L, GF("DEACT OK"), GF("ERROR")) == 1;
}
/*
* SIM card functions
*/
protected:
String getSimCCIDImpl() {
sendAT(GF("+QCCID"));
if (waitResponse(GF(GSM_NL "+QCCID:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
/*
* Phone Call functions
*/
protected:
// Can follow all of the phone call functions from the template
/*
* Messaging functions
*/
protected:
// Can follow all template functions
public:
/** Delete all SMS */
bool deleteAllSMS() {
sendAT(GF("+QMGDA=6"));
if (waitResponse(waitResponse(60000L, GF("OK"), GF("ERROR")) == 1)) {
return true;
}
return false;
}
/*
* Time functions
*/
protected:
// Can follow the standard CCLK function in the template
/*
* Battery functions
*/
// Can follow the battery functions in the template
/*
* Temperature functions
*/
protected:
float getTemperatureImpl() {
sendAT(GF("+QTEMP?"));
if (waitResponse(GF(GSM_NL "+QTEMP:")) != 1) {
return static_cast<float>(-9999);
}
streamSkipUntil(','); // Skip mode
// Read charge of thermistor
// milliVolts = streamGetIntBefore(',');
streamSkipUntil(','); // Skip thermistor charge
float temp = streamGetFloatBefore('\n');
// Wait for final OK
waitResponse();
return temp;
}
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t mux,
bool ssl = false, int timeout_s = 75) {
if (ssl) { DBG("SSL not yet supported on this module!"); }
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
sendAT(GF("+QIOPEN="), mux, GF(",\""), GF("TCP"), GF("\",\""), host,
GF("\","), port);
int8_t rsp = waitResponse(timeout_ms, GF("CONNECT OK" GSM_NL),
GF("CONNECT FAIL" GSM_NL),
GF("ALREADY CONNECT" GSM_NL));
return (1 == rsp);
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
sendAT(GF("+QISEND="), mux, ',', (uint16_t)len);
if (waitResponse(GF(">")) != 1) { return 0; }
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
if (waitResponse(GF(GSM_NL "SEND OK")) != 1) { return 0; }
// bool allAcknowledged = false;
// // bool failed = false;
// while ( !allAcknowledged ) {
// sendAT( GF("+QISACK"));
// if (waitResponse(5000L, GF(GSM_NL "+QISACK:")) != 1) {
// return -1;
// } else {
// streamSkipUntil(','); // Skip total length sent on connection
// streamSkipUntil(','); // Skip length already acknowledged by remote
// // Make sure the total length un-acknowledged is 0
// if ( streamGetIntBefore('\n') == 0 ) {
// allAcknowledged = true;
// }
// }
// }
// waitResponse(5000L);
return len; // TODO(?): get len/ack properly
}
size_t modemRead(size_t size, uint8_t mux) {
if (!sockets[mux]) return 0;
// TODO(?): Does this work????
// AT+QIRD=<id>,<sc>,<sid>,<len>
// id = GPRS context number = 0, set in GPRS connect
// sc = role in connection = 1, client of connection
// sid = index of connection = mux
// len = maximum length of data to retrieve
sendAT(GF("+QIRD=0,1,"), mux, ',', (uint16_t)size);
// If it replies only OK for the write command, it means there is no
// received data in the buffer of the connection.
int8_t res = waitResponse(GF("+QIRD:"), GFP(GSM_OK), GFP(GSM_ERROR));
if (res == 1) {
streamSkipUntil(':'); // skip IP address
streamSkipUntil(','); // skip port
streamSkipUntil(','); // skip connection type (TCP/UDP)
// read the real length of the retrieved data
uint16_t len = streamGetIntBefore('\n');
// We have no way of knowing in advance how much data will be in the
// buffer so when data is received we always assume the buffer is
// completely full. Chances are, this is not true and there's really not
// that much there. In that case, make sure we make sure we re-set the
// amount of data available.
if (len < size) { sockets[mux]->sock_available = len; }
for (uint16_t i = 0; i < len; i++) {
moveCharFromStreamToFifo(mux);
sockets[mux]->sock_available--;
// ^^ One less character available after moving from modem's FIFO to our
// FIFO
}
waitResponse(); // ends with an OK
// DBG("### READ:", len, "from", mux);
return len;
} else {
sockets[mux]->sock_available = 0;
return 0;
}
}
// Not possible to check the number of characters remaining in buffer
size_t modemGetAvailable(uint8_t) {
return 0;
}
bool modemGetConnected(uint8_t mux) {
sendAT(GF("+QISTATE=1,"), mux);
// +QISTATE: 0,"TCP","151.139.237.11",80,5087,4,1,0,0,"uart1"
if (waitResponse(GF("+QISTATE:")) != 1) { return false; }
streamSkipUntil(','); // Skip mux
streamSkipUntil(','); // Skip socket type
streamSkipUntil(','); // Skip remote ip
streamSkipUntil(','); // Skip remote port
streamSkipUntil(','); // Skip local port
int8_t res = streamGetIntBefore(','); // socket state
waitResponse();
// 0 Initial, 1 Opening, 2 Connected, 3 Listening, 4 Closing
return 2 == res;
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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(64);
uint8_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)) {
#if defined TINY_GSM_DEBUG
if (r3 == GFP(GSM_CME_ERROR)) {
streamSkipUntil('\n'); // Read out the error
}
#endif
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF(GSM_NL "+QIRDI:"))) {
streamSkipUntil(','); // Skip the context
streamSkipUntil(','); // Skip the role
int8_t mux = streamGetIntBefore('\n');
// DBG("### Got Data:", mux);
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
// We have no way of knowing how much data actually came in, so
// we set the value to 1500, the maximum possible size.
sockets[mux]->sock_available = 1500;
}
data = "";
} else if (data.endsWith(GF("CLOSED" GSM_NL))) {
int8_t nl = data.lastIndexOf(GSM_NL, data.length() - 8);
int8_t coma = data.indexOf(',', nl + 2);
int8_t mux = data.substring(nl + 2, coma).toInt();
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->sock_connected = false;
}
data = "";
DBG("### Closed: ", mux);
} else if (data.endsWith(GF("+QNITZ:"))) {
streamSkipUntil('\n'); // URC for time sync
data = "";
DBG("### Network time updated.");
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5);
}
public:
Stream& stream;
protected:
GsmClientM95* sockets[TINY_GSM_MUX_COUNT];
const char* gsmNL = GSM_NL;
};
#endif // SRC_TINYGSMCLIENTM95_H_

View File

@ -0,0 +1,622 @@
/**
* @file TinyGsmClientMC60.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*
* @MC60 support added by Tamas Dajka 2017.10.15 - with fixes by Sara Damiano
*
*/
#ifndef SRC_TINYGSMCLIENTMC60_H_
#define SRC_TINYGSMCLIENTMC60_H_
// #pragma message("TinyGSM: TinyGsmClientMC60")
// #define TINY_GSM_DEBUG Serial
#define TINY_GSM_MUX_COUNT 6
#define TINY_GSM_BUFFER_READ_NO_CHECK
#include "TinyGsmBattery.tpp"
#include "TinyGsmCalling.tpp"
#include "TinyGsmGPRS.tpp"
#include "TinyGsmModem.tpp"
#include "TinyGsmSMS.tpp"
#include "TinyGsmTCP.tpp"
#include "TinyGsmTime.tpp"
#define GSM_NL "\r\n"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
#if defined TINY_GSM_DEBUG
static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:";
static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:";
#endif
enum RegStatus {
REG_NO_RESULT = -1,
REG_UNREGISTERED = 0,
REG_SEARCHING = 2,
REG_DENIED = 3,
REG_OK_HOME = 1,
REG_OK_ROAMING = 5,
REG_UNKNOWN = 4,
};
class TinyGsmMC60 : public TinyGsmModem<TinyGsmMC60>,
public TinyGsmGPRS<TinyGsmMC60>,
public TinyGsmTCP<TinyGsmMC60, TINY_GSM_MUX_COUNT>,
public TinyGsmCalling<TinyGsmMC60>,
public TinyGsmSMS<TinyGsmMC60>,
public TinyGsmTime<TinyGsmMC60>,
public TinyGsmBattery<TinyGsmMC60> {
friend class TinyGsmModem<TinyGsmMC60>;
friend class TinyGsmGPRS<TinyGsmMC60>;
friend class TinyGsmTCP<TinyGsmMC60, TINY_GSM_MUX_COUNT>;
friend class TinyGsmCalling<TinyGsmMC60>;
friend class TinyGsmSMS<TinyGsmMC60>;
friend class TinyGsmTime<TinyGsmMC60>;
friend class TinyGsmBattery<TinyGsmMC60>;
/*
* Inner Client
*/
public:
class GsmClientMC60 : public GsmClient {
friend class TinyGsmMC60;
public:
GsmClientMC60() {}
explicit GsmClientMC60(TinyGsmMC60& modem, uint8_t mux = 0) {
init(&modem, mux);
}
bool init(TinyGsmMC60* modem, uint8_t mux = 0) {
this->at = modem;
sock_available = 0;
sock_connected = false;
if (mux < TINY_GSM_MUX_COUNT) {
this->mux = mux;
} else {
this->mux = (mux % TINY_GSM_MUX_COUNT);
}
at->sockets[this->mux] = this;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, false, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void stop(uint32_t maxWaitMs) {
uint32_t startMillis = millis();
dumpModemBuffer(maxWaitMs);
at->sendAT(GF("+QICLOSE="), mux);
sock_connected = false;
at->waitResponse((maxWaitMs - (millis() - startMillis)), GF("CLOSED"),
GF("CLOSE OK"), GF("ERROR"));
}
void stop() override {
stop(75000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
/*
class GsmClientSecureMC60 : public GsmClientMC60
{
public:
GsmClientSecure() {}
GsmClientSecure(TinyGsmMC60& modem, uint8_t mux = 0)
: GsmClient(modem, mux)
{}
public:
int connect(const char* host, uint16_t port, int timeout_s) override {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, true, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
};
*/
/*
* Constructor
*/
public:
explicit TinyGsmMC60(Stream& stream) : stream(stream) {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientMC60"));
if (!testAT()) { return false; }
// sendAT(GF("&FZ")); // Factory + Reset
// waitResponse();
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
#ifdef TINY_GSM_DEBUG
sendAT(GF("+CMEE=2")); // turn on verbose error codes
#else
sendAT(GF("+CMEE=0")); // turn off error codes
#endif
waitResponse();
DBG(GF("### Modem:"), getModemName());
// Enable network time synchronization
sendAT(GF("+QNITZ=1"));
if (waitResponse(10000L) != 1) { return false; }
SimStatus ret = getSimStatus();
// if the sim isn't ready and a pin has been provided, try to unlock the sim
if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
simUnlock(pin);
return (getSimStatus() == SIM_READY);
} else {
// if the sim is ready, or it's locked but no pin has been provided,
// return true
return (ret == SIM_READY || ret == SIM_LOCKED);
}
}
/*
* Power functions
*/
protected:
bool restartImpl(const char* pin = NULL) {
if (!testAT()) { return false; }
if (!setPhoneFunctionality(0)) { return false; }
if (!setPhoneFunctionality(1, true)) { return false; }
delay(3000);
return init(pin);
}
bool powerOffImpl() {
sendAT(GF("+QPOWD=1"));
return waitResponse(GF("NORMAL POWER DOWN")) == 1;
}
// When entering into sleep mode is enabled, DTR is pulled up, and WAKEUP_IN
// is pulled up, the module can directly enter into sleep mode.If entering
// into sleep mode is enabled, DTR is pulled down, and WAKEUP_IN is pulled
// down, there is a need to pull the DTR pin and the WAKEUP_IN pin up first,
// and then the module can enter into sleep mode.
bool sleepEnableImpl(bool enable = true) {
sendAT(GF("+QSCLK="), enable);
return waitResponse() == 1;
}
bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) {
sendAT(GF("+CFUN="), fun, reset ? ",1" : "");
return waitResponse(10000L) == 1;
}
/*
* Generic network functions
*/
public:
RegStatus getRegistrationStatus() {
return (RegStatus)getRegistrationStatusXREG("CREG");
}
protected:
bool isNetworkConnectedImpl() {
RegStatus s = getRegistrationStatus();
return (s == REG_OK_HOME || s == REG_OK_ROAMING);
}
String getLocalIPImpl() {
sendAT(GF("+QILOCIP"));
streamSkipUntil('\n');
String res = stream.readStringUntil('\n');
res.trim();
return res;
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
gprsDisconnect();
// select foreground context 0 = VIRTUAL_UART_1
sendAT(GF("+QIFGCNT=0"));
if (waitResponse() != 1) { return false; }
// Select GPRS (=1) as the Bearer
sendAT(GF("+QICSGP=1,\""), apn, GF("\",\""), user, GF("\",\""), pwd,
GF("\""));
if (waitResponse() != 1) { return false; }
// Define PDP context - is this necessary?
sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"');
waitResponse();
// Activate PDP context - is this necessary?
sendAT(GF("+CGACT=1,1"));
waitResponse(60000L);
// Select TCP/IP transfer mode - NOT transparent mode
sendAT(GF("+QIMODE=0"));
if (waitResponse() != 1) { return false; }
// Enable multiple TCP/IP connections
sendAT(GF("+QIMUX=1"));
if (waitResponse() != 1) { return false; }
// Modem is used as a client
sendAT(GF("+QISRVC=1"));
if (waitResponse() != 1) { return false; }
// Start TCPIP Task and Set APN, User Name and Password
sendAT("+QIREGAPP=\"", apn, "\",\"", user, "\",\"", pwd, "\"");
if (waitResponse() != 1) { return false; }
// Activate GPRS/CSD Context
sendAT(GF("+QIACT"));
if (waitResponse(60000L) != 1) { return false; }
// Check that we have a local IP address
if (localIP() == IPAddress(0, 0, 0, 0)) { return false; }
// Set Method to Handle Received TCP/IP Data
// Mode=2 - Output a notification statement:
// +QIRDI: <id>,<sc>,<sid>,<num>,<len>,< tlen>
sendAT(GF("+QINDI=2"));
if (waitResponse() != 1) { return false; }
return true;
}
bool gprsDisconnectImpl() {
sendAT(GF("+QIDEACT")); // Deactivate the bearer context
return waitResponse(60000L, GF("DEACT OK"), GF("ERROR")) == 1;
}
/*
* SIM card functions
*/
protected:
SimStatus getSimStatusImpl(uint32_t timeout_ms = 10000L) {
for (uint32_t start = millis(); millis() - start < timeout_ms;) {
sendAT(GF("+CPIN?"));
if (waitResponse(GF(GSM_NL "+CPIN:")) != 1) {
delay(1000);
continue;
}
int8_t status = waitResponse(GF("READY"), GF("SIM PIN"), GF("SIM PUK"),
GF("NOT INSERTED"), GF("PH_SIM PIN"),
GF("PH_SIM PUK"));
waitResponse();
switch (status) {
case 2:
case 3: return SIM_LOCKED;
case 5:
case 6: return SIM_ANTITHEFT_LOCKED;
case 1: return SIM_READY;
default: return SIM_ERROR;
}
}
return SIM_ERROR;
}
/*
* Phone Call functions
*/
protected:
// Can follow all of the phone call functions from the template
/*
* Messaging functions
*/
protected:
// Can follow all template functions
public:
/** Delete all SMS */
bool deleteAllSMS() {
sendAT(GF("+QMGDA=6"));
if (waitResponse(waitResponse(60000L, GF("OK"), GF("ERROR")) == 1)) {
return true;
}
return false;
}
/*
* Time functions
*/
protected:
// Can follow the standard CCLK function in the template
/*
* Battery functions
*/
// Can follow battery functions as in the template
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t mux,
bool ssl = false, int timeout_s = 75) {
if (ssl) { DBG("SSL not yet supported on this module!"); }
// By default, MC60 expects IP address as 'host' parameter.
// If it is a domain name, "AT+QIDNSIP=1" should be executed.
// "AT+QIDNSIP=0" is for dotted decimal IP address.
IPAddress addr;
sendAT(GF("+QIDNSIP="),
(TinyGsmIpFromString(host) == IPAddress(0, 0, 0, 0) ? 0 : 1));
if (waitResponse() != 1) { return false; }
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
sendAT(GF("+QIOPEN="), mux, GF(",\""), GF("TCP"), GF("\",\""), host,
GF("\","), port);
int8_t rsp = waitResponse(timeout_ms, GF("CONNECT OK" GSM_NL),
GF("CONNECT FAIL" GSM_NL),
GF("ALREADY CONNECT" GSM_NL));
return (1 == rsp);
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
sendAT(GF("+QISEND="), mux, ',', (uint16_t)len);
if (waitResponse(GF(">")) != 1) { return 0; }
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
if (waitResponse(GF(GSM_NL "SEND OK")) != 1) { return 0; }
bool allAcknowledged = false;
// bool failed = false;
while (!allAcknowledged) {
sendAT(GF("+QISACK="), mux); // If 'mux' is not specified, MC60 returns
// 'ERRROR' (for QIMUX == 1)
if (waitResponse(5000L, GF(GSM_NL "+QISACK:")) != 1) {
return -1;
} else {
streamSkipUntil(','); /** Skip total */
streamSkipUntil(','); /** Skip acknowledged data size */
if (streamGetIntBefore('\n') == 0) { allAcknowledged = true; }
}
}
waitResponse(5000L);
// streamSkipUntil(','); // Skip mux
// return streamGetIntBefore('\n');
return len; // TODO(?): verify len/ack
}
size_t modemRead(size_t size, uint8_t mux) {
if (!sockets[mux]) return 0;
// TODO(?): Does this even work????
// AT+QIRD=<id>,<sc>,<sid>,<len>
// id = GPRS context number = 0, set in GPRS connect
// sc = role in connection = 1, client of connection
// sid = index of connection = mux
// len = maximum length of data to retrieve
sendAT(GF("+QIRD=0,1,"), mux, ',', (uint16_t)size);
// If it replies only OK for the write command, it means there is no
// received data in the buffer of the connection.
int8_t res = waitResponse(GF("+QIRD:"), GFP(GSM_OK), GFP(GSM_ERROR));
if (res == 1) {
streamSkipUntil(':'); // skip IP address
streamSkipUntil(','); // skip port
streamSkipUntil(','); // skip connection type (TCP/UDP)
// read the real length of the retrieved data
uint16_t len = streamGetIntBefore('\n');
// It's possible that the real length available is less than expected
// This is quite likely if the buffer is broken into packets - which may
// be different sizes.
// If so, make sure we make sure we re-set the amount of data available.
if (len < size) { sockets[mux]->sock_available = len; }
for (uint16_t i = 0; i < len; i++) {
moveCharFromStreamToFifo(mux);
sockets[mux]->sock_available--;
// ^^ One less character available after moving from modem's FIFO to our
// FIFO
}
waitResponse(); // ends with an OK
// DBG("### READ:", len, "from", mux);
return len;
} else {
sockets[mux]->sock_available = 0;
return 0;
}
}
// Not possible to check the number of characters remaining in buffer
size_t modemGetAvailable(uint8_t) {
return 0;
}
bool modemGetConnected(uint8_t mux) {
sendAT(GF("+QISTATE=1,"), mux);
// +QISTATE: 0,"TCP","151.139.237.11",80,5087,4,1,0,0,"uart1"
if (waitResponse(GF("+QISTATE:")) != 1) { return false; }
streamSkipUntil(','); // Skip mux
streamSkipUntil(','); // Skip socket type
streamSkipUntil(','); // Skip remote ip
streamSkipUntil(','); // Skip remote port
streamSkipUntil(','); // Skip local port
int8_t res = streamGetIntBefore(','); // socket state
waitResponse();
// 0 Initial, 1 Opening, 2 Connected, 3 Listening, 4 Closing
return 2 == res;
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL, GsmConstStr r6 = 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();
String r6s(r6); r6s.trim();
DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s, ",", r6s);*/
data.reserve(64);
uint8_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)) {
#if defined TINY_GSM_DEBUG
if (r3 == GFP(GSM_CME_ERROR)) {
streamSkipUntil('\n'); // Read out the error
}
#endif
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (r6 && data.endsWith(r6)) {
index = 6;
goto finish;
} else if (data.endsWith(
GF(GSM_NL "+QIRDI:"))) { // TODO(?): QIRD? or QIRDI?
// +QIRDI: <id>,<sc>,<sid>,<num>,<len>,< tlen>
streamSkipUntil(','); // Skip the context
streamSkipUntil(','); // Skip the role
// read the connection id
int8_t mux = streamGetIntBefore(',');
// read the number of packets in the buffer
int8_t num_packets = streamGetIntBefore(',');
// read the length of the current packet
streamSkipUntil(
','); // Skip the length of the current package in the buffer
int16_t len_total =
streamGetIntBefore('\n'); // Total length of all packages
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux] &&
num_packets >= 0 && len_total >= 0) {
sockets[mux]->sock_available = len_total;
}
data = "";
// DBG("### Got Data:", len_total, "on", mux);
} else if (data.endsWith(GF("CLOSED" GSM_NL))) {
int8_t nl = data.lastIndexOf(GSM_NL, data.length() - 8);
int8_t coma = data.indexOf(',', nl + 2);
int8_t mux = data.substring(nl + 2, coma).toInt();
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->sock_connected = false;
}
data = "";
DBG("### Closed: ", mux);
} else if (data.endsWith(GF("+QNITZ:"))) {
streamSkipUntil('\n'); // URC for time sync
DBG("### Network time updated.");
data = "";
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL, GsmConstStr r6 = NULL) {
String data;
return waitResponse(timeout_ms, data, r1, r2, r3, r4, r5, r6);
}
int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL, GsmConstStr r6 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5, r6);
}
public:
Stream& stream;
protected:
GsmClientMC60* sockets[TINY_GSM_MUX_COUNT];
const char* gsmNL = GSM_NL;
};
#endif // SRC_TINYGSMCLIENTMC60_H_

View File

@ -0,0 +1,741 @@
/**
* @file TinyGsmClientSIM5360.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTSIM5360_H_
#define SRC_TINYGSMCLIENTSIM5360_H_
// #define TINY_GSM_DEBUG Serial
// #define TINY_GSM_USE_HEX
#define TINY_GSM_MUX_COUNT 10
#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE
#include "TinyGsmBattery.tpp"
#include "TinyGsmGPRS.tpp"
#include "TinyGsmGSMLocation.tpp"
#include "TinyGsmModem.tpp"
#include "TinyGsmSMS.tpp"
#include "TinyGsmTCP.tpp"
#include "TinyGsmTemperature.tpp"
#include "TinyGsmTime.tpp"
#include "TinyGsmNTP.tpp"
#define GSM_NL "\r\n"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
#if defined TINY_GSM_DEBUG
static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:";
static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:";
#endif
enum RegStatus {
REG_NO_RESULT = -1,
REG_UNREGISTERED = 0,
REG_SEARCHING = 2,
REG_DENIED = 3,
REG_OK_HOME = 1,
REG_OK_ROAMING = 5,
REG_UNKNOWN = 4,
};
class TinyGsmSim5360 : public TinyGsmModem<TinyGsmSim5360>,
public TinyGsmGPRS<TinyGsmSim5360>,
public TinyGsmTCP<TinyGsmSim5360, TINY_GSM_MUX_COUNT>,
public TinyGsmSMS<TinyGsmSim5360>,
public TinyGsmTime<TinyGsmSim5360>,
public TinyGsmNTP<TinyGsmSim5360>,
public TinyGsmGSMLocation<TinyGsmSim5360>,
public TinyGsmBattery<TinyGsmSim5360>,
public TinyGsmTemperature<TinyGsmSim5360> {
friend class TinyGsmModem<TinyGsmSim5360>;
friend class TinyGsmGPRS<TinyGsmSim5360>;
friend class TinyGsmTCP<TinyGsmSim5360, TINY_GSM_MUX_COUNT>;
friend class TinyGsmSMS<TinyGsmSim5360>;
friend class TinyGsmTime<TinyGsmSim5360>;
friend class TinyGsmNTP<TinyGsmSim5360>;
friend class TinyGsmGSMLocation<TinyGsmSim5360>;
friend class TinyGsmBattery<TinyGsmSim5360>;
friend class TinyGsmTemperature<TinyGsmSim5360>;
/*
* Inner Client
*/
public:
class GsmClientSim5360 : public GsmClient {
friend class TinyGsmSim5360;
public:
GsmClientSim5360() {}
explicit GsmClientSim5360(TinyGsmSim5360& modem, uint8_t mux = 0) {
init(&modem, mux);
}
bool init(TinyGsmSim5360* modem, uint8_t mux = 0) {
this->at = modem;
sock_available = 0;
prev_check = 0;
sock_connected = false;
got_data = false;
if (mux < TINY_GSM_MUX_COUNT) {
this->mux = mux;
} else {
this->mux = (mux % TINY_GSM_MUX_COUNT);
}
at->sockets[this->mux] = this;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, false, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void stop(uint32_t maxWaitMs) {
dumpModemBuffer(maxWaitMs);
at->sendAT(GF("+CIPCLOSE="), mux);
sock_connected = false;
at->waitResponse();
}
void stop() override {
stop(15000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
// TODO(?): Add SSL support
/*
class GsmClientSecureSim5360 : public GsmClientSim5360 {
public:
GsmClientSecureSim5360() {}
explicit GsmClientSecureSim5360(TinyGsmSim5360& modem, uint8_t mux = 0)
: GsmClientSim5360(modem, mux) {}
public:
int connect(const char* host, uint16_t port, int timeout_s) override {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, true, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
};
*/
/*
* Constructor
*/
public:
explicit TinyGsmSim5360(Stream& stream) : stream(stream) {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM5360"));
if (!testAT()) { return false; }
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
#ifdef TINY_GSM_DEBUG
sendAT(GF("+CMEE=2")); // turn on verbose error codes
#else
sendAT(GF("+CMEE=0")); // turn off error codes
#endif
waitResponse();
DBG(GF("### Modem:"), getModemName());
// Disable time and time zone URC's
sendAT(GF("+CTZR=0"));
if (waitResponse(10000L) != 1) { return false; }
// Enable automatic time zome update
sendAT(GF("+CTZU=1"));
if (waitResponse(10000L) != 1) { return false; }
SimStatus ret = getSimStatus();
// if the sim isn't ready and a pin has been provided, try to unlock the sim
if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
simUnlock(pin);
return (getSimStatus() == SIM_READY);
} else {
// if the sim is ready, or it's locked but no pin has been provided,
// return true
return (ret == SIM_READY || ret == SIM_LOCKED);
}
}
String getModemNameImpl() {
String name = "SIMCom SIM5360";
sendAT(GF("+CGMM"));
String res2;
if (waitResponse(1000L, res2) != 1) { return name; }
res2.replace(GSM_NL "OK" GSM_NL, "");
res2.replace("_", " ");
res2.trim();
name = res2;
DBG("### Modem:", name);
return name;
}
bool factoryDefaultImpl() { // these commands aren't supported
return false;
}
/*
* Power functions
*/
protected:
bool restartImpl(const char* pin = NULL) {
if (!testAT()) { return false; }
sendAT(GF("+REBOOT"));
// Should return an 'OK' after reboot command is sent
if (waitResponse(10000L) != 1) { return false; }
// After booting, modem sends out messages as each of its
// internal modules loads. The final message is "PB DONE".
if (waitResponse(40000L, GF(GSM_NL "PB DONE")) != 1) { return false; }
return init(pin);
}
bool powerOffImpl() {
sendAT(GF("+CPOF"));
return waitResponse() == 1;
}
bool radioOffImpl() {
if (!setPhoneFunctionality(4)) { return false; }
delay(3000);
return true;
}
bool sleepEnableImpl(bool enable = true) {
sendAT(GF("+CSCLK="), enable);
return waitResponse() == 1;
}
bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) {
sendAT(GF("+CFUN="), fun, reset ? ",1" : "");
return waitResponse(10000L) == 1;
}
/*
* Generic network functions
*/
public:
RegStatus getRegistrationStatus() {
return (RegStatus)getRegistrationStatusXREG("CGREG");
}
protected:
bool isNetworkConnectedImpl() {
RegStatus s = getRegistrationStatus();
return (s == REG_OK_HOME || s == REG_OK_ROAMING);
}
public:
String getNetworkModes() {
sendAT(GF("+CNMP=?"));
if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
return res;
}
int16_t getNetworkMode() {
sendAT(GF("+CNMP?"));
if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return false; }
int16_t mode = streamGetIntBefore('\n');
waitResponse();
return mode;
}
bool setNetworkMode(uint8_t mode) {
sendAT(GF("+CNMP="), mode);
return waitResponse() == 1;
}
String getLocalIPImpl() {
sendAT(GF("+IPADDR")); // Inquire Socket PDP address
// sendAT(GF("+CGPADDR=1")); // Show PDP address
String res;
if (waitResponse(10000L, res) != 1) { return ""; }
res.replace(GSM_NL "OK" GSM_NL, "");
res.replace(GSM_NL, "");
res.trim();
return res;
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
gprsDisconnect(); // Make sure we're not connected first
// Define the PDP context
// The CGDCONT commands set up the "external" PDP context
// Set the external authentication
if (user && strlen(user) > 0) {
sendAT(GF("+CGAUTH=1,0,\""), user, GF("\",\""), pwd, '"');
waitResponse();
}
// Define external PDP context 1
sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"', ",\"0.0.0.0\",0,0");
waitResponse();
// The CGSOCKCONT commands define the "embedded" PDP context for TCP/IP
// Define the socket PDP context
sendAT(GF("+CGSOCKCONT=1,\"IP\",\""), apn, '"');
waitResponse();
// Set the embedded authentication
if (user && strlen(user) > 0) {
sendAT(GF("+CSOCKAUTH=1,1,\""), user, "\",\"", pwd, '"');
waitResponse();
}
// Set active PDP context's profile number
// This ties the embedded TCP/IP application to the external PDP context
sendAT(GF("+CSOCKSETPN=1"));
waitResponse();
// Configure TCP parameters
// Select TCP/IP application mode (command mode)
sendAT(GF("+CIPMODE=0"));
waitResponse();
// Set Sending Mode - send without waiting for peer TCP ACK
sendAT(GF("+CIPSENDMODE=0"));
waitResponse();
// Configure socket parameters
// AT+CIPCCFG= <NmRetry>, <DelayTm>, <Ack>, <errMode>, <HeaderType>,
// <AsyncMode>, <TimeoutVal>
// NmRetry = number of retransmission to be made for an IP packet
// = 10 (default)
// DelayTm = number of milliseconds to delay before outputting received data
// = 0 (default)
// Ack = sets whether reporting a string "Send ok" = 0 (don't report)
// errMode = mode of reporting error result code = 0 (numberic values)
// HeaderType = which data header of receiving data in multi-client mode
// = 1 (+RECEIVE,<link num>,<data length>)
// AsyncMode = sets mode of executing commands
// = 0 (synchronous command executing)
// TimeoutVal = minimum retransmission timeout in milliseconds = 75000
sendAT(GF("+CIPCCFG=10,0,0,0,1,0,75000"));
if (waitResponse() != 1) { return false; }
// Configure timeouts for opening and closing sockets
// AT+CIPTIMEOUT=<netopen_timeout>, <cipopen_timeout>, <cipsend_timeout>
sendAT(GF("+CIPTIMEOUT="), 75000, ',', 15000, ',', 15000);
waitResponse();
// Start the socket service
// This activates and attaches to the external PDP context that is tied
// to the embedded context for TCP/IP (ie AT+CGACT=1,1 and AT+CGATT=1)
// Response may be an immediate "OK" followed later by "+NETOPEN: 0".
// We to ignore any immediate response and wait for the
// URC to show it's really connected.
sendAT(GF("+NETOPEN"));
if (waitResponse(75000L, GF(GSM_NL "+NETOPEN: 0")) != 1) { return false; }
return true;
}
bool gprsDisconnectImpl() {
// Close any open sockets
for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) {
GsmClientSim5360* sock = sockets[mux];
if (sock) { sock->stop(); }
}
// Stop the socket service
// Note: all sockets should be closed first - on 3G/4G models the sockets
// must be closed manually
sendAT(GF("+NETCLOSE"));
if (waitResponse(60000L, GF(GSM_NL "+NETCLOSE: 0")) != 1) { return false; }
return true;
}
bool isGprsConnectedImpl() {
sendAT(GF("+NETOPEN?"));
// May return +NETOPEN: 1, 0. We just confirm that the first number is 1
if (waitResponse(GF(GSM_NL "+NETOPEN: 1")) != 1) { return false; }
waitResponse();
sendAT(GF("+IPADDR")); // Inquire Socket PDP address
// sendAT(GF("+CGPADDR=1")); // Show PDP address
if (waitResponse() != 1) { return false; }
return true;
}
/*
* SIM card functions
*/
protected:
// Gets the CCID of a sim card via AT+CCID
String getSimCCIDImpl() {
sendAT(GF("+CICCID"));
if (waitResponse(GF(GSM_NL "+ICCID:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
/*
* Messaging functions
*/
protected:
// Follows all messaging functions per template
/*
* GSM Location functions
*/
protected:
// SIM5360 and SIM7100 can return a GSM-based location from CLBS as per the
// template; SIM5320 doesn't not appear to be able to
/*
* Time functions
*/
protected:
// Can follow the standard CCLK function in the template
/*
* NTP server functions
*/
// Can sync with server using CNTP as per template
/*
* Battery functions
*/
protected:
// SRGD Note: Returns voltage in VOLTS instead of millivolts
uint16_t getBattVoltageImpl() {
sendAT(GF("+CBC"));
if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return 0; }
streamSkipUntil(','); // Skip battery charge status
streamSkipUntil(','); // Skip battery charge level
// get voltage in VOLTS
float voltage = streamGetFloatBefore('\n');
// Wait for final OK
waitResponse();
// Return millivolts
uint16_t res = voltage * 1000;
return res;
}
// SRGD Note: Returns voltage in VOLTS instead of millivolts
bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent,
uint16_t& milliVolts) {
sendAT(GF("+CBC"));
if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return false; }
chargeState = streamGetIntBefore(',');
percent = streamGetIntBefore(',');
// get voltage in VOLTS
float voltage = streamGetFloatBefore('\n');
milliVolts = voltage * 1000;
// Wait for final OK
waitResponse();
return true;
}
/*
* Temperature functions
*/
protected:
// get temperature in degree celsius
float getTemperatureImpl() {
// Enable Temparature Reading
sendAT(GF("+CMTE=1"));
if (waitResponse() != 1) { return 0; }
// Get Temparature Value
sendAT(GF("+CMTE?"));
if (waitResponse(GF(GSM_NL "+CMTE:")) != 1) { return false; }
float res = streamGetFloatBefore('\n');
// Wait for final OK
waitResponse();
return res;
}
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t mux,
bool ssl = false, int timeout_s = 15) {
if (ssl) { DBG("SSL not yet supported on this module!"); }
// Make sure we'll be getting data manually on this connection
sendAT(GF("+CIPRXGET=1"));
if (waitResponse() != 1) { return false; }
// Establish a connection in multi-socket mode
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
sendAT(GF("+CIPOPEN="), mux, ',', GF("\"TCP"), GF("\",\""), host, GF("\","),
port);
// The reply is +CIPOPEN: ## of socket created
if (waitResponse(timeout_ms, GF(GSM_NL "+CIPOPEN:")) != 1) { return false; }
return true;
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len);
if (waitResponse(GF(">")) != 1) { return 0; }
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
if (waitResponse(GF(GSM_NL "+CIPSEND:")) != 1) { return 0; }
streamSkipUntil(','); // Skip mux
streamSkipUntil(','); // Skip requested bytes to send
// TODO(?): make sure requested and confirmed bytes match
return streamGetIntBefore('\n');
}
size_t modemRead(size_t size, uint8_t mux) {
if (!sockets[mux]) return 0;
#ifdef TINY_GSM_USE_HEX
sendAT(GF("+CIPRXGET=3,"), mux, ',', (uint16_t)size);
if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; }
#else
sendAT(GF("+CIPRXGET=2,"), mux, ',', (uint16_t)size);
if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; }
#endif
streamSkipUntil(','); // Skip Rx mode 2/normal or 3/HEX
streamSkipUntil(','); // Skip mux/cid (connecion id)
int16_t len_requested = streamGetIntBefore(',');
// ^^ Requested number of data bytes (1-1460 bytes)to be read
int16_t len_confirmed = streamGetIntBefore('\n');
// ^^ The data length which not read in the buffer
for (int i = 0; i < len_requested; i++) {
uint32_t startMillis = millis();
#ifdef TINY_GSM_USE_HEX
while (stream.available() < 2 &&
(millis() - startMillis < sockets[mux]->_timeout)) {
TINY_GSM_YIELD();
}
char buf[4] = {
0,
};
buf[0] = stream.read();
buf[1] = stream.read();
char c = strtol(buf, NULL, 16);
#else
while (!stream.available() &&
(millis() - startMillis < sockets[mux]->_timeout)) {
TINY_GSM_YIELD();
}
char c = stream.read();
#endif
sockets[mux]->rx.put(c);
}
// DBG("### READ:", len_requested, "from", mux);
// sockets[mux]->sock_available = modemGetAvailable(mux);
sockets[mux]->sock_available = len_confirmed;
waitResponse();
return len_requested;
}
size_t modemGetAvailable(uint8_t mux) {
if (!sockets[mux]) return 0;
sendAT(GF("+CIPRXGET=4,"), mux);
size_t result = 0;
if (waitResponse(GF("+CIPRXGET:")) == 1) {
streamSkipUntil(','); // Skip mode 4
streamSkipUntil(','); // Skip mux
result = streamGetIntBefore('\n');
waitResponse();
}
// DBG("### Available:", result, "on", mux);
if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); }
return result;
}
bool modemGetConnected(uint8_t mux) {
// Read the status of all sockets at once
sendAT(GF("+CIPCLOSE?"));
if (waitResponse(GF("+CIPCLOSE:")) != 1) { return false; }
for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) {
// +CIPCLOSE:<link0_state>,<link1_state>,...,<link9_state>
bool muxState = stream.parseInt();
if (sockets[muxNo]) { sockets[muxNo]->sock_connected = muxState; }
}
waitResponse(); // Should be an OK at the end
if (!sockets[mux]) return false;
return sockets[mux]->sock_connected;
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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(64);
uint8_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)) {
#if defined TINY_GSM_DEBUG
if (r3 == GFP(GSM_CME_ERROR)) {
streamSkipUntil('\n'); // Read out the error
}
#endif
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) {
int8_t mode = streamGetIntBefore(',');
if (mode == 1) {
int8_t mux = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
}
data = "";
// DBG("### Got Data:", mux);
} else {
data += mode;
}
} else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) {
int8_t mux = streamGetIntBefore(',');
int16_t len = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; }
}
data = "";
// DBG("### Got Data:", len, "on", mux);
} else if (data.endsWith(GF("+IPCLOSE:"))) {
int8_t mux = streamGetIntBefore(',');
streamSkipUntil('\n'); // Skip the reason code
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->sock_connected = false;
}
data = "";
DBG("### Closed: ", mux);
} else if (data.endsWith(GF("+CIPEVENT:"))) {
// Need to close all open sockets and release the network library.
// User will then need to reconnect.
DBG("### Network error!");
if (!isGprsConnected()) { gprsDisconnect(); }
data = "";
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5);
}
public:
Stream& stream;
protected:
GsmClientSim5360* sockets[TINY_GSM_MUX_COUNT];
const char* gsmNL = GSM_NL;
};
#endif // SRC_TINYGSMCLIENTSIM5360_H_

View File

@ -0,0 +1,534 @@
/**
* @file TinyGsmClientSIM7000.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTSIM7000_H_
#define SRC_TINYGSMCLIENTSIM7000_H_
// #define TINY_GSM_DEBUG Serial
// #define TINY_GSM_USE_HEX
#define TINY_GSM_MUX_COUNT 8
#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE
#include "TinyGsmClientSIM70xx.h"
#include "TinyGsmTCP.tpp"
class TinyGsmSim7000 : public TinyGsmSim70xx<TinyGsmSim7000>,
public TinyGsmTCP<TinyGsmSim7000, TINY_GSM_MUX_COUNT> {
friend class TinyGsmSim70xx<TinyGsmSim7000>;
friend class TinyGsmTCP<TinyGsmSim7000, TINY_GSM_MUX_COUNT>;
/*
* Inner Client
*/
public:
class GsmClientSim7000 : public GsmClient {
friend class TinyGsmSim7000;
public:
GsmClientSim7000() {}
explicit GsmClientSim7000(TinyGsmSim7000& modem, uint8_t mux = 0) {
init(&modem, mux);
}
bool init(TinyGsmSim7000* modem, uint8_t mux = 0) {
this->at = modem;
sock_available = 0;
prev_check = 0;
sock_connected = false;
got_data = false;
if (mux < TINY_GSM_MUX_COUNT) {
this->mux = mux;
} else {
this->mux = (mux % TINY_GSM_MUX_COUNT);
}
at->sockets[this->mux] = this;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, false, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void stop(uint32_t maxWaitMs) {
dumpModemBuffer(maxWaitMs);
at->sendAT(GF("+CIPCLOSE="), mux);
sock_connected = false;
at->waitResponse(3000);
}
void stop() override {
stop(15000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
// NOTE: Use modem TINYGSMSIM7000SSL for a secure client!
/*
* Constructor
*/
public:
explicit TinyGsmSim7000(Stream& stream)
: TinyGsmSim70xx<TinyGsmSim7000>(stream) {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM7000"));
if (!testAT()) { return false; }
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
#ifdef TINY_GSM_DEBUG
sendAT(GF("+CMEE=2")); // turn on verbose error codes
#else
sendAT(GF("+CMEE=0")); // turn off error codes
#endif
waitResponse();
DBG(GF("### Modem:"), getModemName());
// Enable Local Time Stamp for getting network time
sendAT(GF("+CLTS=1"));
if (waitResponse(10000L) != 1) { return false; }
// Enable battery checks
sendAT(GF("+CBATCHK=1"));
if (waitResponse() != 1) { return false; }
SimStatus ret = getSimStatus();
// if the sim isn't ready and a pin has been provided, try to unlock the sim
if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
simUnlock(pin);
return (getSimStatus() == SIM_READY);
} else {
// if the sim is ready, or it's locked but no pin has been provided,
// return true
return (ret == SIM_READY || ret == SIM_LOCKED);
}
}
/*
* Power functions
*/
protected:
// Follows the SIM70xx template
/*
* Generic network functions
*/
protected:
String getLocalIPImpl() {
sendAT(GF("+CIFSR;E0"));
String res;
if (waitResponse(10000L, res) != 1) { return ""; }
res.replace(GSM_NL "OK" GSM_NL, "");
res.replace(GSM_NL, "");
res.trim();
return res;
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
gprsDisconnect();
// Bearer settings for applications based on IP
// Set the connection type to GPRS
sendAT(GF("+SAPBR=3,1,\"Contype\",\"GPRS\""));
waitResponse();
// Set the APN
sendAT(GF("+SAPBR=3,1,\"APN\",\""), apn, '"');
waitResponse();
// Set the user name
if (user && strlen(user) > 0) {
sendAT(GF("+SAPBR=3,1,\"USER\",\""), user, '"');
waitResponse();
}
// Set the password
if (pwd && strlen(pwd) > 0) {
sendAT(GF("+SAPBR=3,1,\"PWD\",\""), pwd, '"');
waitResponse();
}
// Define the PDP context
sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"');
waitResponse();
// Attach to GPRS
sendAT(GF("+CGATT=1"));
if (waitResponse(60000L) != 1) { return false; }
// Activate the PDP context
sendAT(GF("+CGACT=1,1"));
waitResponse(60000L);
// Open the definied GPRS bearer context
sendAT(GF("+SAPBR=1,1"));
waitResponse(85000L);
// Query the GPRS bearer context status
sendAT(GF("+SAPBR=2,1"));
if (waitResponse(30000L) != 1) { return false; }
// Set the TCP application toolkit to multi-IP
sendAT(GF("+CIPMUX=1"));
if (waitResponse() != 1) { return false; }
// Put the TCP application toolkit in "quick send" mode
// (thus no extra "Send OK")
sendAT(GF("+CIPQSEND=1"));
if (waitResponse() != 1) { return false; }
// Set the TCP application toolkit to get data manually
sendAT(GF("+CIPRXGET=1"));
if (waitResponse() != 1) { return false; }
// Start the TCP application toolkit task and set APN, USER NAME, PASSWORD
sendAT(GF("+CSTT=\""), apn, GF("\",\""), user, GF("\",\""), pwd, GF("\""));
if (waitResponse(60000L) != 1) { return false; }
// Bring up the TCP application toolkit wireless connection with GPRS or CSD
sendAT(GF("+CIICR"));
if (waitResponse(60000L) != 1) { return false; }
// Get local IP address for the TCP application toolkit
// only assigned after connection
sendAT(GF("+CIFSR;E0"));
if (waitResponse(10000L) != 1) { return false; }
return true;
}
bool gprsDisconnectImpl() {
// Shut the TCP application toolkit connection
// CIPSHUT will close *all* open TCP application toolkit connections
sendAT(GF("+CIPSHUT"));
if (waitResponse(60000L) != 1) { return false; }
sendAT(GF("+CGATT=0")); // Deactivate the bearer context
if (waitResponse(60000L) != 1) { return false; }
return true;
}
/*
* SIM card functions
*/
protected:
// Follows the SIM70xx template
/*
* Messaging functions
*/
protected:
// Follows all messaging functions per template
/*
* GPS/GNSS/GLONASS location functions
*/
protected:
// Follows the SIM70xx template
/*
* Time functions
*/
// Can follow CCLK as per template
/*
* NTP server functions
*/
// Can sync with server using CNTP as per template
/*
* Battery functions
*/
protected:
// Follows all battery functions per template
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t mux,
bool ssl = false, int timeout_s = 75) {
if (ssl) { DBG("SSL only supported using application on SIM7000!"); }
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
// when not using SSL, the TCP application toolkit is more stable
sendAT(GF("+CIPSTART="), mux, ',', GF("\"TCP"), GF("\",\""), host,
GF("\","), port);
return (1 ==
waitResponse(timeout_ms, GF("CONNECT OK" GSM_NL),
GF("CONNECT FAIL" GSM_NL),
GF("ALREADY CONNECT" GSM_NL), GF("ERROR" GSM_NL),
GF("CLOSE OK" GSM_NL)));
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len);
if (waitResponse(GF(">")) != 1) { return 0; }
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
if (waitResponse(GF(GSM_NL "DATA ACCEPT:")) != 1) { return 0; }
streamSkipUntil(','); // Skip mux
return streamGetIntBefore('\n');
}
size_t modemRead(size_t size, uint8_t mux) {
if (!sockets[mux]) return 0;
#ifdef TINY_GSM_USE_HEX
sendAT(GF("+CIPRXGET=3,"), mux, ',', (uint16_t)size);
if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; }
#else
sendAT(GF("+CIPRXGET=2,"), mux, ',', (uint16_t)size);
if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; }
#endif
streamSkipUntil(','); // Skip Rx mode 2/normal or 3/HEX
streamSkipUntil(','); // Skip mux
int16_t len_requested = streamGetIntBefore(',');
// ^^ Requested number of data bytes (1-1460 bytes)to be read
int16_t len_confirmed = streamGetIntBefore('\n');
// ^^ Confirmed number of data bytes to be read, which may be less than
// requested. 0 indicates that no data can be read.
// SRGD NOTE: Contrary to above (which is copied from AT command manual)
// this is actually be the number of bytes that will be remaining in the
// buffer after the read.
for (int i = 0; i < len_requested; i++) {
uint32_t startMillis = millis();
#ifdef TINY_GSM_USE_HEX
while (stream.available() < 2 &&
(millis() - startMillis < sockets[mux]->_timeout)) {
TINY_GSM_YIELD();
}
char buf[4] = {
0,
};
buf[0] = stream.read();
buf[1] = stream.read();
char c = strtol(buf, NULL, 16);
#else
while (!stream.available() &&
(millis() - startMillis < sockets[mux]->_timeout)) {
TINY_GSM_YIELD();
}
char c = stream.read();
#endif
sockets[mux]->rx.put(c);
}
// DBG("### READ:", len_requested, "from", mux);
// sockets[mux]->sock_available = modemGetAvailable(mux);
sockets[mux]->sock_available = len_confirmed;
waitResponse();
return len_requested;
}
size_t modemGetAvailable(uint8_t mux) {
if (!sockets[mux]) return 0;
sendAT(GF("+CIPRXGET=4,"), mux);
size_t result = 0;
if (waitResponse(GF("+CIPRXGET:")) == 1) {
streamSkipUntil(','); // Skip mode 4
streamSkipUntil(','); // Skip mux
result = streamGetIntBefore('\n');
waitResponse();
}
// DBG("### Available:", result, "on", mux);
if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); }
return result;
}
bool modemGetConnected(uint8_t mux) {
sendAT(GF("+CIPSTATUS="), mux);
waitResponse(GF("+CIPSTATUS"));
int8_t res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""),
GF(",\"CLOSING\""), GF(",\"REMOTE CLOSING\""),
GF(",\"INITIAL\""));
waitResponse();
return 1 == res;
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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(64);
uint8_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)) {
#if defined TINY_GSM_DEBUG
if (r3 == GFP(GSM_CME_ERROR)) {
streamSkipUntil('\n'); // Read out the error
}
#endif
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) {
int8_t mode = streamGetIntBefore(',');
if (mode == 1) {
int8_t mux = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
}
data = "";
// DBG("### Got Data:", mux);
} else {
data += mode;
}
} else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) {
int8_t mux = streamGetIntBefore(',');
int16_t len = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; }
}
data = "";
// DBG("### Got Data:", len, "on", mux);
} else if (data.endsWith(GF("CLOSED" GSM_NL))) {
int8_t nl = data.lastIndexOf(GSM_NL, data.length() - 8);
int8_t coma = data.indexOf(',', nl + 2);
int8_t mux = data.substring(nl + 2, coma).toInt();
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->sock_connected = false;
}
data = "";
DBG("### Closed: ", mux);
} else if (data.endsWith(GF("*PSNWID:"))) {
streamSkipUntil('\n'); // Refresh network name by network
data = "";
DBG("### Network name updated.");
} else if (data.endsWith(GF("*PSUTTZ:"))) {
streamSkipUntil('\n'); // Refresh time and time zone by network
data = "";
DBG("### Network time and time zone updated.");
} else if (data.endsWith(GF("+CTZV:"))) {
streamSkipUntil('\n'); // Refresh network time zone by network
data = "";
DBG("### Network time zone updated.");
} else if (data.endsWith(GF("DST: "))) {
streamSkipUntil(
'\n'); // Refresh Network Daylight Saving Time by network
data = "";
DBG("### Daylight savings time state updated.");
} else if (data.endsWith(GF(GSM_NL "SMS Ready" GSM_NL))) {
data = "";
DBG("### Unexpected module reset!");
init();
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5);
}
protected:
GsmClientSim7000* sockets[TINY_GSM_MUX_COUNT];
};
#endif // SRC_TINYGSMCLIENTSIM7000_H_

View File

@ -0,0 +1,730 @@
/**
* @file TinyGsmClientSim7000SSL.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTSIM7000SSL_H_
#define SRC_TINYGSMCLIENTSIM7000SSL_H_
// #define TINY_GSM_DEBUG Serial
// #define TINY_GSM_USE_HEX
#define TINY_GSM_MUX_COUNT 2
#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE
#include "TinyGsmClientSIM70xx.h"
#include "TinyGsmTCP.tpp"
#include "TinyGsmSSL.tpp"
class TinyGsmSim7000SSL
: public TinyGsmSim70xx<TinyGsmSim7000SSL>,
public TinyGsmTCP<TinyGsmSim7000SSL, TINY_GSM_MUX_COUNT>,
public TinyGsmSSL<TinyGsmSim7000SSL> {
friend class TinyGsmSim70xx<TinyGsmSim7000SSL>;
friend class TinyGsmTCP<TinyGsmSim7000SSL, TINY_GSM_MUX_COUNT>;
friend class TinyGsmSSL<TinyGsmSim7000SSL>;
/*
* Inner Client
*/
public:
class GsmClientSim7000SSL : public GsmClient {
friend class TinyGsmSim7000SSL;
public:
GsmClientSim7000SSL() {}
explicit GsmClientSim7000SSL(TinyGsmSim7000SSL& modem, uint8_t mux = 0) {
init(&modem, mux);
}
bool init(TinyGsmSim7000SSL* modem, uint8_t mux = 0) {
this->at = modem;
sock_available = 0;
prev_check = 0;
sock_connected = false;
got_data = false;
if (mux < TINY_GSM_MUX_COUNT) {
this->mux = mux;
} else {
this->mux = (mux % TINY_GSM_MUX_COUNT);
}
at->sockets[this->mux] = this;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, false, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void stop(uint32_t maxWaitMs) {
dumpModemBuffer(maxWaitMs);
at->sendAT(GF("+CACLOSE="), mux);
sock_connected = false;
at->waitResponse(3000);
}
void stop() override {
stop(15000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
class GsmClientSecureSIM7000SSL : public GsmClientSim7000SSL {
public:
GsmClientSecureSIM7000SSL() {}
explicit GsmClientSecureSIM7000SSL(TinyGsmSim7000SSL& modem,
uint8_t mux = 0)
: GsmClientSim7000SSL(modem, mux) {}
public:
bool setCertificate(const String& certificateName) {
return at->setCertificate(certificateName, mux);
}
virtual int connect(const char* host, uint16_t port,
int timeout_s) override {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, true, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
};
/*
* Constructor
*/
public:
explicit TinyGsmSim7000SSL(Stream& stream)
: TinyGsmSim70xx<TinyGsmSim7000SSL>(stream),
certificates() {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM7000SSL"));
if (!testAT()) { return false; }
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
#ifdef TINY_GSM_DEBUG
sendAT(GF("+CMEE=2")); // turn on verbose error codes
#else
sendAT(GF("+CMEE=0")); // turn off error codes
#endif
waitResponse();
DBG(GF("### Modem:"), getModemName());
// Enable Local Time Stamp for getting network time
sendAT(GF("+CLTS=1"));
if (waitResponse(10000L) != 1) { return false; }
// Enable battery checks
sendAT(GF("+CBATCHK=1"));
if (waitResponse() != 1) { return false; }
SimStatus ret = getSimStatus();
// if the sim isn't ready and a pin has been provided, try to unlock the sim
if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
simUnlock(pin);
return (getSimStatus() == SIM_READY);
} else {
// if the sim is ready, or it's locked but no pin has been provided,
// return true
return (ret == SIM_READY || ret == SIM_LOCKED);
}
}
void maintainImpl() {
// Keep listening for modem URC's and proactively iterate through
// sockets asking if any data is avaiable
bool check_socks = false;
for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) {
GsmClientSim7000SSL* sock = sockets[mux];
if (sock && sock->got_data) {
sock->got_data = false;
check_socks = true;
}
}
// modemGetAvailable checks all socks, so we only want to do it once
// modemGetAvailable calls modemGetConnected(), which also checks allf
if (check_socks) { modemGetAvailable(0); }
while (stream.available()) { waitResponse(15, NULL, NULL); }
}
/*
* Power functions
*/
protected:
// Follows the SIM70xx template
/*
* Generic network functions
*/
protected:
String getLocalIPImpl() {
sendAT(GF("+CNACT?"));
if (waitResponse(GF(GSM_NL "+CNACT:")) != 1) { return ""; }
streamSkipUntil('\"');
String res = stream.readStringUntil('\"');
waitResponse();
return res;
}
/*
* Secure socket layer functions
*/
protected:
bool setCertificate(const String& certificateName, const uint8_t mux = 0) {
if (mux >= TINY_GSM_MUX_COUNT) return false;
certificates[mux] = certificateName;
return true;
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
gprsDisconnect();
// Define the PDP context
sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"');
waitResponse();
// Attach to GPRS
sendAT(GF("+CGATT=1"));
if (waitResponse(60000L) != 1) { return false; }
// NOTE: **DO NOT** activate the PDP context
// For who only knows what reason, doing so screws up the rest of the
// process
// Bearer settings for applications based on IP
// Set the user name and password
// AT+CNCFG=<ip_type>[,<APN>[,<usename>,<password>[,<authentication>]]]
//<ip_type> 0: Dual PDN Stack
// 1: Internet Protocol Version 4
// 2: Internet Protocol Version 6
//<authentication> 0: NONE
// 1: PAP
// 2: CHAP
// 3: PAP or CHAP
if (pwd && strlen(pwd) > 0 && user && strlen(user) > 0) {
sendAT(GF("+CNCFG=1,\""), apn, "\",\"", "\",\"", user, pwd, '"');
waitResponse();
} else if (user && strlen(user) > 0) {
// Set the user name only
sendAT(GF("+CNCFG=1,\""), apn, "\",\"", user, '"');
waitResponse();
} else {
// Set the APN only
sendAT(GF("+CNCFG=1,\""), apn, '"');
waitResponse();
}
// Activate application network connection
// This is for most other supported applications outside of the
// TCP application toolkit (ie, SSL)
// AT+CNACT=<mode>,<action>
// <mode> 0: Deactive
// 1: Active
// 2: Auto Active
bool res = false;
int ntries = 0;
while (!res && ntries < 5) {
sendAT(GF("+CNACT=1,\""), apn, GF("\""));
res = waitResponse(60000L, GF(GSM_NL "+APP PDP: ACTIVE"),
GF(GSM_NL "+APP PDP: DEACTIVE")) == 1;
waitResponse();
ntries++;
}
return res;
}
bool gprsDisconnectImpl() {
// Shut down the general application TCP/IP connection
// CNACT will close *all* open application connections
sendAT(GF("+CNACT=0"));
if (waitResponse(60000L) != 1) { return false; }
sendAT(GF("+CGATT=0")); // Deactivate the bearer context
if (waitResponse(60000L) != 1) { return false; }
return true;
}
/*
* SIM card functions
*/
protected:
// Follows the SIM70xx template
/*
* Messaging functions
*/
protected:
// Follows all messaging functions per template
/*
* GPS/GNSS/GLONASS location functions
*/
protected:
// Follows the SIM70xx template
/*
* Time functions
*/
// Can follow CCLK as per template
/*
* NTP server functions
*/
// Can sync with server using CNTP as per template
/*
* Battery functions
*/
protected:
// Follows all battery functions per template
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t mux,
bool ssl = false, int timeout_s = 75) {
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
// set the connection (mux) identifier to use
sendAT(GF("+CACID="), mux);
if (waitResponse(timeout_ms) != 1) return false;
if (ssl) {
// set the ssl version
// AT+CSSLCFG="SSLVERSION",<ctxindex>,<sslversion>
// <ctxindex> PDP context identifier
// <sslversion> 0: QAPI_NET_SSL_PROTOCOL_UNKNOWN
// 1: QAPI_NET_SSL_PROTOCOL_TLS_1_0
// 2: QAPI_NET_SSL_PROTOCOL_TLS_1_1
// 3: QAPI_NET_SSL_PROTOCOL_TLS_1_2
// 4: QAPI_NET_SSL_PROTOCOL_DTLS_1_0
// 5: QAPI_NET_SSL_PROTOCOL_DTLS_1_2
// NOTE: despite docs using caps, "sslversion" must be in lower case
sendAT(GF("+CSSLCFG=\"sslversion\",0,3")); // TLS 1.2
if (waitResponse(5000L) != 1) return false;
}
// enable or disable ssl
// AT+CASSLCFG=<cid>,"SSL",<sslFlag>
// <cid> Application connection ID (set with AT+CACID above)
// <sslFlag> 0: Not support SSL
// 1: Support SSL
sendAT(GF("+CASSLCFG="), mux, ',', GF("ssl,"), ssl);
waitResponse();
if (ssl) {
// set the PDP context to apply SSL to
// AT+CSSLCFG="CTXINDEX",<ctxindex>
// <ctxindex> PDP context identifier
// NOTE: despite docs using caps, "ctxindex" must be in lower case
sendAT(GF("+CSSLCFG=\"ctxindex\",0"));
if (waitResponse(5000L, GF("+CSSLCFG:")) != 1) return false;
streamSkipUntil('\n'); // read out the certificate information
waitResponse();
if (certificates[mux] != "") {
// apply the correct certificate to the connection
// AT+CASSLCFG=<cid>,"CACERT",<caname>
// <cid> Application connection ID (set with AT+CACID above)
// <certname> certificate name
sendAT(GF("+CASSLCFG="), mux, ",CACERT,\"", certificates[mux].c_str(),
"\"");
if (waitResponse(5000L) != 1) return false;
}
// set the protocol
// 0: TCP; 1: UDP
sendAT(GF("+CASSLCFG="), mux, ',', GF("protocol,0"));
waitResponse();
// set the SSL SNI (server name indication)
// NOTE: despite docs using caps, "sni" must be in lower case
sendAT(GF("+CSSLCFG=\"sni\","), mux, ',', GF("\""), host, GF("\""));
waitResponse();
}
// actually open the connection
// AT+CAOPEN=<cid>[,<conn_type>],<server>,<port>
// <cid> TCP/UDP identifier
// <conn_type> "TCP" or "UDP"
// NOTE: the "TCP" can't be included
sendAT(GF("+CAOPEN="), mux, GF(",\""), host, GF("\","), port);
if (waitResponse(timeout_ms, GF(GSM_NL "+CAOPEN:")) != 1) { return 0; }
// returns OK/r/n/r/n+CAOPEN: <cid>,<result>
// <result> 0: Success
// 1: Socket error
// 2: No memory
// 3: Connection limit
// 4: Parameter invalid
// 6: Invalid IP address
// 7: Not support the function
// 12: Cant bind the port
// 13: Cant listen the port
// 20: Cant resolve the host
// 21: Network not active
// 23: Remote refuse
// 24: Certificates time expired
// 25: Certificates common name does not match
// 26: Certificates common name does not match and time expired
// 27: Connect failed
streamSkipUntil(','); // Skip mux
// make sure the connection really opened
int8_t res = streamGetIntBefore('\n');
waitResponse();
return 0 == res;
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
// send data on prompt
sendAT(GF("+CASEND="), mux, ',', (uint16_t)len);
if (waitResponse(GF(">")) != 1) { return 0; }
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
// after posting data, module responds with:
//+CASEND: <cid>,<result>,<sendlen>
if (waitResponse(GF(GSM_NL "+CASEND:")) != 1) { return 0; }
streamSkipUntil(','); // Skip mux
if (streamGetIntBefore(',') != 0) { return 0; } // If result != success
return streamGetIntBefore('\n');
}
size_t modemRead(size_t size, uint8_t mux) {
if (!sockets[mux]) { return 0; }
sendAT(GF("+CARECV="), mux, ',', (uint16_t)size);
if (waitResponse(GF("+CARECV:")) != 1) { return 0; }
// uint8_t ret_mux = stream.parseInt();
// streamSkipUntil(',');
// const int16_t len_confirmed = streamGetIntBefore('\n');
// DBG("### READING:", len_confirmed, "from", ret_mux);
// if (ret_mux != mux) {
// DBG("### Data from wrong mux! Got", ret_mux, "expected", mux);
// waitResponse();
// sockets[mux]->sock_available = modemGetAvailable(mux);
// return 0;
// }
// NOTE: manual says the mux number is returned before the number of
// characters available, but in tests only the number is returned
int16_t len_confirmed = stream.parseInt();
streamSkipUntil(','); // skip the comma
if (len_confirmed <= 0) {
waitResponse();
sockets[mux]->sock_available = modemGetAvailable(mux);
return 0;
}
for (int i = 0; i < len_confirmed; i++) {
uint32_t startMillis = millis();
while (!stream.available() &&
(millis() - startMillis < sockets[mux]->_timeout)) {
TINY_GSM_YIELD();
}
char c = stream.read();
sockets[mux]->rx.put(c);
}
waitResponse();
// DBG("### READ:", len_confirmed, "from", mux);
// make sure the sock available number is accurate again
// the module is **EXTREMELY** testy about being asked to read more from
// the buffer than exits; it will freeze until a hard reset or power cycle!
sockets[mux]->sock_available = modemGetAvailable(mux);
return len_confirmed;
}
size_t modemGetAvailable(uint8_t mux) {
// If the socket doesn't exist, just return
if (!sockets[mux]) { return 0; }
// We need to check if there are any connections open *before* checking for
// available characters. The SIM7000 *will crash* if you ask about data
// when there are no open connections.
if (!modemGetConnected(mux)) { return 0; }
// NOTE: This gets how many characters are available on all connections that
// have data. It does not return all the connections, just those with data.
sendAT(GF("+CARECV?"));
for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) {
// after the last connection, there's an ok, so we catch it right away
int res = waitResponse(3000, GF("+CARECV:"), GFP(GSM_OK), GFP(GSM_ERROR));
// if we get the +CARECV: response, read the mux number and the number of
// characters available
if (res == 1) {
int ret_mux = streamGetIntBefore(',');
size_t result = streamGetIntBefore('\n');
GsmClientSim7000SSL* sock = sockets[ret_mux];
if (sock) { sock->sock_available = result; }
// if the first returned mux isn't 0 (or is higher than expected)
// we need to fill in the missing muxes
if (ret_mux > muxNo) {
for (int extra_mux = muxNo; extra_mux < ret_mux; extra_mux++) {
GsmClientSim7000SSL* isock = sockets[extra_mux];
if (isock) { isock->sock_available = 0; }
}
muxNo = ret_mux;
}
} else if (res == 2) {
// if we get an OK, we've reached the last socket with available data
// so we set any we haven't gotten to yet to 0
for (int extra_mux = muxNo; extra_mux < TINY_GSM_MUX_COUNT;
extra_mux++) {
GsmClientSim7000SSL* isock = sockets[extra_mux];
if (isock) { isock->sock_available = 0; }
}
break;
} else {
// if we got an error, give up
break;
}
// Should be a final OK at the end.
// If every connection was returned, catch the OK here.
// If only a portion were returned, catch it above.
if (muxNo == TINY_GSM_MUX_COUNT - 1) { waitResponse(); }
}
modemGetConnected(mux); // check the state of all connections
if (!sockets[mux]) { return 0; }
return sockets[mux]->sock_available;
}
bool modemGetConnected(uint8_t mux) {
// NOTE: This gets the state of all connections that have been opened
// since the last connection
sendAT(GF("+CASTATE?"));
for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) {
// after the last connection, there's an ok, so we catch it right away
int res = waitResponse(3000, GF("+CASTATE:"), GFP(GSM_OK),
GFP(GSM_ERROR));
// if we get the +CASTATE: response, read the mux number and the status
if (res == 1) {
int ret_mux = streamGetIntBefore(',');
size_t status = streamGetIntBefore('\n');
// 0: Closed by remote server or internal error
// 1: Connected to remote server
// 2: Listening (server mode)
GsmClientSim7000SSL* sock = sockets[ret_mux];
if (sock) { sock->sock_connected = (status == 1); }
// if the first returned mux isn't 0 (or is higher than expected)
// we need to fill in the missing muxes
if (ret_mux > muxNo) {
for (int extra_mux = muxNo; extra_mux < ret_mux; extra_mux++) {
GsmClientSim7000SSL* isock = sockets[extra_mux];
if (isock) { isock->sock_connected = false; }
}
muxNo = ret_mux;
}
} else if (res == 2) {
// if we get an OK, we've reached the last socket with available data
// so we set any we haven't gotten to yet to 0
for (int extra_mux = muxNo; extra_mux < TINY_GSM_MUX_COUNT;
extra_mux++) {
GsmClientSim7000SSL* isock = sockets[extra_mux];
if (isock) { isock->sock_connected = false; }
}
break;
} else {
// if we got an error, give up
break;
}
// Should be a final OK at the end.
// If every connection was returned, catch the OK here.
// If only a portion were returned, catch it above.
if (muxNo == TINY_GSM_MUX_COUNT - 1) { waitResponse(); }
}
return sockets[mux]->sock_connected;
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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(64);
uint8_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)) {
#if defined TINY_GSM_DEBUG
if (r3 == GFP(GSM_CME_ERROR)) {
streamSkipUntil('\n'); // Read out the error
}
#endif
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF("+CARECV:"))) {
int8_t mux = streamGetIntBefore(',');
int16_t len = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; }
}
data = "";
DBG("### Got Data:", len, "on", mux);
} else if (data.endsWith(GF("+CADATAIND:"))) {
int8_t mux = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
}
data = "";
DBG("### Got Data:", mux);
} else if (data.endsWith(GF("+CASTATE:"))) {
int8_t mux = streamGetIntBefore(',');
int8_t state = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
if (state != 1) {
sockets[mux]->sock_connected = false;
DBG("### Closed: ", mux);
}
}
data = "";
} else if (data.endsWith(GF("*PSNWID:"))) {
streamSkipUntil('\n'); // Refresh network name by network
data = "";
DBG("### Network name updated.");
} else if (data.endsWith(GF("*PSUTTZ:"))) {
streamSkipUntil('\n'); // Refresh time and time zone by network
data = "";
DBG("### Network time and time zone updated.");
} else if (data.endsWith(GF("+CTZV:"))) {
streamSkipUntil('\n'); // Refresh network time zone by network
data = "";
DBG("### Network time zone updated.");
} else if (data.endsWith(GF("DST: "))) {
streamSkipUntil(
'\n'); // Refresh Network Daylight Saving Time by network
data = "";
DBG("### Daylight savings time state updated.");
} else if (data.endsWith(GF(GSM_NL "SMS Ready" GSM_NL))) {
data = "";
DBG("### Unexpected module reset!");
init();
data = "";
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5);
}
protected:
GsmClientSim7000SSL* sockets[TINY_GSM_MUX_COUNT];
String certificates[TINY_GSM_MUX_COUNT];
};
#endif // SRC_TINYGSMCLIENTSIM7000SSL_H_

View File

@ -0,0 +1,729 @@
/**
* @file TinyGsmClientSim7080.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTSIM7080_H_
#define SRC_TINYGSMCLIENTSIM7080_H_
// #define TINY_GSM_DEBUG Serial
// #define TINY_GSM_USE_HEX
#define TINY_GSM_MUX_COUNT 12
#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE
#include "TinyGsmClientSIM70xx.h"
#include "TinyGsmTCP.tpp"
#include "TinyGsmSSL.tpp"
class TinyGsmSim7080 : public TinyGsmSim70xx<TinyGsmSim7080>,
public TinyGsmTCP<TinyGsmSim7080, TINY_GSM_MUX_COUNT>,
public TinyGsmSSL<TinyGsmSim7080> {
friend class TinyGsmSim70xx<TinyGsmSim7080>;
friend class TinyGsmTCP<TinyGsmSim7080, TINY_GSM_MUX_COUNT>;
friend class TinyGsmSSL<TinyGsmSim7080>;
/*
* Inner Client
*/
public:
class GsmClientSim7080 : public GsmClient {
friend class TinyGsmSim7080;
public:
GsmClientSim7080() {}
explicit GsmClientSim7080(TinyGsmSim7080& modem, uint8_t mux = 0) {
init(&modem, mux);
}
bool init(TinyGsmSim7080* modem, uint8_t mux = 0) {
this->at = modem;
sock_available = 0;
prev_check = 0;
sock_connected = false;
got_data = false;
if (mux < TINY_GSM_MUX_COUNT) {
this->mux = mux;
} else {
this->mux = (mux % TINY_GSM_MUX_COUNT);
}
at->sockets[this->mux] = this;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, false, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void stop(uint32_t maxWaitMs) {
dumpModemBuffer(maxWaitMs);
at->sendAT(GF("+CACLOSE="), mux);
sock_connected = false;
at->waitResponse(3000);
}
void stop() override {
stop(15000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
class GsmClientSecureSIM7080 : public GsmClientSim7080 {
public:
GsmClientSecureSIM7080() {}
explicit GsmClientSecureSIM7080(TinyGsmSim7080& modem, uint8_t mux = 0)
: GsmClientSim7080(modem, mux) {}
public:
bool setCertificate(const String& certificateName) {
return at->setCertificate(certificateName, mux);
}
virtual int connect(const char* host, uint16_t port,
int timeout_s) override {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, true, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
};
/*
* Constructor
*/
public:
explicit TinyGsmSim7080(Stream& stream)
: TinyGsmSim70xx<TinyGsmSim7080>(stream),
certificates() {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM7080"));
if (!testAT()) { return false; }
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
#ifdef TINY_GSM_DEBUG
sendAT(GF("+CMEE=2")); // turn on verbose error codes
#else
sendAT(GF("+CMEE=0")); // turn off error codes
#endif
waitResponse();
DBG(GF("### Modem:"), getModemName());
// Enable Local Time Stamp for getting network time
sendAT(GF("+CLTS=1"));
if (waitResponse(10000L) != 1) { return false; }
// Enable battery checks
sendAT(GF("+CBATCHK=1"));
if (waitResponse() != 1) { return false; }
SimStatus ret = getSimStatus();
// if the sim isn't ready and a pin has been provided, try to unlock the sim
if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
simUnlock(pin);
return (getSimStatus() == SIM_READY);
} else {
// if the sim is ready, or it's locked but no pin has been provided,
// return true
return (ret == SIM_READY || ret == SIM_LOCKED);
}
}
void maintainImpl() {
// Keep listening for modem URC's and proactively iterate through
// sockets asking if any data is avaiable
bool check_socks = false;
for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) {
GsmClientSim7080* sock = sockets[mux];
if (sock && sock->got_data) {
sock->got_data = false;
check_socks = true;
}
}
// modemGetAvailable checks all socks, so we only want to do it once
// modemGetAvailable calls modemGetConnected(), which also checks allf
if (check_socks) { modemGetAvailable(0); }
while (stream.available()) { waitResponse(15, NULL, NULL); }
}
/*
* Power functions
*/
protected:
// Follows the SIM70xx template
/*
* Generic network functions
*/
protected:
String getLocalIPImpl() {
sendAT(GF("+CNACT?"));
if (waitResponse(GF(GSM_NL "+CNACT:")) != 1) { return ""; }
streamSkipUntil('\"');
String res = stream.readStringUntil('\"');
waitResponse();
return res;
}
/*
* Secure socket layer functions
*/
protected:
bool setCertificate(const String& certificateName, const uint8_t mux = 0) {
if (mux >= TINY_GSM_MUX_COUNT) return false;
certificates[mux] = certificateName;
return true;
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
gprsDisconnect();
// Define the PDP context
sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"');
waitResponse();
// Attach to GPRS
sendAT(GF("+CGATT=1"));
if (waitResponse(60000L) != 1) { return false; }
// NOTE: **DO NOT** activate the PDP context
// For who only knows what reason, doing so screws up the rest of the
// process
// Check the APN returned by the server
// not sure why, but the connection is more consistent with this
sendAT(GF("+CGNAPN"));
waitResponse();
// Bearer settings for applications based on IP
// Set the user name and password
// AT+CNCFG=<pdpidx>,<ip_type>,[<APN>,[<usename>,<password>,[<authentication>]]]
// <pdpidx> PDP Context Identifier - for reasons not understood by me,
// use PDP context identifier of 0 for what we defined as 1 above
// <ip_type> 0: Dual PDN Stack
// 1: Internet Protocol Version 4
// 2: Internet Protocol Version 6
// <authentication> 0: NONE
// 1: PAP
// 2: CHAP
// 3: PAP or CHAP
if (pwd && strlen(pwd) > 0 && user && strlen(user) > 0) {
sendAT(GF("+CNCFG=0,1,\""), apn, "\",\"", user, "\",\"", pwd, '"');
waitResponse();
} else if (user && strlen(user) > 0) {
// Set the user name only
sendAT(GF("+CNCFG=0,1,\""), apn, "\",\"", user, '"');
waitResponse();
} else {
// Set the APN only
sendAT(GF("+CNCFG=0,1,\""), apn, '"');
waitResponse();
}
// Activate application network connection
// AT+CNACT=<pdpidx>,<action>
// <pdpidx> PDP Context Identifier - for reasons not understood by me,
// use PDP context identifier of 0 for what we defined as 1 above
// <action> 0: Deactive
// 1: Active
// 2: Auto Active
bool res = false;
int ntries = 0;
while (!res && ntries < 5) {
sendAT(GF("+CNACT=0,1"));
res = waitResponse(60000L, GF(GSM_NL "+APP PDP: 0,ACTIVE"),
GF(GSM_NL "+APP PDP: 0,DEACTIVE"));
waitResponse();
ntries++;
}
return res;
}
bool gprsDisconnectImpl() {
// Shut down the general application TCP/IP connection
// CNACT will close *all* open application connections
sendAT(GF("+CNACT=0,0"));
if (waitResponse(60000L) != 1) { return false; }
sendAT(GF("+CGATT=0")); // Deactivate the bearer context
if (waitResponse(60000L) != 1) { return false; }
return true;
}
/*
* SIM card functions
*/
protected:
// Follows the SIM70xx template
/*
* Messaging functions
*/
protected:
// Follows all messaging functions per template
/*
* GPS/GNSS/GLONASS location functions
*/
protected:
// Follows the SIM70xx template
/*
* Time functions
*/
// Can follow CCLK as per template
/*
* NTP server functions
*/
// Can sync with server using CNTP as per template
/*
* Battery functions
*/
protected:
// Follows all battery functions per template
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t mux,
bool ssl = false, int timeout_s = 75) {
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
// set the connection (mux) identifier to use
sendAT(GF("+CACID="), mux);
if (waitResponse(timeout_ms) != 1) return false;
if (ssl) {
// set the ssl version
// AT+CSSLCFG="SSLVERSION",<ctxindex>,<sslversion>
// <ctxindex> PDP context identifier
// <sslversion> 0: QAPI_NET_SSL_PROTOCOL_UNKNOWN
// 1: QAPI_NET_SSL_PROTOCOL_TLS_1_0
// 2: QAPI_NET_SSL_PROTOCOL_TLS_1_1
// 3: QAPI_NET_SSL_PROTOCOL_TLS_1_2
// 4: QAPI_NET_SSL_PROTOCOL_DTLS_1_0
// 5: QAPI_NET_SSL_PROTOCOL_DTLS_1_2
// NOTE: despite docs using caps, "sslversion" must be in lower case
sendAT(GF("+CSSLCFG=\"sslversion\",0,3")); // TLS 1.2
if (waitResponse(5000L) != 1) return false;
}
// enable or disable ssl
// AT+CASSLCFG=<cid>,"SSL",<sslFlag>
// <cid> Application connection ID (set with AT+CACID above)
// <sslFlag> 0: Not support SSL
// 1: Support SSL
sendAT(GF("+CASSLCFG="), mux, ',', GF("SSL,"), ssl);
waitResponse();
if (ssl) {
// set the PDP context to apply SSL to
// AT+CSSLCFG="CTXINDEX",<ctxindex>
// <ctxindex> PDP context identifier
// NOTE: despite docs using "CRINDEX" in all caps, the module only
// accepts the command "ctxindex" and it must be in lower case
sendAT(GF("+CSSLCFG=\"ctxindex\",0"));
if (waitResponse(5000L, GF("+CSSLCFG:")) != 1) return false;
streamSkipUntil('\n'); // read out the certificate information
waitResponse();
if (certificates[mux] != "") {
// apply the correct certificate to the connection
// AT+CASSLCFG=<cid>,"CACERT",<caname>
// <cid> Application connection ID (set with AT+CACID above)
// <certname> certificate name
sendAT(GF("+CASSLCFG="), mux, ",CACERT,\"", certificates[mux].c_str(),
"\"");
if (waitResponse(5000L) != 1) return false;
}
// set the SSL SNI (server name indication)
// NOTE: despite docs using caps, "sni" must be in lower case
sendAT(GF("+CSSLCFG=\"sni\","), mux, ',', GF("\""), host, GF("\""));
waitResponse();
}
// actually open the connection
// AT+CAOPEN=<cid>,<pdp_index>,<conn_type>,<server>,<port>[,<recv_mode>]
// <cid> TCP/UDP identifier
// <pdp_index> Index of PDP connection; we set up PCP context 1 above
// <conn_type> "TCP" or "UDP"
// <recv_mode> 0: The received data can only be read manually using
// AT+CARECV=<cid>
// 1: After receiving the data, it will automatically report
// URC:
// +CAURC:
// "recv",<id>,<length>,<remoteIP>,<remote_port><CR><LF><data>
// NOTE: including the <recv_mode> fails
sendAT(GF("+CAOPEN="), mux, GF(",0,\"TCP\",\""), host, GF("\","), port);
if (waitResponse(timeout_ms, GF(GSM_NL "+CAOPEN:")) != 1) { return 0; }
// returns OK/r/n/r/n+CAOPEN: <cid>,<result>
// <result> 0: Success
// 1: Socket error
// 2: No memory
// 3: Connection limit
// 4: Parameter invalid
// 6: Invalid IP address
// 7: Not support the function
// 12: Cant bind the port
// 13: Cant listen the port
// 20: Cant resolve the host
// 21: Network not active
// 23: Remote refuse
// 24: Certificates time expired
// 25: Certificates common name does not match
// 26: Certificates common name does not match and time expired
// 27: Connect failed
streamSkipUntil(','); // Skip mux
// make sure the connection really opened
int8_t res = streamGetIntBefore('\n');
waitResponse();
return 0 == res;
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
// send data on prompt
sendAT(GF("+CASEND="), mux, ',', (uint16_t)len);
if (waitResponse(GF(">")) != 1) { return 0; }
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
// OK after posting data
if (waitResponse() != 1) { return 0; }
return len;
}
size_t modemRead(size_t size, uint8_t mux) {
if (!sockets[mux]) { return 0; }
sendAT(GF("+CARECV="), mux, ',', (uint16_t)size);
if (waitResponse(GF("+CARECV:")) != 1) { return 0; }
// uint8_t ret_mux = stream.parseInt();
// streamSkipUntil(',');
// const int16_t len_confirmed = streamGetIntBefore('\n');
// DBG("### READING:", len_confirmed, "from", ret_mux);
// if (ret_mux != mux) {
// DBG("### Data from wrong mux! Got", ret_mux, "expected", mux);
// waitResponse();
// sockets[mux]->sock_available = modemGetAvailable(mux);
// return 0;
// }
// NOTE: manual says the mux number is returned before the number of
// characters available, but in tests only the number is returned
int16_t len_confirmed = stream.parseInt();
streamSkipUntil(','); // skip the comma
if (len_confirmed <= 0) {
waitResponse();
sockets[mux]->sock_available = modemGetAvailable(mux);
return 0;
}
for (int i = 0; i < len_confirmed; i++) {
uint32_t startMillis = millis();
while (!stream.available() &&
(millis() - startMillis < sockets[mux]->_timeout)) {
TINY_GSM_YIELD();
}
char c = stream.read();
sockets[mux]->rx.put(c);
}
waitResponse();
// make sure the sock available number is accurate again
sockets[mux]->sock_available = modemGetAvailable(mux);
return len_confirmed;
}
size_t modemGetAvailable(uint8_t mux) {
// If the socket doesn't exist, just return
if (!sockets[mux]) { return 0; }
// NOTE: This gets how many characters are available on all connections that
// have data. It does not return all the connections, just those with data.
sendAT(GF("+CARECV?"));
for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) {
// after the last connection, there's an ok, so we catch it right away
int res = waitResponse(3000, GF("+CARECV:"), GFP(GSM_OK), GFP(GSM_ERROR));
// if we get the +CARECV: response, read the mux number and the number of
// characters available
if (res == 1) {
int ret_mux = streamGetIntBefore(',');
size_t result = streamGetIntBefore('\n');
GsmClientSim7080* sock = sockets[ret_mux];
if (sock) { sock->sock_available = result; }
// if the first returned mux isn't 0 (or is higher than expected)
// we need to fill in the missing muxes
if (ret_mux > muxNo) {
for (int extra_mux = muxNo; extra_mux < ret_mux; extra_mux++) {
GsmClientSim7080* isock = sockets[extra_mux];
if (isock) { isock->sock_available = 0; }
}
muxNo = ret_mux;
}
} else if (res == 2) {
// if we get an OK, we've reached the last socket with available data
// so we set any we haven't gotten to yet to 0
for (int extra_mux = muxNo; extra_mux < TINY_GSM_MUX_COUNT;
extra_mux++) {
GsmClientSim7080* isock = sockets[extra_mux];
if (isock) { isock->sock_available = 0; }
}
break;
} else {
// if we got an error, give up
break;
}
// Should be a final OK at the end.
// If every connection was returned, catch the OK here.
// If only a portion were returned, catch it above.
if (muxNo == TINY_GSM_MUX_COUNT - 1) { waitResponse(); }
}
modemGetConnected(mux); // check the state of all connections
if (!sockets[mux]) { return 0; }
return sockets[mux]->sock_available;
}
bool modemGetConnected(uint8_t mux) {
// NOTE: This gets the state of all connections that have been opened
// since the last connection
sendAT(GF("+CASTATE?"));
for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) {
// after the last connection, there's an ok, so we catch it right away
int res = waitResponse(3000, GF("+CASTATE:"), GFP(GSM_OK),
GFP(GSM_ERROR));
// if we get the +CASTATE: response, read the mux number and the status
if (res == 1) {
int ret_mux = streamGetIntBefore(',');
size_t status = streamGetIntBefore('\n');
// 0: Closed by remote server or internal error
// 1: Connected to remote server
// 2: Listening (server mode)
GsmClientSim7080* sock = sockets[ret_mux];
if (sock) { sock->sock_connected = (status == 1); }
// if the first returned mux isn't 0 (or is higher than expected)
// we need to fill in the missing muxes
if (ret_mux > muxNo) {
for (int extra_mux = muxNo; extra_mux < ret_mux; extra_mux++) {
GsmClientSim7080* isock = sockets[extra_mux];
if (isock) { isock->sock_connected = false; }
}
muxNo = ret_mux;
}
} else if (res == 2) {
// if we get an OK, we've reached the last socket with available data
// so we set any we haven't gotten to yet to 0
for (int extra_mux = muxNo; extra_mux < TINY_GSM_MUX_COUNT;
extra_mux++) {
GsmClientSim7080* isock = sockets[extra_mux];
if (isock) { isock->sock_connected = false; }
}
break;
} else {
// if we got an error, give up
break;
}
// Should be a final OK at the end.
// If every connection was returned, catch the OK here.
// If only a portion were returned, catch it above.
if (muxNo == TINY_GSM_MUX_COUNT - 1) { waitResponse(); }
}
return sockets[mux]->sock_connected;
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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(64);
uint8_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)) {
#if defined TINY_GSM_DEBUG
if (r3 == GFP(GSM_CME_ERROR)) {
streamSkipUntil('\n'); // Read out the error
}
#endif
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF("+CARECV:"))) {
int8_t mux = streamGetIntBefore(',');
int16_t len = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; }
}
data = "";
DBG("### Got Data:", len, "on", mux);
} else if (data.endsWith(GF("+CADATAIND:"))) {
int8_t mux = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
}
data = "";
DBG("### Got Data:", mux);
} else if (data.endsWith(GF("+CASTATE:"))) {
int8_t mux = streamGetIntBefore(',');
int8_t state = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
if (state != 1) {
sockets[mux]->sock_connected = false;
DBG("### Closed: ", mux);
}
}
data = "";
} else if (data.endsWith(GF("*PSNWID:"))) {
streamSkipUntil('\n'); // Refresh network name by network
data = "";
DBG("### Network name updated.");
} else if (data.endsWith(GF("*PSUTTZ:"))) {
streamSkipUntil('\n'); // Refresh time and time zone by network
data = "";
DBG("### Network time and time zone updated.");
} else if (data.endsWith(GF("+CTZV:"))) {
streamSkipUntil('\n'); // Refresh network time zone by network
data = "";
DBG("### Network time zone updated.");
} else if (data.endsWith(GF("DST: "))) {
streamSkipUntil(
'\n'); // Refresh Network Daylight Saving Time by network
data = "";
DBG("### Daylight savings time state updated.");
} else if (data.endsWith(GF(GSM_NL "SMS Ready" GSM_NL))) {
data = "";
DBG("### Unexpected module reset!");
init();
data = "";
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5);
}
protected:
GsmClientSim7080* sockets[TINY_GSM_MUX_COUNT];
String certificates[TINY_GSM_MUX_COUNT];
};
#endif // SRC_TINYGSMCLIENTSIM7080_H_

View File

@ -0,0 +1,459 @@
/**
* @file TinyGsmClientSIM70xx.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTSIM70XX_H_
#define SRC_TINYGSMCLIENTSIM70XX_H_
// #define TINY_GSM_DEBUG Serial
// #define TINY_GSM_USE_HEX
#include "TinyGsmBattery.tpp"
#include "TinyGsmGPRS.tpp"
#include "TinyGsmGPS.tpp"
#include "TinyGsmModem.tpp"
#include "TinyGsmSMS.tpp"
#include "TinyGsmTime.tpp"
#include "TinyGsmNTP.tpp"
#include "TinyGsmGSMLocation.tpp"
#define GSM_NL "\r\n"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
#if defined TINY_GSM_DEBUG
static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:";
static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:";
#endif
enum RegStatus {
REG_NO_RESULT = -1,
REG_UNREGISTERED = 0,
REG_SEARCHING = 2,
REG_DENIED = 3,
REG_OK_HOME = 1,
REG_OK_ROAMING = 5,
REG_UNKNOWN = 4,
};
template <class modemType>
class TinyGsmSim70xx : public TinyGsmModem<TinyGsmSim70xx<modemType>>,
public TinyGsmGPRS<TinyGsmSim70xx<modemType>>,
public TinyGsmSMS<TinyGsmSim70xx<modemType>>,
public TinyGsmGPS<TinyGsmSim70xx<modemType>>,
public TinyGsmTime<TinyGsmSim70xx<modemType>>,
public TinyGsmNTP<TinyGsmSim70xx<modemType>>,
public TinyGsmBattery<TinyGsmSim70xx<modemType>>,
public TinyGsmGSMLocation<TinyGsmSim70xx<modemType>> {
friend class TinyGsmModem<TinyGsmSim70xx<modemType>>;
friend class TinyGsmGPRS<TinyGsmSim70xx<modemType>>;
friend class TinyGsmSMS<TinyGsmSim70xx<modemType>>;
friend class TinyGsmGPS<TinyGsmSim70xx<modemType>>;
friend class TinyGsmTime<TinyGsmSim70xx<modemType>>;
friend class TinyGsmNTP<TinyGsmSim70xx<modemType>>;
friend class TinyGsmBattery<TinyGsmSim70xx<modemType>>;
friend class TinyGsmGSMLocation<TinyGsmSim70xx<modemType>>;
/*
* CRTP Helper
*/
protected:
inline const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
inline modemType& thisModem() {
return static_cast<modemType&>(*this);
}
/*
* Constructor
*/
public:
explicit TinyGsmSim70xx(Stream& stream) : stream(stream) {}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
return thisModem().initImpl(pin);
}
String getModemNameImpl() {
String name = "SIMCom SIM7000";
thisModem().sendAT(GF("+GMM"));
String res2;
if (thisModem().waitResponse(5000L, res2) != 1) { return name; }
res2.replace(GSM_NL "OK" GSM_NL, "");
res2.replace("_", " ");
res2.trim();
name = res2;
return name;
}
bool factoryDefaultImpl() { // these commands aren't supported
thisModem().sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write
thisModem().waitResponse();
thisModem().sendAT(GF("+IPR=0")); // Auto-baud
thisModem().waitResponse();
thisModem().sendAT(GF("+IFC=0,0")); // No Flow Control
thisModem().waitResponse();
thisModem().sendAT(GF("+ICF=3,3")); // 8 data 0 parity 1 stop
thisModem().waitResponse();
thisModem().sendAT(GF("+CSCLK=0")); // Disable Slow Clock
thisModem().waitResponse();
thisModem().sendAT(GF("&W")); // Write configuration
return thisModem().waitResponse() == 1;
}
/*
* Power functions
*/
protected:
bool restartImpl(const char* pin = NULL) {
thisModem().sendAT(GF("E0")); // Echo Off
thisModem().waitResponse();
if (!thisModem().setPhoneFunctionality(0)) { return false; }
if (!thisModem().setPhoneFunctionality(1, true)) { return false; }
thisModem().waitResponse(30000L, GF("SMS Ready"));
return thisModem().initImpl(pin);
}
bool powerOffImpl() {
thisModem().sendAT(GF("+CPOWD=1"));
return thisModem().waitResponse(GF("NORMAL POWER DOWN")) == 1;
}
// During sleep, the SIM70xx module has its serial communication disabled.
// In order to reestablish communication pull the DRT-pin of the SIM70xx
// module LOW for at least 50ms. Then use this function to disable sleep
// mode. The DTR-pin can then be released again.
bool sleepEnableImpl(bool enable = true) {
thisModem().sendAT(GF("+CSCLK="), enable);
return thisModem().waitResponse() == 1;
}
bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) {
thisModem().sendAT(GF("+CFUN="), fun, reset ? ",1" : "");
return thisModem().waitResponse(10000L) == 1;
}
/*
* Generic network functions
*/
public:
RegStatus getRegistrationStatus() {
RegStatus epsStatus =
(RegStatus)thisModem().getRegistrationStatusXREG("CEREG");
// If we're connected on EPS, great!
if (epsStatus == REG_OK_HOME || epsStatus == REG_OK_ROAMING) {
return epsStatus;
} else {
// Otherwise, check GPRS network status
// We could be using GPRS fall-back or the board could be being moody
return (RegStatus)thisModem().getRegistrationStatusXREG("CGREG");
}
}
protected:
bool isNetworkConnectedImpl() {
RegStatus s = getRegistrationStatus();
return (s == REG_OK_HOME || s == REG_OK_ROAMING);
}
public:
String getNetworkModes() {
// Get the help string, not the setting value
thisModem().sendAT(GF("+CNMP=?"));
if (thisModem().waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
thisModem().waitResponse();
return res;
}
int16_t getNetworkMode() {
thisModem().sendAT(GF("+CNMP?"));
if (thisModem().waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return false; }
int16_t mode = thisModem().streamGetIntBefore('\n');
thisModem().waitResponse();
return mode;
}
bool setNetworkMode(uint8_t mode) {
// 2 Automatic
// 13 GSM only
// 38 LTE only
// 51 GSM and LTE only
thisModem().sendAT(GF("+CNMP="), mode);
return thisModem().waitResponse() == 1;
}
String getPreferredModes() {
// Get the help string, not the setting value
thisModem().sendAT(GF("+CMNB=?"));
if (thisModem().waitResponse(GF(GSM_NL "+CMNB:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
thisModem().waitResponse();
return res;
}
int16_t getPreferredMode() {
thisModem().sendAT(GF("+CMNB?"));
if (thisModem().waitResponse(GF(GSM_NL "+CMNB:")) != 1) { return false; }
int16_t mode = thisModem().streamGetIntBefore('\n');
thisModem().waitResponse();
return mode;
}
bool setPreferredMode(uint8_t mode) {
// 1 CAT-M
// 2 NB-IoT
// 3 CAT-M and NB-IoT
thisModem().sendAT(GF("+CMNB="), mode);
return thisModem().waitResponse() == 1;
}
bool getNetworkSystemMode(bool& n, int16_t& stat) {
// n: whether to automatically report the system mode info
// stat: the current service. 0 if it not connected
thisModem().sendAT(GF("+CNSMOD?"));
if (thisModem().waitResponse(GF(GSM_NL "+CNSMOD:")) != 1) { return false; }
n = thisModem().streamGetIntBefore(',') != 0;
stat = thisModem().streamGetIntBefore('\n');
thisModem().waitResponse();
return true;
}
bool setNetworkSystemMode(bool n) {
// n: whether to automatically report the system mode info
thisModem().sendAT(GF("+CNSMOD="), int8_t(n));
return thisModem().waitResponse() == 1;
}
String getLocalIPImpl() {
return thisModem().getLocalIPImpl();
}
/*
* GPRS functions
*/
protected:
// should implement in sub-classes
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
return thisModem().gprsConnectImpl(apn, user, pwd);
}
bool gprsDisconnectImpl() {
return thisModem().gprsDisconnectImpl();
}
/*
* SIM card functions
*/
protected:
// Doesn't return the "+CCID" before the number
String getSimCCIDImpl() {
thisModem().sendAT(GF("+CCID"));
if (thisModem().waitResponse(GF(GSM_NL)) != 1) { return ""; }
String res = stream.readStringUntil('\n');
thisModem().waitResponse();
res.trim();
return res;
}
/*
* Messaging functions
*/
protected:
// Follows all messaging functions per template
/*
* GPS/GNSS/GLONASS location functions
*/
protected:
// enable GPS
bool enableGPSImpl() {
thisModem().sendAT(GF("+CGNSPWR=1"));
if (thisModem().waitResponse() != 1) { return false; }
return true;
}
bool disableGPSImpl() {
thisModem().sendAT(GF("+CGNSPWR=0"));
if (thisModem().waitResponse() != 1) { return false; }
return true;
}
// get the RAW GPS output
String getGPSrawImpl() {
thisModem().sendAT(GF("+CGNSINF"));
if (thisModem().waitResponse(10000L, GF(GSM_NL "+CGNSINF:")) != 1) {
return "";
}
String res = stream.readStringUntil('\n');
thisModem().waitResponse();
res.trim();
return res;
}
// get GPS informations
bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0,
int* vsat = 0, int* usat = 0, float* accuracy = 0,
int* year = 0, int* month = 0, int* day = 0, int* hour = 0,
int* minute = 0, int* second = 0) {
thisModem().sendAT(GF("+CGNSINF"));
if (thisModem().waitResponse(10000L, GF(GSM_NL "+CGNSINF:")) != 1) {
return false;
}
thisModem().streamSkipUntil(','); // GNSS run status
if (thisModem().streamGetIntBefore(',') == 1) { // fix status
// init variables
float ilat = 0;
float ilon = 0;
float ispeed = 0;
float ialt = 0;
int ivsat = 0;
int iusat = 0;
float iaccuracy = 0;
int iyear = 0;
int imonth = 0;
int iday = 0;
int ihour = 0;
int imin = 0;
float secondWithSS = 0;
// UTC date & Time
iyear = thisModem().streamGetIntLength(4); // Four digit year
imonth = thisModem().streamGetIntLength(2); // Two digit month
iday = thisModem().streamGetIntLength(2); // Two digit day
ihour = thisModem().streamGetIntLength(2); // Two digit hour
imin = thisModem().streamGetIntLength(2); // Two digit minute
secondWithSS = thisModem().streamGetFloatBefore(
','); // 6 digit second with subseconds
ilat = thisModem().streamGetFloatBefore(','); // Latitude
ilon = thisModem().streamGetFloatBefore(','); // Longitude
ialt = thisModem().streamGetFloatBefore(
','); // MSL Altitude. Unit is meters
ispeed = thisModem().streamGetFloatBefore(
','); // Speed Over Ground. Unit is knots.
thisModem().streamSkipUntil(','); // Course Over Ground. Degrees.
thisModem().streamSkipUntil(','); // Fix Mode
thisModem().streamSkipUntil(','); // Reserved1
iaccuracy = thisModem().streamGetFloatBefore(
','); // Horizontal Dilution Of Precision
thisModem().streamSkipUntil(','); // Position Dilution Of Precision
thisModem().streamSkipUntil(','); // Vertical Dilution Of Precision
thisModem().streamSkipUntil(','); // Reserved2
ivsat = thisModem().streamGetIntBefore(','); // GNSS Satellites in View
iusat = thisModem().streamGetIntBefore(','); // GNSS Satellites Used
thisModem().streamSkipUntil(','); // GLONASS Satellites Used
thisModem().streamSkipUntil(','); // Reserved3
thisModem().streamSkipUntil(','); // C/N0 max
thisModem().streamSkipUntil(','); // HPA
thisModem().streamSkipUntil('\n'); // VPA
// Set pointers
if (lat != NULL) *lat = ilat;
if (lon != NULL) *lon = ilon;
if (speed != NULL) *speed = ispeed;
if (alt != NULL) *alt = ialt;
if (vsat != NULL) *vsat = ivsat;
if (usat != NULL) *usat = iusat;
if (accuracy != NULL) *accuracy = iaccuracy;
if (iyear < 2000) iyear += 2000;
if (year != NULL) *year = iyear;
if (month != NULL) *month = imonth;
if (day != NULL) *day = iday;
if (hour != NULL) *hour = ihour;
if (minute != NULL) *minute = imin;
if (second != NULL) *second = static_cast<int>(secondWithSS);
thisModem().waitResponse();
return true;
}
thisModem().streamSkipUntil('\n'); // toss the row of commas
thisModem().waitResponse();
return false;
}
/*
* Time functions
*/
// Can follow CCLK as per template
/*
* NTP server functions
*/
// Can sync with server using CNTP as per template
/*
* Battery functions
*/
protected:
// Follows all battery functions per template
/*
* Client related functions
*/
// should implement in sub-classes
/*
* Utilities
*/
public:
// should implement in sub-classes
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return thisModem().waitResponse(timeout_ms, data, r1, r2, r3, r4, r5);
}
int8_t waitResponse(uint32_t timeout_ms, GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
String data;
return thisModem().waitResponse(timeout_ms, data, r1, r2, r3, r4, r5);
}
int8_t waitResponse(GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return thisModem().waitResponse(1000, r1, r2, r3, r4, r5);
}
public:
Stream& stream;
protected:
const char* gsmNL = GSM_NL;
};
#endif // SRC_TINYGSMCLIENTSIM70XX_H_

View File

@ -0,0 +1,866 @@
/**
* @file TinyGsmClientSIM7600.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTSIM7600_H_
#define SRC_TINYGSMCLIENTSIM7600_H_
// #define TINY_GSM_DEBUG Serial
// #define TINY_GSM_USE_HEX
#define TINY_GSM_MUX_COUNT 10
#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE
#include "TinyGsmBattery.tpp"
#include "TinyGsmCalling.tpp"
#include "TinyGsmGPRS.tpp"
#include "TinyGsmGPS.tpp"
#include "TinyGsmGSMLocation.tpp"
#include "TinyGsmModem.tpp"
#include "TinyGsmSMS.tpp"
#include "TinyGsmTCP.tpp"
#include "TinyGsmTemperature.tpp"
#include "TinyGsmTime.tpp"
#include "TinyGsmNTP.tpp"
#define GSM_NL "\r\n"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
#if defined TINY_GSM_DEBUG
static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:";
static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:";
#endif
enum RegStatus {
REG_NO_RESULT = -1,
REG_UNREGISTERED = 0,
REG_SEARCHING = 2,
REG_DENIED = 3,
REG_OK_HOME = 1,
REG_OK_ROAMING = 5,
REG_UNKNOWN = 4,
};
class TinyGsmSim7600 : public TinyGsmModem<TinyGsmSim7600>,
public TinyGsmGPRS<TinyGsmSim7600>,
public TinyGsmTCP<TinyGsmSim7600, TINY_GSM_MUX_COUNT>,
public TinyGsmSMS<TinyGsmSim7600>,
public TinyGsmGSMLocation<TinyGsmSim7600>,
public TinyGsmGPS<TinyGsmSim7600>,
public TinyGsmTime<TinyGsmSim7600>,
public TinyGsmNTP<TinyGsmSim7600>,
public TinyGsmBattery<TinyGsmSim7600>,
public TinyGsmTemperature<TinyGsmSim7600>,
public TinyGsmCalling<TinyGsmSim7600> {
friend class TinyGsmModem<TinyGsmSim7600>;
friend class TinyGsmGPRS<TinyGsmSim7600>;
friend class TinyGsmTCP<TinyGsmSim7600, TINY_GSM_MUX_COUNT>;
friend class TinyGsmSMS<TinyGsmSim7600>;
friend class TinyGsmGPS<TinyGsmSim7600>;
friend class TinyGsmGSMLocation<TinyGsmSim7600>;
friend class TinyGsmTime<TinyGsmSim7600>;
friend class TinyGsmNTP<TinyGsmSim7600>;
friend class TinyGsmBattery<TinyGsmSim7600>;
friend class TinyGsmTemperature<TinyGsmSim7600>;
friend class TinyGsmCalling<TinyGsmSim7600>;
/*
* Inner Client
*/
public:
class GsmClientSim7600 : public GsmClient {
friend class TinyGsmSim7600;
public:
GsmClientSim7600() {}
explicit GsmClientSim7600(TinyGsmSim7600& modem, uint8_t mux = 0) {
init(&modem, mux);
}
bool init(TinyGsmSim7600* modem, uint8_t mux = 0) {
this->at = modem;
sock_available = 0;
prev_check = 0;
sock_connected = false;
got_data = false;
if (mux < TINY_GSM_MUX_COUNT) {
this->mux = mux;
} else {
this->mux = (mux % TINY_GSM_MUX_COUNT);
}
at->sockets[this->mux] = this;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, false, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void stop(uint32_t maxWaitMs) {
dumpModemBuffer(maxWaitMs);
at->sendAT(GF("+CIPCLOSE="), mux);
sock_connected = false;
at->waitResponse();
}
void stop() override {
stop(15000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
/*TODO(?))
class GsmClientSecureSIM7600 : public GsmClientSim7600
{
public:
GsmClientSecure() {}
GsmClientSecure(TinyGsmSim7600& modem, uint8_t mux = 0)
: public GsmClient(modem, mux)
{}
public:
int connect(const char* host, uint16_t port, int timeout_s) override {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, true, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
};
*/
/*
* Constructor
*/
public:
explicit TinyGsmSim7600(Stream& stream) : stream(stream) {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM7600"));
if (!testAT()) { return false; }
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
#ifdef TINY_GSM_DEBUG
sendAT(GF("+CMEE=2")); // turn on verbose error codes
#else
sendAT(GF("+CMEE=0")); // turn off error codes
#endif
waitResponse();
DBG(GF("### Modem:"), getModemName());
// Disable time and time zone URC's
sendAT(GF("+CTZR=0"));
if (waitResponse(10000L) != 1) { return false; }
// Enable automatic time zome update
sendAT(GF("+CTZU=1"));
if (waitResponse(10000L) != 1) { return false; }
SimStatus ret = getSimStatus();
// if the sim isn't ready and a pin has been provided, try to unlock the sim
if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
simUnlock(pin);
return (getSimStatus() == SIM_READY);
} else {
// if the sim is ready, or it's locked but no pin has been provided,
// return true
return (ret == SIM_READY || ret == SIM_LOCKED);
}
}
String getModemNameImpl() {
String name = "SIMCom SIM7600";
sendAT(GF("+CGMM"));
String res2;
if (waitResponse(1000L, res2) != 1) { return name; }
res2.replace(GSM_NL "OK" GSM_NL, "");
res2.replace("_", " ");
res2.trim();
name = res2;
DBG("### Modem:", name);
return name;
}
bool factoryDefaultImpl() { // these commands aren't supported
return false;
}
/*
* Power functions
*/
protected:
bool restartImpl(const char* pin = NULL) {
if (!testAT()) { return false; }
sendAT(GF("+CRESET"));
if (waitResponse(10000L) != 1) { return false; }
delay(5000L); // TODO(?): Test this delay!
return init(pin);
}
bool powerOffImpl() {
sendAT(GF("+CPOF"));
return waitResponse() == 1;
}
bool radioOffImpl() {
if (!setPhoneFunctionality(4)) { return false; }
delay(3000);
return true;
}
bool sleepEnableImpl(bool enable = true) {
sendAT(GF("+CSCLK="), enable);
return waitResponse() == 1;
}
bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) {
sendAT(GF("+CFUN="), fun, reset ? ",1" : "");
return waitResponse(10000L) == 1;
}
/*
* Generic network functions
*/
public:
RegStatus getRegistrationStatus() {
return (RegStatus)getRegistrationStatusXREG("CGREG");
}
protected:
bool isNetworkConnectedImpl() {
RegStatus s = getRegistrationStatus();
return (s == REG_OK_HOME || s == REG_OK_ROAMING);
}
public:
String getNetworkModes() {
sendAT(GF("+CNMP=?"));
if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
return res;
}
int16_t getNetworkMode() {
sendAT(GF("+CNMP?"));
if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { return false; }
int16_t mode = streamGetIntBefore('\n');
waitResponse();
return mode;
}
bool setNetworkMode(uint8_t mode) {
sendAT(GF("+CNMP="), mode);
return waitResponse() == 1;
}
String getLocalIPImpl() {
sendAT(GF("+IPADDR")); // Inquire Socket PDP address
// sendAT(GF("+CGPADDR=1")); // Show PDP address
String res;
if (waitResponse(10000L, res) != 1) { return ""; }
res.replace(GSM_NL "OK" GSM_NL, "");
res.replace(GSM_NL, "");
res.trim();
return res;
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
gprsDisconnect(); // Make sure we're not connected first
// Define the PDP context
// The CGDCONT commands set up the "external" PDP context
// Set the external authentication
if (user && strlen(user) > 0) {
sendAT(GF("+CGAUTH=1,0,\""), user, GF("\",\""), pwd, '"');
waitResponse();
}
// Define external PDP context 1
sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"', ",\"0.0.0.0\",0,0");
waitResponse();
// Configure TCP parameters
// Select TCP/IP application mode (command mode)
sendAT(GF("+CIPMODE=0"));
waitResponse();
// Set Sending Mode - send without waiting for peer TCP ACK
sendAT(GF("+CIPSENDMODE=0"));
waitResponse();
// Configure socket parameters
// AT+CIPCCFG= <NmRetry>, <DelayTm>, <Ack>, <errMode>, <HeaderType>,
// <AsyncMode>, <TimeoutVal>
// NmRetry = number of retransmission to be made for an IP packet
// = 10 (default)
// DelayTm = number of milliseconds to delay before outputting received data
// = 0 (default)
// Ack = sets whether reporting a string "Send ok" = 0 (don't report)
// errMode = mode of reporting error result code = 0 (numberic values)
// HeaderType = which data header of receiving data in multi-client mode
// = 1 (+RECEIVE,<link num>,<data length>)
// AsyncMode = sets mode of executing commands
// = 0 (synchronous command executing)
// TimeoutVal = minimum retransmission timeout in milliseconds = 75000
sendAT(GF("+CIPCCFG=10,0,0,0,1,0,75000"));
if (waitResponse() != 1) { return false; }
// Configure timeouts for opening and closing sockets
// AT+CIPTIMEOUT=<netopen_timeout> <cipopen_timeout>, <cipsend_timeout>
sendAT(GF("+CIPTIMEOUT="), 75000, ',', 15000, ',', 15000);
waitResponse();
// Start the socket service
// This activates and attaches to the external PDP context that is tied
// to the embedded context for TCP/IP (ie AT+CGACT=1,1 and AT+CGATT=1)
// Response may be an immediate "OK" followed later by "+NETOPEN: 0".
// We to ignore any immediate response and wait for the
// URC to show it's really connected.
sendAT(GF("+NETOPEN"));
if (waitResponse(75000L, GF(GSM_NL "+NETOPEN: 0")) != 1) { return false; }
return true;
}
bool gprsDisconnectImpl() {
// Close all sockets and stop the socket service
// Note: On the LTE models, this single command closes all sockets and the
// service
sendAT(GF("+NETCLOSE"));
if (waitResponse(60000L, GF(GSM_NL "+NETCLOSE: 0")) != 1) { return false; }
return true;
}
bool isGprsConnectedImpl() {
sendAT(GF("+NETOPEN?"));
// May return +NETOPEN: 1, 0. We just confirm that the first number is 1
if (waitResponse(GF(GSM_NL "+NETOPEN: 1")) != 1) { return false; }
waitResponse();
sendAT(GF("+IPADDR")); // Inquire Socket PDP address
// sendAT(GF("+CGPADDR=1")); // Show PDP address
if (waitResponse() != 1) { return false; }
return true;
}
/*
* SIM card functions
*/
protected:
// Gets the CCID of a sim card via AT+CCID
String getSimCCIDImpl() {
sendAT(GF("+CICCID"));
if (waitResponse(GF(GSM_NL "+ICCID:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
/*
* Phone Call functions
*/
protected:
bool callHangupImpl() {
sendAT(GF("+CHUP"));
return waitResponse() == 1;
}
/*
* Messaging functions
*/
protected:
// Follows all messaging functions per template
/*
* GSM Location functions
*/
protected:
// Can return a GSM-based location from CLBS as per the template
/*
* GPS/GNSS/GLONASS location functions
*/
protected:
// enable GPS
bool enableGPSImpl() {
sendAT(GF("+CGPS=1"));
if (waitResponse() != 1) { return false; }
return true;
}
bool disableGPSImpl() {
sendAT(GF("+CGPS=0"));
if (waitResponse() != 1) { return false; }
return true;
}
// get the RAW GPS output
String getGPSrawImpl() {
sendAT(GF("+CGNSSINFO"));
if (waitResponse(GF(GSM_NL "+CGNSSINFO:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
// get GPS informations
bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0,
int* vsat = 0, int* usat = 0, float* accuracy = 0,
int* year = 0, int* month = 0, int* day = 0, int* hour = 0,
int* minute = 0, int* second = 0) {
sendAT(GF("+CGNSSINFO"));
if (waitResponse(GF(GSM_NL "+CGNSSINFO:")) != 1) { return false; }
uint8_t fixMode = streamGetIntBefore(','); // mode 2=2D Fix or 3=3DFix
// TODO(?) Can 1 be returned
if (fixMode == 1 || fixMode == 2 || fixMode == 3) {
// init variables
float ilat = 0;
char north;
float ilon = 0;
char east;
float ispeed = 0;
float ialt = 0;
int ivsat = 0;
int iusat = 0;
float iaccuracy = 0;
int iyear = 0;
int imonth = 0;
int iday = 0;
int ihour = 0;
int imin = 0;
float secondWithSS = 0;
streamSkipUntil(','); // GPS satellite valid numbers
streamSkipUntil(','); // GLONASS satellite valid numbers
streamSkipUntil(','); // BEIDOU satellite valid numbers
ilat = streamGetFloatBefore(','); // Latitude in ddmm.mmmmmm
north = stream.read(); // N/S Indicator, N=north or S=south
streamSkipUntil(',');
ilon = streamGetFloatBefore(','); // Longitude in ddmm.mmmmmm
east = stream.read(); // E/W Indicator, E=east or W=west
streamSkipUntil(',');
// Date. Output format is ddmmyy
iday = streamGetIntLength(2); // Two digit day
imonth = streamGetIntLength(2); // Two digit month
iyear = streamGetIntBefore(','); // Two digit year
// UTC Time. Output format is hhmmss.s
ihour = streamGetIntLength(2); // Two digit hour
imin = streamGetIntLength(2); // Two digit minute
secondWithSS =
streamGetFloatBefore(','); // 4 digit second with subseconds
ialt = streamGetFloatBefore(','); // MSL Altitude. Unit is meters
ispeed = streamGetFloatBefore(','); // Speed Over Ground. Unit is knots.
streamSkipUntil(','); // Course Over Ground. Degrees.
streamSkipUntil(','); // After set, will report GPS every x seconds
iaccuracy = streamGetFloatBefore(','); // Position Dilution Of Precision
streamSkipUntil(','); // Horizontal Dilution Of Precision
streamSkipUntil(','); // Vertical Dilution Of Precision
streamSkipUntil('\n'); // TODO(?) is one more field reported??
// Set pointers
if (lat != NULL)
*lat = (floor(ilat / 100) + fmod(ilat, 100.) / 60) *
(north == 'N' ? 1 : -1);
if (lon != NULL)
*lon = (floor(ilon / 100) + fmod(ilon, 100.) / 60) *
(east == 'E' ? 1 : -1);
if (speed != NULL) *speed = ispeed;
if (alt != NULL) *alt = ialt;
if (vsat != NULL) *vsat = ivsat;
if (usat != NULL) *usat = iusat;
if (accuracy != NULL) *accuracy = iaccuracy;
if (iyear < 2000) iyear += 2000;
if (year != NULL) *year = iyear;
if (month != NULL) *month = imonth;
if (day != NULL) *day = iday;
if (hour != NULL) *hour = ihour;
if (minute != NULL) *minute = imin;
if (second != NULL) *second = static_cast<int>(secondWithSS);
waitResponse();
return true;
}
waitResponse();
return false;
}
/**
* CGNSSMODE: <gnss_mode>,<dpo_mode>
* This command is used to configure GPS, GLONASS, BEIDOU and QZSS support
* mode. 0 : GLONASS 1 : BEIDOU 2 : GALILEO 3 : QZSS dpo_mode: 1 enable , 0
* disable
*/
String setGNSSModeImpl(uint8_t mode, bool dpo) {
String res;
sendAT(GF("+CGNSSMODE="), mode, ",", dpo);
if (waitResponse(10000L, res) != 1) { return ""; }
res.replace(GSM_NL, "");
res.trim();
return res;
}
uint8_t getGNSSModeImpl() {
sendAT(GF("+CGNSSMODE?"));
if (waitResponse(GF(GSM_NL "+CGNSSMODE:")) != 1) { return 0; }
return stream.readStringUntil(',').toInt();
}
/*
* Time functions
*/
protected:
// Can follow the standard CCLK function in the template
/*
* NTP server functions
*/
// Can sync with server using CNTP as per template
/*
* Battery functions
*/
protected:
// returns volts, multiply by 1000 to get mV
uint16_t getBattVoltageImpl() {
sendAT(GF("+CBC"));
if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return 0; }
// get voltage in VOLTS
float voltage = streamGetFloatBefore('\n');
// Wait for final OK
waitResponse();
// Return millivolts
uint16_t res = voltage * 1000;
return res;
}
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
*/
protected:
// get temperature in degree celsius
uint16_t getTemperatureImpl() {
sendAT(GF("+CPMUTEMP"));
if (waitResponse(GF(GSM_NL "+CPMUTEMP:")) != 1) { return 0; }
// return temperature in C
uint16_t res = streamGetIntBefore('\n');
// Wait for final OK
waitResponse();
return res;
}
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t mux,
bool ssl = false, int timeout_s = 15) {
if (ssl) { DBG("SSL not yet supported on this module!"); }
// Make sure we'll be getting data manually on this connection
sendAT(GF("+CIPRXGET=1"));
if (waitResponse() != 1) { return false; }
// Establish a connection in multi-socket mode
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
sendAT(GF("+CIPOPEN="), mux, ',', GF("\"TCP"), GF("\",\""), host, GF("\","),
port);
// The reply is OK followed by +CIPOPEN: <link_num>,<err> where <link_num>
// is the mux number and <err> should be 0 if there's no error
if (waitResponse(timeout_ms, GF(GSM_NL "+CIPOPEN:")) != 1) { return false; }
uint8_t opened_mux = streamGetIntBefore(',');
uint8_t opened_result = streamGetIntBefore('\n');
if (opened_mux != mux || opened_result != 0) return false;
return true;
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len);
if (waitResponse(GF(">")) != 1) { return 0; }
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
if (waitResponse(GF(GSM_NL "+CIPSEND:")) != 1) { return 0; }
streamSkipUntil(','); // Skip mux
streamSkipUntil(','); // Skip requested bytes to send
// TODO(?): make sure requested and confirmed bytes match
return streamGetIntBefore('\n');
}
size_t modemRead(size_t size, uint8_t mux) {
if (!sockets[mux]) return 0;
#ifdef TINY_GSM_USE_HEX
sendAT(GF("+CIPRXGET=3,"), mux, ',', (uint16_t)size);
if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; }
#else
sendAT(GF("+CIPRXGET=2,"), mux, ',', (uint16_t)size);
if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; }
#endif
streamSkipUntil(','); // Skip Rx mode 2/normal or 3/HEX
streamSkipUntil(','); // Skip mux/cid (connecion id)
int16_t len_requested = streamGetIntBefore(',');
// ^^ Requested number of data bytes (1-1460 bytes)to be read
int16_t len_confirmed = streamGetIntBefore('\n');
// ^^ The data length which not read in the buffer
for (int i = 0; i < len_requested; i++) {
uint32_t startMillis = millis();
#ifdef TINY_GSM_USE_HEX
while (stream.available() < 2 &&
(millis() - startMillis < sockets[mux]->_timeout)) {
TINY_GSM_YIELD();
}
char buf[4] = {
0,
};
buf[0] = stream.read();
buf[1] = stream.read();
char c = strtol(buf, NULL, 16);
#else
while (!stream.available() &&
(millis() - startMillis < sockets[mux]->_timeout)) {
TINY_GSM_YIELD();
}
char c = stream.read();
#endif
sockets[mux]->rx.put(c);
}
// DBG("### READ:", len_requested, "from", mux);
// sockets[mux]->sock_available = modemGetAvailable(mux);
sockets[mux]->sock_available = len_confirmed;
waitResponse();
return len_requested;
}
size_t modemGetAvailable(uint8_t mux) {
if (!sockets[mux]) return 0;
sendAT(GF("+CIPRXGET=4,"), mux);
size_t result = 0;
if (waitResponse(GF("+CIPRXGET:")) == 1) {
streamSkipUntil(','); // Skip mode 4
streamSkipUntil(','); // Skip mux
result = streamGetIntBefore('\n');
waitResponse();
}
// DBG("### Available:", result, "on", mux);
if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); }
return result;
}
bool modemGetConnected(uint8_t mux) {
// Read the status of all sockets at once
sendAT(GF("+CIPCLOSE?"));
if (waitResponse(GF("+CIPCLOSE:")) != 1) {
// return false; // TODO: Why does this not read correctly?
}
for (int muxNo = 0; muxNo < TINY_GSM_MUX_COUNT; muxNo++) {
// +CIPCLOSE:<link0_state>,<link1_state>,...,<link9_state>
bool muxState = stream.parseInt();
if (sockets[muxNo]) { sockets[muxNo]->sock_connected = muxState; }
}
waitResponse(); // Should be an OK at the end
if (!sockets[mux]) return false;
return sockets[mux]->sock_connected;
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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(64);
uint8_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)) {
#if defined TINY_GSM_DEBUG
if (r3 == GFP(GSM_CME_ERROR)) {
streamSkipUntil('\n'); // Read out the error
}
#endif
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) {
int8_t mode = streamGetIntBefore(',');
if (mode == 1) {
int8_t mux = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
}
data = "";
// DBG("### Got Data:", mux);
} else {
data += mode;
}
} else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) {
int8_t mux = streamGetIntBefore(',');
int16_t len = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; }
}
data = "";
// DBG("### Got Data:", len, "on", mux);
} else if (data.endsWith(GF("+IPCLOSE:"))) {
int8_t mux = streamGetIntBefore(',');
streamSkipUntil('\n'); // Skip the reason code
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->sock_connected = false;
}
data = "";
DBG("### Closed: ", mux);
} else if (data.endsWith(GF("+CIPEVENT:"))) {
// Need to close all open sockets and release the network library.
// User will then need to reconnect.
DBG("### Network error!");
if (!isGprsConnected()) { gprsDisconnect(); }
data = "";
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5);
}
public:
Stream& stream;
protected:
GsmClientSim7600* sockets[TINY_GSM_MUX_COUNT];
const char* gsmNL = GSM_NL;
};
#endif // SRC_TINYGSMCLIENTSIM7600_H_

View File

@ -0,0 +1,771 @@
/**
* @file TinyGsmClientSIM800.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTSIM800_H_
#define SRC_TINYGSMCLIENTSIM800_H_
// #pragma message("TinyGSM: TinyGsmClientSIM800")
// #define TINY_GSM_DEBUG Serial
// #define TINY_GSM_USE_HEX
#define TINY_GSM_MUX_COUNT 5
#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE
#include "TinyGsmBattery.tpp"
#include "TinyGsmCalling.tpp"
#include "TinyGsmGPRS.tpp"
#include "TinyGsmGSMLocation.tpp"
#include "TinyGsmModem.tpp"
#include "TinyGsmSMS.tpp"
#include "TinyGsmSSL.tpp"
#include "TinyGsmTCP.tpp"
#include "TinyGsmTime.tpp"
#include "TinyGsmNTP.tpp"
#define GSM_NL "\r\n"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
#if defined TINY_GSM_DEBUG
static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:";
static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:";
#endif
enum RegStatus {
REG_NO_RESULT = -1,
REG_UNREGISTERED = 0,
REG_SEARCHING = 2,
REG_DENIED = 3,
REG_OK_HOME = 1,
REG_OK_ROAMING = 5,
REG_UNKNOWN = 4,
};
class TinyGsmSim800 : public TinyGsmModem<TinyGsmSim800>,
public TinyGsmGPRS<TinyGsmSim800>,
public TinyGsmTCP<TinyGsmSim800, TINY_GSM_MUX_COUNT>,
public TinyGsmSSL<TinyGsmSim800>,
public TinyGsmCalling<TinyGsmSim800>,
public TinyGsmSMS<TinyGsmSim800>,
public TinyGsmGSMLocation<TinyGsmSim800>,
public TinyGsmTime<TinyGsmSim800>,
public TinyGsmNTP<TinyGsmSim800>,
public TinyGsmBattery<TinyGsmSim800> {
friend class TinyGsmModem<TinyGsmSim800>;
friend class TinyGsmGPRS<TinyGsmSim800>;
friend class TinyGsmTCP<TinyGsmSim800, TINY_GSM_MUX_COUNT>;
friend class TinyGsmSSL<TinyGsmSim800>;
friend class TinyGsmCalling<TinyGsmSim800>;
friend class TinyGsmSMS<TinyGsmSim800>;
friend class TinyGsmGSMLocation<TinyGsmSim800>;
friend class TinyGsmTime<TinyGsmSim800>;
friend class TinyGsmNTP<TinyGsmSim800>;
friend class TinyGsmBattery<TinyGsmSim800>;
/*
* Inner Client
*/
public:
class GsmClientSim800 : public GsmClient {
friend class TinyGsmSim800;
public:
GsmClientSim800() {}
explicit GsmClientSim800(TinyGsmSim800& modem, uint8_t mux = 0) {
init(&modem, mux);
}
bool init(TinyGsmSim800* modem, uint8_t mux = 0) {
this->at = modem;
sock_available = 0;
prev_check = 0;
sock_connected = false;
got_data = false;
if (mux < TINY_GSM_MUX_COUNT) {
this->mux = mux;
} else {
this->mux = (mux % TINY_GSM_MUX_COUNT);
}
at->sockets[this->mux] = this;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, false, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void stop(uint32_t maxWaitMs) {
dumpModemBuffer(maxWaitMs);
at->sendAT(GF("+CIPCLOSE="), mux, GF(",1")); // Quick close
sock_connected = false;
at->waitResponse();
}
void stop() override {
stop(15000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
public:
class GsmClientSecureSim800 : public GsmClientSim800 {
public:
GsmClientSecureSim800() {}
explicit GsmClientSecureSim800(TinyGsmSim800& modem, uint8_t mux = 0)
: GsmClientSim800(modem, mux) {}
public:
int connect(const char* host, uint16_t port, int timeout_s) override {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, true, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
};
/*
* Constructor
*/
public:
explicit TinyGsmSim800(Stream& stream) : stream(stream) {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSIM800"));
if (!testAT()) { return false; }
// sendAT(GF("&FZ")); // Factory + Reset
// waitResponse();
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
#ifdef TINY_GSM_DEBUG
sendAT(GF("+CMEE=2")); // turn on verbose error codes
#else
sendAT(GF("+CMEE=0")); // turn off error codes
#endif
waitResponse();
DBG(GF("### Modem:"), getModemName());
// Enable Local Time Stamp for getting network time
sendAT(GF("+CLTS=1"));
if (waitResponse(10000L) != 1) { return false; }
// Enable battery checks
sendAT(GF("+CBATCHK=1"));
waitResponse();
SimStatus ret = getSimStatus();
// if the sim isn't ready and a pin has been provided, try to unlock the sim
if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
simUnlock(pin);
return (getSimStatus() == SIM_READY);
} else {
// if the sim is ready, or it's locked but no pin has been provided,
// return true
return (ret == SIM_READY || ret == SIM_LOCKED);
}
}
String getModemNameImpl() {
String name = "";
#if defined(TINY_GSM_MODEM_SIM800)
name = "SIMCom SIM800";
#elif defined(TINY_GSM_MODEM_SIM808)
name = "SIMCom SIM808";
#elif defined(TINY_GSM_MODEM_SIM868)
name = "SIMCom SIM868";
#elif defined(TINY_GSM_MODEM_SIM900)
name = "SIMCom SIM900";
#endif
sendAT(GF("+GMM"));
String res2;
if (waitResponse(1000L, res2) != 1) { return name; }
res2.replace(GSM_NL "OK" GSM_NL, "");
res2.replace("_", " ");
res2.trim();
name = res2;
DBG("### Modem:", name);
return name;
}
bool factoryDefaultImpl() {
sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write
waitResponse();
sendAT(GF("+IPR=0")); // Auto-baud
waitResponse();
sendAT(GF("+IFC=0,0")); // No Flow Control
waitResponse();
sendAT(GF("+ICF=3,3")); // 8 data 0 parity 1 stop
waitResponse();
sendAT(GF("+CSCLK=0")); // Disable Slow Clock
waitResponse();
sendAT(GF("&W")); // Write configuration
return waitResponse() == 1;
}
/*
bool thisHasSSL() {
#if defined(TINY_GSM_MODEM_SIM900)
return false;
#else
sendAT(GF("+CIPSSL=?"));
if (waitResponse(GF(GSM_NL "+CIPSSL:")) != 1) { return false; }
return waitResponse() == 1;
#endif
}
*/
/*
* Power functions
*/
protected:
bool restartImpl(const char* pin = NULL) {
if (!testAT()) { return false; }
sendAT(GF("&W"));
waitResponse();
if (!setPhoneFunctionality(0)) { return false; }
if (!setPhoneFunctionality(1, true)) { return false; }
delay(3000);
return init(pin);
}
bool powerOffImpl() {
sendAT(GF("+CPOWD=1"));
return waitResponse(10000L, GF("NORMAL POWER DOWN")) == 1;
}
// During sleep, the SIM800 module has its serial communication disabled. In
// order to reestablish communication pull the DRT-pin of the SIM800 module
// LOW for at least 50ms. Then use this function to disable sleep mode. The
// DTR-pin can then be released again.
bool sleepEnableImpl(bool enable = true) {
sendAT(GF("+CSCLK="), enable);
return waitResponse() == 1;
}
// <fun> 0 Minimum functionality
// <fun> 1 Full functionality (Default)
// <fun> 4 Disable phone both transmit and receive RF circuits.
// <rst> Reset the MT before setting it to <fun> power level.
bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) {
sendAT(GF("+CFUN="), fun, reset ? ",1" : "");
return waitResponse(10000L) == 1;
}
/*
* Generic network functions
*/
public:
RegStatus getRegistrationStatus() {
return (RegStatus)getRegistrationStatusXREG("CREG");
}
protected:
bool isNetworkConnectedImpl() {
RegStatus s = getRegistrationStatus();
return (s == REG_OK_HOME || s == REG_OK_ROAMING);
}
String getLocalIPImpl() {
sendAT(GF("+CIFSR;E0"));
String res;
if (waitResponse(10000L, res) != 1) { return ""; }
res.replace(GSM_NL "OK" GSM_NL, "");
res.replace(GSM_NL, "");
res.trim();
return res;
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
gprsDisconnect();
// Bearer settings for applications based on IP
// Set the connection type to GPRS
sendAT(GF("+SAPBR=3,1,\"Contype\",\"GPRS\""));
waitResponse();
// Set the APN
sendAT(GF("+SAPBR=3,1,\"APN\",\""), apn, '"');
waitResponse();
// Set the user name
if (user && strlen(user) > 0) {
sendAT(GF("+SAPBR=3,1,\"USER\",\""), user, '"');
waitResponse();
}
// Set the password
if (pwd && strlen(pwd) > 0) {
sendAT(GF("+SAPBR=3,1,\"PWD\",\""), pwd, '"');
waitResponse();
}
// Define the PDP context
sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"');
waitResponse();
// Activate the PDP context
sendAT(GF("+CGACT=1,1"));
waitResponse(60000L);
// Open the definied GPRS bearer context
sendAT(GF("+SAPBR=1,1"));
waitResponse(85000L);
// Query the GPRS bearer context status
sendAT(GF("+SAPBR=2,1"));
if (waitResponse(30000L) != 1) { return false; }
// Attach to GPRS
sendAT(GF("+CGATT=1"));
if (waitResponse(60000L) != 1) { return false; }
// Set to multi-IP
sendAT(GF("+CIPMUX=1"));
if (waitResponse() != 1) { return false; }
// Put in "quick send" mode (thus no extra "Send OK")
sendAT(GF("+CIPQSEND=1"));
if (waitResponse() != 1) { return false; }
// Set to get data manually
sendAT(GF("+CIPRXGET=1"));
if (waitResponse() != 1) { return false; }
// Start Task and Set APN, USER NAME, PASSWORD
sendAT(GF("+CSTT=\""), apn, GF("\",\""), user, GF("\",\""), pwd, GF("\""));
if (waitResponse(60000L) != 1) { return false; }
// Bring Up Wireless Connection with GPRS or CSD
sendAT(GF("+CIICR"));
if (waitResponse(60000L) != 1) { return false; }
// Get Local IP Address, only assigned after connection
sendAT(GF("+CIFSR;E0"));
if (waitResponse(10000L) != 1) { return false; }
// Configure Domain Name Server (DNS)
sendAT(GF("+CDNSCFG=\"8.8.8.8\",\"8.8.4.4\""));
if (waitResponse() != 1) { return false; }
return true;
}
bool gprsDisconnectImpl() {
// Shut the TCP/IP connection
// CIPSHUT will close *all* open connections
sendAT(GF("+CIPSHUT"));
if (waitResponse(60000L) != 1) { return false; }
sendAT(GF("+CGATT=0")); // Detach from GPRS
if (waitResponse(60000L) != 1) { return false; }
return true;
}
/*
* SIM card functions
*/
protected:
// May not return the "+CCID" before the number
String getSimCCIDImpl() {
sendAT(GF("+CCID"));
if (waitResponse(GF(GSM_NL)) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
// Trim out the CCID header in case it is there
res.replace("CCID:", "");
res.trim();
return res;
}
/*
* Phone Call functions
*/
public:
bool setGsmBusy(bool busy = true) {
sendAT(GF("+GSMBUSY="), busy ? 1 : 0);
return waitResponse() == 1;
}
/*
* Messaging functions
*/
protected:
// Follows all messaging functions per template
/*
* GSM Location functions
*/
protected:
// Depending on the exacty model and firmware revision, should return a
// GSM-based location from CLBS as per the template
// TODO(?): Check number of digits in year (2 or 4)
/*
* GPS/GNSS/GLONASS location functions
*/
protected:
// No functions of this type supported
/*
* Audio functions
*/
public:
bool setVolume(uint8_t volume = 50) {
// Set speaker volume
sendAT(GF("+CLVL="), volume);
return waitResponse() == 1;
}
uint8_t getVolume() {
// Get speaker volume
sendAT(GF("+CLVL?"));
if (waitResponse(GF(GSM_NL)) != 1) { return 0; }
String res = stream.readStringUntil('\n');
waitResponse();
res.replace("+CLVL:", "");
res.trim();
return res.toInt();
}
bool setMicVolume(uint8_t channel, uint8_t level) {
if (channel > 4) { return 0; }
sendAT(GF("+CMIC="), level);
return waitResponse() == 1;
}
bool setAudioChannel(uint8_t channel) {
sendAT(GF("+CHFA="), channel);
return waitResponse() == 1;
}
bool playToolkitTone(uint8_t tone, uint32_t duration) {
sendAT(GF("STTONE="), 1, tone);
delay(duration);
sendAT(GF("STTONE="), 0);
return waitResponse();
}
/*
* Time functions
*/
protected:
// Can follow the standard CCLK function in the template
/*
* NTP server functions
*/
// Can sync with server using CNTP as per template
/*
* Battery functions
*/
protected:
// Follows all battery functions per template
/*
* NTP server functions
*/
// Can sync with server using CNTP as per template
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t mux,
bool ssl = false, int timeout_s = 75) {
int8_t rsp;
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
#if !defined(TINY_GSM_MODEM_SIM900)
sendAT(GF("+CIPSSL="), ssl);
rsp = waitResponse();
if (ssl && rsp != 1) { return false; }
#ifdef TINY_GSM_SSL_CLIENT_AUTHENTICATION
// set SSL options
// +SSLOPT=<opt>,<enable>
// <opt>
// 0 (default) ignore invalid certificate
// 1 client authentication
// <enable>
// 0 (default) close
// 1 open
sendAT(GF("+CIPSSL=1,1"));
if (waitResponse() != 1) return false;
#endif
#endif
sendAT(GF("+CIPSTART="), mux, ',', GF("\"TCP"), GF("\",\""), host,
GF("\","), port);
rsp = waitResponse(
timeout_ms, GF("CONNECT OK" GSM_NL), GF("CONNECT FAIL" GSM_NL),
GF("ALREADY CONNECT" GSM_NL), GF("ERROR" GSM_NL),
GF("CLOSE OK" GSM_NL)); // Happens when HTTPS handshake fails
return (1 == rsp);
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
sendAT(GF("+CIPSEND="), mux, ',', (uint16_t)len);
if (waitResponse(GF(">")) != 1) { return 0; }
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
if (waitResponse(GF(GSM_NL "DATA ACCEPT:")) != 1) { return 0; }
streamSkipUntil(','); // Skip mux
return streamGetIntBefore('\n');
}
size_t modemRead(size_t size, uint8_t mux) {
if (!sockets[mux]) return 0;
#ifdef TINY_GSM_USE_HEX
sendAT(GF("+CIPRXGET=3,"), mux, ',', (uint16_t)size);
if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; }
#else
sendAT(GF("+CIPRXGET=2,"), mux, ',', (uint16_t)size);
if (waitResponse(GF("+CIPRXGET:")) != 1) { return 0; }
#endif
streamSkipUntil(','); // Skip Rx mode 2/normal or 3/HEX
streamSkipUntil(','); // Skip mux
int16_t len_requested = streamGetIntBefore(',');
// ^^ Requested number of data bytes (1-1460 bytes)to be read
int16_t len_confirmed = streamGetIntBefore('\n');
// ^^ Confirmed number of data bytes to be read, which may be less than
// requested. 0 indicates that no data can be read.
// SRGD NOTE: Contrary to above (which is copied from AT command manual)
// this is actually be the number of bytes that will be remaining in the
// buffer after the read.
for (int i = 0; i < len_requested; i++) {
uint32_t startMillis = millis();
#ifdef TINY_GSM_USE_HEX
while (stream.available() < 2 &&
(millis() - startMillis < sockets[mux]->_timeout)) {
TINY_GSM_YIELD();
}
char buf[4] = {
0,
};
buf[0] = stream.read();
buf[1] = stream.read();
char c = strtol(buf, NULL, 16);
#else
while (!stream.available() &&
(millis() - startMillis < sockets[mux]->_timeout)) {
TINY_GSM_YIELD();
}
char c = stream.read();
#endif
sockets[mux]->rx.put(c);
}
// DBG("### READ:", len_requested, "from", mux);
// sockets[mux]->sock_available = modemGetAvailable(mux);
sockets[mux]->sock_available = len_confirmed;
waitResponse();
return len_requested;
}
size_t modemGetAvailable(uint8_t mux) {
if (!sockets[mux]) return 0;
sendAT(GF("+CIPRXGET=4,"), mux);
size_t result = 0;
if (waitResponse(GF("+CIPRXGET:")) == 1) {
streamSkipUntil(','); // Skip mode 4
streamSkipUntil(','); // Skip mux
result = streamGetIntBefore('\n');
waitResponse();
}
// DBG("### Available:", result, "on", mux);
if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); }
return result;
}
bool modemGetConnected(uint8_t mux) {
sendAT(GF("+CIPSTATUS="), mux);
waitResponse(GF("+CIPSTATUS"));
int8_t res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""),
GF(",\"CLOSING\""), GF(",\"REMOTE CLOSING\""),
GF(",\"INITIAL\""));
waitResponse();
return 1 == res;
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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(64);
uint8_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)) {
#if defined TINY_GSM_DEBUG
if (r3 == GFP(GSM_CME_ERROR)) {
streamSkipUntil('\n'); // Read out the error
}
#endif
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) {
int8_t mode = streamGetIntBefore(',');
if (mode == 1) {
int8_t mux = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
}
data = "";
// DBG("### Got Data:", mux);
} else {
data += mode;
}
} else if (data.endsWith(GF(GSM_NL "+RECEIVE:"))) {
int8_t mux = streamGetIntBefore(',');
int16_t len = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; }
}
data = "";
// DBG("### Got Data:", len, "on", mux);
} else if (data.endsWith(GF("CLOSED" GSM_NL))) {
int8_t nl = data.lastIndexOf(GSM_NL, data.length() - 8);
int8_t coma = data.indexOf(',', nl + 2);
int8_t mux = data.substring(nl + 2, coma).toInt();
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->sock_connected = false;
}
data = "";
DBG("### Closed: ", mux);
} else if (data.endsWith(GF("*PSNWID:"))) {
streamSkipUntil('\n'); // Refresh network name by network
data = "";
DBG("### Network name updated.");
} else if (data.endsWith(GF("*PSUTTZ:"))) {
streamSkipUntil('\n'); // Refresh time and time zone by network
data = "";
DBG("### Network time and time zone updated.");
} else if (data.endsWith(GF("+CTZV:"))) {
streamSkipUntil('\n'); // Refresh network time zone by network
data = "";
DBG("### Network time zone updated.");
} else if (data.endsWith(GF("DST:"))) {
streamSkipUntil(
'\n'); // Refresh Network Daylight Saving Time by network
data = "";
DBG("### Daylight savings time state updated.");
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5);
}
public:
Stream& stream;
protected:
GsmClientSim800* sockets[TINY_GSM_MUX_COUNT];
const char* gsmNL = GSM_NL;
};
#endif // SRC_TINYGSMCLIENTSIM800_H_

View File

@ -0,0 +1,168 @@
/**
* @file TinyGsmClientSIM808.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTSIM808_H_
#define SRC_TINYGSMCLIENTSIM808_H_
// #pragma message("TinyGSM: TinyGsmClientSIM808")
#include "TinyGsmClientSIM800.h"
#include "TinyGsmGPS.tpp"
#include "TinyGsmBluetooth.tpp"
class TinyGsmSim808 : public TinyGsmSim800, public TinyGsmGPS<TinyGsmSim808>, public TinyGsmBluetooth<TinyGsmSim808> {
friend class TinyGsmGPS<TinyGsmSim808>;
friend class TinyGsmBluetooth<TinyGsmSim808>;
public:
explicit TinyGsmSim808(Stream& stream) : TinyGsmSim800(stream) {}
/*
* GPS/GNSS/GLONASS location functions
*/
protected:
// enable GPS
bool enableGPSImpl() {
sendAT(GF("+CGNSPWR=1"));
if (waitResponse() != 1) { return false; }
return true;
}
bool disableGPSImpl() {
sendAT(GF("+CGNSPWR=0"));
if (waitResponse() != 1) { return false; }
return true;
}
// get the RAW GPS output
// works only with ans SIM808 V2
String getGPSrawImpl() {
sendAT(GF("+CGNSINF"));
if (waitResponse(10000L, GF(GSM_NL "+CGNSINF:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
// get GPS informations
// works only with ans SIM808 V2
bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0,
int* vsat = 0, int* usat = 0, float* accuracy = 0,
int* year = 0, int* month = 0, int* day = 0, int* hour = 0,
int* minute = 0, int* second = 0) {
sendAT(GF("+CGNSINF"));
if (waitResponse(10000L, GF(GSM_NL "+CGNSINF:")) != 1) { return false; }
streamSkipUntil(','); // GNSS run status
if (streamGetIntBefore(',') == 1) { // fix status
// init variables
float ilat = 0;
float ilon = 0;
float ispeed = 0;
float ialt = 0;
int ivsat = 0;
int iusat = 0;
float iaccuracy = 0;
int iyear = 0;
int imonth = 0;
int iday = 0;
int ihour = 0;
int imin = 0;
float secondWithSS = 0;
// UTC date & Time
iyear = streamGetIntLength(4); // Four digit year
imonth = streamGetIntLength(2); // Two digit month
iday = streamGetIntLength(2); // Two digit day
ihour = streamGetIntLength(2); // Two digit hour
imin = streamGetIntLength(2); // Two digit minute
secondWithSS =
streamGetFloatBefore(','); // 6 digit second with subseconds
ilat = streamGetFloatBefore(','); // Latitude
ilon = streamGetFloatBefore(','); // Longitude
ialt = streamGetFloatBefore(','); // MSL Altitude. Unit is meters
ispeed = streamGetFloatBefore(','); // Speed Over Ground. Unit is knots.
streamSkipUntil(','); // Course Over Ground. Degrees.
streamSkipUntil(','); // Fix Mode
streamSkipUntil(','); // Reserved1
iaccuracy =
streamGetFloatBefore(','); // Horizontal Dilution Of Precision
streamSkipUntil(','); // Position Dilution Of Precision
streamSkipUntil(','); // Vertical Dilution Of Precision
streamSkipUntil(','); // Reserved2
ivsat = streamGetIntBefore(','); // GNSS Satellites in View
iusat = streamGetIntBefore(','); // GNSS Satellites Used
streamSkipUntil(','); // GLONASS Satellites Used
streamSkipUntil(','); // Reserved3
streamSkipUntil(','); // C/N0 max
streamSkipUntil(','); // HPA
streamSkipUntil('\n'); // VPA
// Set pointers
if (lat != NULL) *lat = ilat;
if (lon != NULL) *lon = ilon;
if (speed != NULL) *speed = ispeed;
if (alt != NULL) *alt = ialt;
if (vsat != NULL) *vsat = ivsat;
if (usat != NULL) *usat = iusat;
if (accuracy != NULL) *accuracy = iaccuracy;
if (iyear < 2000) iyear += 2000;
if (year != NULL) *year = iyear;
if (month != NULL) *month = imonth;
if (day != NULL) *day = iday;
if (hour != NULL) *hour = ihour;
if (minute != NULL) *minute = imin;
if (second != NULL) *second = static_cast<int>(secondWithSS);
waitResponse();
return true;
}
streamSkipUntil('\n'); // toss the row of commas
waitResponse();
return false;
}
/*
* Bluetooth functions
*/
bool enableBluetoothImpl() {
sendAT(GF("+BTPOWER=1"));
if (waitResponse() != 1) { return false; }
return true;
}
bool disableBluetoothImpl() {
sendAT(GF("+BTPOWER=0"));
if (waitResponse() != 1) { return false; }
return true;
}
bool setBluetoothVisibilityImpl(bool visible) {
sendAT(GF("+BTVIS="), visible);
if (waitResponse() != 1) {
return false;
}
return true;
}
bool setBluetoothHostNameImpl(const char* name) {
sendAT(GF("+BTHOST="), name);
if (waitResponse() != 1) {
return false;
}
return true;
}
};
#endif // SRC_TINYGSMCLIENTSIM808_H_

View File

@ -0,0 +1,900 @@
/**
* @file TinyGsmClientSaraR4.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTSARAR4_H_
#define SRC_TINYGSMCLIENTSARAR4_H_
// #pragma message("TinyGSM: TinyGsmClientSaraR4")
// #define TINY_GSM_DEBUG Serial
#define TINY_GSM_MUX_COUNT 7
#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE
#include "TinyGsmBattery.tpp"
#include "TinyGsmGPRS.tpp"
#include "TinyGsmGPS.tpp"
#include "TinyGsmGSMLocation.tpp"
#include "TinyGsmModem.tpp"
#include "TinyGsmSMS.tpp"
#include "TinyGsmSSL.tpp"
#include "TinyGsmTCP.tpp"
#include "TinyGsmTemperature.tpp"
#include "TinyGsmTime.tpp"
#define GSM_NL "\r\n"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
#if defined TINY_GSM_DEBUG
static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:";
static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:";
#endif
enum RegStatus {
REG_NO_RESULT = -1,
REG_UNREGISTERED = 0,
REG_SEARCHING = 2,
REG_DENIED = 3,
REG_OK_HOME = 1,
REG_OK_ROAMING = 5,
REG_UNKNOWN = 4,
};
class TinyGsmSaraR4 : public TinyGsmModem<TinyGsmSaraR4>,
public TinyGsmGPRS<TinyGsmSaraR4>,
public TinyGsmTCP<TinyGsmSaraR4, TINY_GSM_MUX_COUNT>,
public TinyGsmSSL<TinyGsmSaraR4>,
public TinyGsmBattery<TinyGsmSaraR4>,
public TinyGsmGSMLocation<TinyGsmSaraR4>,
public TinyGsmGPS<TinyGsmSaraR4>,
public TinyGsmSMS<TinyGsmSaraR4>,
public TinyGsmTemperature<TinyGsmSaraR4>,
public TinyGsmTime<TinyGsmSaraR4> {
friend class TinyGsmModem<TinyGsmSaraR4>;
friend class TinyGsmGPRS<TinyGsmSaraR4>;
friend class TinyGsmTCP<TinyGsmSaraR4, TINY_GSM_MUX_COUNT>;
friend class TinyGsmSSL<TinyGsmSaraR4>;
friend class TinyGsmBattery<TinyGsmSaraR4>;
friend class TinyGsmGSMLocation<TinyGsmSaraR4>;
friend class TinyGsmGPS<TinyGsmSaraR4>;
friend class TinyGsmSMS<TinyGsmSaraR4>;
friend class TinyGsmTemperature<TinyGsmSaraR4>;
friend class TinyGsmTime<TinyGsmSaraR4>;
/*
* Inner Client
*/
public:
class GsmClientSaraR4 : public GsmClient {
friend class TinyGsmSaraR4;
public:
GsmClientSaraR4() {}
explicit GsmClientSaraR4(TinyGsmSaraR4& modem, uint8_t mux = 0) {
init(&modem, mux);
}
bool init(TinyGsmSaraR4* modem, uint8_t mux = 0) {
this->at = modem;
sock_available = 0;
prev_check = 0;
sock_connected = false;
got_data = false;
if (mux < TINY_GSM_MUX_COUNT) {
this->mux = mux;
} else {
this->mux = (mux % TINY_GSM_MUX_COUNT);
}
at->sockets[this->mux] = this;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
// stop(); // DON'T stop!
TINY_GSM_YIELD();
rx.clear();
uint8_t oldMux = mux;
sock_connected = at->modemConnect(host, port, &mux, false, timeout_s);
if (mux != oldMux) {
DBG("WARNING: Mux number changed from", oldMux, "to", mux);
at->sockets[oldMux] = NULL;
}
at->sockets[mux] = this;
at->maintain();
return sock_connected;
}
virtual 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, 120);
}
int connect(IPAddress ip, uint16_t port) override {
return connect(ip, port, 120);
}
void stop(uint32_t maxWaitMs) {
uint32_t startMillis = millis();
dumpModemBuffer(maxWaitMs);
// We want to use an async socket close because the syncrhonous close of
// an open socket is INCREDIBLY SLOW and the modem can freeze up. But we
// only attempt the async close if we already KNOW the socket is open
// because calling the async close on a closed socket and then attempting
// opening a new socket causes the board to lock up for 2-3 minutes and
// then finally return with a "new" socket that is immediately closed.
// Attempting to close a socket that is already closed with a synchronous
// close quickly returns an error.
if (at->supportsAsyncSockets && sock_connected) {
DBG("### Closing socket asynchronously! Socket might remain open "
"until arrival of +UUSOCL:",
mux);
// faster asynchronous close
// NOT supported on SARA-R404M / SARA-R410M-01B
at->sendAT(GF("+USOCL="), mux, GF(",1"));
// NOTE: can take up to 120s to get a response
at->waitResponse((maxWaitMs - (millis() - startMillis)));
// We set the sock as disconnected right away because it can no longer
// be used
sock_connected = false;
} else {
// synchronous close
at->sendAT(GF("+USOCL="), mux);
// NOTE: can take up to 120s to get a response
at->waitResponse((maxWaitMs - (millis() - startMillis)));
sock_connected = false;
}
}
void stop() override {
stop(135000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
public:
class GsmClientSecureR4 : public GsmClientSaraR4 {
public:
GsmClientSecureR4() {}
explicit GsmClientSecureR4(TinyGsmSaraR4& modem, uint8_t mux = 0)
: GsmClientSaraR4(modem, mux) {}
public:
int connect(const char* host, uint16_t port, int timeout_s) override {
// stop(); // DON'T stop!
TINY_GSM_YIELD();
rx.clear();
uint8_t oldMux = mux;
sock_connected = at->modemConnect(host, port, &mux, true, timeout_s);
if (mux != oldMux) {
DBG("WARNING: Mux number changed from", oldMux, "to", mux);
at->sockets[oldMux] = NULL;
}
at->sockets[mux] = this;
at->maintain();
return sock_connected;
}
int connect(IPAddress ip, uint16_t port, int timeout_s) override {
return connect(TinyGsmStringFromIp(ip).c_str(), port, timeout_s);
}
int connect(const char* host, uint16_t port) override {
return connect(host, port, 120);
}
int connect(IPAddress ip, uint16_t port) override {
return connect(ip, port, 120);
}
};
/*
* Constructor
*/
public:
explicit TinyGsmSaraR4(Stream& stream)
: stream(stream),
has2GFallback(false),
supportsAsyncSockets(false) {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSaraR4"));
if (!testAT()) { return false; }
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
#ifdef TINY_GSM_DEBUG
sendAT(GF("+CMEE=2")); // turn on verbose error codes
#else
sendAT(GF("+CMEE=0")); // turn off error codes
#endif
waitResponse();
String modemName = getModemName();
DBG(GF("### Modem:"), modemName);
if (modemName.startsWith("u-blox SARA-R412")) {
has2GFallback = true;
} else {
has2GFallback = false;
}
if (modemName.startsWith("u-blox SARA-R404M") ||
modemName.startsWith("u-blox SARA-R410M-01B")) {
supportsAsyncSockets = false;
} else {
supportsAsyncSockets = true;
}
// Enable automatic time zome update
sendAT(GF("+CTZU=1"));
if (waitResponse(10000L) != 1) { return false; }
SimStatus ret = getSimStatus();
// if the sim isn't ready and a pin has been provided, try to unlock the sim
if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
simUnlock(pin);
return (getSimStatus() == SIM_READY);
} else {
// if the sim is ready, or it's locked but no pin has been provided,
// return true
return (ret == SIM_READY || ret == SIM_LOCKED);
}
}
// only difference in implementation is the warning on the wrong type
String getModemNameImpl() {
sendAT(GF("+CGMI"));
String res1;
if (waitResponse(1000L, res1) != 1) { return "u-blox Cellular Modem"; }
res1.replace(GSM_NL "OK" GSM_NL, "");
res1.trim();
sendAT(GF("+GMM"));
String res2;
if (waitResponse(1000L, res2) != 1) { return "u-blox Cellular Modem"; }
res2.replace(GSM_NL "OK" GSM_NL, "");
res2.trim();
String name = res1 + String(' ') + res2;
DBG("### Modem:", name);
if (!name.startsWith("u-blox SARA-R4") &&
!name.startsWith("u-blox SARA-N4")) {
DBG("### WARNING: You are using the wrong TinyGSM modem!");
}
return name;
}
bool factoryDefaultImpl() {
sendAT(GF("&F")); // Resets the current profile, other NVM not affected
return waitResponse() == 1;
}
/*
* Power functions
*/
protected:
// using +CFUN=15 instead of the more common CFUN=1,1
bool restartImpl(const char* pin = NULL) {
if (!testAT()) { return false; }
if (!setPhoneFunctionality(15)) { return false; }
delay(3000); // TODO(?): Verify delay timing here
return init(pin);
}
bool powerOffImpl() {
sendAT(GF("+CPWROFF"));
return waitResponse(40000L) == 1;
}
bool sleepEnableImpl(bool enable = true) TINY_GSM_ATTR_NOT_AVAILABLE;
bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) {
sendAT(GF("+CFUN="), fun, reset ? ",1" : "");
return waitResponse(10000L) == 1;
}
/*
* Generic network functions
*/
public:
RegStatus getRegistrationStatus() {
// Check first for EPS registration
RegStatus epsStatus = (RegStatus)getRegistrationStatusXREG("CEREG");
// If we're connected on EPS, great!
if (epsStatus == REG_OK_HOME || epsStatus == REG_OK_ROAMING) {
return epsStatus;
} else {
// Otherwise, check generic network status
return (RegStatus)getRegistrationStatusXREG("CREG");
}
}
protected:
bool isNetworkConnectedImpl() {
RegStatus s = getRegistrationStatus();
return (s == REG_OK_HOME || s == REG_OK_ROAMING);
}
public:
bool setURAT(uint8_t urat) {
// AT+URAT=<SelectedAcT>[,<PreferredAct>[,<2ndPreferredAct>]]
sendAT(GF("+COPS=2")); // Deregister from network
if (waitResponse() != 1) { return false; }
sendAT(GF("+URAT="), urat); // Radio Access Technology (RAT) selection
if (waitResponse() != 1) { return false; }
sendAT(GF("+COPS=0")); // Auto-register to the network
if (waitResponse() != 1) { return false; }
return restart();
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
// gprsDisconnect();
sendAT(GF("+CGATT=1")); // attach to GPRS
if (waitResponse(360000L) != 1) { return false; }
// Using CGDCONT sets up an "external" PCP context, i.e. a data connection
// using the external IP stack (e.g. Windows dial up) and PPP link over the
// serial interface. This is the only command set supported by the LTE-M
// and LTE NB-IoT modules (SARA-R4xx, SARA-N4xx)
// Set the authentication
if (user && strlen(user) > 0) {
sendAT(GF("+CGAUTH=1,0,\""), user, GF("\",\""), pwd, '"');
waitResponse();
}
sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); // Define PDP context 1
waitResponse();
sendAT(GF("+CGACT=1,1")); // activate PDP profile/context 1
if (waitResponse(150000L) != 1) { return false; }
return true;
}
bool gprsDisconnectImpl() {
// Mark all the sockets as closed
// This ensures that asynchronously closed sockets are marked closed
for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) {
GsmClientSaraR4* sock = sockets[mux];
if (sock && sock->sock_connected) { sock->sock_connected = false; }
}
// sendAT(GF("+CGACT=0,1")); // Deactivate PDP context 1
sendAT(GF("+CGACT=0")); // Deactivate all contexts
if (waitResponse(40000L) != 1) {
// return false;
}
sendAT(GF("+CGATT=0")); // detach from GPRS
if (waitResponse(360000L) != 1) { return false; }
return true;
}
/*
* SIM card functions
*/
protected:
// This uses "CGSN" instead of "GSN"
String getIMEIImpl() {
sendAT(GF("+CGSN"));
if (waitResponse(GF(GSM_NL)) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
/*
* Messaging functions
*/
protected:
String sendUSSDImpl(const String& code) TINY_GSM_ATTR_NOT_IMPLEMENTED;
bool sendSMS_UTF16Impl(const String& number, const void* text,
size_t len) TINY_GSM_ATTR_NOT_IMPLEMENTED;
/*
* GSM/GPS/GNSS/GLONASS Location functions
* NOTE: u-blox modules use the same function to get location data from both
* GSM tower triangulation and from dedicated GPS/GNSS/GLONASS receivers. The
* only difference in which sensor the data is requested from. If a GNSS
* location is requested from a modem without a GNSS receiver installed on the
* I2C port, the GSM-based "Cell Locate" location will be returned instead.
*/
protected:
bool enableGPSImpl() {
// AT+UGPS=<mode>[,<aid_mode>[,<GNSS_systems>]]
// <mode> - 0: GNSS receiver powered off, 1: on
// <aid_mode> - 0: no aiding (default)
// <GNSS_systems> - 3: GPS + SBAS (default)
sendAT(GF("+UGPS=1,0,3"));
if (waitResponse(10000L, GF(GSM_NL "+UGPS:")) != 1) { return false; }
return waitResponse(10000L) == 1;
}
bool disableGPSImpl() {
sendAT(GF("+UGPS=0"));
if (waitResponse(10000L, GF(GSM_NL "+UGPS:")) != 1) { return false; }
return waitResponse(10000L) == 1;
}
String inline getUbloxLocationRaw(int8_t sensor) {
// AT+ULOC=<mode>,<sensor>,<response_type>,<timeout>,<accuracy>
// <mode> - 2: single shot position
// <sensor> - 0: use the last fix in the internal database and stop the GNSS
// receiver
// - 1: use the GNSS receiver for localization
// - 2: use cellular CellLocate location information
// - 3: ?? use the combined GNSS receiver and CellLocate service
// information ?? - Docs show using sensor 3 and it's
// documented for the +UTIME command but not for +ULOC
// <response_type> - 0: standard (single-hypothesis) response
// <timeout> - Timeout period in seconds
// <accuracy> - Target accuracy in meters (1 - 999999)
sendAT(GF("+ULOC=2,"), sensor, GF(",0,120,1"));
// wait for first "OK"
if (waitResponse(10000L) != 1) { return ""; }
// wait for the final result - wait full timeout time
if (waitResponse(120000L, GF(GSM_NL "+UULOC:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
String getGsmLocationRawImpl() {
return getUbloxLocationRaw(2);
}
String getGPSrawImpl() {
return getUbloxLocationRaw(1);
}
inline bool getUbloxLocation(int8_t sensor, float* lat, float* lon,
float* speed = 0, float* alt = 0, int* vsat = 0,
int* usat = 0, float* accuracy = 0,
int* year = 0, int* month = 0, int* day = 0,
int* hour = 0, int* minute = 0,
int* second = 0) {
// AT+ULOC=<mode>,<sensor>,<response_type>,<timeout>,<accuracy>
// <mode> - 2: single shot position
// <sensor> - 2: use cellular CellLocate location information
// - 0: use the last fix in the internal database and stop the GNSS
// receiver
// - 1: use the GNSS receiver for localization
// - 3: ?? use the combined GNSS receiver and CellLocate service
// information ?? - Docs show using sensor 3 and it's documented
// for the +UTIME command but not for +ULOC
// <response_type> - 0: standard (single-hypothesis) response
// <timeout> - Timeout period in seconds
// <accuracy> - Target accuracy in meters (1 - 999999)
sendAT(GF("+ULOC=2,"), sensor, GF(",0,120,1"));
// wait for first "OK"
if (waitResponse(10000L) != 1) { return false; }
// wait for the final result - wait full timeout time
if (waitResponse(120000L, GF(GSM_NL "+UULOC: ")) != 1) { return false; }
// +UULOC: <date>, <time>, <lat>, <long>, <alt>, <uncertainty>, <speed>,
// <direction>, <vertical_acc>, <sensor_used>, <SV_used>, <antenna_status>,
// <jamming_status>
// init variables
float ilat = 0;
float ilon = 0;
float ispeed = 0;
float ialt = 0;
int iusat = 0;
float iaccuracy = 0;
int iyear = 0;
int imonth = 0;
int iday = 0;
int ihour = 0;
int imin = 0;
float secondWithSS = 0;
// Date & Time
iday = streamGetIntBefore('/'); // Two digit day
imonth = streamGetIntBefore('/'); // Two digit month
iyear = streamGetIntBefore(','); // Four digit year
ihour = streamGetIntBefore(':'); // Two digit hour
imin = streamGetIntBefore(':'); // Two digit minute
secondWithSS = streamGetFloatBefore(','); // 6 digit second with subseconds
ilat = streamGetFloatBefore(','); // Estimated latitude, in degrees
ilon = streamGetFloatBefore(','); // Estimated longitude, in degrees
ialt = streamGetFloatBefore(
','); // Estimated altitude, in meters - only forGNSS
// positioning, 0 in case of CellLocate
if (ialt != 0) { // values not returned for CellLocate
iaccuracy =
streamGetFloatBefore(','); // Maximum possible error, in meters
ispeed = streamGetFloatBefore(','); // Speed over ground m/s3
streamSkipUntil(','); // Course over ground in degree (0 deg - 360 deg)
streamSkipUntil(','); // Vertical accuracy, in meters
streamSkipUntil(','); // Sensor used for the position calculation
iusat = streamGetIntBefore(','); // Number of satellite used
streamSkipUntil(','); // Antenna status
streamSkipUntil('\n'); // Jamming status
} else {
iaccuracy =
streamGetFloatBefore('\n'); // Maximum possible error, in meters
}
// Set pointers
if (lat != NULL) *lat = ilat;
if (lon != NULL) *lon = ilon;
if (speed != NULL) *speed = ispeed;
if (alt != NULL) *alt = ialt;
if (vsat != NULL) *vsat = 0; // Number of satellites viewed not reported;
if (usat != NULL) *usat = iusat;
if (accuracy != NULL) *accuracy = iaccuracy;
if (iyear < 2000) iyear += 2000;
if (year != NULL) *year = iyear;
if (month != NULL) *month = imonth;
if (day != NULL) *day = iday;
if (hour != NULL) *hour = ihour;
if (minute != NULL) *minute = imin;
if (second != NULL) *second = static_cast<int>(secondWithSS);
// final ok
waitResponse();
return true;
}
bool getGsmLocationImpl(float* lat, float* lon, float* accuracy = 0,
int* year = 0, int* month = 0, int* day = 0,
int* hour = 0, int* minute = 0, int* second = 0) {
return getUbloxLocation(2, lat, lon, 0, 0, 0, 0, accuracy, year, month, day,
hour, minute, second);
}
bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0,
int* vsat = 0, int* usat = 0, float* accuracy = 0,
int* year = 0, int* month = 0, int* day = 0, int* hour = 0,
int* minute = 0, int* second = 0) {
return getUbloxLocation(1, lat, lon, speed, alt, vsat, usat, accuracy, year,
month, day, hour, minute, second);
}
/*
* Time functions
*/
protected:
// Can follow the standard CCLK function in the template
/*
* Battery functions
*/
protected:
uint16_t getBattVoltageImpl() TINY_GSM_ATTR_NOT_AVAILABLE;
int8_t getBattPercentImpl() {
sendAT(GF("+CIND?"));
if (waitResponse(GF(GSM_NL "+CIND:")) != 1) { return 0; }
int8_t res = streamGetIntBefore(',');
int8_t percent = res * 20; // return is 0-5
// Wait for final OK
waitResponse();
return percent;
}
uint8_t getBattChargeStateImpl() TINY_GSM_ATTR_NOT_AVAILABLE;
bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent,
uint16_t& milliVolts) {
chargeState = 0;
percent = getBattPercent();
milliVolts = 0;
return true;
}
/*
* Temperature functions
*/
protected:
float getTemperatureImpl() {
// First make sure the temperature is set to be in celsius
sendAT(GF("+UTEMP=0")); // Would use 1 for Fahrenheit
if (waitResponse() != 1) { return static_cast<float>(-9999); }
sendAT(GF("+UTEMP?"));
if (waitResponse(GF(GSM_NL "+UTEMP:")) != 1) {
return static_cast<float>(-9999);
}
int16_t res = streamGetIntBefore('\n');
float temp = -9999;
if (res != -1) { temp = (static_cast<float>(res)) / 10; }
return temp;
}
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t* mux,
bool ssl = false, int timeout_s = 120) {
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
uint32_t startMillis = millis();
// create a socket
sendAT(GF("+USOCR=6"));
// reply is +USOCR: ## of socket created
if (waitResponse(GF(GSM_NL "+USOCR:")) != 1) { return false; }
*mux = streamGetIntBefore('\n');
waitResponse();
if (ssl) {
sendAT(GF("+USOSEC="), *mux, ",1");
waitResponse();
}
// Enable NODELAY
// AT+USOSO=<socket>,<level>,<opt_name>,<opt_val>[,<opt_val2>]
// <level> - 0 for IP, 6 for TCP, 65535 for socket level options
// <opt_name> TCP/1 = no delay (do not delay send to coalesce packets)
// NOTE: Enabling this may increase data plan usage
// sendAT(GF("+USOSO="), *mux, GF(",6,1,1"));
// waitResponse();
// Enable KEEPALIVE, 30 sec
// sendAT(GF("+USOSO="), *mux, GF(",6,2,30000"));
// waitResponse();
// connect on the allocated socket
// Use an asynchronous open to reduce the number of terminal freeze-ups
// This is still blocking until the URC arrives
// The SARA-R410M-02B with firmware revisions prior to L0.0.00.00.05.08
// has a nasty habit of locking up when opening a socket, especially if
// the cellular service is poor.
// NOT supported on SARA-R404M / SARA-R410M-01B
if (supportsAsyncSockets) {
DBG("### Opening socket asynchronously! Socket cannot be used until "
"the URC '+UUSOCO' appears.");
sendAT(GF("+USOCO="), *mux, ",\"", host, "\",", port, ",1");
if (waitResponse(timeout_ms - (millis() - startMillis),
GF(GSM_NL "+UUSOCO:")) == 1) {
streamGetIntBefore(','); // skip repeated mux
int8_t connection_status = streamGetIntBefore('\n');
DBG("### Waited", millis() - startMillis, "ms for socket to open");
return (0 == connection_status);
} else {
DBG("### Waited", millis() - startMillis,
"but never got socket open notice");
return false;
}
} else {
// use synchronous open
sendAT(GF("+USOCO="), *mux, ",\"", host, "\",", port);
int8_t rsp = waitResponse(timeout_ms - (millis() - startMillis));
return (1 == rsp);
}
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
sendAT(GF("+USOWR="), mux, ',', (uint16_t)len);
if (waitResponse(GF("@")) != 1) { return 0; }
// 50ms delay, see AT manual section 25.10.4
delay(50);
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
if (waitResponse(GF(GSM_NL "+USOWR:")) != 1) { return 0; }
streamSkipUntil(','); // Skip mux
int16_t sent = streamGetIntBefore('\n');
waitResponse(); // sends back OK after the confirmation of number sent
return sent;
}
size_t modemRead(size_t size, uint8_t mux) {
if (!sockets[mux]) return 0;
sendAT(GF("+USORD="), mux, ',', (uint16_t)size);
if (waitResponse(GF(GSM_NL "+USORD:")) != 1) { return 0; }
streamSkipUntil(','); // Skip mux
int16_t len = streamGetIntBefore(',');
streamSkipUntil('\"');
for (int i = 0; i < len; i++) { moveCharFromStreamToFifo(mux); }
streamSkipUntil('\"');
waitResponse();
// DBG("### READ:", len, "from", mux);
sockets[mux]->sock_available = modemGetAvailable(mux);
return len;
}
size_t modemGetAvailable(uint8_t mux) {
if (!sockets[mux]) return 0;
// NOTE: Querying a closed socket gives an error "operation not allowed"
sendAT(GF("+USORD="), mux, ",0");
size_t result = 0;
uint8_t res = waitResponse(GF(GSM_NL "+USORD:"));
// Will give error "operation not allowed" when attempting to read a socket
// that you have already told to close
if (res == 1) {
streamSkipUntil(','); // Skip mux
result = streamGetIntBefore('\n');
// if (result) DBG("### DATA AVAILABLE:", result, "on", mux);
waitResponse();
}
if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); }
// DBG("### Available:", result, "on", mux);
return result;
}
bool modemGetConnected(uint8_t mux) {
// NOTE: Querying a closed socket gives an error "operation not allowed"
sendAT(GF("+USOCTL="), mux, ",10");
uint8_t res = waitResponse(GF(GSM_NL "+USOCTL:"));
if (res != 1) { return false; }
streamSkipUntil(','); // Skip mux
streamSkipUntil(','); // Skip type
int8_t result = streamGetIntBefore('\n');
// 0: the socket is in INACTIVE status (it corresponds to CLOSED status
// defined in RFC793 "TCP Protocol Specification" [112])
// 1: the socket is in LISTEN status
// 2: the socket is in SYN_SENT status
// 3: the socket is in SYN_RCVD status
// 4: the socket is in ESTABILISHED status
// 5: the socket is in FIN_WAIT_1 status
// 6: the socket is in FIN_WAIT_2 status
// 7: the sokcet is in CLOSE_WAIT status
// 8: the socket is in CLOSING status
// 9: the socket is in LAST_ACK status
// 10: the socket is in TIME_WAIT status
waitResponse();
return (result != 0);
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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(64);
uint8_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)) {
#if defined TINY_GSM_DEBUG
if (r3 == GFP(GSM_CME_ERROR)) {
streamSkipUntil('\n'); // Read out the error
}
#endif
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF("+UUSORD:"))) {
int8_t mux = streamGetIntBefore(',');
int16_t len = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
// max size is 1024
if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; }
}
data = "";
DBG("### URC Data Received:", len, "on", mux);
} else if (data.endsWith(GF("+UUSOCL:"))) {
int8_t mux = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->sock_connected = false;
}
data = "";
DBG("### URC Sock Closed: ", mux);
} else if (data.endsWith(GF("+UUSOCO:"))) {
int8_t mux = streamGetIntBefore('\n');
int8_t socket_error = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux] &&
socket_error == 0) {
sockets[mux]->sock_connected = true;
}
data = "";
DBG("### URC Sock Opened: ", mux);
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5);
}
public:
Stream& stream;
protected:
GsmClientSaraR4* sockets[TINY_GSM_MUX_COUNT];
const char* gsmNL = GSM_NL;
bool has2GFallback;
bool supportsAsyncSockets;
};
#endif // SRC_TINYGSMCLIENTSARAR4_H_

View File

@ -0,0 +1,733 @@
/**
* @file TinyGsmClientSequansMonarch.h
* @author Michael Krumpus
* @license LGPL-3.0
* @copyright Copyright (c) 2019 Michael Krumpus
* @date Jan 2019
*/
#ifndef SRC_TINYGSMCLIENTSEQUANSMONARCH_H_
#define SRC_TINYGSMCLIENTSEQUANSMONARCH_H_
// #define TINY_GSM_DEBUG Serial
#define TINY_GSM_MUX_COUNT 6
#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE
#include "TinyGsmCalling.tpp"
#include "TinyGsmGPRS.tpp"
#include "TinyGsmModem.tpp"
#include "TinyGsmSMS.tpp"
#include "TinyGsmSSL.tpp"
#include "TinyGsmTCP.tpp"
#include "TinyGsmTemperature.tpp"
#include "TinyGsmTime.tpp"
#define GSM_NL "\r\n"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
#if defined TINY_GSM_DEBUG
static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:";
static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:";
#endif
enum RegStatus {
REG_NO_RESULT = -1,
REG_UNREGISTERED = 0,
REG_SEARCHING = 2,
REG_DENIED = 3,
REG_OK_HOME = 1,
REG_OK_ROAMING = 5,
REG_UNKNOWN = 4,
};
enum SocketStatus {
SOCK_CLOSED = 0,
SOCK_ACTIVE_DATA = 1,
SOCK_SUSPENDED = 2,
SOCK_SUSPENDED_PENDING_DATA = 3,
SOCK_LISTENING = 4,
SOCK_INCOMING = 5,
SOCK_OPENING = 6,
};
class TinyGsmSequansMonarch
: public TinyGsmModem<TinyGsmSequansMonarch>,
public TinyGsmGPRS<TinyGsmSequansMonarch>,
public TinyGsmTCP<TinyGsmSequansMonarch, TINY_GSM_MUX_COUNT>,
public TinyGsmSSL<TinyGsmSequansMonarch>,
public TinyGsmCalling<TinyGsmSequansMonarch>,
public TinyGsmSMS<TinyGsmSequansMonarch>,
public TinyGsmTime<TinyGsmSequansMonarch>,
public TinyGsmTemperature<TinyGsmSequansMonarch> {
friend class TinyGsmModem<TinyGsmSequansMonarch>;
friend class TinyGsmGPRS<TinyGsmSequansMonarch>;
friend class TinyGsmTCP<TinyGsmSequansMonarch, TINY_GSM_MUX_COUNT>;
friend class TinyGsmSSL<TinyGsmSequansMonarch>;
friend class TinyGsmCalling<TinyGsmSequansMonarch>;
friend class TinyGsmSMS<TinyGsmSequansMonarch>;
friend class TinyGsmTime<TinyGsmSequansMonarch>;
friend class TinyGsmTemperature<TinyGsmSequansMonarch>;
/*
* Inner Client
*/
public:
class GsmClientSequansMonarch : public GsmClient {
friend class TinyGsmSequansMonarch;
public:
GsmClientSequansMonarch() {}
explicit GsmClientSequansMonarch(TinyGsmSequansMonarch& modem,
uint8_t mux = 1) {
init(&modem, mux);
}
bool init(TinyGsmSequansMonarch* modem, uint8_t mux = 1) {
this->at = modem;
sock_available = 0;
prev_check = 0;
sock_connected = false;
got_data = false;
// adjust for zero indexed socket array vs Sequans' 1 indexed mux numbers
// using modulus will force 6 back to 0
if (mux >= 1 && mux <= TINY_GSM_MUX_COUNT) {
this->mux = mux;
} else {
this->mux = (mux % TINY_GSM_MUX_COUNT) + 1;
}
at->sockets[this->mux % TINY_GSM_MUX_COUNT] = this;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
if (sock_connected) stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, false, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void stop(uint32_t maxWaitMs) {
dumpModemBuffer(maxWaitMs);
at->sendAT(GF("+SQNSH="), mux);
sock_connected = false;
at->waitResponse();
}
void stop() override {
stop(15000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
public:
class GsmClientSecureSequansMonarch : public GsmClientSequansMonarch {
public:
GsmClientSecureSequansMonarch() {}
explicit GsmClientSecureSequansMonarch(TinyGsmSequansMonarch& modem,
uint8_t mux = 1)
: GsmClientSequansMonarch(modem, mux) {}
protected:
bool strictSSL = false;
public:
int connect(const char* host, uint16_t port, int timeout_s) override {
stop();
TINY_GSM_YIELD();
rx.clear();
// configure security profile 1 with parameters:
if (strictSSL) {
// require minimum of TLS 1.2 (3)
// only support cipher suite 0x3D: TLS_RSA_WITH_AES_256_CBC_SHA256
// verify server certificate against imported CA certs 0 and enforce
// validity period (3)
at->sendAT(GF("+SQNSPCFG=1,3,\"0x3D\",3,0,,,\"\",\"\""));
} else {
// use TLS 1.0 or higher (1)
// support wider variety of cipher suites
// do not verify server certificate (0)
at->sendAT(GF("+SQNSPCFG=1,1,\"0x2F;0x35;0x3C;0x3D\",0,,,,\"\",\"\""));
}
if (at->waitResponse() != 1) {
DBG("failed to configure security profile");
return false;
}
sock_connected = at->modemConnect(host, port, mux, true, timeout_s);
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void setStrictSSL(bool strict) {
strictSSL = strict;
}
};
/*
* Constructor
*/
public:
explicit TinyGsmSequansMonarch(Stream& stream) : stream(stream) {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSequansMonarch"));
if (!testAT()) { return false; }
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
#ifdef TINY_GSM_DEBUG
sendAT(GF("+CMEE=2")); // turn on verbose error codes
#else
sendAT(GF("+CMEE=0")); // turn off error codes
#endif
waitResponse();
DBG(GF("### Modem:"), getModemName());
// Make sure the module is enabled. Unlike others, the VZN20Q powers on
// with CFUN=0 not CFUN=1 (that is, at minimum functionality instead of full
// functionality The module cannot even detect the sim card if the cellular
// functionality is disabled so unless we explicitly enable the
// functionality the init will fail.
sendAT(GF("+CFUN=1"));
waitResponse();
// Disable time and time zone URC's
sendAT(GF("+CTZR=0"));
if (waitResponse(10000L) != 1) { return false; }
// Enable automatic time zome update
sendAT(GF("+CTZU=1"));
if (waitResponse(10000L) != 1) { return false; }
SimStatus ret = getSimStatus();
// if the sim isn't ready and a pin has been provided, try to unlock the sim
if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
simUnlock(pin);
return (getSimStatus() == SIM_READY);
} else {
// if the sim is ready, or it's locked but no pin has been provided,
// return true
return (ret == SIM_READY || ret == SIM_LOCKED);
}
}
String getModemNameImpl() {
sendAT(GF("+CGMI"));
String res1;
if (waitResponse(1000L, res1) != 1) { return "unknown"; }
res1.replace("\r\nOK\r\n", "");
res1.replace("\rOK\r", "");
res1.trim();
sendAT(GF("+CGMM"));
String res2;
if (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() {
sendAT(GF("&F0")); // Factory
waitResponse();
sendAT(GF("Z")); // default configuration
waitResponse();
sendAT(GF("+IPR=0")); // Auto-baud
return waitResponse() == 1;
}
void maintainImpl() {
for (int mux = 1; mux <= TINY_GSM_MUX_COUNT; mux++) {
GsmClientSequansMonarch* sock = sockets[mux % TINY_GSM_MUX_COUNT];
if (sock && sock->got_data) {
sock->got_data = false;
sock->sock_available = modemGetAvailable(mux);
// modemGetConnected() always checks the state of ALL socks
modemGetConnected();
}
}
while (stream.available()) { waitResponse(15, NULL, NULL); }
}
/*
* Power functions
*/
protected:
bool restartImpl(const char* pin = NULL) {
if (!testAT()) { return false; }
sendAT(GF("+CFUN=0"));
int8_t res = waitResponse(20000L, GFP(GSM_OK), GFP(GSM_ERROR),
GF("+SYSSTART"));
if (res != 1 && res != 3) { return false; }
sendAT(GF("+CFUN=1,1"));
res = waitResponse(20000L, GF("+SYSSTART"), GFP(GSM_ERROR));
if (res != 1 && res != 3) { return false; }
delay(1000);
return init(pin);
}
bool powerOffImpl() {
// NOTE: The only way to turn the modem back on after this shutdown is with
// a hard reset
sendAT(GF("+SQNSSHDN"));
return waitResponse();
}
// When power saving is enabled, UART0 interface is activated with sleep mode
// support. Module power state is controlled by RTS0 line. When no activity
// on UART, CTS line will be set to OFF state (driven high level) <timeout>
// milliseconds (100ms to 10s, default 5s) after the last sent character,
// then module will go to sleep mode as soon as DTE set RTS line to OFF state
// (driver high level).
bool sleepEnableImpl(bool enable = true) {
sendAT(GF("+SQNIPSCFG="), enable);
return waitResponse() == 1;
}
bool setPhoneFunctionality(uint8_t fun,
bool reset = false) TINY_GSM_ATTR_NOT_IMPLEMENTED;
/*
* Generic network functions
*/
public:
RegStatus getRegistrationStatus() {
return (RegStatus)getRegistrationStatusXREG("CEREG");
}
protected:
bool isNetworkConnectedImpl() {
RegStatus s = getRegistrationStatus();
return (s == REG_OK_HOME || s == REG_OK_ROAMING);
}
String getLocalIPImpl() {
sendAT(GF("+CGPADDR=3"));
if (waitResponse(10000L, GF("+CGPADDR: 3,\"")) != 1) { return ""; }
String res = stream.readStringUntil('\"');
waitResponse();
return res;
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
gprsDisconnect();
// Define the PDP context (This uses context #3!)
sendAT(GF("+CGDCONT=3,\"IPV4V6\",\""), apn, '"');
waitResponse();
// Set authentication
if (user && strlen(user) > 0) {
sendAT(GF("+CGAUTH=3,1,\""), user, GF("\",\""), pwd, GF("\""));
waitResponse();
}
// Activate the PDP context
sendAT(GF("+CGACT=1,3"));
waitResponse(60000L);
// Attach to GPRS
sendAT(GF("+CGATT=1"));
if (waitResponse(60000L) != 1) { return false; }
return true;
}
bool gprsDisconnectImpl() {
// Detach from PS network
sendAT(GF("+CGATT=0"));
if (waitResponse(60000L) != 1) { return false; }
// Dectivate all PDP contexts
sendAT(GF("+CGACT=0"));
if (waitResponse(60000L) != 1) { return false; }
return true;
}
/*
* SIM card functions
*/
protected:
String getSimCCIDImpl() {
sendAT(GF("+SQNCCID"));
if (waitResponse(GF(GSM_NL "+SQNCCID:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
/*
* Phone Call functions
*/
protected:
bool callAnswerImpl() TINY_GSM_ATTR_NOT_AVAILABLE;
bool dtmfSendImpl(char cmd,
int duration_ms = 100) TINY_GSM_ATTR_NOT_AVAILABLE;
/*
* Messaging functions
*/
protected:
// Follows all messaging functions per template
/*
* Time functions
*/
protected:
// Can follow the standard CCLK function in the template
/*
* Temperature functions
*/
protected:
float getTemperatureImpl() {
sendAT(GF("+SMDTH"));
if (waitResponse(10000L, GF("+SMDTH: ")) != 1) {
return static_cast<float>(-9999);
}
String res;
if (waitResponse(1000L, res) != 1) { return static_cast<float>(-9999); }
if (res.indexOf("ERROR") >= 0) { return static_cast<float>(-9999); }
return res.toFloat();
}
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t mux,
bool ssl = false, int timeout_s = 75) {
int8_t rsp;
uint32_t startMillis = millis();
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
if (ssl) {
// enable SSl and use security profile 1
// AT+SQNSSCFG=<connId>,<enable>,<spId>
sendAT(GF("+SQNSSCFG="), mux, GF(",1,1"));
if (waitResponse() != 1) {
DBG("### WARNING: failed to configure secure socket");
return false;
}
}
// Socket configuration
// AT+SQNSCFG:<connId1>, <cid1>, <pktSz1>, <maxTo1>, <connTo1>, <txTo1>
// <connId1> = Connection ID = mux
// <cid1> = PDP context ID = 3 - this is number set up above in the
// GprsConnect function
// <pktSz1> = Packet Size, used for online data mode only = 300 (default)
// <maxTo1> = Max timeout in seconds = 90 (default)
// <connTo1> = Connection timeout in hundreds of milliseconds
// = 600 (default)
// <txTo1> = Data sending timeout in hundreds of milliseconds,
// used for online data mode only = 50 (default)
sendAT(GF("+SQNSCFG="), mux, GF(",3,300,90,600,50"));
waitResponse(5000L);
// Socket configuration extended
// AT+SQNSCFGEXT:<connId1>, <srMode1>, <recvDataMode1>, <keepalive1>,
// <listenAutoRsp1>, <sendDataMode1>
// <connId1> = Connection ID = mux
// <srMode1> = Send/Receive URC model = 1 - data amount mode
// <recvDataMode1> = Receive data mode = 0 - data as text (1 for hex)
// <keepalive1> = unused = 0
// <listenAutoRsp1> = Listen auto-response mode = 0 - deactivated
// <sendDataMode1> = Send data mode = 1 - data as hex (0 for text)
sendAT(GF("+SQNSCFGEXT="), mux, GF(",1,0,0,0,1"));
waitResponse(5000L);
// Socket dial
// AT+SQNSD=<connId>,<txProt>,<rPort>,<IPaddr>[,<closureType>[,<lPort>[,<connMode>[,acceptAnyRemote]]]]
// <connId> = Connection ID = mux
// <txProt> = Transmission protocol = 0 - TCP (1 for UDP)
// <rPort> = Remote host port to contact
// <IPaddr> = Any valid IP address in the format xxx.xxx.xxx.xxx or any
// host name solved with a DNS query
// <closureType> = Socket closure behaviour for TCP, has no effect for UDP
// = 0 - local port closes when remote does (default)
// <lPort> = UDP connection local port, has no effect for TCP connections.
// <connMode> = Connection mode = 1 - command mode connection
// <acceptAnyRemote> = Applies to UDP only
sendAT(GF("+SQNSD="), mux, ",0,", port, ',', GF("\""), host, GF("\""),
",0,0,1");
rsp = waitResponse((timeout_ms - (millis() - startMillis)), GFP(GSM_OK),
GFP(GSM_ERROR), GF("NO CARRIER" GSM_NL));
// creation of socket failed immediately.
if (rsp != 1) { return false; }
// wait until we get a good status
bool connected = false;
while (!connected && ((millis() - startMillis) < timeout_ms)) {
connected = modemGetConnected(mux);
delay(100); // socket may be in opening state
}
return connected;
}
int modemSend(const void* buff, size_t len, uint8_t mux) {
if (sockets[mux % TINY_GSM_MUX_COUNT]->sock_connected == false) {
DBG("### Sock closed, cannot send data!");
return 0;
}
sendAT(GF("+SQNSSENDEXT="), mux, ',', (uint16_t)len);
waitResponse(10000L, GF(GSM_NL "> "));
// Translate bytes into char to be able to send them as an hex string
char char_command[2];
for (size_t i=0; i<len; i++) {
memset(&char_command, 0, sizeof(char_command));
sprintf(&char_command[0], "%02X", reinterpret_cast<const uint8_t*>(buff)[i]);
stream.write(char_command, sizeof(char_command));
}
stream.flush();
if (waitResponse() != 1) {
DBG("### no OK after send");
return 0;
}
return len;
// uint8_t nAttempts = 5;
// bool gotPrompt = false;
// while (nAttempts > 0 && !gotPrompt) {
// sendAT(GF("+SQNSSEND="), mux);
// if (waitResponse(5000, GF(GSM_NL "> ")) == 1) {
// gotPrompt = true;
// }
// nAttempts--;
// delay(50);
// }
// if (gotPrompt) {
// stream.write(reinterpret_cast<const uint8_t*>(buff), len);
// stream.write(reinterpret_cast<char>0x1A);
// stream.flush();
// if (waitResponse() != 1) {
// DBG("### no OK after send");
// return 0;
// }
// return len;
// }
// return 0;
}
size_t modemRead(size_t size, uint8_t mux) {
sendAT(GF("+SQNSRECV="), mux, ',', (uint16_t)size);
if (waitResponse(GF("+SQNSRECV: ")) != 1) { return 0; }
streamSkipUntil(','); // Skip mux
int16_t len = streamGetIntBefore('\n');
for (int i = 0; i < len; i++) {
uint32_t startMillis = millis();
while (!stream.available() &&
((millis() - startMillis) <
sockets[mux % TINY_GSM_MUX_COUNT]->_timeout)) {
TINY_GSM_YIELD();
}
char c = stream.read();
sockets[mux % TINY_GSM_MUX_COUNT]->rx.put(c);
}
// DBG("### READ:", len, "from", mux);
waitResponse();
sockets[mux % TINY_GSM_MUX_COUNT]->sock_available = modemGetAvailable(mux);
return len;
}
size_t modemGetAvailable(uint8_t mux) {
sendAT(GF("+SQNSI="), mux);
size_t result = 0;
if (waitResponse(GF("+SQNSI:")) == 1) {
streamSkipUntil(','); // Skip mux
streamSkipUntil(','); // Skip total sent
streamSkipUntil(','); // Skip total received
result = streamGetIntBefore(','); // keep data not yet read
waitResponse();
}
// DBG("### Available:", result, "on", mux);
return result;
}
bool modemGetConnected(uint8_t mux = 1) {
// This single command always returns the connection status of all
// six possible sockets.
sendAT(GF("+SQNSS"));
for (int muxNo = 1; muxNo <= TINY_GSM_MUX_COUNT; muxNo++) {
if (waitResponse(GFP(GSM_OK), GF(GSM_NL "+SQNSS: ")) != 2) { break; }
uint8_t status = 0;
// if (streamGetIntBefore(',') != muxNo) { // check the mux no
// DBG("### Warning: misaligned mux numbers!");
// }
streamSkipUntil(','); // skip mux [use muxNo]
status = stream.parseInt(); // Read the status
// if mux is in use, will have comma then other info after the status
// if not, there will be new line immediately after status
// streamSkipUntil('\n'); // Skip port and IP info
// SOCK_CLOSED = 0,
// SOCK_ACTIVE_DATA = 1,
// SOCK_SUSPENDED = 2,
// SOCK_SUSPENDED_PENDING_DATA = 3,
// SOCK_LISTENING = 4,
// SOCK_INCOMING = 5,
// SOCK_OPENING = 6,
GsmClientSequansMonarch* sock = sockets[muxNo % TINY_GSM_MUX_COUNT];
if (sock) {
sock->sock_connected = ((status != SOCK_CLOSED) &&
(status != SOCK_INCOMING) &&
(status != SOCK_OPENING));
}
}
waitResponse(); // Should be an OK at the end
return sockets[mux % TINY_GSM_MUX_COUNT]->sock_connected;
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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(64);
uint8_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)) {
#if defined TINY_GSM_DEBUG
if (r3 == GFP(GSM_CME_ERROR)) {
streamSkipUntil('\n'); // Read out the error
}
#endif
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF(GSM_NL "+SQNSRING:"))) {
int8_t mux = streamGetIntBefore(',');
int16_t len = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT &&
sockets[mux % TINY_GSM_MUX_COUNT]) {
sockets[mux % TINY_GSM_MUX_COUNT]->got_data = true;
sockets[mux % TINY_GSM_MUX_COUNT]->sock_available = len;
}
data = "";
DBG("### URC Data Received:", len, "on", mux);
} else if (data.endsWith(GF("SQNSH: "))) {
int8_t mux = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT &&
sockets[mux % TINY_GSM_MUX_COUNT]) {
sockets[mux % TINY_GSM_MUX_COUNT]->sock_connected = false;
}
data = "";
DBG("### URC Sock Closed: ", mux);
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5);
}
public:
Stream& stream;
protected:
GsmClientSequansMonarch* sockets[TINY_GSM_MUX_COUNT];
// GSM_NL (\r\n) is not accepted with SQNSSENDEXT in data mode so use \n
const char* gsmNL = "\n";
};
#endif // SRC_TINYGSMCLIENTSEQUANSMONARCH_H_

View File

@ -0,0 +1,854 @@
/**
* @file TinyGsmClientUBLOX.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMCLIENTUBLOX_H_
#define SRC_TINYGSMCLIENTUBLOX_H_
// #pragma message("TinyGSM: TinyGsmClientUBLOX")
// #define TINY_GSM_DEBUG Serial
#define TINY_GSM_MUX_COUNT 7
#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE
#include "TinyGsmBattery.tpp"
#include "TinyGsmCalling.tpp"
#include "TinyGsmGPRS.tpp"
#include "TinyGsmGPS.tpp"
#include "TinyGsmGSMLocation.tpp"
#include "TinyGsmModem.tpp"
#include "TinyGsmSMS.tpp"
#include "TinyGsmSSL.tpp"
#include "TinyGsmTCP.tpp"
#include "TinyGsmTime.tpp"
#define GSM_NL "\r\n"
static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL;
static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL;
#if defined TINY_GSM_DEBUG
static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:";
static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:";
#endif
enum RegStatus {
REG_NO_RESULT = -1,
REG_UNREGISTERED = 0,
REG_SEARCHING = 2,
REG_DENIED = 3,
REG_OK_HOME = 1,
REG_OK_ROAMING = 5,
REG_UNKNOWN = 4,
};
class TinyGsmUBLOX : public TinyGsmModem<TinyGsmUBLOX>,
public TinyGsmGPRS<TinyGsmUBLOX>,
public TinyGsmTCP<TinyGsmUBLOX, TINY_GSM_MUX_COUNT>,
public TinyGsmSSL<TinyGsmUBLOX>,
public TinyGsmCalling<TinyGsmUBLOX>,
public TinyGsmSMS<TinyGsmUBLOX>,
public TinyGsmGSMLocation<TinyGsmUBLOX>,
public TinyGsmGPS<TinyGsmUBLOX>,
public TinyGsmTime<TinyGsmUBLOX>,
public TinyGsmBattery<TinyGsmUBLOX> {
friend class TinyGsmModem<TinyGsmUBLOX>;
friend class TinyGsmGPRS<TinyGsmUBLOX>;
friend class TinyGsmTCP<TinyGsmUBLOX, TINY_GSM_MUX_COUNT>;
friend class TinyGsmSSL<TinyGsmUBLOX>;
friend class TinyGsmCalling<TinyGsmUBLOX>;
friend class TinyGsmSMS<TinyGsmUBLOX>;
friend class TinyGsmGSMLocation<TinyGsmUBLOX>;
friend class TinyGsmGPS<TinyGsmUBLOX>;
friend class TinyGsmTime<TinyGsmUBLOX>;
friend class TinyGsmBattery<TinyGsmUBLOX>;
/*
* Inner Client
*/
public:
class GsmClientUBLOX : public GsmClient {
friend class TinyGsmUBLOX;
public:
GsmClientUBLOX() {}
explicit GsmClientUBLOX(TinyGsmUBLOX& modem, uint8_t mux = 0) {
init(&modem, mux);
}
bool init(TinyGsmUBLOX* modem, uint8_t mux = 0) {
this->at = modem;
sock_available = 0;
prev_check = 0;
sock_connected = false;
got_data = false;
if (mux < TINY_GSM_MUX_COUNT) {
this->mux = mux;
} else {
this->mux = (mux % TINY_GSM_MUX_COUNT);
}
at->sockets[this->mux] = this;
return true;
}
public:
virtual int connect(const char* host, uint16_t port, int timeout_s) {
// stop(); // DON'T stop!
TINY_GSM_YIELD();
rx.clear();
uint8_t oldMux = mux;
sock_connected = at->modemConnect(host, port, &mux, false, timeout_s);
if (mux != oldMux) {
DBG("WARNING: Mux number changed from", oldMux, "to", mux);
at->sockets[oldMux] = NULL;
}
at->sockets[mux] = this;
at->maintain();
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
void stop(uint32_t maxWaitMs) {
dumpModemBuffer(maxWaitMs);
at->sendAT(GF("+USOCL="), mux);
at->waitResponse(); // should return within 1s
sock_connected = false;
}
void stop() override {
stop(15000L);
}
/*
* Extended API
*/
String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
/*
* Inner Secure Client
*/
public:
class GsmClientSecureUBLOX : public GsmClientUBLOX {
public:
GsmClientSecureUBLOX() {}
explicit GsmClientSecureUBLOX(TinyGsmUBLOX& modem, uint8_t mux = 0)
: GsmClientUBLOX(modem, mux) {}
public:
int connect(const char* host, uint16_t port, int timeout_s) override {
// stop(); // DON'T stop!
TINY_GSM_YIELD();
rx.clear();
uint8_t oldMux = mux;
sock_connected = at->modemConnect(host, port, &mux, true, timeout_s);
if (mux != oldMux) {
DBG("WARNING: Mux number changed from", oldMux, "to", mux);
at->sockets[oldMux] = NULL;
}
at->sockets[mux] = this;
at->maintain();
return sock_connected;
}
TINY_GSM_CLIENT_CONNECT_OVERRIDES
};
/*
* Constructor
*/
public:
explicit TinyGsmUBLOX(Stream& stream) : stream(stream) {
memset(sockets, 0, sizeof(sockets));
}
/*
* Basic functions
*/
protected:
bool initImpl(const char* pin = NULL) {
DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION);
DBG(GF("### TinyGSM Compiled Module: TinyGsmClientUBLOX"));
if (!testAT()) { return false; }
sendAT(GF("E0")); // Echo Off
if (waitResponse() != 1) { return false; }
#ifdef TINY_GSM_DEBUG
sendAT(GF("+CMEE=2")); // turn on verbose error codes
#else
sendAT(GF("+CMEE=0")); // turn off error codes
#endif
waitResponse();
DBG(GF("### Modem:"), getModemName());
// Enable automatic time zome update
sendAT(GF("+CTZU=1"));
waitResponse(10000L);
// Ignore the response, in case the network doesn't support it.
// if (waitResponse(10000L) != 1) { return false; }
SimStatus ret = getSimStatus();
// if the sim isn't ready and a pin has been provided, try to unlock the sim
if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) {
simUnlock(pin);
return (getSimStatus() == SIM_READY);
} else {
// if the sim is ready, or it's locked but no pin has been provided,
// return true
return (ret == SIM_READY || ret == SIM_LOCKED);
}
}
// only difference in implementation is the warning on the wrong type
String getModemNameImpl() {
sendAT(GF("+CGMI"));
String res1;
if (waitResponse(1000L, res1) != 1) { return "u-blox Cellular Modem"; }
res1.replace(GSM_NL "OK" GSM_NL, "");
res1.trim();
sendAT(GF("+GMM"));
String res2;
if (waitResponse(1000L, res2) != 1) { return "u-blox Cellular Modem"; }
res2.replace(GSM_NL "OK" GSM_NL, "");
res2.trim();
String name = res1 + String(' ') + res2;
if (name.startsWith("u-blox SARA-R4") ||
name.startsWith("u-blox SARA-N4")) {
DBG("### WARNING: You are using the wrong TinyGSM modem!");
} else if (name.startsWith("u-blox SARA-N2")) {
DBG("### SARA N2 NB-IoT modems not supported!");
}
return name;
}
bool factoryDefaultImpl() {
sendAT(GF("+UFACTORY=0,1")); // No factory restore, erase NVM
waitResponse();
return setPhoneFunctionality(16); // Reset
}
/*
* Power functions
*/
protected:
bool restartImpl(const char* pin = NULL) {
if (!testAT()) { return false; }
if (!setPhoneFunctionality(16)) { return false; }
delay(3000); // TODO(?): Verify delay timing here
return init(pin);
}
bool powerOffImpl() {
sendAT(GF("+CPWROFF"));
return waitResponse(40000L) == 1;
}
bool sleepEnableImpl(bool enable = true) TINY_GSM_ATTR_NOT_AVAILABLE;
bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) {
sendAT(GF("+CFUN="), fun, reset ? ",1" : "");
return waitResponse(10000L) == 1;
}
/*
* Generic network functions
*/
public:
RegStatus getRegistrationStatus() {
return (RegStatus)getRegistrationStatusXREG("CGREG");
}
bool setRadioAccessTecnology(int selected, int preferred) {
// selected:
// 0: GSM / GPRS / eGPRS (single mode)
// 1: GSM / UMTS (dual mode)
// 2: UMTS (single mode)
// 3: LTE (single mode)
// 4: GSM / UMTS / LTE (tri mode)
// 5: GSM / LTE (dual mode)
// 6: UMTS / LTE (dual mode)
// preferred:
// 0: GSM / GPRS / eGPRS
// 2: UTRAN
// 3: LTE
sendAT(GF("+URAT="), selected, GF(","), preferred);
if (waitResponse() != 1) { return false; }
return true;
}
bool getCurrentRadioAccessTecnology(int&) {
// @TODO
return false;
}
protected:
bool isNetworkConnectedImpl() {
RegStatus s = getRegistrationStatus();
if (s == REG_OK_HOME || s == REG_OK_ROAMING)
return true;
else if (s == REG_UNKNOWN) // for some reason, it can hang at unknown..
return isGprsConnected();
else
return false;
}
String getLocalIPImpl() {
sendAT(GF("+UPSND=0,0"));
if (waitResponse(GF(GSM_NL "+UPSND:")) != 1) { return ""; }
streamSkipUntil(','); // Skip PSD profile
streamSkipUntil('\"'); // Skip request type
String res = stream.readStringUntil('\"');
if (waitResponse() != 1) { return ""; }
return res;
}
/*
* GPRS functions
*/
protected:
bool gprsConnectImpl(const char* apn, const char* user = NULL,
const char* pwd = NULL) {
// gprsDisconnect();
sendAT(GF("+CGATT=1")); // attach to GPRS
if (waitResponse(360000L) != 1) { return false; }
// Setting up the PSD profile/PDP context with the UPSD commands sets up an
// "internal" PDP context, i.e. a data connection using the internal IP
// stack and related AT commands for sockets.
// Packet switched data configuration
// AT+UPSD=<profile_id>,<param_tag>,<param_val>
// profile_id = 0 - PSD profile identifier, in range 0-6 (NOT PDP context)
// param_tag = 1: APN
// param_tag = 2: username
// param_tag = 3: password
// param_tag = 7: IP address Note: IP address set as "0.0.0.0" means
// dynamic IP address assigned during PDP context activation
sendAT(GF("+UPSD=0,1,\""), apn, '"'); // Set APN for PSD profile 0
waitResponse();
if (user && strlen(user) > 0) {
sendAT(GF("+UPSD=0,2,\""), user, '"'); // Set user for PSD profile 0
waitResponse();
}
if (pwd && strlen(pwd) > 0) {
sendAT(GF("+UPSD=0,3,\""), pwd, '"'); // Set password for PSD profile 0
waitResponse();
}
sendAT(GF("+UPSD=0,7,\"0.0.0.0\"")); // Dynamic IP on PSD profile 0
waitResponse();
// Packet switched data action
// AT+UPSDA=<profile_id>,<action>
// profile_id = 0: PSD profile identifier, in range 0-6 (NOT PDP context)
// action = 3: activate; it activates a PDP context with the specified
// profile, using the current parameters
sendAT(GF(
"+UPSDA=0,3")); // Activate the PDP context associated with profile 0
if (waitResponse(360000L) != 1) { // Should return ok
return false;
}
// Packet switched network-assigned data - Returns the current (dynamic)
// network-assigned or network-negotiated value of the specified parameter
// for the active PDP context associated with the specified PSD profile.
// AT+UPSND=<profile_id>,<param_tag>
// profile_id = 0: PSD profile identifier, in range 0-6 (NOT PDP context)
// param_tag = 8: PSD profile status: if the profile is active the return
// value is 1, 0 otherwise
sendAT(GF("+UPSND=0,8")); // Check if PSD profile 0 is now active
int8_t res = waitResponse(GF(",8,1"), GF(",8,0"));
waitResponse(); // Should return another OK
if (res == 1) {
return true; // It's now active
} else if (res == 2) { // If it's not active yet, wait for the +UUPSDA URC
if (waitResponse(180000L, GF("+UUPSDA: 0")) != 1) { // 0=successful
return false;
}
streamSkipUntil('\n'); // Ignore the IP address, if returned
} else {
return false;
}
return true;
}
bool gprsDisconnectImpl() {
sendAT(GF(
"+UPSDA=0,4")); // Deactivate the PDP context associated with profile 0
if (waitResponse(360000L) != 1) { return false; }
sendAT(GF("+CGATT=0")); // detach from GPRS
if (waitResponse(360000L) != 1) { return false; }
return true;
}
/*
* SIM card functions
*/
protected:
// This uses "CGSN" instead of "GSN"
String getIMEIImpl() {
sendAT(GF("+CGSN"));
if (waitResponse(GF(GSM_NL)) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
/*
* Phone Call functions
*/
protected:
// Can follow all of the phone call functions from the template
/*
* Messaging functions
*/
protected:
// Can follow all template functions
/*
* GSM/GPS/GNSS/GLONASS Location functions
* NOTE: u-blox modules use the same function to get location data from both
* GSM tower triangulation and from dedicated GPS/GNSS/GLONASS receivers. The
* only difference in which sensor the data is requested from. If a GNSS
* location is requested from a modem without a GNSS receiver installed on the
* I2C port, the GSM-based "Cell Locate" location will be returned instead.
*/
protected:
bool enableGPSImpl() {
// AT+UGPS=<mode>[,<aid_mode>[,<GNSS_systems>]]
// <mode> - 0: GNSS receiver powered off, 1: on
// <aid_mode> - 0: no aiding (default)
// <GNSS_systems> - 3: GPS + SBAS (default)
sendAT(GF("+UGPS=1,0,3"));
if (waitResponse(10000L, GF(GSM_NL "+UGPS:")) != 1) { return false; }
return waitResponse(10000L) == 1;
}
bool disableGPSImpl() {
sendAT(GF("+UGPS=0"));
if (waitResponse(10000L, GF(GSM_NL "+UGPS:")) != 1) { return false; }
return waitResponse(10000L) == 1;
}
String inline getUbloxLocationRaw(int8_t sensor) {
// AT+ULOC=<mode>,<sensor>,<response_type>,<timeout>,<accuracy>
// <mode> - 2: single shot position
// <sensor> - 0: use the last fix in the internal database and stop the GNSS
// receiver
// - 1: use the GNSS receiver for localization
// - 2: use cellular CellLocate location information
// - 3: ?? use the combined GNSS receiver and CellLocate service
// information ?? - Docs show using sensor 3 and it's
// documented for the +UTIME command but not for +ULOC
// <response_type> - 0: standard (single-hypothesis) response
// <timeout> - Timeout period in seconds
// <accuracy> - Target accuracy in meters (1 - 999999)
sendAT(GF("+ULOC=2,"), sensor, GF(",0,120,1"));
// wait for first "OK"
if (waitResponse(10000L) != 1) { return ""; }
// wait for the final result - wait full timeout time
if (waitResponse(120000L, GF(GSM_NL "+UULOC:")) != 1) { return ""; }
String res = stream.readStringUntil('\n');
waitResponse();
res.trim();
return res;
}
String getGsmLocationRawImpl() {
return getUbloxLocationRaw(2);
}
String getGPSrawImpl() {
return getUbloxLocationRaw(1);
}
inline bool getUbloxLocation(int8_t sensor, float* lat, float* lon,
float* speed = 0, float* alt = 0, int* vsat = 0,
int* usat = 0, float* accuracy = 0,
int* year = 0, int* month = 0, int* day = 0,
int* hour = 0, int* minute = 0,
int* second = 0) {
// AT+ULOC=<mode>,<sensor>,<response_type>,<timeout>,<accuracy>
// <mode> - 2: single shot position
// <sensor> - 2: use cellular CellLocate location information
// - 0: use the last fix in the internal database and stop the GNSS
// receiver
// - 1: use the GNSS receiver for localization
// - 3: ?? use the combined GNSS receiver and CellLocate service
// information ?? - Docs show using sensor 3 and it's documented
// for the +UTIME command but not for +ULOC
// <response_type> - 0: standard (single-hypothesis) response
// <timeout> - Timeout period in seconds
// <accuracy> - Target accuracy in meters (1 - 999999)
sendAT(GF("+ULOC=2,"), sensor, GF(",0,120,1"));
// wait for first "OK"
if (waitResponse(10000L) != 1) { return false; }
// wait for the final result - wait full timeout time
if (waitResponse(120000L, GF(GSM_NL "+UULOC: ")) != 1) { return false; }
// +UULOC: <date>, <time>, <lat>, <long>, <alt>, <uncertainty>, <speed>,
// <direction>, <vertical_acc>, <sensor_used>, <SV_used>, <antenna_status>,
// <jamming_status>
// init variables
float ilat = 0;
float ilon = 0;
float ispeed = 0;
float ialt = 0;
int iusat = 0;
float iaccuracy = 0;
int iyear = 0;
int imonth = 0;
int iday = 0;
int ihour = 0;
int imin = 0;
float secondWithSS = 0;
// Date & Time
iday = streamGetIntBefore('/'); // Two digit day
imonth = streamGetIntBefore('/'); // Two digit month
iyear = streamGetIntBefore(','); // Four digit year
ihour = streamGetIntBefore(':'); // Two digit hour
imin = streamGetIntBefore(':'); // Two digit minute
secondWithSS = streamGetFloatBefore(','); // 6 digit second with subseconds
ilat = streamGetFloatBefore(','); // Estimated latitude, in degrees
ilon = streamGetFloatBefore(','); // Estimated longitude, in degrees
ialt = streamGetFloatBefore(
','); // Estimated altitude, in meters - only forGNSS
// positioning, 0 in case of CellLocate
if (ialt != 0) { // values not returned for CellLocate
iaccuracy =
streamGetFloatBefore(','); // Maximum possible error, in meters
ispeed = streamGetFloatBefore(','); // Speed over ground m/s3
streamSkipUntil(','); // Course over ground in degree (0 deg - 360 deg)
streamSkipUntil(','); // Vertical accuracy, in meters
streamSkipUntil(','); // Sensor used for the position calculation
iusat = streamGetIntBefore(','); // Number of satellite used
streamSkipUntil(','); // Antenna status
streamSkipUntil('\n'); // Jamming status
} else {
iaccuracy =
streamGetFloatBefore('\n'); // Maximum possible error, in meters
}
// Set pointers
if (lat != NULL) *lat = ilat;
if (lon != NULL) *lon = ilon;
if (speed != NULL) *speed = ispeed;
if (alt != NULL) *alt = ialt;
if (vsat != NULL) *vsat = 0; // Number of satellites viewed not reported;
if (usat != NULL) *usat = iusat;
if (accuracy != NULL) *accuracy = iaccuracy;
if (iyear < 2000) iyear += 2000;
if (year != NULL) *year = iyear;
if (month != NULL) *month = imonth;
if (day != NULL) *day = iday;
if (hour != NULL) *hour = ihour;
if (minute != NULL) *minute = imin;
if (second != NULL) *second = static_cast<int>(secondWithSS);
// final ok
waitResponse();
return true;
}
bool getGsmLocationImpl(float* lat, float* lon, float* accuracy = 0,
int* year = 0, int* month = 0, int* day = 0,
int* hour = 0, int* minute = 0, int* second = 0) {
return getUbloxLocation(2, lat, lon, 0, 0, 0, 0, accuracy, year, month, day,
hour, minute, second);
}
bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0,
int* vsat = 0, int* usat = 0, float* accuracy = 0,
int* year = 0, int* month = 0, int* day = 0, int* hour = 0,
int* minute = 0, int* second = 0) {
return getUbloxLocation(1, lat, lon, speed, alt, vsat, usat, accuracy, year,
month, day, hour, minute, second);
}
/*
* Time functions
*/
protected:
// Can follow the standard CCLK function in the template
/*
* Battery functions
*/
protected:
uint16_t getBattVoltageImpl() TINY_GSM_ATTR_NOT_AVAILABLE;
int8_t getBattPercentImpl() {
sendAT(GF("+CIND?"));
if (waitResponse(GF(GSM_NL "+CIND:")) != 1) { return 0; }
int8_t res = streamGetIntBefore(',');
int8_t percent = res * 20; // return is 0-5
// Wait for final OK
waitResponse();
return percent;
}
uint8_t getBattChargeStateImpl() TINY_GSM_ATTR_NOT_AVAILABLE;
bool getBattStatsImpl(uint8_t& chargeState, int8_t& percent,
uint16_t& milliVolts) {
chargeState = 0;
percent = getBattPercent();
milliVolts = 0;
return true;
}
/*
* Temperature functions
*/
// This would only available for a small number of modules in this group
// (TOBY-L)
float getTemperatureImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED;
/*
* Client related functions
*/
protected:
bool modemConnect(const char* host, uint16_t port, uint8_t* mux,
bool ssl = false, int timeout_s = 120) {
uint32_t timeout_ms = ((uint32_t)timeout_s) * 1000;
uint32_t startMillis = millis();
// create a socket
sendAT(GF("+USOCR=6"));
// reply is +USOCR: ## of socket created
if (waitResponse(GF(GSM_NL "+USOCR:")) != 1) { return false; }
*mux = streamGetIntBefore('\n');
waitResponse();
if (ssl) {
sendAT(GF("+USOSEC="), *mux, ",1");
waitResponse();
}
// Enable NODELAY
// AT+USOSO=<socket>,<level>,<opt_name>,<opt_val>[,<opt_val2>]
// <level> - 0 for IP, 6 for TCP, 65535 for socket level options
// <opt_name> TCP/1 = no delay (do not delay send to coalesce packets)
// NOTE: Enabling this may increase data plan usage
// sendAT(GF("+USOSO="), *mux, GF(",6,1,1"));
// waitResponse();
// Enable KEEPALIVE, 30 sec
// sendAT(GF("+USOSO="), *mux, GF(",6,2,30000"));
// waitResponse();
// connect on the allocated socket
sendAT(GF("+USOCO="), *mux, ",\"", host, "\",", port);
int8_t rsp = waitResponse(timeout_ms - (millis() - startMillis));
return (1 == rsp);
}
int16_t modemSend(const void* buff, size_t len, uint8_t mux) {
sendAT(GF("+USOWR="), mux, ',', (uint16_t)len);
if (waitResponse(GF("@")) != 1) { return 0; }
// 50ms delay, see AT manual section 25.10.4
delay(50);
stream.write(reinterpret_cast<const uint8_t*>(buff), len);
stream.flush();
if (waitResponse(GF(GSM_NL "+USOWR:")) != 1) { return 0; }
streamSkipUntil(','); // Skip mux
int16_t sent = streamGetIntBefore('\n');
waitResponse(); // sends back OK after the confirmation of number sent
return sent;
}
size_t modemRead(size_t size, uint8_t mux) {
if (!sockets[mux]) return 0;
sendAT(GF("+USORD="), mux, ',', (uint16_t)size);
if (waitResponse(GF(GSM_NL "+USORD:")) != 1) { return 0; }
streamSkipUntil(','); // Skip mux
int16_t len = streamGetIntBefore(',');
streamSkipUntil('\"');
for (int i = 0; i < len; i++) { moveCharFromStreamToFifo(mux); }
streamSkipUntil('\"');
waitResponse();
// DBG("### READ:", len, "from", mux);
sockets[mux]->sock_available = modemGetAvailable(mux);
return len;
}
size_t modemGetAvailable(uint8_t mux) {
if (!sockets[mux]) return 0;
// NOTE: Querying a closed socket gives an error "operation not allowed"
sendAT(GF("+USORD="), mux, ",0");
size_t result = 0;
uint8_t res = waitResponse(GF(GSM_NL "+USORD:"));
// Will give error "operation not allowed" when attempting to read a socket
// that you have already told to close
if (res == 1) {
streamSkipUntil(','); // Skip mux
result = streamGetIntBefore('\n');
// if (result) DBG("### DATA AVAILABLE:", result, "on", mux);
waitResponse();
}
if (!result) { sockets[mux]->sock_connected = modemGetConnected(mux); }
// DBG("### AvailablE:", result, "on", mux);
return result;
}
bool modemGetConnected(uint8_t mux) {
// NOTE: Querying a closed socket gives an error "operation not allowed"
sendAT(GF("+USOCTL="), mux, ",10");
uint8_t res = waitResponse(GF(GSM_NL "+USOCTL:"));
if (res != 1) { return false; }
streamSkipUntil(','); // Skip mux
streamSkipUntil(','); // Skip type
int8_t result = streamGetIntBefore('\n');
// 0: the socket is in INACTIVE status (it corresponds to CLOSED status
// defined in RFC793 "TCP Protocol Specification" [112])
// 1: the socket is in LISTEN status
// 2: the socket is in SYN_SENT status
// 3: the socket is in SYN_RCVD status
// 4: the socket is in ESTABILISHED status
// 5: the socket is in FIN_WAIT_1 status
// 6: the socket is in FIN_WAIT_2 status
// 7: the sokcet is in CLOSE_WAIT status
// 8: the socket is in CLOSING status
// 9: the socket is in LAST_ACK status
// 10: the socket is in TIME_WAIT status
waitResponse();
return (result != 0);
}
/*
* Utilities
*/
public:
// TODO(vshymanskyy): Optimize this!
int8_t waitResponse(uint32_t timeout_ms, String& data,
GsmConstStr r1 = GFP(GSM_OK),
GsmConstStr r2 = GFP(GSM_ERROR),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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(64);
uint8_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)) {
#if defined TINY_GSM_DEBUG
if (r3 == GFP(GSM_CME_ERROR)) {
streamSkipUntil('\n'); // Read out the error
}
#endif
index = 3;
goto finish;
} else if (r4 && data.endsWith(r4)) {
index = 4;
goto finish;
} else if (r5 && data.endsWith(r5)) {
index = 5;
goto finish;
} else if (data.endsWith(GF("+UUSORD:"))) {
int8_t mux = streamGetIntBefore(',');
int16_t len = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->got_data = true;
// max size is 1024
if (len >= 0 && len <= 1024) { sockets[mux]->sock_available = len; }
}
data = "";
// DBG("### URC Data Received:", len, "on", mux);
} else if (data.endsWith(GF("+UUSOCL:"))) {
int8_t mux = streamGetIntBefore('\n');
if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) {
sockets[mux]->sock_connected = false;
}
data = "";
DBG("### URC Sock Closed: ", mux);
}
}
} while (millis() - startMillis < timeout_ms);
finish:
if (!index) {
data.trim();
if (data.length()) { DBG("### Unhandled:", data); }
data = "";
}
// 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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
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),
#if defined TINY_GSM_DEBUG
GsmConstStr r3 = GFP(GSM_CME_ERROR),
GsmConstStr r4 = GFP(GSM_CMS_ERROR),
#else
GsmConstStr r3 = NULL, GsmConstStr r4 = NULL,
#endif
GsmConstStr r5 = NULL) {
return waitResponse(1000, r1, r2, r3, r4, r5);
}
public:
Stream& stream;
protected:
GsmClientUBLOX* sockets[TINY_GSM_MUX_COUNT];
const char* gsmNL = GSM_NL;
};
#endif // SRC_TINYGSMCLIENTUBLOX_H_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,120 @@
/**
* @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.11.5"
#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
#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
#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__) && !defined(__AVR_ATmega4809__)
#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, int rx,int tx,uint32_t minimum = 9600,
uint32_t maximum = 115200) {
static uint32_t rates[] = {115200,38400,9600};
for (uint8_t i = 0; i < sizeof(rates) / sizeof(rates[0]); i++) {
uint32_t rate = rates[i];
if (rate < minimum || rate > maximum) continue;
Serial.println("Trying baud rate12"+String(i)+ "...");
Serial.println("Trying baud rate"+String(rate)+"...");
SerialAT.begin(rate, SERIAL_8N1, rx, tx);
delay(10);
for (int j = 0; j < 10; j++) {
SerialAT.print("AT\r\n");
delay(2000);
String input = SerialAT.readString();
Serial.println(input);
if (input.indexOf("OK") >= 0) {
DBG("Modem responded at rate", rate);
return rate;
}
}
}
SerialAT.begin(minimum, SERIAL_8N1, rx, tx);
return 0;
}
#endif // SRC_TINYGSMCOMMON_H_

View File

@ -0,0 +1,141 @@
#ifndef TinyGsmFifo_h
#define TinyGsmFifo_h
template <class T, unsigned N>
class TinyGsmFifo
{
public:
TinyGsmFifo()
{
clear();
}
void clear()
{
_r = 0;
_w = 0;
}
// writing thread/context API
//-------------------------------------------------------------
bool writeable(void)
{
return free() > 0;
}
int free(void)
{
int s = _r - _w;
if (s <= 0)
s += N;
return s - 1;
}
bool put(const T& c)
{
int i = _w;
int j = i;
i = _inc(i);
if (i == _r) // !writeable()
return false;
_b[j] = c;
_w = i;
return true;
}
int put(const T* p, int n, bool t = false)
{
int c = n;
while (c)
{
int f;
while ((f = free()) == 0) // wait for space
{
if (!t) return n - c; // no more space and not blocking
/* nothing / just wait */;
}
// check free space
if (c < f) f = c;
int w = _w;
int m = N - w;
// check wrap
if (f > m) f = m;
memcpy(&_b[w], p, f);
_w = _inc(w, f);
c -= f;
p += f;
}
return n - c;
}
// reading thread/context API
// --------------------------------------------------------
bool readable(void)
{
return (_r != _w);
}
size_t size(void)
{
int s = _w - _r;
if (s < 0)
s += N;
return s;
}
bool get(T* p)
{
int r = _r;
if (r == _w) // !readable()
return false;
*p = _b[r];
_r = _inc(r);
return true;
}
int get(T* p, int n, bool t = false)
{
int c = n;
while (c)
{
int f;
for (;;) // wait for data
{
f = size();
if (f) break; // free space
if (!t) return n - c; // no space and not blocking
/* nothing / just wait */;
}
// check available data
if (c < f) f = c;
int r = _r;
int m = N - r;
// check wrap
if (f > m) f = m;
memcpy(p, &_b[r], f);
_r = _inc(r, f);
c -= f;
p += f;
}
return n - c;
}
uint8_t peek()
{
return _b[_r];
}
private:
int _inc(int i, int n = 1)
{
return (i + n) % N;
}
T _b[N];
int _w;
int _r;
};
#endif

View File

@ -0,0 +1,171 @@
/**
* @file TinyGsmGPRS.tpp
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMGPRS_H_
#define SRC_TINYGSMGPRS_H_
#include "TinyGsmCommon.h"
#define TINY_GSM_MODEM_HAS_GPRS
enum SimStatus {
SIM_ERROR = 0,
SIM_READY = 1,
SIM_LOCKED = 2,
SIM_ANTITHEFT_LOCKED = 3,
};
template <class modemType>
class TinyGsmGPRS {
public:
/*
* 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();
}
// Asks for International Mobile Subscriber Identity IMSI
String getIMSI() {
return thisModem().getIMSIImpl();
}
SimStatus getSimStatus(uint32_t timeout_ms = 10000L) {
return thisModem().getSimStatusImpl(timeout_ms);
}
/*
* 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();
}
/*
* CRTP Helper
*/
protected:
inline const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
inline modemType& thisModem() {
return static_cast<modemType&>(*this);
}
/*
* 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"));
thisModem().streamSkipUntil('\n'); // skip first newline
String res = thisModem().stream.readStringUntil('\n');
thisModem().waitResponse();
res.trim();
return res;
}
// Asks for International Mobile Subscriber Identity IMSI via the AT+CIMI
// command
String getIMSIImpl() {
thisModem().sendAT(GF("+CIMI"));
thisModem().streamSkipUntil('\n'); // skip first newline
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;
}
int8_t 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;
}
/*
* 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; }
int8_t res = thisModem().streamGetIntBefore('\n');
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;
}
};
#endif // SRC_TINYGSMGPRS_H_

View File

@ -0,0 +1,82 @@
/**
* @file TinyGsmGPS.tpp
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMGPS_H_
#define SRC_TINYGSMGPS_H_
#include "TinyGsmCommon.h"
#define TINY_GSM_MODEM_HAS_GPS
template <class modemType>
class TinyGsmGPS {
public:
/*
* GPS/GNSS/GLONASS location functions
*/
bool enableGPS() {
return thisModem().enableGPSImpl();
}
bool disableGPS() {
return thisModem().disableGPSImpl();
}
String getGPSraw() {
return thisModem().getGPSrawImpl();
}
bool getGPS(float* lat, float* lon, float* speed = 0, float* alt = 0,
int* vsat = 0, int* usat = 0, float* accuracy = 0, int* year = 0,
int* month = 0, int* day = 0, int* hour = 0, int* minute = 0,
int* second = 0) {
return thisModem().getGPSImpl(lat, lon, speed, alt, vsat, usat, accuracy,
year, month, day, hour, minute, second);
}
bool getGPSTime(int* year, int* month, int* day, int* hour, int* minute,
int* second) {
float lat = 0;
float lon = 0;
return thisModem().getGPSImpl(&lat, &lon, 0, 0, 0, 0, 0, year, month, day,
hour, minute, second);
}
String setGNSSMode(uint8_t mode, bool dpo) {
return thisModem().setGNSSModeImpl(mode, dpo);
}
uint8_t getGNSSMode() {
return thisModem().getGNSSModeImpl();
}
/*
* CRTP Helper
*/
protected:
inline const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
inline modemType& thisModem() {
return static_cast<modemType&>(*this);
}
/*
* GPS/GNSS/GLONASS location functions
*/
bool enableGPSImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED;
bool disableGPSImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED;
String getGPSrawImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED;
bool getGPSImpl(float* lat, float* lon, float* speed = 0, float* alt = 0,
int* vsat = 0, int* usat = 0, float* accuracy = 0,
int* year = 0, int* month = 0, int* day = 0, int* hour = 0,
int* minute = 0,
int* second = 0) TINY_GSM_ATTR_NOT_IMPLEMENTED;
String setGNSSModeImpl(uint8_t mode, bool dpo) TINY_GSM_ATTR_NOT_IMPLEMENTED;
uint8_t getGNSSModeImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
#endif // SRC_TINYGSMGPS_H_

View File

@ -0,0 +1,149 @@
/**
* @file TinyGsmGSMLocation.h
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMGSMLOCATION_H_
#define SRC_TINYGSMGSMLOCATION_H_
#include "TinyGsmCommon.h"
#define TINY_GSM_MODEM_HAS_GSM_LOCATION
template <class modemType>
class TinyGsmGSMLocation {
public:
/*
* GSM Location functions
*/
String getGsmLocationRaw() {
return thisModem().getGsmLocationRawImpl();
}
String getGsmLocation() {
return thisModem().getGsmLocationRawImpl();
}
bool getGsmLocation(float* lat, float* lon, float* accuracy = 0,
int* year = 0, int* month = 0, int* day = 0,
int* hour = 0, int* minute = 0, int* second = 0) {
return thisModem().getGsmLocationImpl(lat, lon, accuracy, year, month, day,
hour, minute, second);
};
bool getGsmLocationTime(int* year, int* month, int* day, int* hour,
int* minute, int* second) {
float lat = 0;
float lon = 0;
float accuracy = 0;
return thisModem().getGsmLocation(&lat, &lon, &accuracy, year, month, day,
hour, minute, second);
}
/*
* CRTP Helper
*/
protected:
inline const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
inline modemType& thisModem() {
return static_cast<modemType&>(*this);
}
/*
* GSM Location functions
* Template is based on SIMCOM commands
*/
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;
// }
String getGsmLocationRawImpl() {
// AT+CLBS=<type>,<cid>
// <type> 1 = location using 3 cell's information
// 3 = get number of times location has been accessed
// 4 = Get longitude latitude and date time
thisModem().sendAT(GF("+CLBS=1,1"));
// Should get a location code of "0" indicating success
if (thisModem().waitResponse(120000L, GF("+CLBS: ")) != 1) { return ""; }
int8_t locationCode = thisModem().streamGetIntLength(2);
// 0 = success, else, error
if (locationCode != 0) {
thisModem().waitResponse(); // should be an ok after the error
return "";
}
String res = thisModem().stream.readStringUntil('\n');
thisModem().waitResponse();
res.trim();
return res;
}
bool getGsmLocationImpl(float* lat, float* lon, float* accuracy = 0,
int* year = 0, int* month = 0, int* day = 0,
int* hour = 0, int* minute = 0, int* second = 0) {
// AT+CLBS=<type>,<cid>
// <type> 1 = location using 3 cell's information
// 3 = get number of times location has been accessed
// 4 = Get longitude latitude and date time
thisModem().sendAT(GF("+CLBS=4,1"));
// Should get a location code of "0" indicating success
if (thisModem().waitResponse(120000L, GF("+CLBS: ")) != 1) { return false; }
int8_t locationCode = thisModem().streamGetIntLength(2);
// 0 = success, else, error
if (locationCode != 0) {
thisModem().waitResponse(); // should be an ok after the error
return false;
}
// init variables
float ilat = 0;
float ilon = 0;
float iaccuracy = 0;
int iyear = 0;
int imonth = 0;
int iday = 0;
int ihour = 0;
int imin = 0;
int isec = 0;
ilat = thisModem().streamGetFloatBefore(','); // Latitude
ilon = thisModem().streamGetFloatBefore(','); // Longitude
iaccuracy = thisModem().streamGetIntBefore(','); // Positioning accuracy
// Date & Time
iyear = thisModem().streamGetIntBefore('/');
imonth = thisModem().streamGetIntBefore('/');
iday = thisModem().streamGetIntBefore(',');
ihour = thisModem().streamGetIntBefore(':');
imin = thisModem().streamGetIntBefore(':');
isec = thisModem().streamGetIntBefore('\n');
// Set pointers
if (lat != NULL) *lat = ilat;
if (lon != NULL) *lon = ilon;
if (accuracy != NULL) *accuracy = iaccuracy;
if (iyear < 2000) iyear += 2000;
if (year != NULL) *year = iyear;
if (month != NULL) *month = imonth;
if (day != NULL) *day = iday;
if (hour != NULL) *hour = ihour;
if (minute != NULL) *minute = imin;
if (second != NULL) *second = isec;
// Final OK
thisModem().waitResponse();
return true;
}
};
#endif // SRC_TINYGSMGSMLOCATION_H_

View File

@ -0,0 +1,354 @@
/**
* @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(const char* pin = NULL) {
return thisModem().restartImpl(pin);
}
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, bool check_signal = false) {
return thisModem().waitForNetworkImpl(timeout_ms, check_signal);
}
// 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().stream.parseInt();
thisModem().waitResponse();
return status;
}
bool waitForNetworkImpl(uint32_t timeout_ms = 60000L,
bool check_signal = false) {
for (uint32_t start = millis(); millis() - start < timeout_ms;) {
if (check_signal) { thisModem().getSignalQuality(); }
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_

View File

@ -0,0 +1,92 @@
/**
* @file TinyGsmNTP.tpp
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMNTP_H_
#define SRC_TINYGSMNTP_H_
#include "TinyGsmCommon.h"
#define TINY_GSM_MODEM_HAS_NTP
template <class modemType>
class TinyGsmNTP {
public:
/*
* NTP server functions
*/
public:
bool TinyGsmIsValidNumber(String str) {
if (!(str.charAt(0) == '+' || str.charAt(0) == '-' ||
isDigit(str.charAt(0))))
return false;
for (byte i = 1; i < str.length(); i++) {
if (!(isDigit(str.charAt(i)) || str.charAt(i) == '.')) { return false; }
}
return true;
}
byte NTPServerSync(String server = "pool.ntp.org", byte TimeZone = 3) {
return thisModem().NTPServerSyncImpl(server, TimeZone);
}
String ShowNTPError(byte error) {
return thisModem().ShowNTPErrorImpl(error);
}
/*
* CRTP Helper
*/
protected:
inline const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
inline modemType& thisModem() {
return static_cast<modemType&>(*this);
}
/*
* NTP server functions
*/
protected:
byte NTPServerSyncImpl(String server = "pool.ntp.org", byte TimeZone = 3) {
// Set GPRS bearer profile to associate with NTP sync
// this may fail, it's not supported by all modules
thisModem().sendAT(GF("+CNTPCID=1"));
thisModem().waitResponse(10000L);
// Set NTP server and timezone
thisModem().sendAT(GF("+CNTP=\""), server, "\",", String(TimeZone));
if (thisModem().waitResponse(10000L) != 1) { return -1; }
// Request network synchronization
thisModem().sendAT(GF("+CNTP"));
if (thisModem().waitResponse(10000L, GF("+CNTP:"))) {
String result = thisModem().stream.readStringUntil('\n');
result.trim();
if (TinyGsmIsValidNumber(result)) { return result.toInt(); }
} else {
return -1;
}
return -1;
}
String ShowNTPErrorImpl(byte error) {
switch (error) {
case 1: return "Network time synchronization is successful";
case 61: return "Network error";
case 62: return "DNS resolution error";
case 63: return "Connection error";
case 64: return "Service response error";
case 65: return "Service response timeout";
default: return "Unknown error: " + String(error);
}
}
};
#endif // SRC_TINYGSMNTP_H_

View File

@ -0,0 +1,224 @@
/**
* @file TinyGsmSMS.tpp
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMSMS_H_
#define SRC_TINYGSMSMS_H_
#include "TinyGsmCommon.h"
#define TINY_GSM_MODEM_HAS_SMS
template <class modemType>
class TinyGsmSMS {
public:
/*
* 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);
}
/*
* CRTP Helper
*/
protected:
inline const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
inline modemType& thisModem() {
return static_cast<modemType&>(*this);
}
/*
* Messaging functions
*/
protected:
static inline String TinyGsmDecodeHex7bit(String& instr) {
String result;
byte reminder = 0;
int8_t bitstate = 7;
for (uint8_t 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 (uint16_t 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 (uint16_t 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(',');
int8_t dcs = thisModem().streamGetIntBefore('\n');
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 =
const_cast<uint16_t*>(reinterpret_cast<const 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();
}
};
#endif // SRC_TINYGSMSMS_H_

View File

@ -0,0 +1,71 @@
/**
* @file TinyGsmSSL.tpp
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMSSL_H_
#define SRC_TINYGSMSSL_H_
#include "TinyGsmCommon.h"
#define TINY_GSM_MODEM_HAS_SSL
template <class modemType>
class TinyGsmSSL {
public:
/*
* SSL functions
*/
bool addCertificate(const char* filename) {
return thisModem().addCertificateImpl(filename);
}
bool deleteCertificate() {
return thisModem().deleteCertificateImpl();
}
/*
* CRTP Helper
*/
protected:
inline const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
inline modemType& thisModem() {
return static_cast<modemType&>(*this);
}
/*
* Inner Secure Client
*/
/*
public:
class GsmClientSecure : public GsmClient {
public:
GsmClientSecureSim800() {}
explicit GsmClientSecureSim800(TinyGsmSim800& modem, uint8_t mux = 0)
: GsmClientSim800(modem, mux) {}
public:
int connect(const char* host, uint16_t port, int timeout_s) overide {
stop();
TINY_GSM_YIELD();
rx.clear();
sock_connected = at->modemConnect(host, port, mux, true, timeout_s);
return sock_connected;
}
};*/
/*
* SSL functions
*/
protected:
bool addCertificateImpl(const char* filename) TINY_GSM_ATTR_NOT_IMPLEMENTED;
bool deleteCertificateImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
#endif // SRC_TINYGSMSSL_H_

View File

@ -0,0 +1,364 @@
/**
* @file TinyGsmTCP.tpp
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMTCP_H_
#define SRC_TINYGSMTCP_H_
#include "TinyGsmCommon.h"
#define TINY_GSM_MODEM_HAS_TCP
#include "TinyGsmFifo.h"
#if !defined(TINY_GSM_RX_BUFFER)
#define TINY_GSM_RX_BUFFER 64
#endif
// Because of the ordering of resolution of overrides in templates, these need
// to be written out every time. This macro is to shorten that.
#define TINY_GSM_CLIENT_CONNECT_OVERRIDES \
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); \
}
// // For modules that do not store incoming data in any sort of buffer
// #define TINY_GSM_NO_MODEM_BUFFER
// // Data is stored in a buffer, but we can only read from the buffer,
// // not check how much data is stored in it
// #define TINY_GSM_BUFFER_READ_NO_CHECK
// // Data is stored in a buffer and we can both read and check the size
// // of the buffer
// #define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE
template <class modemType, uint8_t muxCount>
class TinyGsmTCP {
public:
/*
* Basic functions
*/
void maintain() {
return thisModem().maintainImpl();
}
/*
* CRTP Helper
*/
protected:
inline const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
inline 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 TinyGsmTCP<modemType, 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
// virtual 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 inline 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();
#if defined TINY_GSM_NO_MODEM_BUFFER
// Returns the number of characters available in the TinyGSM fifo
if (!rx.size() && sock_connected) { at->maintain(); }
return rx.size();
#elif defined TINY_GSM_BUFFER_READ_NO_CHECK
// Returns the combined number of characters available in the TinyGSM
// fifo and the modem chips internal fifo.
if (!rx.size()) { at->maintain(); }
return static_cast<uint16_t>(rx.size()) + sock_available;
#elif defined TINY_GSM_BUFFER_READ_AND_CHECK_SIZE
// 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.
if (!rx.size()) {
if (millis() - prev_check > 500) {
// setting got_data to true will tell maintain to run
// modemGetAvailable(mux)
got_data = true;
prev_check = millis();
}
at->maintain();
}
return static_cast<uint16_t>(rx.size()) + sock_available;
#else
#error Modem client has been incorrectly created
#endif
}
int read(uint8_t* buf, size_t size) override {
TINY_GSM_YIELD();
size_t cnt = 0;
#if defined TINY_GSM_NO_MODEM_BUFFER
// 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.
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: Read directly into user buffer? */
if (!rx.size() && sock_connected) { at->maintain(); }
}
return cnt;
#elif defined TINY_GSM_BUFFER_READ_NO_CHECK
// Reads characters out of the TinyGSM fifo, and from the modem chip's
// internal fifo if avaiable.
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;
#elif defined TINY_GSM_BUFFER_READ_AND_CHECK_SIZE
// 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.
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) {
// setting got_data to true will tell maintain to run
// modemGetAvailable()
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;
#else
#error Modem client has been incorrectly created
#endif
}
int read() override {
uint8_t c;
if (read(&c, 1) == 1) { return c; }
return -1;
}
int peek() override {
return (uint8_t)rx.peek();
}
void flush() override {
at->stream.flush();
}
uint8_t connected() override {
if (available()) { return true; }
#if defined TINY_GSM_BUFFER_READ_AND_CHECK_SIZE
// If the modem is one where we can read and check the size of the buffer,
// then the 'available()' function will call a check of the current size
// of the buffer and state of the connection. [available calls maintain,
// maintain calls modemGetAvailable, modemGetAvailable calls
// modemGetConnected] This cascade means that the sock_connected value
// should be correct and all we need
return sock_connected;
#elif defined TINY_GSM_NO_MODEM_BUFFER || defined TINY_GSM_BUFFER_READ_NO_CHECK
// If the modem doesn't have an internal buffer, or if we can't check how
// many characters are in the buffer then the cascade won't happen.
// We need to call modemGetConnected to check the sock state.
return at->modemGetConnected(mux);
#else
#error Modem client has been incorrectly created
#endif
}
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.
inline void dumpModemBuffer(uint32_t maxWaitMs) {
#if defined TINY_GSM_BUFFER_READ_AND_CHECK_SIZE || \
defined TINY_GSM_BUFFER_READ_NO_CHECK
TINY_GSM_YIELD();
uint32_t startMillis = millis();
while (sock_available > 0 && (millis() - startMillis < maxWaitMs)) {
rx.clear();
at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux);
}
rx.clear();
at->streamClear();
#elif defined TINY_GSM_NO_MODEM_BUFFER
rx.clear();
at->streamClear();
#else
#error Modem client has been incorrectly created
#endif
}
modemType* at;
uint8_t mux;
uint16_t sock_available;
uint32_t prev_check;
bool sock_connected;
bool got_data;
RxFifo rx;
};
/*
* Basic functions
*/
protected:
void maintainImpl() {
#if defined TINY_GSM_BUFFER_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);
}
#elif defined TINY_GSM_NO_MODEM_BUFFER || defined TINY_GSM_BUFFER_READ_NO_CHECK
// Just listen for any URC's
thisModem().waitResponse(100, NULL, NULL);
#else
#error Modem client has been incorrectly created
#endif
}
// 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.
inline void moveCharFromStreamToFifo(uint8_t mux) {
if (!thisModem().sockets[mux]) return;
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_TINYGSMTCP_H_

View File

@ -0,0 +1,40 @@
/**
* @file TinyGsmTemperature.tpp
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMTEMPERATURE_H_
#define SRC_TINYGSMTEMPERATURE_H_
#include "TinyGsmCommon.h"
#define TINY_GSM_MODEM_HAS_TEMPERATURE
template <class modemType>
class TinyGsmTemperature {
public:
/*
* Temperature functions
*/
float getTemperature() {
return thisModem().getTemperatureImpl();
}
/*
* CRTP Helper
*/
protected:
inline const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
inline modemType& thisModem() {
return static_cast<modemType&>(*this);
}
float getTemperatureImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
#endif // SRC_TINYGSMTEMPERATURE_H_

View File

@ -0,0 +1,106 @@
/**
* @file TinyGsmTime.tpp
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMTIME_H_
#define SRC_TINYGSMTIME_H_
#include "TinyGsmCommon.h"
#define TINY_GSM_MODEM_HAS_TIME
enum TinyGSMDateTimeFormat { DATE_FULL = 0, DATE_TIME = 1, DATE_DATE = 2 };
template <class modemType>
class TinyGsmTime {
public:
/*
* Time functions
*/
String getGSMDateTime(TinyGSMDateTimeFormat format) {
return thisModem().getGSMDateTimeImpl(format);
}
bool getNetworkTime(int* year, int* month, int* day, int* hour, int* minute,
int* second, float* timezone) {
return thisModem().getNetworkTimeImpl(year, month, day, hour, minute,
second, timezone);
}
/*
* CRTP Helper
*/
protected:
inline const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
inline modemType& thisModem() {
return static_cast<modemType&>(*this);
}
/*
* 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;
}
thisModem().waitResponse(); // Ends with OK
return res;
}
bool getNetworkTimeImpl(int* year, int* month, int* day, int* hour,
int* minute, int* second, float* timezone) {
thisModem().sendAT(GF("+CCLK?"));
if (thisModem().waitResponse(2000L, GF("+CCLK: \"")) != 1) { return false; }
int iyear = 0;
int imonth = 0;
int iday = 0;
int ihour = 0;
int imin = 0;
int isec = 0;
int itimezone = 0;
// Date & Time
iyear = thisModem().streamGetIntBefore('/');
imonth = thisModem().streamGetIntBefore('/');
iday = thisModem().streamGetIntBefore(',');
ihour = thisModem().streamGetIntBefore(':');
imin = thisModem().streamGetIntBefore(':');
isec = thisModem().streamGetIntLength(2);
char tzSign = thisModem().stream.read();
itimezone = thisModem().streamGetIntBefore('\n');
if (tzSign == '-') { itimezone = itimezone * -1; }
// Set pointers
if (iyear < 2000) iyear += 2000;
if (year != NULL) *year = iyear;
if (month != NULL) *month = imonth;
if (day != NULL) *day = iday;
if (hour != NULL) *hour = ihour;
if (minute != NULL) *minute = imin;
if (second != NULL) *second = isec;
if (timezone != NULL) *timezone = static_cast<float>(itimezone) / 4.0;
// Final OK
thisModem().waitResponse();
return true;
}
};
#endif // SRC_TINYGSMTIME_H_

View File

@ -0,0 +1,49 @@
/**
* @file TinyGsmWifi.tpp
* @author Volodymyr Shymanskyy
* @license LGPL-3.0
* @copyright Copyright (c) 2016 Volodymyr Shymanskyy
* @date Nov 2016
*/
#ifndef SRC_TINYGSMWIFI_H_
#define SRC_TINYGSMWIFI_H_
#include "TinyGsmCommon.h"
#define TINY_GSM_MODEM_HAS_WIFI
template <class modemType>
class TinyGsmWifi {
public:
/*
* WiFi functions
*/
bool networkConnect(const char* ssid, const char* pwd) {
return thisModem().networkConnectImpl(ssid, pwd);
}
bool networkDisconnect() {
return thisModem().networkDisconnectImpl();
}
/*
* CRTP Helper
*/
protected:
inline const modemType& thisModem() const {
return static_cast<const modemType&>(*this);
}
inline modemType& thisModem() {
return static_cast<modemType&>(*this);
}
/*
* WiFi functions
*/
bool networkConnectImpl(const char* ssid,
const char* pwd) TINY_GSM_ATTR_NOT_IMPLEMENTED;
bool networkDisconnectImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};
#endif // SRC_TINYGSMWIFI_H_