diff --git a/BoardInterface.cpp b/BoardInterface.cpp index 0d1a52a..aefd472 100644 --- a/BoardInterface.cpp +++ b/BoardInterface.cpp @@ -112,13 +112,13 @@ void BoardInterface::loadSettings() { liveData->settings.sdcardEnabled = 0; liveData->settings.sdcardAutstartLog = 1; liveData->settings.gprsEnabled = 0; - tmpStr = "internet.t-mobile.cz"; + tmpStr = "not_set"; tmpStr.toCharArray(liveData->settings.gprsApn, tmpStr.length() + 1); // Remote upload liveData->settings.remoteUploadEnabled = 0; - tmpStr = "http://api.example.com"; + tmpStr = "not_set"; tmpStr.toCharArray(liveData->settings.remoteApiUrl, tmpStr.length() + 1); - tmpStr = "example"; + tmpStr = "not_set"; tmpStr.toCharArray(liveData->settings.remoteApiKey, tmpStr.length() + 1); liveData->settings.headlightsReminder = 0; diff --git a/SIM800L.cpp b/SIM800L.cpp new file mode 100644 index 0000000..d79eda8 --- /dev/null +++ b/SIM800L.cpp @@ -0,0 +1,945 @@ +/******************************************************************************** + * 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_SAPBR2[] PROGMEM = "AT+SAPBR=2,1"; +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 +const char AT_RSP_SAPBR[] PROGMEM = "+SAPBR: 1,1,"; // Expected answer SAPBR:1,1 + +/** + * 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); +} + +/** + * Check if GPRS is connected + */ +bool SIM800L::isConnectedGPRS() { + sendCommand_P(AT_CMD_SAPBR2); + return readResponseCheckAnswer_P(65000, AT_RSP_SAPBR); +} + +/** + * 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; +} diff --git a/SIM800L.h b/SIM800L.h new file mode 100644 index 0000000..d49597c --- /dev/null +++ b/SIM800L.h @@ -0,0 +1,139 @@ +/******************************************************************************** + * 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. + *******************************************************************************/ +#ifndef _SIM800L_H_ +#define _SIM800L_H_ + +#include + +#define DEFAULT_TIMEOUT 5000 +#define RESET_PIN_NOT_USED -1 + +enum PowerMode {MINIMUM, NORMAL, POW_UNKNOWN, SLEEP, POW_ERROR}; +enum NetworkRegistration {NOT_REGISTERED, REGISTERED_HOME, SEARCHING, DENIED, NET_UNKNOWN, REGISTERED_ROAMING, NET_ERROR}; + +class SIM800L { + public: + // Initialize the driver + // Parameters: + // _stream : Stream opened to the SIM800L module (Software or Hardware, usually 9600 bps) + // _pinRst (optional) : pin to the reset of the SIM800L module + // _internalBufferSize (optional): size in bytes of the internal buffer to handle general IO with the module + // (including URL and maximum payload to send through POST method) + // _recvBufferSize (optional) : size in bytes of the reception buffer (max data to receive from GET or POST) + // _debugStream (optional) : Stream opened to the debug console (Software of Hardware) + SIM800L(Stream* _stream, uint8_t _pinRst = RESET_PIN_NOT_USED, uint16_t _internalBufferSize = 128, uint16_t _recvBufferSize = 256, Stream* _debugStream = NULL); + ~SIM800L(); + + // Force a reset of the module + void reset(); + + // Status functions + bool isReady(); + uint8_t getSignal(); + PowerMode getPowerMode(); + NetworkRegistration getRegistrationStatus(); + char* getVersion(); + char* getFirmware(); + char* getSimCardNumber(); + + // Define the power mode (for parameter: see PowerMode enum) + bool setPowerMode(PowerMode powerMode); + + // Enable/disable GPRS + bool setupGPRS(const char *apn); + bool connectGPRS(); + bool isConnectedGPRS(); + bool disconnectGPRS(); + + // HTTP methods + uint16_t doGet(const char* url, uint16_t serverReadTimeoutMs); + uint16_t doGet(const char* url, const char* headers, uint16_t serverReadTimeoutMs); + uint16_t doPost(const char* url, const char* contentType, const char* payload, uint16_t clientWriteTimeoutMs, uint16_t serverReadTimeoutMs); + uint16_t doPost(const char* url, const char* headers, const char* contentType, const char* payload, uint16_t clientWriteTimeoutMs, uint16_t serverReadTimeoutMs); + + // Obtain results after HTTP successful connections (size and buffer) + uint16_t getDataSizeReceived(); + char* getDataReceived(); + + protected: + // Send command + void sendCommand(const char* command); + // Send comment from PROGMEM + void sendCommand_P(const char* command); + // Send command with parameter within quotes (template : command"parameter") + void sendCommand(const char* command, const char* parameter); + // Send command with parameter within quotes from PROGMEM (template : command"parameter") + void sendCommand_P(const char* command, const char* parameter); + + // Read from module (timeout in millisec) + bool readResponse(uint16_t timeout, uint8_t crlfToWait = 2); + // Read from module and expect a specific answer defined in PROGMEM (timeout in millisec) + bool readResponseCheckAnswer_P(uint16_t timeout, const char* expectedAnswer, uint8_t crlfToWait = 2); + + // Purge the serial + void purgeSerial(); + + // Find string in another string + int16_t strIndex(const char* str, const char* findStr, uint16_t startIdx = 0); + + // Manage internal buffer + void initInternalBuffer(); + void initRecvBuffer(); + + // Initiate HTTP/S connection + uint16_t initiateHTTP(const char* url, const char* headers); + uint16_t terminateHTTP(); + + private: + // Serial line with SIM800L + Stream* stream = NULL; + + // Serial console for the debug + Stream* debugStream = NULL; + + // Details about the circuit: pins + uint8_t pinReset = 0; + + // Internal memory for the shared buffer + // Used for all reception of message from the module + char *internalBuffer; + uint16_t internalBufferSize = 0; + + // Reception buffer + char *recvBuffer; + uint16_t recvBufferSize = 0; + uint16_t dataSize = 0; + + // Enable debug mode + bool enableDebug = false; +}; + +#endif // _SIM800L_H_ diff --git a/evDash.ino b/evDash.ino index bd4f548..34fa144 100644 --- a/evDash.ino +++ b/evDash.ino @@ -420,6 +420,9 @@ bool sim800lSetup() { serial->begin(9600); sim800l = new SIM800L((Stream *)serial, SIM800L_RST, 512 , 512); + // SIM800L DebugMode: + //sim800l = new SIM800L((Stream *)serial, SIM800L_RST, 512 , 512, (Stream *)&Serial); + bool sim800l_ready = sim800l->isReady(); for (uint8_t i = 0; i < 5 && !sim800l_ready; i++) { Serial.println("Problem to initialize SIM800L module, retry in 1 sec"); @@ -437,16 +440,16 @@ bool sim800lSetup() { bool sim800l_gprs = sim800l->setupGPRS(liveData->settings.gprsApn); for (uint8_t i = 0; i < 5 && !sim800l_gprs; i++) { - Serial.println("Problem to set GPRS connection, retry in 1 sec"); + Serial.println("Problem to set GPRS APN, retry in 1 sec"); delay(1000); sim800l_gprs = sim800l->setupGPRS(liveData->settings.gprsApn); } if (sim800l_gprs) { liveData->params.sim800l_enabled = true; - Serial.println("GPRS OK"); + Serial.println("GPRS APN set OK"); } else { - Serial.println("Problem to set GPRS"); + Serial.println("Problem to set GPRS APN"); } } @@ -458,22 +461,27 @@ bool sendDataViaGPRS() { NetworkRegistration network = sim800l->getRegistrationStatus(); if (network != REGISTERED_HOME && network != REGISTERED_ROAMING) { - Serial.println("SIM800L module not connected to network!"); + Serial.println("SIM800L module not connected to network, skipping data send"); return false; } - bool connected = sim800l->connectGPRS(); - for (uint8_t i = 0; i < 5 && !connected; i++) { - delay(1000); - connected = sim800l->connectGPRS(); - } - - if (!connected) { - Serial.println("GPRS not connected! Reseting SIM800L module!"); - sim800l->reset(); - sim800lSetup(); - - return false; + if(!sim800l->isConnectedGPRS()) { + Serial.println("GPRS not connected... Connecting"); + bool connected = sim800l->connectGPRS(); + for (uint8_t i = 0; i < 5 && !connected; i++) { + Serial.println("Problem to connect GPRS, retry in 1 sec"); + delay(1000); + connected = sim800l->connectGPRS(); + } + if(connected) { + Serial.println("GPRS connected!"); + } else { + Serial.println("GPRS not connected! Reseting SIM800L module!"); + sim800l->reset(); + sim800lSetup(); + + return false; + } } Serial.println("Start HTTP POST..."); @@ -513,8 +521,6 @@ bool sendDataViaGPRS() { Serial.println(rc); } - sim800l->disconnectGPRS(); - return true; } #endif //SIM800L_ENABLED