v2.9
This commit is contained in:
47
lib/TinyGSM/src/ArduinoCompat/Client.h
Normal file
47
lib/TinyGSM/src/ArduinoCompat/Client.h
Normal 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
|
||||
146
lib/TinyGSM/src/ArduinoCompat/IPAddress.h
Normal file
146
lib/TinyGSM/src/ArduinoCompat/IPAddress.h
Normal 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
|
||||
6
lib/TinyGSM/src/TinyGSM.h
Normal file
6
lib/TinyGSM/src/TinyGSM.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef TINYGSM_H
|
||||
#define TINYGSM_H
|
||||
|
||||
#include "TinyGsmClient.h"
|
||||
|
||||
#endif
|
||||
98
lib/TinyGSM/src/TinyGsmBattery.tpp
Normal file
98
lib/TinyGSM/src/TinyGsmBattery.tpp
Normal 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_
|
||||
57
lib/TinyGSM/src/TinyGsmBluetooth.tpp
Normal file
57
lib/TinyGSM/src/TinyGsmBluetooth.tpp
Normal 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_
|
||||
90
lib/TinyGSM/src/TinyGsmCalling.tpp
Normal file
90
lib/TinyGSM/src/TinyGsmCalling.tpp
Normal 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_
|
||||
121
lib/TinyGSM/src/TinyGsmClient.h
Normal file
121
lib/TinyGSM/src/TinyGsmClient.h
Normal 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_
|
||||
580
lib/TinyGSM/src/TinyGsmClientA6.h
Normal file
580
lib/TinyGSM/src/TinyGsmClientA6.h
Normal 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_
|
||||
727
lib/TinyGSM/src/TinyGsmClientBG96.h
Normal file
727
lib/TinyGSM/src/TinyGsmClientBG96.h
Normal 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_
|
||||
485
lib/TinyGSM/src/TinyGsmClientESP8266.h
Normal file
485
lib/TinyGSM/src/TinyGsmClientESP8266.h
Normal 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_
|
||||
473
lib/TinyGSM/src/TinyGsmClientM590.h
Normal file
473
lib/TinyGSM/src/TinyGsmClientM590.h
Normal 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_
|
||||
636
lib/TinyGSM/src/TinyGsmClientM95.h
Normal file
636
lib/TinyGSM/src/TinyGsmClientM95.h
Normal 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_
|
||||
622
lib/TinyGSM/src/TinyGsmClientMC60.h
Normal file
622
lib/TinyGSM/src/TinyGsmClientMC60.h
Normal 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_
|
||||
741
lib/TinyGSM/src/TinyGsmClientSIM5360.h
Normal file
741
lib/TinyGSM/src/TinyGsmClientSIM5360.h
Normal 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_
|
||||
534
lib/TinyGSM/src/TinyGsmClientSIM7000.h
Normal file
534
lib/TinyGSM/src/TinyGsmClientSIM7000.h
Normal 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_
|
||||
730
lib/TinyGSM/src/TinyGsmClientSIM7000SSL.h
Normal file
730
lib/TinyGSM/src/TinyGsmClientSIM7000SSL.h
Normal 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: Can’t bind the port
|
||||
// 13: Can’t listen the port
|
||||
// 20: Can’t resolve the host
|
||||
// 21: Network not active
|
||||
// 23: Remote refuse
|
||||
// 24: Certificate’s time expired
|
||||
// 25: Certificate’s common name does not match
|
||||
// 26: Certificate’s 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_
|
||||
729
lib/TinyGSM/src/TinyGsmClientSIM7080.h
Normal file
729
lib/TinyGSM/src/TinyGsmClientSIM7080.h
Normal 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: Can’t bind the port
|
||||
// 13: Can’t listen the port
|
||||
// 20: Can’t resolve the host
|
||||
// 21: Network not active
|
||||
// 23: Remote refuse
|
||||
// 24: Certificate’s time expired
|
||||
// 25: Certificate’s common name does not match
|
||||
// 26: Certificate’s 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_
|
||||
459
lib/TinyGSM/src/TinyGsmClientSIM70xx.h
Normal file
459
lib/TinyGSM/src/TinyGsmClientSIM70xx.h
Normal 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_
|
||||
866
lib/TinyGSM/src/TinyGsmClientSIM7600.h
Normal file
866
lib/TinyGSM/src/TinyGsmClientSIM7600.h
Normal 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_
|
||||
771
lib/TinyGSM/src/TinyGsmClientSIM800.h
Normal file
771
lib/TinyGSM/src/TinyGsmClientSIM800.h
Normal 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_
|
||||
168
lib/TinyGSM/src/TinyGsmClientSIM808.h
Normal file
168
lib/TinyGSM/src/TinyGsmClientSIM808.h
Normal 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_
|
||||
900
lib/TinyGSM/src/TinyGsmClientSaraR4.h
Normal file
900
lib/TinyGSM/src/TinyGsmClientSaraR4.h
Normal 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_
|
||||
733
lib/TinyGSM/src/TinyGsmClientSequansMonarch.h
Normal file
733
lib/TinyGSM/src/TinyGsmClientSequansMonarch.h
Normal 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_
|
||||
854
lib/TinyGSM/src/TinyGsmClientUBLOX.h
Normal file
854
lib/TinyGSM/src/TinyGsmClientUBLOX.h
Normal 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_
|
||||
1638
lib/TinyGSM/src/TinyGsmClientXBee.h
Normal file
1638
lib/TinyGSM/src/TinyGsmClientXBee.h
Normal file
File diff suppressed because it is too large
Load Diff
120
lib/TinyGSM/src/TinyGsmCommon.h
Normal file
120
lib/TinyGSM/src/TinyGsmCommon.h
Normal 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_
|
||||
141
lib/TinyGSM/src/TinyGsmFifo.h
Normal file
141
lib/TinyGSM/src/TinyGsmFifo.h
Normal 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
|
||||
171
lib/TinyGSM/src/TinyGsmGPRS.tpp
Normal file
171
lib/TinyGSM/src/TinyGsmGPRS.tpp
Normal 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_
|
||||
82
lib/TinyGSM/src/TinyGsmGPS.tpp
Normal file
82
lib/TinyGSM/src/TinyGsmGPS.tpp
Normal 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_
|
||||
149
lib/TinyGSM/src/TinyGsmGSMLocation.tpp
Normal file
149
lib/TinyGSM/src/TinyGsmGSMLocation.tpp
Normal 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_
|
||||
354
lib/TinyGSM/src/TinyGsmModem.tpp
Normal file
354
lib/TinyGSM/src/TinyGsmModem.tpp
Normal 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_
|
||||
92
lib/TinyGSM/src/TinyGsmNTP.tpp
Normal file
92
lib/TinyGSM/src/TinyGsmNTP.tpp
Normal 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_
|
||||
224
lib/TinyGSM/src/TinyGsmSMS.tpp
Normal file
224
lib/TinyGSM/src/TinyGsmSMS.tpp
Normal 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_
|
||||
71
lib/TinyGSM/src/TinyGsmSSL.tpp
Normal file
71
lib/TinyGSM/src/TinyGsmSSL.tpp
Normal 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_
|
||||
364
lib/TinyGSM/src/TinyGsmTCP.tpp
Normal file
364
lib/TinyGSM/src/TinyGsmTCP.tpp
Normal 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_
|
||||
40
lib/TinyGSM/src/TinyGsmTemperature.tpp
Normal file
40
lib/TinyGSM/src/TinyGsmTemperature.tpp
Normal 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_
|
||||
106
lib/TinyGSM/src/TinyGsmTime.tpp
Normal file
106
lib/TinyGSM/src/TinyGsmTime.tpp
Normal 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_
|
||||
49
lib/TinyGSM/src/TinyGsmWifi.tpp
Normal file
49
lib/TinyGSM/src/TinyGsmWifi.tpp
Normal 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_
|
||||
Reference in New Issue
Block a user