0
votes

I'm a noob at this, so hopefully a stupidly obvious one.

I'm trying to create a simple temperature/humidity sensor that takes readings from a DHT22 and uses an ESP8266 to ping them to Thingspeak's API to then graph out/store etc.

I've pasted code below - it worked on Arduino Uno and I'm trying to shrinkify it onto a ESP8266 so I can produce lots of little temperature sensors for around the house.

Symptoms

  • It's connecting fine to wifi
  • It's generating a correct API string (I've tested by manually cutting and pasting it into browser)
  • Temp sensors are generating correct readings also
  • It's returning 'Data Fail!' in the serial monitor, suggesting it's this point in the code where the error is occurring

I can't tell if it's something odd about moving from Arduino Uno to ESP8266 that's causing the problem (ie different libraries required, TCP commands being different etc)

Any help from a more seasoned veteran would be much appreciated!

Here's a snippet of the serial monitor output and the code (just passwords/apis etc)

22:16:50.266 -> **************
22:16:57.579 -> Wifi Connection Successful
22:16:57.579 -> The IP Address of the Sensor is:192.168.1.211
22:16:57.579 -> Humidity: 41.50
22:16:57.579 -> Temperature: 21.70
22:16:57.579 -> AT+CIPSTART="TCP","api.thingspeak.com",80
22:17:00.574 -> AT+CIPSEND=63
22:17:01.561 -> AT+CIPCLOSE
22:17:02.577 -> Data Fail!
22:17:02.577 -> GET /update?apikey=<REMOVED>&field1=21.70&field2=41.50


#include<stdlib.h>
#include "DHT.h"
#include <ESP8266WiFi.h>

#define SSID "<REMOVED>" //your network name
#define PASS "<REMOVED>" //your network password
#define API "<REMOVED>" //api string
#define IP "api.thingspeak.com" // thingspeak.com
#define DHTPIN 4     // what pin the DHT sensor is connected to
#define DHTTYPE DHT22   // Change to DHT22 if that's what you have
#define Baud_Rate 115200 //Another common value is 9600
#define DELAY_TIME 300000 //time in ms between posting data to ThingSpeak

//Can use a post also
String GET = String("GET /update?apikey=") + API + "&field1=";
String FIELD2 = "&field2=";

//if you want to add more fields this is how
//String FIELD3 = "&field3=";

bool updated;

DHT dht(DHTPIN, DHTTYPE);

//this runs once
void setup()
{
  delay(5000);
  Serial.begin(Baud_Rate);
  // Connect to WIFI
  WiFi.begin(SSID, PASS);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print("*");
  }

  Serial.println("");
  Serial.println("Wifi Connection Successful");
  Serial.print("The IP Address of the Sensor is:");
  Serial.println(WiFi.localIP()); //Print the IP Address


  //initalize DHT sensor
  dht.begin();
}

//this runs over and over
void loop() {
  float h = dht.readHumidity();
  Serial.print("Humidity: ");
  Serial.println(h);
  // Read temperature as Fahrenheit (isFahrenheit = true)
  float c = dht.readTemperature();
  Serial.print("Temperature: ");
  Serial.println(c);

  // Check if any reads failed and exit early (to try again).
  if (isnan(h) || isnan(c)) {
    Serial.println("Reading DHT22 Failed, exiting");
    return;
  }

  //update ThingSpeak channel with new values
  updated = updateTemp(String(c), String(h));

   //wait for delay time before attempting to post again
  delay(DELAY_TIME);
}

bool updateTemp(String tempC, String humid) {
  //initialize your AT command string
  String cmd = "AT+CIPSTART=\"TCP\",\"";

  //add IP address and port
  cmd += IP;
  cmd += "\",80";

  //connect
  Serial.println(cmd);
  delay(2000);
  if (Serial.find("Error")) {
    return false;
  }

  //build GET command, ThingSpeak takes Post or Get commands for updates, I use a Get
  cmd = GET;
  cmd += tempC;
  cmd += FIELD2;
  cmd += humid;
  cmd += "\r\n";  

  //continue to add data here if you have more fields such as a light sensor
  //cmd += FIELD3;
  //cmd += <field 3 value>

  //Serial.println(cmd);
  //Use AT commands to send data
  Serial.print("AT+CIPSEND=");
  Serial.println(cmd.length());
  if (Serial.find(">")) {
    //send through command to update values
    Serial.print(cmd);
  } else {
    Serial.println("AT+CIPCLOSE");
  }

  if (Serial.find("OK")) {
    //success! Your most recent values should be online.
    Serial.println("Data Sent!");
    return true;
  } else {
    Serial.println("Data Fail!");
    Serial.println(cmd);
    return false;
  }
}

boolean connectWiFi() {
  //set ESP8266 mode with AT commands
  Serial.println("AT+CWMODE=1");
  delay(2000);

  //build connection command
  String cmd = "AT+CWJAP=\"";
  cmd += SSID;
  cmd += "\",\"";
  cmd += PASS;
  cmd += "\"";

  //connect to WiFi network and wait 5 seconds
  Serial.println(cmd);
  delay(5000);

  //if connected return true, else false
  if (Serial.find("OK")) {
    Serial.println("WIFI connected");
    return true;
  } else {
    Serial.println("WIFI not connected");
    return false;
  }
}


2
A question and two remarks: I could not find the part where data is sent via WiFi only Serial. On the ESPs (as well on Arduinos btw) never use delay. This stops processing (so all wifi/spiffs a.s.o) functions and for stability reasons never use the String class. It fractures your heap and crashes the ESP - use fixed char arrays which are compiled to the flash and use pointers for the const chars (i.e. command sequences, texts aso.) As a test: remove your ESP from the USB on PC and try to get data OTA (over the air)Codebreaker007
Re wifi - yes, it should be sending via serial TCP connection over wifi, ie this section: //Serial.println(cmd); //Use AT commands to send data Serial.print("AT+CIPSEND="); Serial.println(cmd.length()); if (Serial.find(">")) { //send through command to update values Serial.print(cmd); } else { Serial.println("AT+CIPCLOSE"); }Alphatester77
Re delay - interesting, never heard this before - indeed this is all from many tutorials who say you have to use delay to ensure it doesn't hammer a server etc? How should I build a delay in then?Alphatester77
Re test - will have a go with it detached from the PC and see what happens?Alphatester77

2 Answers

1
votes

First before we discuss of the problem you are facing, you need to understand the two different ways of using ESP8266:

  • Use it as a WiFi modem;
  • Use it as a stand-alone MCU.

When using it as a WiFi modem, you communicate with it using AT-command, and by default that is what most of ESP8266 module does, it shipped with At-command firmware.

When you using an ESP8266 as an MCU, you flash the ESP8266 with Arduino sketch, which overrides the AT-Command firmware and upload it with Arduino bootloader, and allow the ESP8266 to be used as a stand-alone MCU like an Arduino.

You mentioned that you be able to communicate with ESP8266 without any problem when using Arduino Uno, which suggests that you were communicating with the ESP8266 using AT-command (and part of your code suggested that this was the case).

When you upload an Arduino sketch to the ESP8266 to use it as a stand-alone MCU, you need to communicate with ESP8266 using Arduino library such as ESP8266WiFi which will take care of the lower level communication with the ESP8266 chip. This is why you be able to establish a WiFi by using the capability provided by ESP8266WiFi class.

However, your code in your updateTemp() is still using AT-command that no longer work. You will need either use ESP8266WebClient or ESP8266HTTPClient(this one is simpler and easier) library for establishing a http connection. Here is the code that I modified for using the ESP8266HTTClient library to handle the HTTP requests. BTW, the code compiled okay, but I have not test it with thinkspeak server as I don't use thinkspeak and don't have an api-key for it.

#include <DHT.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>

#define SSID "<REMOVED>" //your network name
#define PASS "<REMOVED>" //your network password
#define API "<REMOVED>" //api string
#define IP "api.thingspeak.com" // thingspeak.com
#define PORT 80
#define DHTPIN 4     // what pin the DHT sensor is connected to
#define DHTTYPE DHT22   // Change to DHT22 if that's what you have
#define BAUD_RATE 115200 //Another common value is 9600
#define DELAY_TIME 300000 //time in ms between posting data to ThingSpeak

DHT dht(DHTPIN, DHTTYPE);

//this runs once
void setup()
{
  Serial.begin(BAUD_RATE);

  // Connect to WIFI
  WiFi.begin(SSID, PASS);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print("*");
  }
  Serial.println("");
  Serial.println("Wifi Connection Successful");
  Serial.print("The IP Address of the Sensor is:");
  Serial.println(WiFi.localIP());

  //initalize DHT sensor
  dht.begin();
}

//this runs over and over
void loop() {
  float h = dht.readHumidity();
  Serial.print("Humidity: ");
  Serial.println(h);
  // Read temperature as Fahrenheit (isFahrenheit = true)
  float c = dht.readTemperature();
  Serial.print("Temperature: ");
  Serial.println(c);

  // Check if any reads failed and exit early (to try again).
  if (isnan(h) || isnan(c)) {
    Serial.println("Reading DHT22 Failed, exiting");
    return;
  }

  //update ThingSpeak channel with new values
  updateTemp(c, h);

  //wait for delay time before attempting to post again
  delay(DELAY_TIME);
}


bool updateTemp(float tempC, float humid) {

  WiFiClient client;    // Create a WiFiClient to for TCP connection

  if (!client.connect(IP, PORT)) {
   Serial.println("HTTP connection failed");
   return false;
  }

  Serial.println("Sending data to server");
  if (client.connected()) {
    client.print("GET /update?api_key="); client.print(API);
    client.print("&field1="); client.print(String(tempC));
    client.print("&field2="); client.print(String(humid));
    client.println(" HTTP/1.1");
    client.print("Host: "); client.println(IP);
    client.println("Connection: close");
    client.println();    //extra "\r\n" as per HTTP protocol
  }

  // wait for data to be available
  unsigned long timeout = millis();
  while (client.available() == 0) {
    if (millis() - timeout > 5000) {
     Serial.println("HTTP Client Timeout !");
     client.stop();
     return false;
    }
  }

  Serial.println("Receiving HTTP response");
  while (client.available()) {
   char ch = static_cast<char>(client.read());
   Serial.print(ch);
  }
  Serial.println();

  Serial.println("Closing TCP connection");
  client.stop();
  return true;
}
0
votes

There are three issues you have to tackle:
Get rid of delay() and I mean not that you should not call routines only from time to time to not hammer your server. The reason: The delay() functions stops the CPU for the amount of time specified.
OK in Setup for waiting for some HW to initialize and ok for quick test/debug insimple routines. BAD idea in a server/client scenario (where communication tasks run paralell to user tasks) and deadly on e.g. ESP32 with 2 Cpus. A short how to for your program.

unsigned long previousMillis = 0; 

setup() {
 ....
} 

loop(){
  if (millis() - previousMillis > DELAY_TIME) {   // we check for elapsed time

   // What ever needs to be done all DELAY_TIME ms  

   previousMillis  = millis(); // we "reset" the timer
   }
// other code which needs no delayed execution
}

Change also the code for Arduino and bring it to a new level-
Second point: Get rid of the String class and use fixed chars instead.
Reasonthe String class has bad memory managementand fractures your heap, which causes the ESPs to crash sooner or later, chars are compiled to flash and do not have this problem. A short example how to with one of your commands/functions:

// instead of #define API "<REMOVED>" //api String use
const char* API = "<REMOVED>" // pointer to the char API

//Can use a post also
char fixPartGET[128] = '\0';  // takes 255 chars and the terminator, if needed enlarge
char cmd[256] = '\0';  // takes 127 chars and the terminator, if needed enlarge
char numBuffer [16] = '\0'; // we use for float conversions
strcpy (fixPartGET, "GET /update?apikey="); // Copy empties
strcat (fixPartGET, API);
strcat (fixPartGET, "&field1=");
const char* FIELD2 = "&field2=";

setup(){
    ....
}

loop(){
    ....
}

bool updateTemp( float c, float h) {

  if (WiFi.status() != WL_CONNECTED) {
    return false;
  }

  //build GET command, ThingSpeak takes Post or Get commands for updates, I use a Get
  strcpy(cmd, fixPartGET); // start building the GET command
  // The dtostrf() function converts the double value passed in into an ASCII representation
  dtostrf(c, 6, 2, numBuffer); // change to what you need as precision
  strcat(cmd, numBuffer);
  strcat(cmd, FIELD2);
  dtostrf(h, 6, 2, numBuffer); // change to what you need as precision
  strcat(cmd, numBuffer);
  strcat(cmd, "\r\n");  

  //continue to add data here if you have more fields such as a light sensor
  //strcat(cmd, FIELD3);

  //Some other code to discuss
return true;
}

And as last point
WiFi communication with the AP -look into the examples of the WiFi library and safe the hassle with serial AT commands. At the moment these commands run over the USB cable and not the WiFi. The nodeMCU is a powerful machine compared to Arduino but much more prone to instability due to the technical concept. My proposedchanges 1 & 2 can and should be implemented on the Arduino as a first getting used to this methods. If this runs go for changing AT to WiFi client functionality on the NodeMCU. The ESP8266WiFi.h lib examples WiFiClientBasic and WiFiClient might help to get quick results (look for client.print(...) ).