carduinoesp8266software-serial

Crash when reading const* in c on ESP8266


I'm making a system that reads sensor value from Arduino Uno through SoftwareSerial and publishes it via MQTT. However, the problem I'm facing I think is more general, I must admit I am new to c.

I'm reading the data, and splitting it into two const* variables that are defined on the top of my program.

When I read back the saved "data" and "topic" variable that I have parsed from the Serial connection, I only get garbage output, and usually a crash that restarts the device.

It prints them successfully inside the read-from-serial function, but it can't be correctly read later. Can it have something to do with how the data is saved? Can I explicitly allocate some memory for the variables?

I'm using a ESP8266 (ESP07) chip with lowered baud rate and proper voltage supply. It seems to be running well and stable.

#include <StringSplitter.h>
#include <PubSubClient.h>
#include <ESP8266WiFi.h>
#include <time.h>

//const char* ssid = "xxxx";
//const char* password =  "xxxx";
const char* ssid = "xxxx";
const char* password =  "xxxx";
const char* mqttServer = "xxxx;
const int mqttPort = xxxx;
const char* mqttUser = "xxxx";
const char* mqttPassword = "xxxx";
int timezone = 1;
int dst = 0;

The data is stored here:

char* data;
char* topic;
boolean newData = false;
boolean unpublishedData = false;

WiFiClient espClient;
PubSubClient client(espClient);

void setup() {

  Serial.begin(19200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println("Connecting to WiFi..");
  }
  Serial.println("Connected to the WiFi network");
  configTime(timezone * 3600, dst * 0, "pool.ntp.org", "time.nist.gov");


  client.setServer(mqttServer, mqttPort);
  client.setCallback(callback);

  while (!client.connected()) {
    Serial.println("Connecting to MQTT...");

    if (client.connect("ESP8266Client", mqttUser, mqttPassword )) {

      Serial.println("connected");

    } else {

      Serial.print("failed with state ");
      Serial.print(client.state());
      delay(2000);

    }
    // wait and determine if we have a valid time from the network.
    time_t now = time(nullptr);
    Serial.print("Waiting for network time.");
    while (now <= 1500000000) {
      Serial.print(".");
      delay(300); // allow a few seconds to connect to network time.
      now = time(nullptr);
    }
  }
  Serial.println("");

  time_t now = time(nullptr);
  Serial.println(ctime(&now));

  String datatext = "val: ";
  String timetext = ", time: ";
  String dataToBeSent = "test";
  String timeToBeSent = ctime(&now);

  String publishString = datatext + dataToBeSent + timetext + timeToBeSent;
  Serial.println("Attempting to publish: " + publishString);
  client.publish("trykk/sensor0", (char*) publishString.c_str());
  client.subscribe("trykk/sensor0");

}

void callback(char* topic, byte* payload, unsigned int length) {

  Serial.print("Message arrived in topic: ");
  Serial.println(topic);

  Serial.print("Message:");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }

  Serial.println();
  Serial.println("-----------------------");

}

void loop() {
  client.loop();
  recvWithStartEndMarkers();
  showNewData();
  publishReceived();
}

void publishReceived() {
  if (unpublishedData) {
    Serial.println("Hello from inside the publisher loop!");
    time_t now = time(nullptr);
    char* timeNow = ctime(&now);

It fails here, reading the data:

    char publishText[30]; //TODO: make it JSON
    strcpy( publishText, data );
    strcat( publishText, " " );
    strcat( publishText, timeNow );

    Serial.print("publishText: ");
    Serial.println(publishText);
    Serial.print("topic: "); 
    Serial.println(topic);
    client.publish(topic, publishText);
    client.subscribe(topic);
    unpublishedData = false;
  } else if (!data) {
    Serial.println("No data saved to array.");
  } else if (!topic) {
    Serial.println("No topic saved to array.");
  }
}

void recvWithStartEndMarkers() {
  int numChars = 32;
  char receivedChars[numChars];
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';
  char rc;

  if (Serial.available() > 0) {
    Serial.println("Hello from inside the receive loop!");
    delay(100);
    while (Serial.available() > 0 && newData == false) {
      rc = Serial.read();
      Serial.println("Reading from data line.");

      if (recvInProgress == true) {
        if (rc != endMarker) {
          receivedChars[ndx] = rc;
          ndx++;
          if (ndx >= numChars) {
            ndx = numChars - 1;
          }
        }
        else {
          Serial.println("Found the end marker.");
          receivedChars[ndx] = '\0'; // terminate the string
          recvInProgress = false;
          ndx = 0;
          newData = true;
          unpublishedData = true;

This part prints the values correctly back to me:

          //Split the string
          Serial.print("ESP debug: read: ");
          Serial.println(receivedChars);
          const char s[2] = ":";
          *data = strtok(receivedChars, s);
          Serial.print(data);
          Serial.print(" ");
          *topic = strtok(NULL, s);
          Serial.println(topic);
        }
      }

      else if (rc == startMarker) {
        recvInProgress = true;
        Serial.println("Found start marker");
      }
    }
  }
}

//This is gutted as it gave me problems reading the variables
void showNewData() {
  if (newData == true) {
    Serial.print("This just in ... ");
    Serial.print("Topic: ");
    Serial.print("stuff");
    Serial.print(", data: ");
    Serial.println("more stuff");
    newData = false;
  }
}

Solution

  • After fixing the assignment of strtok's result to data as shown in bruno's answer there is another bug that can lead to a crash.

    Your function loop() calls recvWithStartEndMarkers() first, then publishReceived().

    void loop() {
      client.loop();
      recvWithStartEndMarkers();
      showNewData();
      publishReceived();
    }
    

    In function recvWithStartEndMarkers you read some data into a local array receivedChars, feed this into strtok and write a pointer returned from strtok to a global variable data.

    void recvWithStartEndMarkers() {
      int numChars = 32;
      char receivedChars[numChars]; /* this is a local variable with automatic storage */
      /* ... */
    
        while (Serial.available() > 0 && newData == false) {
          /* ... */
              receivedChars[ndx] = rc;
              ndx++;
              if (ndx >= numChars) {
                ndx = numChars - 1;
              }
              /* ... */
              receivedChars[ndx] = '\0'; // terminate the string
              /* Now there is a terminated string in the local variable */
              /* ... */
    
              //Split the string
              /* ... */
              const char s[2] = ":";
              data = strtok(receivedChars, s); /* strtok modifies the input in receivedChars and returns a pointer to parts of this array. */ 
              /* ... */
    }
    

    After leaving the function the memory that was receivedChars is no longer valid. This means data will point to this invalid memory on the stack.

    Later you want to access the global variable data in a function publishReceived(). Accessing this memory is is unspecified behavior. You may still get the data, you may get something else or your program may crash.

    void publishReceived() {
      /* ... */
        char publishText[30]; //TODO: make it JSON
        strcpy( publishText, data ); /* This will try to copy whatever is now in the memory that was part of receivedChars inside recvWithStartEndMarkers() but may now contain something else, e.g. local data of function publishReceived(). */
      /* ... */
    

    To fix this you could use strdup in recvWithStartEndMarkers():

    data = strtok(receivedChars, s);
    if(data != NULL) data = strdup(data);
    

    Then you have to free(data) somewhere when you no longer need the data or before calling recvWithStartEndMarkers() again.

    Or make data an array and use strncpy in recvWithStartEndMarkers().