govorox / SSLClient

SSLClient - generic secure client Arduino library using mbedtls
GNU General Public License v3.0
84 stars 39 forks source link

Aws mqtt start_ssl_client(): Client provider not initialised #49

Closed icabrera29 closed 1 year ago

icabrera29 commented 1 year ago

Hi, i´m connecting successfully to Aws mqtt but after i publish some messages the client disconnects and when it tries to reconnect i get the error: start_ssl_client(): Client provider not initialised

i´m using the latest version of this library

File chunk json size: 3750
Chunk successfully uploaded: 

Chunk id: 082

File chunk json size: 3750
Chunk successfully uploaded: 

Chunk id: 083

File chunk json size: 3750
Chunk successfully uploaded: 

Chunk id: 084

File chunk json size: 3750
[ 65871][E][ssl_client.cpp:171] client_net_send(): write failed
[ 65871][E][ssl_client.cpp:41] _handle_error(): [send_ssl_data():882]: (-78) UNKNOWN ERROR CODE (004E)
Chunk Upload failed

Mqtt service not connected
Attempting MQTT connection...Board id: 00F897BC
Thing name: 
ESP32-00F897BC-69132
[ 69133][E][ssl_client.cpp:618] start_ssl_client(): Client provider not initialised
[ 69135][E][SSLClient.cpp:133] connect(): start_ssl_client: -1
failed, rc=-2...try again in 5 seconds

Mqtt retry: 1Attempting MQTT connection...Board id: 00F897BC
Thing name: 
ESP32-00F897BC-74151
[ 74152][E][ssl_client.cpp:618] start_ssl_client(): Client provider not initialised
[ 74154][E][SSLClient.cpp:133] connect(): start_ssl_client: -1
failed, rc=-2...try again in 5 seconds

Mqtt retry: 2Attempting MQTT connection...Board id: 00F897BC
Thing name: 
ESP32-00F897BC-79170
[ 79171][E][ssl_client.cpp:618] start_ssl_client(): Client provider not initialised
[ 79173][E][SSLClient.cpp:133] connect(): start_ssl_client: -1
failed, rc=-2...try again in 5 seconds

Code:

void reconnect()
{
  // Loop until we're reconnected
  while (!client.connected())
  {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    uint32_t id = getBoardId();

    Serial.print("Board id: ");
    Serial.printf("%08X\n", id);

    long now = millis();

    snprintf(thingName, sizeof(thingName),"ESP32-%08X-%ld",id, now);
    Serial.print("Thing name: \n");
    Serial.print(thingName);
    Serial.print("\n");

    if (client.connect(thingName))
    {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic", "hello world example");
      listDirUploadFiles(SD, "/esp32", 0);
      // ... and resubscribe
      client.subscribe("inTopic");
    }
    else
    {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println("...try again in 5 seconds");
      if(mqttRetry == 5) {
        Serial.print("\nPowering down and restarting the modem\n");
        sim_modem.gprsDisconnect();
        sim_modem.poweroff();
        mqttRetry = 0;
        delay(15000);
        return;
      }
      mqttRetry++;
      Serial.print("\nMqtt retry: ");
      Serial.print(mqttRetry);
      delay(5000);
    }
  }
}

void loop()
{
  SerialMon.print("Initializing modem...");
  if (!sim_modem.init())
  {
    SerialMon.print(" fail... restarting modem...");
    setupModem();
    // Restart takes quite some time
    // Use modem.init() if you don't need the complete restart
    if (!sim_modem.restart())
    {
      SerialMon.println(" fail... even after restart");
      return;
    }
  }
  SerialMon.println(" OK");

  // General information
  String name = sim_modem.getModemName();
  Serial.println("Modem Name: " + name);
  String modem_info = sim_modem.getModemInfo();
  Serial.println("Modem Info: " + modem_info);

  // Set modes
  /*
    2 Automatic
    13 GSM only
    38 LTE only
    51 GSM and LTE only
  * * * */
  sim_modem.setNetworkMode(38);
  delay(3000);
  /*
    1 CAT-M
    2 NB-Iot
    3 CAT-M and NB-IoT
  * * */
  sim_modem.setPreferredMode(1);
  delay(3000);

  // Wait for network availability
  SerialMon.print("Waiting for network...");
  if (!sim_modem.waitForNetwork())
  {
    SerialMon.println(" fail");
    delay(10000);
    return;
  }
  SerialMon.println(" OK");

  // Connect to the GPRS network
  SerialMon.print("Connecting to network...");
  if (!sim_modem.isNetworkConnected())
  {
    SerialMon.println(" fail");
    delay(10000);
    return;
  }
  SerialMon.println(" OK");

  // Connect to APN
  SerialMon.print("Connecting to APN: ");
  SerialMon.print(apn);
  if (!sim_modem.gprsConnect(apn, gprs_user, gprs_pass))
  {
    SerialMon.println(" fail");
    return;
  }
  digitalWrite(LED_PIN, HIGH);
  SerialMon.println(" OK");

  // More info..
  Serial.println("");
  String ccid = sim_modem.getSimCCID();
  Serial.println("CCID: " + ccid);
  String imei = sim_modem.getIMEI();
  Serial.println("IMEI: " + imei);
  String cop = sim_modem.getOperator();
  Serial.println("Operator: " + cop);
  IPAddress local = sim_modem.localIP();
  Serial.println("Local IP: " + String(local));
  int csq = sim_modem.getSignalQuality();
  Serial.println("Signal quality: " + String(csq));

  // As long as we have connectivity
  while (sim_modem.isGprsConnected())
  {
    // We maintain connectivity with the broker
    if (!client.connected())
    {
      reconnect();
    }
    long now2 = millis();
    int timeResult = (int)now2 % 60000;
    if (timeResult == 0) {
      int minutes = now2 / 60000;
      char time[50];
      snprintf(time, sizeof(time),"%i minutes",minutes);
      Serial.print("\n");
      Serial.print(time);
      Serial.print("\n");
    }

    if (now2 - lastUpload2 > 250000) {
      Serial.print("Upload Files\n");

      // Attempt to reconnect
      listDirUploadFiles(SD, "/esp32", 0);

      lastUpload2 = now2;
    }
    // We are listening to the events
    client.loop();
  }

  // Disconnect GPRS and PowerOff
  // Apparently the "gprsDisconnect()" method (TinyGSM) are not working well with the SIM7000...
  // ...you have to use additionally "poweroff()".
  // With that, the modem can be connected again in the next cycle of the loop.
  sim_modem.gprsDisconnect();
  sim_modem.poweroff();

  delay(15000);
}
RobertByrnes commented 1 year ago

@icabrera29 I have seen similar behaviour. If you look into the code the sslclient_context takes Client pointer: Client* client; This happens when you make the ssl client object. Client provider not initialised and a return value of -1 occurs when this pointer is NULL. The object has either gone out of scope of or has been destroyed.

I have been handling this by encapsulating my full client stack (http, ssl, modem or whatever) in a function or scope so that the whole thing gets recreated when reconnecting. This is a work around. Still need to look at a better solution.

icabrera29 commented 1 year ago

Hi @RobertByrnes thanks, could you share an example?

RobertByrnes commented 1 year ago

Hi @RobertByrnes thanks, could you share an example?

@icabrera29 What I can't see in the code you shared is your global scope, i,e, the code you have written above your void setup() {}, you probably have declared your client and sim_modem objects. Move those declarations inside your void loop() {} function. This means you'll have to pass them into your reconnect() function. Sharing your entire code would make it easier to explain...

icabrera29 commented 1 year ago

Like this? @RobertByrnes

void reconnect(TinyGsm &sim_modem, PubSubClient &client)
{
  // Loop until we're reconnected
  while (!client.connected())
  {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    uint32_t id = getBoardId();

    Serial.print("Board id: ");
    Serial.printf("%08X\n", id);

    long now = millis();

    snprintf(thingName, sizeof(thingName),"ESP32-%08X-%ld",id, now);
    Serial.print("Thing name: \n");
    Serial.print(thingName);
    Serial.print("\n");

    if (client.connect(thingName))
    {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic", "hello world example");
      listDirUploadFiles(SD, "/esp32", 0);
      // ... and resubscribe
      client.subscribe("inTopic");
    }
    else
    {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println("...try again in 5 seconds");
      if(mqttRetry == 5) {
        Serial.print("\nPowering down and restarting the modem\n");
        sim_modem.gprsDisconnect();
        sim_modem.poweroff();
        mqttRetry = 0;
        delay(15000);
        return;
      }
      mqttRetry++;
      Serial.print("\nMqtt retry: ");
      Serial.print(mqttRetry);
      delay(5000);
    }
  }
}

void loop()
{
  TinyGsm sim_modem(SerialAT);
  TinyGsmClient gsm_transpor_layer(sim_modem);
  SSLClient secure_presentation_layer(&gsm_transpor_layer);
  PubSubClient client(secure_presentation_layer);

  SerialMon.print("Initializing modem...");
  if (!sim_modem.init())
  {
    SerialMon.print(" fail... restarting modem...");
    setupModem();
    // Restart takes quite some time
    // Use modem.init() if you don't need the complete restart
    if (!sim_modem.restart())
    {
      SerialMon.println(" fail... even after restart");
      return;
    }
  }
  SerialMon.println(" OK");

  // General information
  String name = sim_modem.getModemName();
  Serial.println("Modem Name: " + name);
  String modem_info = sim_modem.getModemInfo();
  Serial.println("Modem Info: " + modem_info);

  // Set modes
  /*
    2 Automatic
    13 GSM only
    38 LTE only
    51 GSM and LTE only
  * * * */
  sim_modem.setNetworkMode(38);
  delay(3000);
  /*
    1 CAT-M
    2 NB-Iot
    3 CAT-M and NB-IoT
  * * */
  sim_modem.setPreferredMode(1);
  delay(3000);

  // Wait for network availability
  SerialMon.print("Waiting for network...");
  if (!sim_modem.waitForNetwork())
  {
    SerialMon.println(" fail");
    delay(10000);
    return;
  }
  SerialMon.println(" OK");

  // Connect to the GPRS network
  SerialMon.print("Connecting to network...");
  if (!sim_modem.isNetworkConnected())
  {
    SerialMon.println(" fail");
    delay(10000);
    return;
  }
  SerialMon.println(" OK");

  // Connect to APN
  SerialMon.print("Connecting to APN: ");
  SerialMon.print(apn);
  if (!sim_modem.gprsConnect(apn, gprs_user, gprs_pass))
  {
    SerialMon.println(" fail");
    return;
  }
  digitalWrite(LED_PIN, HIGH);
  SerialMon.println(" OK");

  // More info..
  Serial.println("");
  String ccid = sim_modem.getSimCCID();
  Serial.println("CCID: " + ccid);
  String imei = sim_modem.getIMEI();
  Serial.println("IMEI: " + imei);
  String cop = sim_modem.getOperator();
  Serial.println("Operator: " + cop);
  IPAddress local = sim_modem.localIP();
  Serial.println("Local IP: " + String(local));
  int csq = sim_modem.getSignalQuality();
  Serial.println("Signal quality: " + String(csq));

  // As long as we have connectivity
  while (sim_modem.isGprsConnected())
  {
    secure_presentation_layer.setCACert(ca_cert);
    secure_presentation_layer.setCertificate(AWS_CERT_CRT);
    secure_presentation_layer.setPrivateKey(AWS_CERT_PRIVATE);
    // We maintain connectivity with the broker
    if (!client.connected())
    {
      reconnect(sim_modem, client);
    }
    long now2 = millis();
    int timeResult = (int)now2 % 60000;
    if (timeResult == 0) {
      int minutes = now2 / 60000;
      char time[50];
      snprintf(time, sizeof(time),"%i minutes",minutes);
      Serial.print("\n");
      Serial.print(time);
      Serial.print("\n");
    }

    if (now2 - lastUpload2 > 250000) {
      Serial.print("Upload Files\n");

      // Attempt to reconnect
      listDirUploadFiles(SD, "/esp32", 0);

      lastUpload2 = now2;
    }
    // We are listening to the events
    client.loop();
  }

  // Disconnect GPRS and PowerOff
  // Apparently the "gprsDisconnect()" method (TinyGSM) are not working well with the SIM7000...
  // ...you have to use additionally "poweroff()".
  // With that, the modem can be connected again in the next cycle of the loop.
  sim_modem.gprsDisconnect();
  sim_modem.poweroff();

  delay(15000);
}
RobertByrnes commented 1 year ago

@icabrera29 yes try that, might also be worth updating to 1.1.4 version of SSLClient. Bug fix in there which may have an effect.

pcxx13 commented 1 year ago

I suspect I have the same or very similar issue using HTTPS. I am using Lillygo T-SIM7600E-H with TinyGSM and have been trying to update to SSLClient 1.1.4.

When SSLClient successfully completes its first POST, it stops the SSLClient as seen below:

17:32:12.691 -> Status code: 200 17:32:12.691 - [ 39116][V][SSLClient.cpp:72] stop(): Stopping ssl client 17:32:12.691 -> [ 39116][V][ssl_client.cpp:403] stop_ssl_socket(): Cleaning SSL connection.

This seems to kill the GPRS connection permanently. Even though I try to reconnect the GPRS again, every POST attempt after the SSLClient has been stopped gives the "start_ssl_client: -1". I tried all different combinations to restart the GPRS again and can't seem to find a workaround or solution.

The only way I have been able to use SSLClient is to use this branched version https://github.com/Bengarman/SSLClient. This version is very old but doesn't seem to stop the SSL client therefore not killing the GPRS connection. I would love to use all the improvements @RobertByrnes has been so kindly working on and upgrade to 1.1.4 but everytime I try the library in my code it fails after the first successful post so I revert back to the branched older version.

Here is an extract of my code:

void heartbeatTaskFunction(void* pvparameter) {
``
   if (!modem.isGprsConnected()) {
    Serial.println("GPRS connection fail");
    modem.gprsConnect(apn, gprsUser, gprsPass);
    delay(2000);
  }

  lastHeartbeatTime = millis();  // set current time to lastHeartbeatTime

  for (;;) {
    if (millis() > (lastHeartbeatTime + heartBeatPeriod)) {  // Update alive status
      Serial.print("Starting heartbeat (seconds): ");
      Serial.println(heartBeatPeriod / 1000);
      int coreID = xPortGetCoreID();
      Serial.print("CPU CORE IS: ");
      Serial.println(coreID);

      if (xSemaphoreTake(modemMutex, portMAX_DELAY) == pdTRUE) {
        if (http_client.connected() != true) {
          for (int i = 0; i < 5; i++) {  // try to connect 5 times
            Serial.println("HTTPS client not connected");
            Serial.println("Reconnecting https");
            http_client.connect(FIREBASE_HOST, SSL_PORT);
            delay(2000);
            if (http_client.connected() == true) {
              break;
            }
          }
        }

        http_client.connectionKeepAlive();
        Serial.println("Https connected");
        digitalWrite(LED_PIN, HIGH);

        int statusCode = 0;

        // Format time & date into ISO8601 format
        String datetime = modem.getGSMDateTime(DATE_FULL);
        int plusSignIndex = datetime.indexOf("+");
        String timeZoneOffsetStr = datetime.substring(plusSignIndex);

        // Calculate the time zone offset in minutes
        int timeZoneOffsetBlocks = timeZoneOffsetStr.toInt();   // Convert the offset to an integer
        int timeZoneOffsetMinutes = timeZoneOffsetBlocks * 15;  // Each block is 15 minutes

        // Remove the time zone offset from the original string
        datetime = datetime.substring(0, plusSignIndex);

        // Convert date part to ISO 8601 format
        datetime = "20" + datetime;
        datetime.replace("/", "-");
        datetime.replace(",", "T");

        // Calculate hours and minutes components of the timezone offset
        int timeZoneHours = timeZoneOffsetMinutes / 60;
        int timeZoneMinutes = timeZoneOffsetMinutes % 60;

        // Append the calculated time zone offset in ISO8601 format with leading zeros
        if (timeZoneOffsetMinutes >= 0) {
          datetime += ".000+";
        } else {
          datetime += ".000-";
        }

        // Add leading zero for hours if needed
        if (timeZoneHours < 10) {
          datetime += "0";
        }

        datetime += String(timeZoneHours, DEC);

        // Add a colon separator
        datetime += ":";

        // Add leading zero for minutes if needed
        if (timeZoneMinutes < 10) {
          datetime += "0";
        }

        datetime += String(timeZoneMinutes, DEC);

        // Build JSON content for heartbeat
        String keepAliveDocString = "";
        StaticJsonDocument<100> alivedoc;
        alivedoc["date_time"] = datetime;
        alivedoc["signalQ"] = modem.getSignalQuality();
        serializeJson(alivedoc, keepAliveDocString);

        String url = "";
        url += FIREBASE_PATH_STATUS + "/esp32" + ".json";
        url += "?auth=" + FIREBASE_AUTH;

        String contentType = "application/json";

        Serial.print("POST:");
        Serial.println(url);
        Serial.print("Data:");
        Serial.println(keepAliveDocString);

        http_client.put(url, contentType, keepAliveDocString);

        statusCode = http_client.responseStatusCode();
        Serial.print("Status code: ");
        Serial.println(statusCode);

        http_client.stop();
        digitalWrite(LED_PIN, LOW);

        if (statusCode == 200) {
          Serial.println("Heartbeat Success");
        } else {
          Serial.println("Heartbeat failed to send");
        }

        lastHeartbeatTime = lastHeartbeatTime + heartBeatPeriod;  // Set new heartbeat time

        // modem.gprsDisconnect();

        xSemaphoreGive(modemMutex);
      }
    }
  }
}
RobertByrnes commented 1 year ago

@pcxx13 looks like you have Tiny Gsm and maybe ArduinoHttp client declared globally? I think what is going on is that SSLClient::stop() zeros the client pointer. I reckon if you logged out you pointers to http_client and modem they'd be valid. The ssl client context -> client has been zerod so all subsequent calls fail.

memset() does this. I did change that from bzero() but bzero() was there anyway doing the same job. I tried this evening declaring my SSL and HttpClient at top scope with memset() commented and was able to make multiple successfull http requests using the ssl client.

Before this becomes a fix I want to investigate why it was there in the first place.

Stop() is called in the destructor() so that makes sense so possibly moving the memset() call to the destructor is enough to ensure all memory is cleaned up.

Will PR once verified...

pcxx13 commented 1 year ago

@RobertByrnes thank you for your reply. You are spot on, I am using the latest versions of ArduinoHttp client v0.5.0 and also TinyGSM v0.11.7. I am also using the two ESP32 cores with mutex implemented and I know I need to be very careful doing this but that doesn't seem to be causing me any problems at the moment. Below extract declarations and the layer stack.

#define TINY_GSM_MODEM_SIM7600
#define TINY_GSM_RX_BUFFER 1024
#define SerialMon Serial
#define SerialAT Serial1

// #define DUMP_AT_COMMANDS
// #define TINY_GSM_DEBUG SerialMon

#include <Arduino.h>        //
#include <TaskScheduler.h>  //  TaskScheduler 3.7.0
#include <PhoneDTMF.h>      //  PhoneDTMF 0.0.3  with modified  PhoneDTMF.h  const uint8_t  DTMF_MAP[TONES * 2] = {0x11,0x21,0x41,0x12,0x22,0x42,0x14,0x24,0x44,0x28,0x81,0x82,0x84,0x88,0x18,0x48};
                            //                               const char  DTMF_CHAR[TONES * 2] =   { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'D', 'E', 'F', 'Z', 'B', 'C'};
#include <driver/adc.h>     //
#include <Ticker.h>         //  Ticker 4.4.0
#include <TinyGsmClient.h>  //  TinyGSM 0.11.7
#include "SSLClient.h"      // https://github.com/Bengarman/SSLClient v1.1.0
#include "secrets.h"
#include <ArduinoJson.h>        //  ArduinoJson 6.21.3
#include <ArduinoHttpClient.h>  // ArduinoHttpClient 0.5.0

// Task handles
TaskHandle_t heartbeatTask;
SemaphoreHandle_t modemMutex;

// Function prototype for the task
void heartbeatTaskFunction(void* pvparameter);

// Firebase parameters
const char FIREBASE_HOST[] = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
const String FIREBASE_AUTH = "XXXXXXXXXXXXXXXXXXXXXXXXX";
const String FIREBASE_PATH_EVENT = "/event";
const String FIREBASE_PATH_STATUS = "/status";
const int SSL_PORT = 443;

#ifdef DUMP_AT_COMMANDS
#include <StreamDebugger.h>
StreamDebugger debugger(SerialAT, SerialMon);
TinyGsm modem(debugger);
#else
TinyGsm modem(SerialAT);
#endif

// Define cellular modem settings
#define UART_BAUD 115200
#define MODEM_TX 27
#define MODEM_RX 26
#define MODEM_PWRKEY 4
#define MODEM_DTR 32
#define MODEM_RI 33
#define MODEM_FLIGHT 25
#define MODEM_STATUS 34
#define LED_PIN 12

// Mobile APN
const char apn[] = "yesinternet";
const char gprsUser[] = "";
const char gprsPass[] = "";

// SSL Layers stack
TinyGsmClient gsm_client_modem(modem);
SSLClient gsm_client_secure_modem(&gsm_client_modem);
HttpClient http_client = HttpClient(gsm_client_secure_modem, FIREBASE_HOST, SSL_PORT);

Really appreciate your support and happy to test any PR. I am a novice programer and have a reasonable handle of C and a basic understanding of CPP. It's fun learning and making great projects!

pcxx13 commented 1 year ago

Thank you @RobertByrnes - currently testing, so far so good!

@pcxx13 This is good news 👏 also working for me. If anymore trouble along this line just post a new comment here and we can reopen.