Internet of Things with the ESP8266

The ESP8266 SDK comes with a very useful set of examples in the directory IoT_Demo.  With it I built and operated some useful devices, namely the DHT11 and DHT22 temperature and humidity sensor and the BME280 temperature, humidity, and pressure sensor.  This latter device has an i2c interface.  The data is sent to my account at iot.espressif.cn where it can be accessed with a browser, or can be accessed using curl commands with json data from any other client.  For the latter one can also use a python script to make this easy.

Development Environment

All development was done in Oracle VM Virtualbox on a Win7 machine using the Espressif ubuntu bundle:  Linux esp8266-VirtualBox 3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:12 UTC 2014 i686 i686 i686 GNU/Linux.  Device communication was done with an USB UART with 3.3 volts out and I used the serial console in PUTTY on the Win7 machine.

Hardware

The devices are the esp-12F (ESP8266MOD) mounted to a self-designed PCB that plugs directly into an USB 5 volt wall charger, thus eliminating any dangling power cords.  The PCB has a one amp 5 volt to 3.3 volt regulator along with reset button.  It also has a pin header that accomodates the UART connection as well as GPIO pins.  Sensors mount to a daughter board that plugs into the pin header.  Except for the daughter board, the device is enclosed in a 3D printed enclosure with cover that has been modified from a design found at Thingiverse.  All of this is seen in the image below.

20191211_125900-1
DHT11 sensor mounted to the daughter board that plugs into the device.

Project Flow

This project was actually a logical series of individual projects where each project built on the previous one.  It began with building the IoT_Demo defining the “light” application, except there was no LED light sensor attached initially.  This allowed me to work through a variety of issues. Once this was working, the LED daughter board was attached and complete communication with the cloud account was accomplished.  Then the project changed with the addition of code and code changes to send DHT11 and DHT22 sensor data to the cloud.  Finally, code addition and code changes were incorporated to send BME280 sensor data to the cloud.  This sensor, unlike the DHT sensors, connects to the ESP8266 by i2C.  This resulted in devices of all three types seen here:

all-three.jpg
All three IoT devices.

Note that the temperature/humidity devices are tethered to a flex cable so they are not affected by the heating from the ESP8266 device and at the same time can be more easily be placed in the environment that is to be measured.  The following descriptions are in order of the project flow.

Building the IoT Demo for LIGHT_DEVICE

As mentioned, getting the project built and communicating with my cloud account at iot.espressif.cn required working through several issues.  To accomplish this the “user_light” project was built since I could build and operate the device and add the tri-color LED daughter board later.  Sending color commands to the device does not require the sensor to be present.  The following is a step-by-step description of getting this project running successfully.

  1. First step is to set in “user_config.h” the #define LIGHT_DEVICE to 1 and make sure that #define ESP_PLATFORM is also set to 1.  Lastly set  #define ESP_DOMAIN “iot.espressif.cn”.  This is the URL where the cloud account exists. (One can also create an account at “iot.espressif.com”. This is a different server.)
  2. Then in  “user_light.h” set PRIV_PARAM_START_SEC  0xFC and in “user_esp_platform.h” set  ESP_PARAM_START_SEC 0xFD
  3. This next step fixes one of the issues encountered and usually only needs to be done once.  The linker script found at ESP8266_NONOS_SDK/ld/eagle.app.v6.ld will likely have a default value for the length of the application’s binary eagle.irom0text.bin that is too small.  To fix this problem set the value indicated in the image:
    linker.jpg
    The value with the red box is large enough for the various IoT projects.  Note that the value with the blue box is the origin of where eagle.irom0text.bin will be located.
  4. Choose selections in “gen_misc.sh”:
    1. STEP 1: choose boot version(0=boot_v1.1, 1=boot_v1.2+, 2=none)
      enter(0/1/2, default 2): 2
    2. STEP 2: choose bin generate(0=eagle.flash.bin+eagle.irom0text.bin, 1=user1.bin, 2=user2.bin)
      enter (0/1/2, default 0):   0
    3. STEP 3: choose spi speed(0=20MHz, 1=26.7MHz, 2=40MHz, 3=80MHz)
      enter (0/1/2/3, default 2):  2
    4. STEP 4: choose spi mode(0=QIO, 1=QOUT, 2=DIO, 3=DOUT)
      enter (0/1/2/3, default 0):  0
    5. STEP 5: choose spi size and map
      0= 512KB( 256KB+ 256KB)
      2=1024KB( 512KB+ 512KB)
      3=2048KB( 512KB+ 512KB)
      4=4096KB( 512KB+ 512KB)
      5=2048KB(1024KB+1024KB)
      6=4096KB(1024KB+1024KB)
      7=4096KB(2048KB+2048KB) not support ,just for compatible with nodeMCU board
      8=8192KB(1024KB+1024KB)
      9=16384KB(1024KB+1024KB)
      enter (0/2/3/4/5/6/7/8/9, default 0):  6 (we have 4MB flash size and want 1024KB partitions)
  5. Before building the project and flashing a device, one normally at this point creates the device at the account at iot.espressif.cn and downloads a master key bin file that is flashed to the device.  Without it the device cannot be activated at the cloud account, but will still operate in softAP mode and can be sent curl commands directly.  How to create an account and create devices at the cloud server can be found on the server itself.
  6. Once “gen_misc.sh” has been run the project specific files are found in the bin/ directory, namely eagle.flash.bin and eagle.irom0text.bin.  The flash tool is now used to flash the device.  In the following please note the addresses where the various files are to be located in flash.  (Also note that in the image the application specific files have been moved out of the VM Virtualbox to a directory called firmware on the Win7 machine where also sit the files blank.bin and esp_init_data_default.bin.  The flash tool in installed on the Win7 machine and therefore all files must sit on that machine.)
    flash_tool
    The second issue encountered is that devices can have their flash memory wired either QI0 or DI0.  If they are not wired for QI0, then the flash will proceed to a successful conclusion but the device will not boot successfully.  Also note that the build and the flash have to be the same. If built as QI0 but the flashtool is DIO, the boot will also fail.  So, in gen_misc.sh change STEP 4 accordingly as well as the selection in the flashtool.
  7. The final step is to allow the device to self-activate at the cloud account by sending a curl command to switch the device from softAP mode to station mode along with data for your local WIFI’s SSID and password.  Recall that the device already has burned in ROM the URL of the cloud set in 1 above as well as the master key.  The following command was sent from an Ubuntu 16.04 machine (note that data is sent in JSON form and the number for “token” is just an arbitrary number):
  8. curl -X POST -H “Content-Type:application/json” -d ‘{“Request”:{“Station”:{“Connect_Station”:{“ssid”:”yourSSID”, “password”:”yourpassword”, “token”:”3214567890123456789012345678901234567123″}}}}’ http://192.168.4.1/config?command=wifi
  9. Once the curl command is received the device will change to station mode, will connect to “yourSSID”, will become connected to the internet, and then will send an “activate” request to the cloud server using the master key as the authorization token.  At the cloud the device will show it was activated:
    device_activated
    After activation the device will always boot up in station mode and connect to the cloud server.  At this point everything is working and the only thing that remains is to connect the daughter board with the tri-color LED and begin sending curl commands to control it.

LIGHT_DEVICE Operation

The tri-color LED that was purchased was already mounted on a small PCB with a pin header.  As seen in the photo a daughter board was fashioned into which the LED board plugged and in turn plugged into the pin header of the ESP8266 device.

light_board
Tri-color LED mounted to daughter board.

Port Pins and Set Up

The port pins for Red Blue Green are PWM driven.  The set up for this is depicted completely in the image below.  Note that there are four pins driven by the PWM interface but only three are used.  On the left note the definitions that are found in “user_light.h”.

IoT_Light_Ports

Operation and How Light Settings Actually Work

Now operation of the light setting is done via curl commands.  But before we begin we have to fix another of those issues that have been mentioned.  Initial testing showed that the settings for green were always 0 when the device was monitored with the UART.  It was found that in the function user_esp_platform_set_info in “user_esp_platform.c” the parsing of “z” is incorrect since the contents of pdata for “z” do not end with a “,”. Rather “z” is the last data point and the pointer pbuf was changed to:

//                pbuf = (char *)os_strchr(pstr, ',');
                pbuf =  pdata +  os_strlen(pdata);

One other issue was found, and this was a problem with the document 2b-esp8266_sdk_iot_demo_en_v1.3.  On page 19 it states the “x” is frequency, range 1 – 500, and “y” (red), “z” (green), and “k” (blue) have a range of 0 – 255.  This is not quite right and in fact wrong. The truth is much more complicated. These settings did not get full brightness from the LED.  When the device boots the LED comes on much brighter.  By monitoring the device during boot the settings are:

LIGHT PARAM: R: -1
LIGHT PARAM: G: -1
LIGHT PARAM: B: -1
LIGHT PARAM: CW: -1
LIGHT PARAM: WW: -1
LIGHT PARAM: P: 1000
malloc:1
prd:1000  r : 22222  g: 22222  b: 22222  cw: 22222  ww: 22222

These numbers bear no relationship to what is stated in the document. At first this greatly confused me. Where is the frequency, supposedly the value of x? Clearly P and prd refer to period, not frequency. And what is the meaning of the r, g, b values that default to this particular value of 22222? In order to get clear answers to these questions, a series of curl commands that set the light parameters were sent to a device and the red channel was observed on a DSO(digital oscilloscope). The below example sends x=1000 and y=11111.

command:
curl -g -H "Content-Type:application/json" -H "Authorization: token 7dcc6782a15601308e069707233356b8a025ca21" -d '{"datapoint": {"x": 1000, "y": 11111, "z": 0, "k": 0, "l": 50 }}' http://iot.espressif.cn/v1/datastreams/light/datapoint/?deliver_to_device=true
response:
{"datapoint": {"id": 29597176, "created": "2019-12-18 02:22:34", "updated": "2019-12-18 02:22:34", "visibly": 1, "datastream_id": 19689, "datatype": 0, "at": "2019-12-18 02:22:34", "x": 1000.00000, "y": 11111.00000, "z": 0.00000, "k": 0.00000, "l": 50.00000}, "nonce": 320610282, "status": 200}
On time is 500 uSec. Period is ~1100 uSec.

After series of measurements it was found that x is the period and y is 22.222 times the ON time in microseconds. These settings are independent from each other. Therefore, it cannot be said that the y is the duty cycle since that is a percent of the period value. y sets the absolute ON time, so long as the period is long enough. If the period is shortened below the value of ON time set by y, then the ON time becomes fixed as the value of the period. For y to again control then its value has to be accordingly lowered. One other point: y, z, and k values clamp at 22222 even though a larger value may be sent. At the cloud account the larger value is reported but the actual value on the device remains at 22222.

On time setting clamps at 22222

One last point. When the device is connected to the cloud and a client send as curl command interrogating the device parameters, the cloud always reports the values that were last sent to the device. Therefore, when the device boots up anew, it boots to the default values described above and does not report these values to the cloud. So, both client and cloud do not have information of the values of the newly booted device.

DHT11/DHT22 Temperature/Humidity Sensor

To use this sensor a number of changes and additions are made. One of the first things to do is get the necessary source and include files for the sensor here. Put the respective files in the directories driver/ and include/driver/. The uart function is not used.

Changes and Additions

In “user_config.h” change out the light device for the sensor device and humiture sub device.

#define ESP_PLATFORM        1
#define LEWEI_PLATFORM      0

#define USE_OPTIMIZE_PRINTF

#if ESP_PLATFORM
#define PLUG_DEVICE             0
#define LIGHT_DEVICE            0
#define SENSOR_DEVICE			1

#if SENSOR_DEVICE
#define HUMITURE_SUB_DEVICE         1
#define FLAMMABLE_GAS_SUB_DEVICE    0
#endif

Now for the changes, which are almost all in user_esp_platform.c . Add at the beginning of the file:

#include dht11.h

DHT_Sensor sensor;

Change UPLOAD_FRAME (note that the format for “x” and “y” is changed).

#define UPLOAD_FRAME  "{\"nonce\": %d, \"path\": \"/v1/datastreams/tem_hum/datapoint/\", \"method\": \"POST\", \
\"body\": {\"datapoint\": {\"x\": %s%d.%d,\"y\": %d.%d}}, \"meta\": {\"Authorization\": \"token %s\"}}\n"

In the function esp_platform_init(void) comment out as shown and add as shown. Pin 7 maps to GPIO13.

#elif SENSOR_DEVICE
//    user_sensor_init(esp_param.activeflag);
    sensor.pin = 7;
    sensor.type = DHT11;
    DHTInit( &sensor);

In user_esp_platform_sent make the additions and changes indicated in the following. Note what is added and what is commented out.

LOCAL void ICACHE_FLASH_ATTR
user_esp_platform_sent(struct espconn *pespconn)
{
    uint8 devkey[token_size] = {0};
    uint32 nonce;
    char *pbuf = (char *)os_zalloc(packet_size);

    /*add*/
    DHT_Sensor_Data results;
    /*end add*/

    os_memcpy(devkey, esp_param.devkey, 40);
    if (esp_param.activeflag == 0xFF) {
        esp_param.activeflag = 0;
    }
    if (pbuf != NULL) {
        if (esp_param.activeflag == 0) {
            uint8 token[token_size] = {0};
            uint8 bssid[6];
            active_nonce = os_random() & 0x7FFFFFFF;
            os_memcpy(token, esp_param.token, 40);
            wifi_get_macaddr(STATION_IF, bssid);
            os_sprintf(pbuf, ACTIVE_FRAME, active_nonce, token, MAC2STR(bssid),iot_version, devkey);
        }

#if SENSOR_DEVICE
#if HUMITURE_SUB_DEVICE
        else {
#if 0
            uint16 tp, rh;
            uint8 data[4];
            if (user_mvh3004_read_th(data)) {
                rh = data[0] << 8 | data[1];
                tp = data[2] << 8 | data[3];
            }
#else
            uint16 tp, rh;
            uint8 *data;
            uint32 tp_t, rh_t;
            char buff[20];

            /*add*/
            float tpp, rhh;
            /*end add*/

            data = (uint8 *)user_mvh3004_get_poweron_th();
            rh = data[0] << 8 | data[1];
            tp = data[2] << 8 | data[3];

            /*add*/
            DHTRead(&sensor, &results);
            os_printf("Temperature: %s C\r\n", DHTFloat2String(buff, results.temperature));
            os_printf("Humidity: %s %%\r\n", DHTFloat2String(buff, results.humidity));
            tpp = results.temperature;
            rhh = results.humidity;
            /*end add*/

#endif
            tp_t = (tp >> 2) * 165 * 100 / (16384 - 1);
            rh_t = (rh & 0x3fff) * 100 * 100 / (16384 - 1);

            /*add*/
            os_sprintf(pbuf, UPLOAD_FRAME, count, "", (int)tpp, (int)(tpp - (int)tpp)*100, (int)rhh, (int)(rhh - (int)rhh)*100, devkey);
            /*end add*/

/*begin comment out*/
/*
            if (tp_t >= 4000) {
                os_sprintf(pbuf, UPLOAD_FRAME, count, "", tp_t / 100 - 40, tp_t % 100, rh_t / 100, rh_t % 100, devkey);
            } else {
                tp_t = 4000 - tp_t;
                os_sprintf(pbuf, UPLOAD_FRAME, count, "-", tp_t / 100, tp_t % 100, rh_t / 100, rh_t % 100, devkey);
            }*/
/*end comment out*/
        }

In the file gpio16.h comment out #define GPIO_INTERRUPT_ENABLE. Interrupts are not used and enabling them will cause a compiler error due to a bug in the code.

In dht22.c the following replacement must be made – sleepms(250) is not permissible:

     //argument is uint16 - not exceed 65
	sleepms(63);
        sleepms(63);
        sleepms(64);

The next and last change is a temporary one and its explanation will clear up some possible confusion. This was another issue found. Initially in user_config.h one should comment out #define SENSOR_DEEP_SLEEP. For the device to wake up from deep sleep, the RST pin must be jumpered to connect to the GPIO16 pin. The jumper from RST to GPIO16 must be added after flashing and activating. Otherwise it will not flash or boot properly. With the jumper in place I found the device would not flash. Also it was unresponsive to the reset button given that the LED did not flash upon RST. It appears that the full code needs to be executing before the behavior is proper. The full code does not execute until after the device activates. Therefore with DEEP_SLEEP commented out the os_timer takes over and the device reconnects and sends data every 1000 milliseconds. So, after debugging the code and activating the device at the cloud successfully, one adds back #define SENSOR_DEEP_SLEEP, rebuilds the code, flashes the device, and then adds the jumper. The device will now operate where it sleeps, wakes up, reconnects, and sends data every 30 seconds. This is a desireable mode of operation because the device uses little energy when sleeping. This is a possible mode for battery operation.

Activation and Operation

At the account at iot.espressif.cn create the device, download the master key bin file, flash it to 0xfe000, and then from any client send the curl command described above for the light device in step 8.

Two units were commissioned, one with DHT11 sensor and the other with the DHT22 sensor. The main apparent difference between the two is the latter has another decimal point of precision. Here is shown the DHT22 active at the cloud:

Temperture and Humidity Sensor

The BME280 Temperature, Humidity, and Pressure Sensor

This sensor is made by Bosch and information about it is found here. To get the drivers that were used in this project download them from here. To begin remove the drivers for the DHT11 sensor from the driver/ directory and copy to the directory only the two files shown, leaving the others as is. The directory should appear as shown:

drwxrwxr-x 3 esp8266 esp8266  4096  3月 19  2019 ./
drwxrwxr-x 6 esp8266 esp8266  4096  3月 30  2019 ../
-rw-rw-r-- 1 esp8266 esp8266  8174  3月 19  2019 bme280.c   //copy to here
-rw-rw-r-- 1 esp8266 esp8266  2965  3月 19  2019 i2c.c      //copy to here
-rwxrwxr-x 1 esp8266 esp8266 10344  3月 19  2019 i2c_master.c //leave original file
-rwxrwxr-x 1 esp8266 esp8266  7205 11月  4  2017 key.c  //leave original file
-rwxrwxr-x 1 esp8266 esp8266  1446 11月  4  2017 Makefile*
drwxrwxr-x 3 esp8266 esp8266  4096  2月 28  2018 .output/

In the include/driver/ directory also copy only the two files shown:

drwxrwxr-x 2 esp8266 esp8266 4096  3月 19  2019 ./
drwxrwxr-x 4 esp8266 esp8266 4096  3月 19  2019 ../
-rw-rw-r-- 1 esp8266 esp8266 3236  3月 18  2019 bme280.h //copy to here
-rw-rw-r-- 1 esp8266 esp8266 1716  3月 18  2019 i2c.h  //copy to here
-rwxrwxr-x 1 esp8266 esp8266 1905  3月 18  2019 i2c_master.h
-rwxrwxr-x 1 esp8266 esp8266 1824 11月  4  2017 key.h

One last item. The file i2c_master.h has been changed a bit in that GPIO13 is defined as the SDA line and GPIO14 is defined as the SDL line. Here it is in its entirety:

#ifndef __I2C_MASTER_H__
#define __I2C_MASTER_H__

#include "c_types.h"

//#define I2C_MASTER_SDA_MUX PERIPHS_IO_MUX_GPIO2_U
#define I2C_MASTER_SDA_MUX PERIPHS_IO_MUX_MTCK_U
#define I2C_MASTER_SDA_GPIO 13
//#define I2C_MASTER_SDA_GPIO 2
//#define I2C_MASTER_SDA_FUNC FUNC_GPIO2
#define I2C_MASTER_SDA_FUNC FUNC_GPIO13

#define I2C_MASTER_SCL_MUX PERIPHS_IO_MUX_MTMS_U
#define I2C_MASTER_SCL_GPIO 14
#define I2C_MASTER_SCL_FUNC FUNC_GPIO14

//#define I2C_MASTER_SCL_MUX PERIPHS_IO_MUX_GPIO0_U
//#define I2C_MASTER_SCL_GPIO 0
//#define I2C_MASTER_SCL_FUNC FUNC_GPIO0

#if 0
#define I2C_MASTER_GPIO_SET(pin)  \
    gpio_output_set(1<<pin,0,1<<pin,0)

#define I2C_MASTER_GPIO_CLR(pin) \
    gpio_output_set(0,1<<pin,1<<pin,0)

#define I2C_MASTER_GPIO_OUT(pin,val) \
    if(val) I2C_MASTER_GPIO_SET(pin);\
    else I2C_MASTER_GPIO_CLR(pin)
#endif

#define I2C_MASTER_SDA_HIGH_SCL_HIGH()  \
    gpio_output_set(1<<I2C_MASTER_SDA_GPIO | 1<<I2C_MASTER_SCL_GPIO, 0, 1<<I2C_MASTER_SDA_GPIO | 1<<I2C_MASTER_SCL_GPIO, 0)

#define I2C_MASTER_SDA_HIGH_SCL_LOW()  \
    gpio_output_set(1<<I2C_MASTER_SDA_GPIO, 1<<I2C_MASTER_SCL_GPIO, 1<<I2C_MASTER_SDA_GPIO | 1<<I2C_MASTER_SCL_GPIO, 0)

#define I2C_MASTER_SDA_LOW_SCL_HIGH()  \
    gpio_output_set(1<<I2C_MASTER_SCL_GPIO, 1<<I2C_MASTER_SDA_GPIO, 1<<I2C_MASTER_SDA_GPIO | 1<<I2C_MASTER_SCL_GPIO, 0)

#define I2C_MASTER_SDA_LOW_SCL_LOW()  \
    gpio_output_set(0, 1<<I2C_MASTER_SDA_GPIO | 1<<I2C_MASTER_SCL_GPIO, 1<<I2C_MASTER_SDA_GPIO | 1<<I2C_MASTER_SCL_GPIO, 0)

void i2c_master_gpio_init(void);
void i2c_master_init(void);

#define i2c_master_wait    os_delay_us
void i2c_master_stop(void);
void i2c_master_start(void);
void i2c_master_setAck(uint8 level);
uint8 i2c_master_getAck(void);
uint8 i2c_master_readByte(void);
void i2c_master_writeByte(uint8 wrdata);

bool i2c_master_checkAck(void);
void i2c_master_send_ack(void);
void i2c_master_send_nack(void);

#endif

Changes and Addition to Switch to the BME280 Sensor

This is similar to the DHT11/22 sensors in that most of the changes are in user_esp_platform.c. To begin, in esp_platform_init(void) make the following changes:

#elif SENSOR_DEVICE
//    user_sensor_init(esp_param.activeflag);
//    sensor.pin = 7;
//    sensor.type = DHT22;
//    DHTInit( &sensor)

        /*add*/
        i2c_master_gpio_init();
        if(BME280_Init(BME280_OS_T_16, BME280_OS_P_16, BME280_OS_H_16,
                                        BME280_FILTER_16, BME280_MODE_NORMAL, BME280_TSB_05))
                ESP_DEBUG("BMP180 init error.\r\n");
        else {
                ESP_DEBUG("BMP180 init done.\r\n");
        }
        /*end add*/

In user_esp_platform_sent(struct espconn *pespconn) make these changes:

        /*add*/
        sint32 temperature;
        uint32 pressure, humidity;
        char data[100];
        /*end add*/

            uint16 tp, rh;
//            uint8 *data;
            uint32 tp_t, rh_t;
            char buff[20];
            float tpp, rhh;
//            data = (uint8 *)user_mvh3004_get_poweron_th();

//            rh = data[0] << 8 | data[1];
//            tp = data[2] << 8 | data[3];
//            DHTRead(&sensor, &results);
        os_printf("now read sensor\r\n");

        /*add*/
        if(BME280_ReadAll(&temperature, &pressure, &humidity))
                os_printf("Sensor read error!\r\n");
        else
        {
                os_sprintf(data, "Temperature: %d.%02u C, Humidity: %u.%02u rH, Pressure: %u.%02u hPa",
                                        temperature / 100, temperature % 100,                              //C
                                        humidity >> 10, ((humidity & 0x000003FF) * 100) >> 10,        //rH
                                        //pressure >> 8, ((pressure & 0x000000FF) * 100) >> 8,          //Pa
                                   (pressure >> 8) / 100, (pressure >> 8) % 100);                          //hPa
                os_printf("Temperature: %d.%02u C, Humidity: %u.%02u rH, Pressure: %u.%02u hPa",
                                        temperature / 100, temperature % 100,                               //C
                                        humidity >> 10, ((humidity & 0x000003FF) * 100) >> 10,          //rH
                                        //pressure >> 8, ((pressure & 0x000000FF) * 100) >> 8,            //Pa
                                        (pressure >> 8) / 100, (pressure >> 8) % 100); 
                os_printf("\r\n");
        }
        /*end add*/

//            os_printf("Temperature: %s C\r\n", DHTFloat2String(buff, results.temperature));
//            os_printf("Humidity: %s %%\r\n", DHTFloat2String(buff, results.humidity));
//            tpp = results.temperature;
//            rhh = results.humidity;
#endif
            tp_t = (tp >> 2) * 165 * 100 / (16384 - 1);
            rh_t = (rh & 0x3fff) * 100 * 100 / (16384 - 1);

             /*add*/
             os_sprintf(pbuf, UPLOAD_FRAME, count, "", temperature/100, temperature%100, humidity>>10, ((humidity & 0x000003FF) * 100) >> 10,
                    (pressure >> 8) / 100, (pressure >> 8) % 100,  devkey);
            /*end add*/

//            os_sprintf(pbuf, UPLOAD_FRAME, count, "", (int)tpp, (int)(tpp - (int)tpp)*100, (int)rhh, (int)(rhh - (int)rhh)*100, devkey);

At the beginning of user_esp_platform.c change UPLOAD_FRAME by adding the “z” parameter:

#if HUMITURE_SUB_DEVICE
#define UPLOAD_FRAME  "{\"nonce\": %d, \"path\": \"/v1/datastreams/tem_hum/datapoint/\", \"method\": \"POST\", \
\"body\": {\"datapoint\": {\"x\": %s%d.%d,\"y\": %d.%d,\"z\": %d.%d}}, \"meta\": {\"Authorization\": \"token %s\"}}\n"

Lastly, comment out #define SENSOR_DEEP_SLEEP. Instead the timer in user_esp_platform_recon_cb will define the timing of the reconnect cycle with the cloud. Therefore, the device can be activated normally without the issue of connecting RST to GPIO16 as with the DHT11/22 sensors.

Build, Activate, and Operate the BME280

After building and flashing, the device can be created and activated at the cloud account like the devices above. Here is shown the device sending temperature, humidity, and atmospheric pressure data approximately every 6 seconds:

Testing of BME280 and DHT22 Sensors

It is straightforward to compare the temperature and humidity values of these sensors to each other. However, how can one get an idea of their humidity accuracy? The answer is easy. Put them in a sealed container with a saturated solution of common table salt. For this testing there were one DHT22 sensor, one DHT22 sensor, and three BME280 sensors.

Saturated Salt Solutions for Humidity Testing

One can easily find on the web tables for the equilibrium values of humidity versus temperature for a variety of common saturated salt solutions. When a salt solution that is saturated is placed in a sealed container the humidity of the air space above the solution equilibrates to a particular value less than 100 % RH. Here is a quote from an Omega Engineering brochure:

Saturated Salt Solutions
A very convenient method to calibrate humidity sensors is the use of saturated salt solutions. At any temperature, the concentration
of a saturated solution is fixed and does not have to be determined. By providing excess solute, the solution will remain saturated even in the presence of modest moisture sources and sinks. When the solute is a solid in the pure phase, it is easy to determine that there is saturation.

Here is the set up used. Note that a tight seal is required and one knows the salt solution is saturated when it becomes slushy with a lot of salt that no longer dissolves. One should choose a container and salt bath size so that the air space is not so large that it takes quite long before equilibrium is reached after the lid is put on.

Saturated salt chamber with NaCl

Two salts were used. Common table salt and pharmaceutical grade magnesium chloride. In the table one can see the relative humidity is quite constant around room temperature.

Temperature (C)NaCl (%RH)MgCl2 (%RH)
1575.61 ± 0.1833.30 ± 0.21
2075.47 ± 0.1433.07 ± 0.18
2575.29 ± 0.1232.78 ± 0.16

Results

Before doing the humidity testing it was first determined that for the BME280 there were some self-heating effects caused by the very short standby time of 0.5 milliseconds between measurements within the device. This value is set by the value BME280_TSB_05 in esp_platform_init(void) and was changed to BME280_TSB_1000 , one second. This had the effect of increasing the RH reading 1.5 to 2.0 % RH and slightly reducing the temperature reading by about 0.5 C.

This table contains all of the results.

Results

It can be seen that all sensors agree reasonably well with each other on temperature. Much less so on humidity. Any table salt must be at least 97.5 % pure, so it seems that salt purity may not explain the variation in the numbers in this column. Also, only two devices report the forecasted 75 % RH. Regarding the magnesium chloride being pharmaceutical grade suggests a high purity material, but the supplier did not mark the purity on the product. So, the three devices all reported a result a little above the forecasted 33 % RH. One last comment worth making is that one can test 100 % RH by not adding any salt at all. However, the sensors become wet and do not readily recover, particularly the DHT sensors. This topic is not covered in this post.

Conclusions

Three types of IoT devices were developed using the ESP8266 and the SDK provided by Espressif. Along the way in this post various issues that were encountered were addressed as well as the apparent mystery regarding how the tri-colored LED device is actually controlled. The two types of temperature/humidity devices were compared and humidity accuracy was tested using two different saturated salt baths. The results suggest neither device type is very accurate measuring humidity.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *