ESP8266 + DS18B20 + web server

I have a bunch of DS18B20 sensors in my ventilation system. The measurements are used to generate stats and to adjust the heating. From my point of view, it is good to have a web server which is taking care of the data manipulation.

If you are interested in general Client-Server communication and web page generation using ESP8266 take a look at my older post: Client-server communication using ESP8266

The problem

I want to have all my temperature readings from DS18B20 in one place. This place is my remote server running database and website. For me, the easiest way to handle this was to use ESP8266 and send the data over WiFi.

Arduino IDE preparation

For the ease of programming and general ease of use, I decided to use NodeMCU instead of bare ESP8266. This way I can use Arduino IDE to compile and upload my sketches to the board.

The Arduino IDE needs to be configured for NodeMCU and ESP8266. Go to File -> Preferences and paste http://arduino.esp8266.com/stable/package_esp8266com_index.json in the Additional Boards Manager URLs field:

Once saved, go to Tools -> Board […] -> Boards Manager and enter NodeMCE in the search field, select the latest version and install.

Once installed, you can select your version of the NodeMCU board from the Tools -> Board […] menu.

Libraries

In order to work with the DS18B20 and WiFi, I installed libraries for OneWire communication protocol and Dallas Temperature sensors (DS18B20). In the Arduino IDE, you can install them by selecting the “Tools” menu and clicking on the Manage Libraries submenu.

For the first library, OneWire communication protocol, enter “onewire” in the search box and select the one that is developed by Jim Studt, Tom Pollard, and others. Select the latest version and click Install.

For the Dallas Temperature, in the search field enter “dallas temperature” and select the one that is developed by Miles Burton and others. Select the latest version and click Install.

How will my program work?

Once the board is powered on, I want it to obtain WiFi connection and to check for available temperature sensors. This is performed in the setup function which is executed once at the very beginning.

On the contrary, the loop function is executed over and over again “forever”. You can break the cycle by turning off the power or by pressing reset button on the board. In my loop function, I want the board to read the temperature from the sensors and send these readings to the server. After each cycle of reading and sending, I want the board to wait for some time before continuing.

Step by step – initial setup

At the very beginning there are libraries loaded and variables created:

#include <OneWire.h>
#include <DallasTemperature.h>
#include <ESP8266WiFi.h> 

// WiFi setup
const char* ssid = "SSID_of_the_network"; 
const char* password = "wifi_password";

// Receiver server setup
const char* receiverHost = "receiver.server.com";
const int httpGetPort = 80;
String receiverURL = "/receive.php";

// Data wire is plugged TO GPIO 4
#define ONE_WIRE_BUS 4

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

// Number of devices found
int numberOfDevices;

// Device addresses storage
DeviceAddress tempDeviceAddress; 

You should fill the WiFi related information (Update on 2021-02-17: check my new post on WiFi Manager for easy network management) and the information about the server. The script is prepared to send temperature readings to the server using GET calls. If your server is working under http://yourDomain.com/yourReceiverURL/ you should feel receiverHost with yourDomain.com and receiverURL with /yourReceiverURL/.

The setup function will take care of the WiFi connection and OneWire device detection:

void setup() {
  Serial.begin(115200);

  // Start up the library, grap a count of devices
  sensors.begin();
  numberOfDevices = sensors.getDeviceCount();
  
  // Loop through each device, print out address
  for(int i=0;i<numberOfDevices; i++){
    // Search the wire for address
    if(sensors.getAddress(tempDeviceAddress, i)){
      Serial.print("Found device ");
      Serial.print(i, DEC);
      Serial.print(" with address: ");
      printAddress(tempDeviceAddress);
      Serial.println();
    } else {
      Serial.print("Found ghost device at ");
      Serial.print(i, DEC);
      Serial.print(" but could not detect address. Check power and cabling");
    }
  }

  // WIFI configuration
  delay(10);
  WiFi.hostname("ESPboard-temperature-receiver");
  WiFi.begin(ssid, password); // Connect to the network
  Serial.print("Connecting to ");
  Serial.print(ssid); Serial.println(" ...");

  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {    
    // Wait for the Wi-Fi to connect
    delay(1000);
    Serial.print(++i); Serial.print('.');
  }

  Serial.println('\n');
  Serial.println("Connection established!");  
  Serial.print("IP address:\t");
  Serial.println(WiFi.localIP());
}

There are various debug information provided by Serial connection so you can review them in Arduino IDE while testing your solution. Once you will have your solution working fine, you can get rid of them. On the other hand, they do no harm to the board nor to the code so you can leave them for future use.

Utility functions

There are three utility functions, two small ones:

// print device address in human readable form
void printAddress(DeviceAddress deviceAddress) {
  for (uint8_t i = 0; i < 8; i++){
    if (deviceAddress[i] < 16) Serial.print("0");
      Serial.print(deviceAddress[i], HEX);
  }
}

// convert device address to string
String convertAddressToString(DeviceAddress deviceAddress) {
  String addrToReturn = "";
  for (uint8_t i = 0; i < 8; i++){
    if (deviceAddress[i] < 16) addrToReturn += "0";
    addrToReturn += String(deviceAddress[i], HEX);
  }
  return addrToReturn;
}

The first one is printing an address of the device as a readable HEX string – it is also used when sending data to the server instead of the raw numeric value. The second one is converting an address to the string and returning the string instead of printing it.

Sending the data to the server

The main part of the program is the function which is taking care of actual data sending:

// send data to the receiver host
void postData() {
  WiFiClient clientGet;
  // create URL and set parameters
  String src = "ESP";
  String getReceiverURLtemp = receiverURL + "?src=" + src;

  // Send the command to get temperatures
  sensors.requestTemperatures(); 
  
  // Loop through each device, collect temperature data
  for(int i=0;i<numberOfDevices; i++){
    // Search the wire for address
    if(sensors.getAddress(tempDeviceAddress, i)){
      float tempC = sensors.getTempC(tempDeviceAddress);
      getReceiverURLtemp = getReceiverURLtemp + "&d" + i + "=" + convertAddressToString(tempDeviceAddress) + "&d" + i + "t=" + tempC;
      Serial.println(getReceiverURLtemp);
    }
  }

  Serial.println("-------------------------------");
  Serial.print(">>> Connecting to host");
  
  if (!clientGet.connect(receiverHost, httpGetPort)) {
    Serial.print("Connection failed");
  } else {
    clientGet.println("GET " + getReceiverURLtemp + " HTTP/1.1");
    clientGet.print("Host: ");
    clientGet.println(receiverHost);
    clientGet.println("User-Agent: ESP8266/1.0");
    clientGet.println("Connection: close\r\n\r\n");
    
    unsigned long timeoutP = millis();
    while (clientGet.available() == 0) {
      if (millis() - timeoutP > 10000) {
        Serial.print(">>> Client Timeout");
        clientGet.stop();
        return;
      }
    }
    
    // check the first line of the response, just in case
    while(clientGet.available()){
      String retLine = clientGet.readStringUntil('\r');
      Serial.print(">>> Host returned: ");
      Serial.println(retLine);
      
      if (retLine == "HTTP/1.1 200 OK") {
        Serial.println(">>> Communication successful");
      } else {
        Serial.println(">>> Communication failed!!!");
      }
      break; 
    }
  } 
                        
  Serial.print(">>> Closing host");         
  clientGet.stop();
}

The function is creating a connection to the server, collects the temperature measurements and sends them all along with the device addresses in GET string (in the URL of the request).

The sample URL looks like this:

/receive.php?src=ESPTemperature&d0=10f4269c0108001f&d0t=2.87&d1=10a5049c0108007d&d1t=21.19

There is source information followed by the address and temperature of each sensor. The d0 variable and d1 variable are handling addresses, d0t and d1t are handling temperature readings in Celcius.

The main loop

Last but not least, the main loop function:

void loop() {
  sensors.requestTemperatures(); // Send the command to get temperatures
  
  // Loop through each device, print out temperature data
  for(int i=0;i<numberOfDevices; i++){
    // Search the wire for address
    if(sensors.getAddress(tempDeviceAddress, i)){
      // Output the device ID
      Serial.print("Temperature for device: ");
      Serial.println(convertAddressToString(tempDeviceAddress));
      // Print the data
      float tempC = sensors.getTempC(tempDeviceAddress);
      Serial.print("Temp C: ");
      Serial.println(tempC);
    }
  }
  postData();
  delay(60*1000);
}

In my case, the function is reading temperatures, sends them through the serial port for debug purposes and later calls the postData function which is taking care of the server communication. It is also configured to wait one second in each cycle. Of course, you can get rid of the debugging code, leaving the function in minimal form:

void loop() {
  postData();
  delay(60*1000);
}

I strongly recommend leaving the serial communication in place. From time to time there is a need to check what is going on with the board and it is much easier to do if you have your debug code in place.

The whole program

Below you can review the whole program in one piece. Even today when I’m looking at the code I’m able to find things that should be adjusted, but it is working as is. Most likely I will spend some time adjusting it in the future and I will post the update once adjusted.

#include <OneWire.h>
#include <DallasTemperature.h>
#include <ESP8266WiFi.h> 

// WiFi setup
const char* ssid = "SSID_of_the_network"; 
const char* password = "wifi_password";

// Receiver server setup
const char* receiverHost = "receiver.server.com";
const int httpGetPort = 80;
String receiverURL = "/receive.php";

// Data wire is plugged TO GPIO 4
#define ONE_WIRE_BUS 4

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

// Number of devices found
int numberOfDevices;

// Device addresses storage
DeviceAddress tempDeviceAddress; 

void setup() {
  Serial.begin(115200);

  // Start up the library, grap a count of devices
  sensors.begin();
  numberOfDevices = sensors.getDeviceCount();
  
  // Loop through each device, print out address
  for(int i=0;i<numberOfDevices; i++){
    // Search the wire for address
    if(sensors.getAddress(tempDeviceAddress, i)){
      Serial.print("Found device ");
      Serial.print(i, DEC);
      Serial.print(" with address: ");
      printAddress(tempDeviceAddress);
      Serial.println();
    } else {
      Serial.print("Found ghost device at ");
      Serial.print(i, DEC);
      Serial.print(" but could not detect address. Check power and cabling");
    }
  }

  // WIFI configuration
  delay(10);
  WiFi.hostname("ESPboard-temperature-receiver");
  WiFi.begin(ssid, password); // Connect to the network
  Serial.print("Connecting to ");
  Serial.print(ssid); Serial.println(" ...");

  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {    
    // Wait for the Wi-Fi to connect
    delay(1000);
    Serial.print(++i); Serial.print('.');
  }

  Serial.println('\n');
  Serial.println("Connection established!");  
  Serial.print("IP address:\t");
  Serial.println(WiFi.localIP());
}

// print device address in human readable form
void printAddress(DeviceAddress deviceAddress) {
  for (uint8_t i = 0; i < 8; i++){
    if (deviceAddress[i] < 16) Serial.print("0");
      Serial.print(deviceAddress[i], HEX);
  }
}

// convert device address to string
String convertAddressToString(DeviceAddress deviceAddress) {
  String addrToReturn = "";
  for (uint8_t i = 0; i < 8; i++){
    if (deviceAddress[i] < 16) addrToReturn += "0";
    addrToReturn += String(deviceAddress[i], HEX);
  }
  return addrToReturn;
}

// send data to the receiver host
void postData() {
  WiFiClient clientGet;
  // create URL and set parameters
  String src = "ESP";
  String getReceiverURLtemp = receiverURL + "?src=" + src;

  // Send the command to get temperatures
  sensors.requestTemperatures(); 
  
  // Loop through each device, collect temperature data
  for(int i=0;i<numberOfDevices; i++){
    // Search the wire for address
    if(sensors.getAddress(tempDeviceAddress, i)){
      float tempC = sensors.getTempC(tempDeviceAddress);
      getReceiverURLtemp = getReceiverURLtemp + "&d" + i + "=" + convertAddressToString(tempDeviceAddress) + "&d" + i + "t=" + tempC;
      Serial.println(getReceiverURLtemp);
    }
  }

  Serial.println("-------------------------------");
  Serial.print(">>> Connecting to host");
  
  if (!clientGet.connect(receiverHost, httpGetPort)) {
    Serial.print("Connection failed");
  } else {
    clientGet.println("GET " + getReceiverURLtemp + " HTTP/1.1");
    clientGet.print("Host: ");
    clientGet.println(receiverHost);
    clientGet.println("User-Agent: ESP8266/1.0");
    clientGet.println("Connection: close\r\n\r\n");
    
    unsigned long timeoutP = millis();
    while (clientGet.available() == 0) {
      if (millis() - timeoutP > 10000) {
        Serial.print(">>> Client Timeout");
        clientGet.stop();
        return;
      }
    }
    
    // check the first line of the response, just in case
    while(clientGet.available()){
      String retLine = clientGet.readStringUntil('\r');
      Serial.print(">>> Host returned: ");
      Serial.println(retLine);
      
      if (retLine == "HTTP/1.1 200 OK") {
        Serial.println(">>> Communication successful");
      } else {
        Serial.println(">>> Communication failed!!!");
      }
      break; 
    }
  } 
                        
  Serial.print(">>> Closing host");         
  clientGet.stop();
}



void loop() {
  sensors.requestTemperatures(); // Send the command to get temperatures
  
  // Loop through each device, print out temperature data
  for(int i=0;i<numberOfDevices; i++){
    // Search the wire for address
    if(sensors.getAddress(tempDeviceAddress, i)){
      // Output the device ID
      Serial.print("Temperature for device: ");
      Serial.println(convertAddressToString(tempDeviceAddress));
      // Print the data
      float tempC = sensors.getTempC(tempDeviceAddress);
      Serial.print("Temp C: ");
      Serial.println(tempC);
    }
  }
  postData();
  delay(60*1000);
}