Unable to allocate memory

Hello,

I'm trying to send a SMS and read data from Firebase with my NodeMCU Amica V2. The code works fine when I either only send a SMS or only read data from Firebase. However, when I combine this two codes SMS isn't sent. This is the error I get:

+ Connected.
+ Post an HTTP send SMS request.
+ Connection is closed.
+ Response:

31.0630+ Using fingerprint 'BC B0 1A 32 80 5D E6 E4 A2 29 66 2B 08 C8 E0 4C 45 29 3F D0'
+ Connecting to api.twilio.com
- Connection failed.
Last SSL error was:
Unable to allocate memory for SSL structures and buffers.
ERRCODE: -1000

And here is my code:

#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <base64.h>
#include "OneWire.h"
#include "DallasTemperature.h"
#include <ArduinoJson.h>
#include <string>
using namespace std;
#include <sstream>
#include <iostream>
#include "FirebaseArduino.h"

// Your network SSID and password
const char* ssid = "*****";
const char* password = "*****";

const char* account_sid = "*****";
const char* auth_token = "*****";
String to_number    = "+*****";
String from_number = "+*****";
String message_body    = "Hello from Twilio and the ESP8266!";

// Find the api.twilio.com SHA1 fingerprint using,
//  echo | openssl s_client -connect api.twilio.com:443 | openssl x509 -fingerprint
const char fingerprint[] = "BC B0 1A 32 80 5D E6 E4 A2 29 66 2B 08 C8 E0 4C 45 29 3F D0";

#define ONE_WIRE_BUS 4
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

#define FIREBASE_HOST "***.firebaseio.com"
#define FIREBASE_AUTH "*****"

float temp1 = 0;
//float temp2 = 0;
String b;
int c;

String urlencode(String str) {
  String encodedString = "";
  char c;
  char code0;
  char code1;
  char code2;
  for (int i = 0; i < str.length(); i++) {
    c = str.charAt(i);
    if (c == ' ') {
      encodedString += '+';
    } else if (isalnum(c)) {
      encodedString += c;
    } else {
      code1 = (c & 0xf) + '0';
      if ((c & 0xf) > 9) {
        code1 = (c & 0xf) - 10 + 'A';
      }
      c = (c >> 4) & 0xf;
      code0 = c + '0';
      if (c > 9) {
        code0 = c - 10 + 'A';
      }
      code2 = '\0';
      encodedString += '%';
      encodedString += code0;
      encodedString += code1;
    }
    yield();
  }
  return encodedString;
}

String get_auth_header(const String& user, const String& password) {
  size_t toencodeLen = user.length() + password.length() + 2;
  char toencode[toencodeLen];
  memset(toencode, 0, toencodeLen);
  snprintf(toencode, toencodeLen, "%s:%s", user.c_str(), password.c_str());
  String encoded = base64::encode((uint8_t*)toencode, toencodeLen - 1);
  String encoded_string = String(encoded);
  std::string::size_type i = 0;
  // Strip newlines (after every 72 characters in spec)
  while (i < encoded_string.length()) {
    i = encoded_string.indexOf('\n', i);
    if (i == -1) {
      break;
    }
    encoded_string.remove(i, 1);
  }
  return "Authorization: Basic " + encoded_string;
}

void setup() {
  Serial.begin(115200); // 115200 or 9600
  delay(1000);        // Give the serial connection time to start before the first print.
  Serial.println(""); // Newline after garbage characters.
  Serial.println(F("+++ Setup."));

  // ----------------------------------------------------
  Serial.println("+ Connect to WiFi. ");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("+ Connected to WiFi, IP address: ");
  Serial.println(WiFi.localIP());

// ----------------------------------------------------
sensors.begin();
Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
}

void loop() {
      sensors.requestTemperatures();
  temp1 = sensors.getTempCByIndex(0);
  Serial.print(temp1);
  b = Firebase.getString("002/alarm");
    c = b.toInt();
    Serial.print(c);
      if (temp1 > c) {
      // Use WiFiClientSecure to create a TLS 1.2 connection.
  //  Note, using a cert fingerprint is required.
  WiFiClientSecure client;
  client.setFingerprint(fingerprint);
  Serial.printf("+ Using fingerprint '%s'\n", fingerprint);
  const char* host = "api.twilio.com";
  const int   httpsPort = 443;
  Serial.print("+ Connecting to ");
  Serial.println(host);
  if (!client.connect(host, httpsPort)) {
    Serial.println("- Connection failed.");
    char buf[200];
                int err = client.getLastSSLError(buf, 199);

                buf[199] = '\0';

                Serial.println("Last SSL error was:");

                Serial.println(buf);

                Serial.print("ERRCODE: "); 
                Serial.println(err);
///* END snippet from pushdata-io project */            
//              return false;
  }
  Serial.println("+ Connected.");
    Serial.println("+ Post an HTTP send SMS request.");
  String post_data = "To=" + urlencode(to_number)
                     + "&From=" + urlencode(from_number)
                     + "&Body=" + urlencode(message_body);
  String auth_header = get_auth_header(account_sid, auth_token);
  String http_request = "POST /2010-04-01/Accounts/" + String(account_sid) + "/Messages HTTP/1.1\r\n"
                        + auth_header + "\r\n" 
                        + "Host: " + host + "\r\n"
                        + "Cache-control: no-cache\r\n"
                        + "User-Agent: ESP8266 Twilio Example\r\n"
                        + "Content-Type: application/x-www-form-urlencoded\r\n"
                        + "Content-Length: " + post_data.length() + "\r\n"
                        + "Connection: close\r\n"
                        + "\r\n"
                        + post_data
                        + "\r\n";
  client.println(http_request);
  // Read the response.
  String response = "";
  while (client.connected()) {
    String line = client.readStringUntil('\n');
    response += (line);
    response += ("\r\n");
  }
  Serial.println("+ Connection is closed.");
  Serial.println("+ Response:");
  Serial.println(response);
      }
  delay(5000);
}

Does that mean that the NodeMCU doesn't have enough memory? I also tried changing delay() to milis(), but that didn't solve the problem.

Any help would be appreciated!

Clearly. There is quite a lot of RAM on an ESP8266 (compared to some Arduinos), but I guess not enough. Delay and millis is a separate issue.

Do you have any large arrays, or lots of strings not in Flash? You might be able to free up some space if so.

Your code uses globally defined variables of type String. The variable type String (the one with the capital "S" eat up memory over time because each time a new assignement is made to a variable of type String additional memory is reserved for this variable. It is only a matter of time when a microcontrollers RAM will be filled up completely from this mechanism. This mechanism is working fine on a PC because a PC has so much memory. On microcontrollers RAM is limited and causes these problems.

There is a library called PString that offers a bit more comfort than using cstrings or arrays of chars. PStrings are based on arrays of char but still comfortable commands like

MyPString = MyOtherPString; are possible.

New assignments to variables of type PStrings do not need additional memory.
The length of PStrings is defined at creation. If you try to write a longer char-sequence into a PString the part that does not fit into it just gets truncated and that's all what's happening. Therefore the use of PStrings is safe but you need to calculate the maximum length that can occur and define this maximum-length in your code.

here is a democode for a simple substring parser. I have reduced the array-sizes so it can compile on an Arduino Uno.
It shows some techniques how PStrings can be used but not all.

#include <PString.h>
char    XML_Str_AoC[256] = " "; 
PString XML_Str_PS(XML_Str_AoC, sizeof(XML_Str_AoC));

char    XML_SubStr_AoC[256] = " ";
PString XML_SubStr_PS(XML_SubStr_AoC, sizeof(XML_SubStr_AoC));

char    Value_AoC[32] = " ";
PString Value_PS(Value_AoC, sizeof(Value_AoC));

char    IDStr_AoC[32] = "=";
PString IDStr_PS(IDStr_AoC, sizeof(IDStr_AoC));

char    Separator_AoC[32] = "=";
PString Separator_PS(Separator_AoC, sizeof(Separator_AoC));

unsigned long IDPos;
unsigned long ValuePos;
unsigned long EndOfValuePos;
unsigned long StrLen;

unsigned long BeginOfSubStr (char* p_PointerToSource, char* p_PointerToSubStr) {
  unsigned long result = strstr(p_PointerToSource,p_PointerToSubStr) - p_PointerToSource;
  return result;
}

unsigned long EndOfSubStr (char* p_PointerToSource, char* p_PointerToSubStr) {
  unsigned long result = strstr(p_PointerToSource,p_PointerToSubStr) - p_PointerToSource + strlen(p_PointerToSubStr);
  return result;
}

// my personal naming-convention parameter of functions start with prefix "p_"
void ExtractUntilSeparator(char* p_PointerToTarget, char* p_PointerToSeparator, char* p_PointerToSource)
{
  Serial.println("entering ExtractUntilSeparator");
  unsigned int LengthUntilDelimiter = strstr(p_PointerToSource,p_PointerToSeparator) - p_PointerToSource + 1;

  strlcpy(p_PointerToTarget,p_PointerToSource,LengthUntilDelimiter);

  Serial.print("p_PointerToSource:>#");
  Serial.print(p_PointerToSource);
  Serial.println("#");

  Serial.print("p_PointerToTarget:>#");
  Serial.print(p_PointerToTarget);
  Serial.println("#");
  
  Serial.print("p_PointerToSeparator:>#");
  Serial.print(p_PointerToSeparator);
  Serial.println("#");
  
  Serial.println("leaving ExtractUntilSeparator");
}

// my personal naming-convention parameter of functions start with "p_"
void ExtractValueBehindSeparator(char* p_PointerToTarget, char* p_PointerToSeparator, char* p_PointerToSource)
{
  Serial.println("entering ExtractValueBehindSeparator");
  unsigned int PosOfSeparatorEnd = strstr(p_PointerToSource,p_PointerToSeparator) - p_PointerToSource + strlen(p_PointerToSeparator);
  // if separatorstring was  found   
  if (PosOfSeparatorEnd < strlen(p_PointerToSource) )
  {
    Serial.print("PosOfSeparatorEnd:>#");
    Serial.print(PosOfSeparatorEnd);
    Serial.println("#");
    unsigned int NoOfBytesUntilEoString = strlen (p_PointerToSource) - PosOfSeparatorEnd + 1; 
    
    strlcpy(p_PointerToTarget, p_PointerToSource + PosOfSeparatorEnd, NoOfBytesUntilEoString);
  }
  else 
  { 
    p_PointerToTarget = ""; // if no separator was found there is nothing behind the separator
  }

  Serial.print("p_PointerToSource:>#");
  Serial.print(p_PointerToSource);
  Serial.println("#");

  Serial.print("p_PointerToTarget:>#");
  Serial.print(p_PointerToTarget);
  Serial.println("#");
  
  Serial.print("p_PointerToSeparator:>#");
  Serial.print(p_PointerToSeparator);
  Serial.println("#");
  
  Serial.println("leaving ExtractValueBehindSeparator");
}

void setup() 
{
  Serial.begin(115200);
  Serial.println();
  Serial.println("setup-Start");

  XML_Str_PS += "HTTP/1.0 200 OK ";
  XML_Str_PS += "Content-Type: text/xml ";
  XML_Str_PS += "Access-Control-Allow-Origin: * ";
/*
  XML_Str_PS += "X-Frame-Options: SAMEORIGIN ";
  XML_Str_PS += "X-Content-Type-Options: nosniff ";
  XML_Str_PS += "X-XSS-Protection: 1; mode=block ";
  XML_Str_PS += "X-Robots-Tag: none ";
  XML_Str_PS += "X-Download-Options: noopen ";
  XML_Str_PS += "X-Permitted-Cross-Domain-Policies: none ";
  XML_Str_PS += "Referrer-Policy: no-referrer ";
  XML_Str_PS += "Content-Length: 300 ";
  XML_Str_PS += "Connection: close ";
  XML_Str_PS += "Date: Fri, 19 Jun 2020 20:20:38 GMT ";
  */
  XML_Str_PS += "<?xml version='1.0' encoding='ISO-8859-1' ?><systemVariables><systemVariable name='Grube Stand' variable='3.000000' value='3.000000' value_list='' value_text='' ise_id='2632' min='0' max='65000' unit='cm' type='4' subtype='0' timestamp='1592598038' value_name_0='' value_name_1=''/></systemVariables>"; 

  Serial.print("XML_Str_PS#");
  Serial.print(XML_Str_PS);
  Serial.print("#");
  Serial.println();

  IDStr_PS = "'Grube Stand'";

  Serial.print("1: IDStr_PS#");
  Serial.print(IDStr_PS);
  Serial.print("#");
  Serial.println();

  IDPos = BeginOfSubStr(XML_Str_PS,IDStr_PS);
  Serial.print("IDPos:");
  Serial.print(IDPos);
  Serial.print("#");
  Serial.println();
  
  IDStr_PS = "value_list=";

  Serial.print("2:IDStr_PS#");
  Serial.print(IDStr_PS);
  Serial.print("#");
  Serial.println();
  
  ValuePos = BeginOfSubStr(XML_Str_AoC,IDStr_AoC);

  Serial.print("XML_Str_PS#");
  Serial.print(XML_Str_PS);
  Serial.print("#");
  Serial.println();

  Serial.print("ValuePos:");
  Serial.print(ValuePos);
  Serial.print("#");
  Serial.println();

  StrLen = ValuePos - IDPos; 
  Serial.print("StrLen:");
  Serial.print(StrLen);
  Serial.print("#");
  Serial.println();

//Grube Stand' variable='3.000000' value='3.000000'   
  strncpy(XML_SubStr_PS,  XML_Str_PS + IDPos, StrLen);

  Serial.print("SubStr#");
  Serial.print(XML_SubStr_PS);
  Serial.print("#");
  Serial.println();

  XML_Str_PS = XML_SubStr_PS;
  Separator_PS = "value='";

  IDPos = EndOfSubStr(XML_SubStr_PS,Separator_PS);

  Serial.print("3: IDPos:");
  Serial.print(IDPos);
  Serial.print("#");
  Serial.println();

  StrLen = strlen(XML_Str_PS) - IDPos + 1;

  Serial.print("StrLen:");
  Serial.print(StrLen);
  Serial.print("#");
  Serial.println();

  strncpy(XML_SubStr_PS,  XML_Str_PS + IDPos, StrLen);

  Serial.print("SubStr#");
  Serial.print(XML_SubStr_PS);
  Serial.print("#");
  Serial.println();

  XML_Str_PS = XML_SubStr_PS;
  Separator_PS = "'";

  ExtractUntilSeparator(XML_SubStr_PS, Separator_PS, XML_Str_PS);
  Serial.print("value SubStr#");
  Serial.print(XML_SubStr_PS);
  Serial.print("#");
  Serial.println();

}

void loop() {
}

Though there is a danger in this too: In this demo-code I'm using commands like strlcpy
memcpy etc. those commands do a straight forward writing into RAM at the given adress.
The compiler has no chance to check if this RAM-adress makes sense or not.
If these adresses are wrong these commands will overwrite mercyless the content of that part of the RAM.

So the adressing has to be checked very carefully.
A part of the variables that is used here is of type "pointer" the difference between the variable itself and its pointer is a simple "*" so leaving out this character in the code or adding it at the wrong place will cause unpredictable errors.

there is a library that says it is safe to use. But I haven't done much testing with it. And maybe the overhead it creates is using too much RAM for your application

Safe, Robust, Debuggable String class for Arduino

best regards Stefan

Hi Stefan,

thank you very much for your reply. I found your post very helpful and am trying to implement PString library. Do you think that there could also be a problem with conflicting clients, since both Firebase and Twilio connect using http and they could use the same client for that? Could this be the reason for as to why the ESP cannot connect to Twilio?

Best regards,
Nina

ninakos:
Hi Stefan,

thank you very much for your reply. I found your post very helpful and am trying to implement PString library. Do you think that there could also be a problem with conflicting clients, since both Firebase and Twilio connect using http and they could use the same client for that? Could this be the reason for as to why the ESP cannot connect to Twilio?

Best regards,
Nina

I don't know much about web-clients. You may start a new thread with a title that directly asks this question to get the attention of people who have knowledge about web-clients.
I haven't analysed your code. One idea I have is to establish the connection to one of them.After data-exchange is done close this connection before connecting to the other. But I donÄt know if this is a practical approach.

As your problem seems to be related to RAM maybe adding serial debug-out-put at several places inside your code can give hints what uses a lot of RAM

I did a gearch for Arduino ESP free RAM and found this link

best regards Stefan

Hi,

thank you for the link you sent. I found it very helpful. I used the code to get free heap and it printed values at around 18800 after the error that it was unable to allocate memory for SSL structures and buffers. Should that be a sufficient amount of heap for the program to work? If I used the free heap command in the setup, I got around 35500.
I will also post a new topic on web-clients as you recommended.

Best wishes,
Nina