Сбой при чтении const* в c на ESP8266

Я делаю систему, которая считывает значение датчика с Arduino Uno через SoftwareSerial и публикует его через MQTT. Однако проблема, с которой я столкнулся, я думаю, более общая, я должен признать, что я новичок в c.

Я читаю данные и разбиваю их на две переменные const*, которые определены в верхней части моей программы.

Когда я считываю сохраненные переменные «данные» и «тема», которые я проанализировал из последовательного соединения, я получаю только вывод мусора и, как правило, сбой, который перезапускает устройство.

Он успешно печатает их внутри функции чтения из серийного номера, но не может быть правильно прочитан позже. Может ли это иметь какое-то отношение к тому, как сохраняются данные? Могу ли я явно выделить часть памяти для переменных?

Я использую чип ESP8266 (ESP07) с пониженной скоростью передачи данных и правильным питанием. Вроде работает хорошо и стабильно.

#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;

Данные хранятся здесь:

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);

Здесь происходит сбой при чтении данных:

    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;

Эта часть правильно печатает значения обратно мне:

          //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;
  }
}

person Vincent Vega    schedule 12.03.2019    source источник
comment
Совершенно непонятно, что вы спрашиваете. Вы предшествуете второму (большому) блоку кода Данные хранятся здесь: - какие данные хранятся где? При чем здесь const* в названии? К какому символу он относится - у вас есть несколько const*, и неясно, есть ли у вас какие-либо проблемы с их чтением?   -  person Clifford    schedule 12.03.2019
comment
У вас опечатка, просто замените *data = strtok(receivedChars, s); на data = strtok(receivedChars, s); в recvWithStartEndMarkers, смотрите мой ответ   -  person bruno    schedule 12.03.2019


Ответы (2)


После исправления назначения результата strtok data, как показано в ответе Бруно, появляется еще одна ошибка, которая может привести к сбою.

Ваша функция loop() сначала вызывает recvWithStartEndMarkers(), затем publishReceived().

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

В функции recvWithStartEndMarkers вы читаете некоторые данные в локальный массив receivedChars, передаете их в strtok и записываете указатель, возвращенный из strtok, в глобальную переменную 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. */ 
          /* ... */
}

После выхода из функции память, которая была receivedChars, больше недействительна. Это означает, что data будет указывать на эту недопустимую память в стеке.

Позже вы захотите получить доступ к глобальной переменной data в функции publishReceived(). Доступ к этой памяти является неуказанным поведением. Вы все еще можете получить данные, вы можете получить что-то еще или ваша программа может дать сбой.

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(). */
  /* ... */

Чтобы исправить это, вы можете использовать strdup в recvWithStartEndMarkers():

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

Затем вам нужно free(data) где-то, когда вам больше не нужны данные или перед повторным вызовом recvWithStartEndMarkers().

Или сделайте data массивом и используйте strncpy в recvWithStartEndMarkers().

person Bodo    schedule 12.03.2019
comment
Большое спасибо. Ссылка, выходящая за рамки, была источником моих проблем! Другая ошибка была просто из эксперимента, и я забыл изменить ее обратно, компилятор бы ее поймал. - person Vincent Vega; 13.03.2019

Из вашего кода:

char* data;
...
*data = strtok(receivedChars, s);

strtok возвращает char*, но вы делаете *data = strtok(...), в то время как data сами по себе являются (не инициализированными) char *, это несовместимо, и у вас есть первый «шанс» для сбоя потому что вы пишете на случайный адрес.

Если у вас нет сбоя и ваша программа может продолжать работу, данные не изменяются сами по себе и остаются неинициализированными.

In

strcpy( publishText, data );
...
Serial.print(data);

Когда вы используете данные как char*, выполняя Serial.print(data); и strcpy( publishText, data );, вы читаете со случайного (и, конечно, недействительного) адреса, что приводит к сбою.

Чтобы исправить, просто замените *data = strtok(receivedChars, s); на data = strtok(receivedChars, s);

person bruno    schedule 12.03.2019