You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
935 lines
28 KiB
935 lines
28 KiB
/********************************************************************************
|
|
* Arduino-SIM800L-driver *
|
|
* ---------------------- *
|
|
* Arduino driver for GSM/GPRS module SIMCom SIM800L to make HTTP/S connections *
|
|
* with GET and POST methods *
|
|
* Author: Olivier Staquet *
|
|
* Last version available on https://github.com/ostaquet/Arduino-SIM800L-driver *
|
|
********************************************************************************
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2019 Olivier Staquet
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*******************************************************************************/
|
|
#include "SIM800L.h"
|
|
|
|
/**
|
|
* AT commands required (const char in PROGMEM to save memory usage)
|
|
*/
|
|
const char AT_CMD_BASE[] PROGMEM = "AT"; // Basic AT command to check the link
|
|
|
|
const char AT_CMD_CSQ[] PROGMEM = "AT+CSQ"; // Check the signal strengh
|
|
const char AT_CMD_ATI[] PROGMEM = "ATI"; // Output version of the module
|
|
const char AT_CMD_GMR[] PROGMEM = "AT+GMR"; // Output version of the firmware
|
|
const char AT_CMD_SIM_CARD[] PROGMEM = "AT+CCID"; // Get Sim Card version
|
|
|
|
const char AT_CMD_CFUN_TEST[] PROGMEM = "AT+CFUN?"; // Check the current power mode
|
|
const char AT_CMD_CFUN0[] PROGMEM = "AT+CFUN=0"; // Switch minimum power mode
|
|
const char AT_CMD_CFUN1[] PROGMEM = "AT+CFUN=1"; // Switch normal power mode
|
|
const char AT_CMD_CFUN4[] PROGMEM = "AT+CFUN=4"; // Switch sleep power mode
|
|
|
|
const char AT_CMD_CREG_TEST[] PROGMEM = "AT+CREG?"; // Check the network registration status
|
|
const char AT_CMD_SAPBR_GPRS[] PROGMEM = "AT+SAPBR=3,1,\"Contype\",\"GPRS\""; // Configure the GPRS bearer
|
|
const char AT_CMD_SAPBR_APN[] PROGMEM = "AT+SAPBR=3,1,\"APN\","; // Configure the APN for the GPRS
|
|
const char AT_CMD_SAPBR1[] PROGMEM = "AT+SAPBR=1,1"; // Connect GPRS
|
|
const char AT_CMD_SAPBR0[] PROGMEM = "AT+SAPBR=0,1"; // Disconnect GPRS
|
|
|
|
const char AT_CMD_HTTPINIT[] PROGMEM = "AT+HTTPINIT"; // Init HTTP connection
|
|
const char AT_CMD_HTTPPARA_CID[] PROGMEM = "AT+HTTPPARA=\"CID\",1"; // Connect HTTP through GPRS bearer
|
|
const char AT_CMD_HTTPPARA_URL[] PROGMEM = "AT+HTTPPARA=\"URL\","; // Define the URL to connect in HTTP
|
|
const char AT_CMD_HTTPPARA_USERDATA[] PROGMEM = "AT+HTTPPARA=\"USERDATA\","; // Define the header(s)
|
|
const char AT_CMD_HTTPPARA_CONTENT[] PROGMEM = "AT+HTTPPARA=\"CONTENT\","; // Define the content type for the HTTP POST
|
|
const char AT_CMD_HTTPSSL_Y[] PROGMEM = "AT+HTTPSSL=1"; // Enable SSL for HTTP connection
|
|
const char AT_CMD_HTTPSSL_N[] PROGMEM = "AT+HTTPSSL=0"; // Disable SSL for HTTP connection
|
|
const char AT_CMD_HTTPACTION0[] PROGMEM = "AT+HTTPACTION=0"; // Launch HTTP GET action
|
|
const char AT_CMD_HTTPACTION1[] PROGMEM = "AT+HTTPACTION=1"; // Launch HTTP POST action
|
|
const char AT_CMD_HTTPREAD[] PROGMEM = "AT+HTTPREAD"; // Start reading HTTP return data
|
|
const char AT_CMD_HTTPTERM[] PROGMEM = "AT+HTTPTERM"; // Terminate HTTP connection
|
|
|
|
const char AT_RSP_OK[] PROGMEM = "OK"; // Expected answer OK
|
|
const char AT_RSP_DOWNLOAD[] PROGMEM = "DOWNLOAD"; // Expected answer DOWNLOAD
|
|
const char AT_RSP_HTTPREAD[] PROGMEM = "+HTTPREAD: "; // Expected answer HTTPREAD
|
|
|
|
/**
|
|
* Constructor; Init the driver, communication with the module and shared
|
|
* buffer used by the driver (to avoid multiples allocation)
|
|
*/
|
|
SIM800L::SIM800L(Stream* _stream, uint8_t _pinRst, uint16_t _internalBufferSize, uint16_t _recvBufferSize, Stream* _debugStream) {
|
|
// Store local variables
|
|
stream = _stream;
|
|
enableDebug = _debugStream != NULL;
|
|
debugStream = _debugStream;
|
|
pinReset = _pinRst;
|
|
|
|
if(pinReset != RESET_PIN_NOT_USED) {
|
|
// Setup the reset pin and force a reset of the module
|
|
pinMode(pinReset, OUTPUT);
|
|
reset();
|
|
}
|
|
|
|
// Prepare internal buffers
|
|
if(enableDebug) {
|
|
debugStream->print(F("SIM800L : Prepare internal buffer of "));
|
|
debugStream->print(_internalBufferSize);
|
|
debugStream->println(F(" bytes"));
|
|
}
|
|
internalBufferSize = _internalBufferSize;
|
|
internalBuffer = (char*) malloc(internalBufferSize);
|
|
|
|
if(enableDebug) {
|
|
debugStream->print(F("SIM800L : Prepare reception buffer of "));
|
|
debugStream->print(_recvBufferSize);
|
|
debugStream->println(F(" bytes"));
|
|
}
|
|
recvBufferSize = _recvBufferSize;
|
|
recvBuffer = (char *) malloc(recvBufferSize);
|
|
}
|
|
|
|
/**
|
|
* Destructor; cleanup the memory allocated by the driver
|
|
*/
|
|
SIM800L::~SIM800L() {
|
|
free(internalBuffer);
|
|
free(recvBuffer);
|
|
}
|
|
|
|
/**
|
|
* Do HTTP/S POST to a specific URL
|
|
*/
|
|
uint16_t SIM800L::doPost(const char* url, const char* contentType, const char* payload, uint16_t clientWriteTimeoutMs, uint16_t serverReadTimeoutMs) {
|
|
return doPost(url, NULL, contentType, payload, clientWriteTimeoutMs , serverReadTimeoutMs);
|
|
}
|
|
|
|
/**
|
|
* Do HTTP/S POST to a specific URL with headers
|
|
*/
|
|
uint16_t SIM800L::doPost(const char* url, const char* headers, const char* contentType, const char* payload, uint16_t clientWriteTimeoutMs, uint16_t serverReadTimeoutMs) {
|
|
// Cleanup the receive buffer
|
|
initRecvBuffer();
|
|
dataSize = 0;
|
|
|
|
// Initiate HTTP/S session with the module
|
|
uint16_t initRC = initiateHTTP(url, headers);
|
|
if(initRC > 0) {
|
|
return initRC;
|
|
}
|
|
|
|
// Define the content type
|
|
sendCommand_P(AT_CMD_HTTPPARA_CONTENT, contentType);
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_OK)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : doPost() - Unable to define the content type"));
|
|
return 702;
|
|
}
|
|
|
|
// Prepare to send the payload
|
|
char* tmpBuf = (char*)malloc(30);
|
|
sprintf(tmpBuf, "AT+HTTPDATA=%d,%d", strlen(payload), clientWriteTimeoutMs);
|
|
sendCommand(tmpBuf);
|
|
free(tmpBuf);
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_DOWNLOAD)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : doPost() - Unable to send payload to module"));
|
|
return 707;
|
|
}
|
|
|
|
// Write the payload on the module
|
|
if(enableDebug) {
|
|
debugStream->print(F("SIM800L : doPost() - Payload to send : "));
|
|
debugStream->println(payload);
|
|
}
|
|
|
|
purgeSerial();
|
|
stream->write(payload);
|
|
stream->flush();
|
|
delay(500);
|
|
|
|
// Start HTTP POST action
|
|
sendCommand_P(AT_CMD_HTTPACTION1);
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_OK)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : doPost() - Unable to initiate POST action"));
|
|
return 703;
|
|
}
|
|
|
|
// Wait answer from the server
|
|
if(!readResponse(serverReadTimeoutMs)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : doPost() - Server timeout"));
|
|
return 408;
|
|
}
|
|
|
|
// Extract status information
|
|
int16_t idxBase = strIndex(internalBuffer, "+HTTPACTION: 1,");
|
|
if(idxBase < 0) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : doPost() - Invalid answer on HTTP POST"));
|
|
return 703;
|
|
}
|
|
|
|
// Get the HTTP return code
|
|
uint16_t httpRC = 0;
|
|
httpRC += (internalBuffer[idxBase + 15] - '0') * 100;
|
|
httpRC += (internalBuffer[idxBase + 16] - '0') * 10;
|
|
httpRC += (internalBuffer[idxBase + 17] - '0') * 1;
|
|
|
|
if(enableDebug) {
|
|
debugStream->print(F("SIM800L : doPost() - HTTP status "));
|
|
debugStream->println(httpRC);
|
|
}
|
|
|
|
if(httpRC == 200) {
|
|
// Get the size of the data to receive
|
|
dataSize = 0;
|
|
for(uint16_t i = 0; (internalBuffer[idxBase + 19 + i] - '0') >= 0 && (internalBuffer[idxBase + 19 + i] - '0') <= 9; i++) {
|
|
if(i != 0) {
|
|
dataSize = dataSize * 10;
|
|
}
|
|
dataSize += (internalBuffer[idxBase + 19 + i] - '0');
|
|
}
|
|
|
|
if(enableDebug) {
|
|
debugStream->print(F("SIM800L : doPost() - Data size received of "));
|
|
debugStream->print(dataSize);
|
|
debugStream->println(F(" bytes"));
|
|
}
|
|
|
|
// Ask for reading and detect the start of the reading...
|
|
sendCommand_P(AT_CMD_HTTPREAD);
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_HTTPREAD, 2)) {
|
|
return 705;
|
|
}
|
|
|
|
// Read number of bytes defined in the dataSize
|
|
for(uint16_t i = 0; i < dataSize && i < recvBufferSize; i++) {
|
|
while(!stream->available());
|
|
if(stream->available()) {
|
|
// Load the next char
|
|
recvBuffer[i] = stream->read();
|
|
// If the character is CR or LF, ignore it (it's probably part of the module communication schema)
|
|
if((recvBuffer[i] == '\r') || (recvBuffer[i] == '\n')) {
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(recvBufferSize < dataSize) {
|
|
dataSize = recvBufferSize;
|
|
if(enableDebug) {
|
|
debugStream->println(F("SIM800L : doPost() - Buffer overflow while loading data from HTTP. Keep only first bytes..."));
|
|
}
|
|
}
|
|
|
|
// We are expecting a final OK
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_OK)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : doPost() - Invalid end of data while reading HTTP result from the module"));
|
|
return 705;
|
|
}
|
|
|
|
if(enableDebug) {
|
|
debugStream->print(F("SIM800L : doPost() - Received from HTTP POST : "));
|
|
debugStream->println(recvBuffer);
|
|
}
|
|
}
|
|
|
|
// Terminate HTTP/S session
|
|
uint16_t termRC = terminateHTTP();
|
|
if(termRC > 0) {
|
|
return termRC;
|
|
}
|
|
|
|
return httpRC;
|
|
}
|
|
|
|
/**
|
|
* Do HTTP/S GET on a specific URL
|
|
*/
|
|
uint16_t SIM800L::doGet(const char* url, uint16_t serverReadTimeoutMs) {
|
|
return doGet(url, NULL, serverReadTimeoutMs);
|
|
}
|
|
|
|
/**
|
|
* Do HTTP/S GET on a specific URL with headers
|
|
*/
|
|
uint16_t SIM800L::doGet(const char* url, const char* headers, uint16_t serverReadTimeoutMs) {
|
|
// Cleanup the receive buffer
|
|
initRecvBuffer();
|
|
dataSize = 0;
|
|
|
|
// Initiate HTTP/S session
|
|
uint16_t initRC = initiateHTTP(url, headers);
|
|
if(initRC > 0) {
|
|
return initRC;
|
|
}
|
|
|
|
// Start HTTP GET action
|
|
sendCommand_P(AT_CMD_HTTPACTION0);
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_OK)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : doGet() - Unable to initiate GET action"));
|
|
return 703;
|
|
}
|
|
|
|
// Wait answer from the server
|
|
if(!readResponse(serverReadTimeoutMs)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : doGet() - Server timeout"));
|
|
return 408;
|
|
}
|
|
|
|
// Extract status information
|
|
int16_t idxBase = strIndex(internalBuffer, "+HTTPACTION: 0,");
|
|
if(idxBase < 0) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : doGet() - Invalid answer on HTTP GET"));
|
|
return 703;
|
|
}
|
|
|
|
// Get the HTTP return code
|
|
uint16_t httpRC = 0;
|
|
httpRC += (internalBuffer[idxBase + 15] - '0') * 100;
|
|
httpRC += (internalBuffer[idxBase + 16] - '0') * 10;
|
|
httpRC += (internalBuffer[idxBase + 17] - '0') * 1;
|
|
|
|
if(enableDebug) {
|
|
debugStream->print(F("SIM800L : doGet() - HTTP status "));
|
|
debugStream->println(httpRC);
|
|
}
|
|
|
|
if(httpRC == 200) {
|
|
// Get the size of the data to receive
|
|
dataSize = 0;
|
|
for(uint16_t i = 0; (internalBuffer[idxBase + 19 + i] - '0') >= 0 && (internalBuffer[idxBase + 19 + i] - '0') <= 9; i++) {
|
|
if(i != 0) {
|
|
dataSize = dataSize * 10;
|
|
}
|
|
dataSize += (internalBuffer[idxBase + 19 + i] - '0');
|
|
}
|
|
|
|
if(enableDebug) {
|
|
debugStream->print(F("SIM800L : doGet() - Data size received of "));
|
|
debugStream->print(dataSize);
|
|
debugStream->println(F(" bytes"));
|
|
}
|
|
|
|
// Ask for reading and detect the start of the reading...
|
|
sendCommand_P(AT_CMD_HTTPREAD);
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_HTTPREAD, 2)) {
|
|
return 705;
|
|
}
|
|
|
|
// Read number of bytes defined in the dataSize
|
|
for(uint16_t i = 0; i < dataSize && i < recvBufferSize; i++) {
|
|
while(!stream->available());
|
|
if(stream->available()) {
|
|
// Load the next char
|
|
recvBuffer[i] = stream->read();
|
|
// If the character is CR or LF, ignore it (it's probably part of the module communication schema)
|
|
if((recvBuffer[i] == '\r') || (recvBuffer[i] == '\n')) {
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(recvBufferSize < dataSize) {
|
|
dataSize = recvBufferSize;
|
|
if(enableDebug) {
|
|
debugStream->println(F("SIM800L : doGet() - Buffer overflow while loading data from HTTP. Keep only first bytes..."));
|
|
}
|
|
}
|
|
|
|
// We are expecting a final OK
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_OK)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : doGet() - Invalid end of data while reading HTTP result from the module"));
|
|
return 705;
|
|
}
|
|
|
|
if(enableDebug) {
|
|
debugStream->print(F("SIM800L : doGet() - Received from HTTP GET : "));
|
|
debugStream->println(recvBuffer);
|
|
}
|
|
}
|
|
|
|
// Terminate HTTP/S session
|
|
uint16_t termRC = terminateHTTP();
|
|
if(termRC > 0) {
|
|
return termRC;
|
|
}
|
|
|
|
return httpRC;
|
|
}
|
|
|
|
/**
|
|
* Meta method to initiate the HTTP/S session on the module
|
|
*/
|
|
uint16_t SIM800L::initiateHTTP(const char* url, const char* headers) {
|
|
// Init HTTP connection
|
|
sendCommand_P(AT_CMD_HTTPINIT);
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_OK)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : initiateHTTP() - Unable to init HTTP"));
|
|
return 701;
|
|
}
|
|
|
|
// Use the GPRS bearer
|
|
sendCommand_P(AT_CMD_HTTPPARA_CID);
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_OK)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : initiateHTTP() - Unable to define bearer"));
|
|
return 702;
|
|
}
|
|
|
|
// Define URL to look for
|
|
sendCommand_P(AT_CMD_HTTPPARA_URL, url);
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_OK)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : initiateHTTP() - Unable to define the URL"));
|
|
return 702;
|
|
}
|
|
|
|
// Set Headers
|
|
if (headers != NULL) {
|
|
sendCommand_P(AT_CMD_HTTPPARA_USERDATA, headers);
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_OK)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : initiateHTTP() - Unable to define Headers"));
|
|
return 702;
|
|
}
|
|
}
|
|
|
|
// Check if the firmware support HTTPSSL command
|
|
bool isSupportSSL = false;
|
|
char* version = getVersion();
|
|
int16_t rIdx = strIndex(version, "R");
|
|
if(rIdx > 0) {
|
|
uint8_t releaseInt = (version[rIdx + 1] - '0') * 10 + (version[rIdx + 2] - '0');
|
|
|
|
// The release should be greater or equals to 14 to support SSL stack
|
|
if(releaseInt >= 14) {
|
|
isSupportSSL = true;
|
|
if(enableDebug) debugStream->println(F("SIM800L : initiateHTTP() - Support of SSL enabled"));
|
|
} else {
|
|
isSupportSSL = false;
|
|
if(enableDebug) debugStream->println(F("SIM800L : initiateHTTP() - Support of SSL disabled (SIM800L firware below R14)"));
|
|
}
|
|
}
|
|
|
|
// Send HTTPSSL command only if the version is greater or equals to 14
|
|
if(isSupportSSL) {
|
|
// HTTP or HTTPS
|
|
if(strIndex(url, "https://") == 0) {
|
|
sendCommand_P(AT_CMD_HTTPSSL_Y);
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_OK)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : initiateHTTP() - Unable to switch to HTTPS"));
|
|
return 702;
|
|
}
|
|
} else {
|
|
sendCommand_P(AT_CMD_HTTPSSL_N);
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_OK)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : initiateHTTP() - Unable to switch to HTTP"));
|
|
return 702;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Meta method to terminate the HTTP/S session on the module
|
|
*/
|
|
uint16_t SIM800L::terminateHTTP() {
|
|
// Close HTTP connection
|
|
sendCommand_P(AT_CMD_HTTPTERM);
|
|
if(!readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_OK)) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : terminateHTTP() - Unable to close HTTP session"));
|
|
return 706;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Force a reset of the module
|
|
*/
|
|
void SIM800L::reset() {
|
|
if(pinReset != RESET_PIN_NOT_USED)
|
|
{
|
|
// Some logging
|
|
if(enableDebug) debugStream->println(F("SIM800L : Reset"));
|
|
|
|
// Reset the device
|
|
digitalWrite(pinReset, HIGH);
|
|
delay(500);
|
|
digitalWrite(pinReset, LOW);
|
|
delay(500);
|
|
digitalWrite(pinReset, HIGH);
|
|
delay(1000);
|
|
} else {
|
|
// Some logging
|
|
if(enableDebug) debugStream->println(F("SIM800L : Reset requested but reset pin undefined"));
|
|
if(enableDebug) debugStream->println(F("SIM800L : No reset"));
|
|
}
|
|
|
|
// Purge the serial
|
|
stream->flush();
|
|
while (stream->available()) {
|
|
stream->read();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the size of data received after the last successful HTTP connection
|
|
*/
|
|
uint16_t SIM800L::getDataSizeReceived() {
|
|
return dataSize;
|
|
}
|
|
|
|
/**
|
|
* Return the buffer of data received after the last successful HTTP connection
|
|
*/
|
|
char* SIM800L::getDataReceived() {
|
|
return recvBuffer;
|
|
}
|
|
|
|
/**
|
|
* Status function: Check if AT command works
|
|
*/
|
|
bool SIM800L::isReady() {
|
|
sendCommand_P(AT_CMD_BASE);
|
|
return readResponseCheckAnswer_P(DEFAULT_TIMEOUT, AT_RSP_OK);
|
|
}
|
|
|
|
/**
|
|
* Status function: Check the power mode
|
|
*/
|
|
PowerMode SIM800L::getPowerMode() {
|
|
sendCommand_P(AT_CMD_CFUN_TEST);
|
|
if(readResponse(DEFAULT_TIMEOUT)) {
|
|
// Check if there is an error
|
|
int16_t errIdx = strIndex(internalBuffer, "ERROR");
|
|
if(errIdx > 0) {
|
|
return POW_ERROR;
|
|
}
|
|
|
|
// Extract the value
|
|
int16_t idx = strIndex(internalBuffer, "+CFUN: ");
|
|
char value = internalBuffer[idx + 7];
|
|
|
|
// Prepare the clear output
|
|
switch(value) {
|
|
case '0' : return MINIMUM;
|
|
case '1' : return NORMAL;
|
|
case '4' : return SLEEP;
|
|
default : return POW_UNKNOWN;
|
|
}
|
|
}
|
|
return POW_ERROR;
|
|
}
|
|
|
|
/**
|
|
* Status function: Get version of the module
|
|
*/
|
|
char* SIM800L::getVersion() {
|
|
sendCommand_P(AT_CMD_ATI);
|
|
if(readResponse(DEFAULT_TIMEOUT)) {
|
|
// Extract the value
|
|
int16_t idx = strIndex(internalBuffer, "SIM");
|
|
int16_t idxEnd = strIndex(internalBuffer, "\r", idx+1);
|
|
|
|
// Store it on the recv buffer (not used at the moment)
|
|
initRecvBuffer();
|
|
for(uint16_t i = 0; i < idxEnd - idx; i++) {
|
|
recvBuffer[i] = internalBuffer[idx + i];
|
|
}
|
|
return getDataReceived();
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Status function: Get firmware version
|
|
*/
|
|
char* SIM800L::getFirmware() {
|
|
sendCommand_P(AT_CMD_GMR);
|
|
if(readResponse(DEFAULT_TIMEOUT)) {
|
|
// Extract the value
|
|
int16_t idx = strIndex(internalBuffer, "AT+GMR") + 9;
|
|
int16_t idxEnd = strIndex(internalBuffer, "\r", idx+1);
|
|
|
|
// Store it on the recv buffer (not used at the moment)
|
|
initRecvBuffer();
|
|
for(uint16_t i = 0; i < idxEnd - idx; i++) {
|
|
recvBuffer[i] = internalBuffer[idx + i];
|
|
}
|
|
return getDataReceived();
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Status function: Requests the simcard number
|
|
*/
|
|
char* SIM800L::getSimCardNumber() {
|
|
sendCommand_P(AT_CMD_SIM_CARD);
|
|
if(readResponse(DEFAULT_TIMEOUT)) {
|
|
int16_t idx = strIndex(internalBuffer, "AT+CCID") + 10;
|
|
int16_t idxEnd = strIndex(internalBuffer, "\r", idx+1);
|
|
|
|
// Store it on the recv buffer (not used at the moment)
|
|
initRecvBuffer();
|
|
for(uint16_t i = 0; i < idxEnd - idx; i++) {
|
|
recvBuffer[i] = internalBuffer[idx + i];
|
|
}
|
|
return getDataReceived();
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Status function: Check if the module is registered on the network
|
|
*/
|
|
NetworkRegistration SIM800L::getRegistrationStatus() {
|
|
sendCommand_P(AT_CMD_CREG_TEST);
|
|
if(readResponse(DEFAULT_TIMEOUT)) {
|
|
// Check if there is an error
|
|
int16_t errIdx = strIndex(internalBuffer, "ERROR");
|
|
if(errIdx > 0) {
|
|
return NET_ERROR;
|
|
}
|
|
|
|
// Extract the value
|
|
int16_t idx = strIndex(internalBuffer, "+CREG: ");
|
|
char value = internalBuffer[idx + 9];
|
|
|
|
// Prepare the clear output
|
|
switch(value) {
|
|
case '0' : return NOT_REGISTERED;
|
|
case '1' : return REGISTERED_HOME;
|
|
case '2' : return SEARCHING;
|
|
case '3' : return DENIED;
|
|
case '5' : return REGISTERED_ROAMING;
|
|
default : return NET_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
return NET_ERROR;
|
|
}
|
|
|
|
/**
|
|
* Setup the GPRS connectivity
|
|
* As input, give the APN string of the operator
|
|
*/
|
|
bool SIM800L::setupGPRS(const char* apn) {
|
|
// Prepare the GPRS connection as the bearer
|
|
sendCommand_P(AT_CMD_SAPBR_GPRS);
|
|
if(!readResponseCheckAnswer_P(20000, AT_RSP_OK)) {
|
|
return false;
|
|
}
|
|
|
|
// Set the config of the bearer with the APN
|
|
sendCommand_P(AT_CMD_SAPBR_APN, apn);
|
|
return readResponseCheckAnswer_P(20000, AT_RSP_OK);
|
|
}
|
|
|
|
/**
|
|
* Open the GPRS connectivity
|
|
*/
|
|
bool SIM800L::connectGPRS() {
|
|
sendCommand_P(AT_CMD_SAPBR1);
|
|
// Timout is max 85 seconds according to SIM800 specifications
|
|
// We will wait for 65s to be within uint16_t
|
|
return readResponseCheckAnswer_P(65000, AT_RSP_OK);
|
|
}
|
|
|
|
/**
|
|
* Close the GPRS connectivity
|
|
*/
|
|
bool SIM800L::disconnectGPRS() {
|
|
sendCommand_P(AT_CMD_SAPBR0);
|
|
// Timout is max 65 seconds according to SIM800 specifications
|
|
return readResponseCheckAnswer_P(65000, AT_RSP_OK);
|
|
}
|
|
|
|
/**
|
|
* Define the power mode
|
|
* Available : MINIMUM, NORMAL, SLEEP
|
|
* Return true is the mode is correctly switched
|
|
*/
|
|
bool SIM800L::setPowerMode(PowerMode powerMode) {
|
|
// Check if the power mode requested is not ERROR or UNKNOWN
|
|
if(powerMode == POW_ERROR || powerMode == POW_UNKNOWN) {
|
|
return false;
|
|
}
|
|
|
|
// Check the current power mode
|
|
PowerMode currentPowerMode = getPowerMode();
|
|
|
|
// If the current power mode is undefined, abord
|
|
if(currentPowerMode == POW_ERROR || currentPowerMode == POW_UNKNOWN) {
|
|
return false;
|
|
}
|
|
|
|
// If the current power mode is the same that the requested power mode, say it's OK
|
|
if(currentPowerMode == powerMode) {
|
|
return true;
|
|
}
|
|
|
|
// If SLEEP or MINIMUM, only NORMAL is allowed
|
|
if((currentPowerMode == SLEEP || currentPowerMode == MINIMUM) && (powerMode != NORMAL)) {
|
|
return false;
|
|
}
|
|
|
|
// Send the command
|
|
char value;
|
|
switch(powerMode) {
|
|
case MINIMUM :
|
|
sendCommand_P(AT_CMD_CFUN0);
|
|
break;
|
|
case SLEEP :
|
|
sendCommand_P(AT_CMD_CFUN4);
|
|
break;
|
|
case NORMAL :
|
|
default :
|
|
sendCommand_P(AT_CMD_CFUN1);
|
|
}
|
|
|
|
// Read but don't care about the result
|
|
purgeSerial();
|
|
|
|
// Check the current power mode
|
|
currentPowerMode = getPowerMode();
|
|
|
|
// If the current power mode is the same that the requested power mode, say it's OK
|
|
return currentPowerMode == powerMode;
|
|
}
|
|
|
|
/**
|
|
* Status function: Check the strengh of the signal
|
|
*/
|
|
uint8_t SIM800L::getSignal() {
|
|
sendCommand_P(AT_CMD_CSQ);
|
|
if(readResponse(DEFAULT_TIMEOUT)) {
|
|
int16_t idxBase = strIndex(internalBuffer, "AT+CSQ");
|
|
if(idxBase != 0) {
|
|
return 0;
|
|
}
|
|
int16_t idxEnd = strIndex(internalBuffer, ",", idxBase);
|
|
uint8_t value = internalBuffer[idxEnd - 1] - '0';
|
|
if(internalBuffer[idxEnd - 2] != ' ') {
|
|
value += (internalBuffer[idxEnd - 2] - '0') * 10;
|
|
}
|
|
if(value > 31) {
|
|
return 0;
|
|
}
|
|
return value;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************************
|
|
* HELPERS
|
|
*****************************************************************************************/
|
|
/**
|
|
* Find string "findStr" in another string "str"
|
|
* Returns true if found, false elsewhere
|
|
*/
|
|
int16_t SIM800L::strIndex(const char* str, const char* findStr, uint16_t startIdx) {
|
|
int16_t firstIndex = -1;
|
|
int16_t sizeMatch = 0;
|
|
for(int16_t i = startIdx; i < strlen(str); i++) {
|
|
if(sizeMatch >= strlen(findStr)) {
|
|
break;
|
|
}
|
|
if(str[i] == findStr[sizeMatch]) {
|
|
if(firstIndex < 0) {
|
|
firstIndex = i;
|
|
}
|
|
sizeMatch++;
|
|
} else {
|
|
firstIndex = -1;
|
|
sizeMatch = 0;
|
|
}
|
|
}
|
|
|
|
if(sizeMatch >= strlen(findStr)) {
|
|
return firstIndex;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Init internal buffer
|
|
*/
|
|
void SIM800L::initInternalBuffer() {
|
|
for(uint16_t i = 0; i < internalBufferSize; i++) {
|
|
internalBuffer[i] = '\0';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Init recv buffer
|
|
*/
|
|
void SIM800L::initRecvBuffer() {
|
|
// Cleanup the receive buffer
|
|
for(uint16_t i = 0; i < recvBufferSize; i++) {
|
|
recvBuffer[i] = 0;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************************
|
|
* LOW LEVEL FUNCTIONS TO COMMUNICATE WITH THE SIM800L MODULE
|
|
*****************************************************************************************/
|
|
/**
|
|
* Send AT command to the module
|
|
*/
|
|
void SIM800L::sendCommand(const char* command) {
|
|
if(enableDebug) {
|
|
debugStream->print(F("SIM800L : Send \""));
|
|
debugStream->print(command);
|
|
debugStream->println(F("\""));
|
|
}
|
|
|
|
purgeSerial();
|
|
stream->write(command);
|
|
stream->write("\r\n");
|
|
purgeSerial();
|
|
}
|
|
|
|
/**
|
|
* Send AT command coming from the PROGMEM
|
|
*/
|
|
void SIM800L::sendCommand_P(const char* command) {
|
|
char cmdBuff[32];
|
|
strcpy_P(cmdBuff, command);
|
|
sendCommand(cmdBuff);
|
|
}
|
|
|
|
/**
|
|
* Send AT command to the module with a parameter
|
|
*/
|
|
void SIM800L::sendCommand(const char* command, const char* parameter) {
|
|
if(enableDebug) {
|
|
debugStream->print(F("SIM800L : Send \""));
|
|
debugStream->print(command);
|
|
debugStream->print(F("\""));
|
|
debugStream->print(parameter);
|
|
debugStream->print(F("\""));
|
|
debugStream->println(F("\""));
|
|
}
|
|
|
|
purgeSerial();
|
|
stream->write(command);
|
|
stream->write("\"");
|
|
stream->write(parameter);
|
|
stream->write("\"");
|
|
stream->write("\r\n");
|
|
purgeSerial();
|
|
}
|
|
|
|
/**
|
|
* Send AT command coming from the PROGMEM with a parameter
|
|
*/
|
|
void SIM800L::sendCommand_P(const char* command, const char* parameter) {
|
|
char cmdBuff[32];
|
|
strcpy_P(cmdBuff, command);
|
|
sendCommand(cmdBuff, parameter);
|
|
}
|
|
|
|
/**
|
|
* Purge the serial data
|
|
*/
|
|
void SIM800L::purgeSerial() {
|
|
stream->flush();
|
|
while (stream->available()) {
|
|
stream->read();
|
|
}
|
|
stream->flush();
|
|
}
|
|
|
|
/**
|
|
* Read from module and expect a specific answer (timeout in millisec)
|
|
*/
|
|
bool SIM800L::readResponseCheckAnswer_P(uint16_t timeout, const char* expectedAnswer, uint8_t crlfToWait) {
|
|
if(readResponse(timeout, crlfToWait)) {
|
|
// Prepare the local expected answer
|
|
char rspBuff[16];
|
|
strcpy_P(rspBuff, expectedAnswer);
|
|
|
|
// Check if it's the expected answer
|
|
int16_t idx = strIndex(internalBuffer, rspBuff);
|
|
if(idx > 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Read from the module for a specific number of CRLF
|
|
* True if we have some data
|
|
*/
|
|
bool SIM800L::readResponse(uint16_t timeout, uint8_t crlfToWait) {
|
|
uint16_t currentSizeResponse = 0;
|
|
bool seenCR = false;
|
|
uint8_t countCRLF = 0;
|
|
|
|
// First of all, cleanup the buffer
|
|
initInternalBuffer();
|
|
|
|
uint32_t timerStart = millis();
|
|
|
|
while(1) {
|
|
// While there is data available on the buffer, read it until the max size of the response
|
|
if(stream->available()) {
|
|
// Load the next char
|
|
internalBuffer[currentSizeResponse] = stream->read();
|
|
|
|
// Detect end of transmission (CRLF)
|
|
if(internalBuffer[currentSizeResponse] == '\r') {
|
|
seenCR = true;
|
|
} else if (internalBuffer[currentSizeResponse] == '\n' && seenCR) {
|
|
countCRLF++;
|
|
if(countCRLF == crlfToWait) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : End of transmission"));
|
|
break;
|
|
}
|
|
} else {
|
|
seenCR = false;
|
|
}
|
|
|
|
// Prepare for next read
|
|
currentSizeResponse++;
|
|
|
|
// Avoid buffer overflow
|
|
if(currentSizeResponse == internalBufferSize) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : Received maximum buffer size"));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If timeout, abord the reading
|
|
if(millis() - timerStart > timeout) {
|
|
if(enableDebug) debugStream->println(F("SIM800L : Receive timeout"));
|
|
// Timeout, return false to parent function
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(enableDebug) {
|
|
debugStream->print(F("SIM800L : Receive \""));
|
|
debugStream->print(internalBuffer);
|
|
debugStream->println(F("\""));
|
|
}
|
|
|
|
// If we are here, it's OK ;-)
|
|
return true;
|
|
}
|