Wemos D1 Mini Experimenter board


Ian Sexton 2017


Introduction
Specification
The Board
Wemos pins
Using the Board
Code
In the beginning - Blink
Testing the relay
Fade the LED
Test the switch
Test the switch with interupts
Another switch?
Analogue input
I2C
I2C scanner
BMP180 Pressure/temperature
BME280 Pressure/temperature/humidity
BH1750 Lux sensor
VEML6070 UV sensor
OLED
VL53L0X LIDAR
Using D1 & D2
DHT11
One-Wire DS18B20 Temperature sensor
Neopixel
Piezo Buzzer
WiFi
A Webserver (AP mode)
Connecting to an existing WiFi network (STA mode)
A Webserver (STA mode)
SPIFFS
A real webserver
OTA programming
Sleep
MQTT
Node-Red 1
Node-Red 2
Node-Red Dashboard
Links




Introduction


Some time ago I designed a 'general purpose' board for learning about the ESP8266 WiFi microcontroller. I was very pleased with this board but, with the benefit of hindsight I made a few unfortunate design choices.

Many PCB manufacturers in China offer special pricing for boards which are smaller than 50mm x 50mm. To keep the board small I elected to use the 'ESP03' device, which at the time was as good a choice as any.

Unfortunately the ESP03 hasn't kept up with the competition. Some other ESP8266 variants now have 4 Mbytes of FLASH memory but the ESP03 provides only 512 kbytes.

My board didn't have a USB to serial converter so a separate device was required such as this one. This isn't a big deal but it does mean more wires.

I began to prefer the ESP12 variant which has 4Mbytes of FLASH. Developing for these devices is also simplified by virtue of a companion product called NODEMCU. In essence, the NODEMCU is just a ESP12 & USB/serial device on the same board. They are also very cheap. I also became very fond of a similar product, the Wemos D1 Mini, this is similar in concept to the NODEMCU devices but restricts available pins to the useful ones. Also, there are several Arduino style 'shields' available for the D1 mini which makes it a little more accessible to the newcomer.

I redesigned my general purpose board to use a D1 mini device. Ordered some PCBs from China & this is what I ended up with:

PCB
              layout
Built & tested: £24.99







It's much better in our university labs if everything is on one board. Our technicians prefer this approach as it's easier to keep track of things. It's also a part of my teaching ethos that students should be able to work at home & have fun with their work. This board is inexpensive and the software is free.

The specification

The board


PCB photo

The image above shows a fully populated board. There is a connector available to extend the neopixel array (top right) and a DC jack socket is provided for powering longer strips.

The relay is mains rated but I'm not sure it would meet relevant safety standards - I won't be held liable if you connect mains voltages to the relay.
It's also worth noting that many readily available I2C devices can be plugged straight into the I2C sockets. See the examples below.

Pins


It can be confusing to understand which pins are which on many ESP8266 boards. You will see them variously described as GPIOx or Dx or whatever...
Use the pin descriptions on the silk screen on the board & you won't go wrong as long as you describe your board as 'Wemos D1 R2 & mini' in the Arduino IDE under the tools/boards menu.

The image below illustrates the relationship between 'Wemos pins', 'Arduino/ESP8266 pins', and actual pins on the chip.

Wemos D1 pins

As long as you are using the Arduino IDE you can describe pins in either of two ways. Our board has a LED connected to Wemos pin D0, which is equivalent to: Arduino/ESP8266 pin GPIO16.
We can use either here, eg:

#define LED 16
or
#define LED D0

Using the board


At this point I am only interested in using the Arduino IDE. Several other options are available which may (or may not!) be described later.

If you don't have the Arduino IDE installed you should get it here. I prefer to use the 'non-administrator' installation which is essentially just a .zip file.

Off the peg, the Arduino does not support the Wemos device, or any other ESP8266 device for that matter, so a little more work is required to get started. Excellent instructions are available here telling you how to do this.

In addition, you should install Python 2.7 and the file upload tool. These facilitate OTA (Over The Air) programming and filesystem management respectively. The documentation for the Arduino/ESP8266 is good & gettting better. Take the time to read it!

Get a micro USB cable. Plug it into the Wemos board & we're good to go!

Code


I've gathered together (& written) several programs for the board which (should!) work straight out of the box. You should be able to copy & paste these straight into your Arduino IDE. It's useful if you have some experience of working with Arduinos but it's not essential.

Various third party libraries are used in these examples. Links are provided for these at the end of the page. [to do]

Wherever possible I've acknowledged authors of code, libraries, and sources of knowledge. If I've missed anyone I apologise.

In the beginning - Blink!

In the same way that 'Hello world' is the first program for many programmers, flashing a light is often the first for embedded programmers. You can run the basic Arduino blink example program & it will work. A tiny blue led will flash on the Wemos board. We can change this (and make it smaller) to flash the white LED on our board:

//Blink Turns on an LED on for one second, then off for one second, repeatedly.

#define LED D0    //white LED is on D0
void setup() {
  pinMode(LED, OUTPUT);  // initialize LED pin as an output.
}

void loop() {
  digitalWrite(LED, !digitalRead(LED)); //toggle the LED
  delay(1000);                       // wait for a second
}

Let's test the relay

This is the same program as 'blink' but you will hear the relay click instead of seeing the LED flash:

#define RELAY D7
void setup() {
  pinMode(RELAY, OUTPUT);
}

void loop() {
  digitalWrite(RELAY, !digitalRead(RELAY));   // toggle relay
  delay(1000);                       // wait for a second
}

Fade the LED

This is almost copied from the Arduino basic 'fade' example. Brightness of the LED is controlled by PWM using the 'analogWrite' function.

/*
 Fade

 This example shows how to fade an LED
 using the analogWrite() function.

 The analogWrite() function uses PWM.
 Available values are 0-1023 (ie 10 bits)
 This example code is in the public domain.
 */
#define LED D0
int brightness = 0;    // how bright the LED is to start with
int fadeAmount = 5;    // how many points to fade the LED by

void setup() {
  // declare pin to be an output:
  pinMode(LED, OUTPUT);
}

// the loop routine runs over and over again forever:
void loop() {
  analogWrite(LED, brightness);  // set the brightness

  // change the brightness for next time through the loop:
  brightness = brightness + fadeAmount;

  // reverse the direction of the fading at the ends of the fade:
  if (brightness <= 0 || brightness >= 1023) {
    fadeAmount = -fadeAmount;
  }
  // wait to see the dimming effect
  delay(10);
}

Test the switch

Now we are getting clever! Inputs and outputs together. Let's reflect the status of the switch on the LED. When you press the button the LED is off & vice versa.

See if you can make it work the other way around. The switch input is normally high & goes low when we press the button. The LED is anode driven, ie a '1' lights it.

#define LED D0
#define SWITCH D3

void setup() {
  pinMode(LED,OUTPUT);
  pinMode(SWITCH,INPUT);
}

void loop() {
  digitalWrite(LED,digitalRead(SWITCH));  //reflect switch status on LED
}

Test the switch with interrupts

This is a little more complex. The button generates an interrupt. The interrupt service routine (ISR) changes a variable which the loop() function uses.
Interrupts work much the same here as they do with an ordinary Arduino.

// This program demonstrates interrupts. A push button switch generates an interrupt.
// The interrupt is used to toggle the state of a LED
// It doesn't always work perfectly - a neat demo of 'switch bounce'

#define LED D0
#define SWITCH D3

volatile byte state = LOW;  //volatile = tell the compiler that this is changed by an interrupt

void setup() {
  pinMode(LED, OUTPUT);
  pinMode(SWITCH, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(SWITCH), blink, FALLING);
  Serial.begin(115200);
}

void loop() {
  digitalWrite(LED, state);
  Serial.println(state);
  delay(200);
}

void blink() {    //the ISR - good practice to keep it as brief as possible
  state = !state;
}

Another switch?

The piezo transducer on the board can also function as a crude microphone. If we give it a healthy tap with (eg) a pen it is quite capable of generating an interrupt!
Instead of using the button try using the piezo instead. All you need to do is use D8 instead of D3 in the above program.


Analogue input

There is a potentiometer on the board. Here we use it to control the brightness of the LED. The program is slightly tweaked. The A/D converter returns a 10 bit value (ie 0-1023) however, in practice, values below around 7 are rarely achieved with our circuit.
There is no provision for an external analogue input on the board.

#define LED D0
#define POT A0

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

void loop() {
  int x = analogRead(POT);
  Serial.println(x);
  if (x < 9)    //can't get down to zero with circuit as is
    x = 0;
  analogWrite(LED, x);    //control white LED brightness
  delay(100);             //just for the print
}

I2C

The ESP8266 uses software driven I2C which is managed by the 'Wire' library. Any pins can be used for I2C but we have followed the Wemos convention & used their default pins, D1=SCL & D2=SDA.

If you are not using the I2C bus, D1 & D2 are available for other use as General Purpose I/O. You might (for example) attach an ultrasonic rangefinder to these pins.

A shortcoming of the ESP8266 is its lack of I/O pins but this isn't usually a show stopper. If you are desperate for more I/O you might consider a I2C port expander.

I2C scanner


Sometimes it isn't entirely obvious what a device's I2C address is. You might have plugged in a device & it doesn't seem to work. The code below scans the I2C bus and as long as devices are correctly connected will reveal their addresses.
This program is much more useful than you might imagine...

#include <Wire.h>

void setup()
{
  Wire.begin();
  Serial.begin(115200);
  Serial.println("\r\nI2C Scanner");
}
void loop()
{
  byte error, address;
  int nDevices;
  Serial.println("Scanning...");
  nDevices = 0;
  for (address = 1; address < 127; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.print(address, HEX);
      Serial.println("  !");

      nDevices++;
    }
    else if (error == 4)
    {
      Serial.print("Unknow error at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");

  delay(5000);           // wait 5 seconds for next scan
}

I2C - BMP180 Temperature/Pressure Sensor


Most of the BMP180 boards on Ebay etc follow our pin format for I2C. Here is the code to test it. It's based entirely on the Adafruit demo example and uses their libraries.

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP085_U.h>

/* This driver uses the Adafruit unified sensor library (Adafruit_Sensor),
   which provides a common 'type' for sensor data and some helper functions.

   THIS SEEMS TO WORK 
   
   To use this driver you will also need to download the Adafruit_Sensor
   library and include it in your libraries folder.

   You should also assign a unique ID to this sensor for use with
   the Adafruit Sensor API so that you can identify this particular
   sensor in any data logs, etc.  To assign a unique ID, simply
   provide an appropriate value in the constructor below (12345
   is used by default in this example).
   
   Connections
   ===========
   Connect SCL to analog 5
   Connect SDA to analog 4
   Connect VDD to 3.3V DC
   Connect GROUND to common ground
    
   History
   =======
   2013/JUN/17  - Updated altitude calculations (KTOWN)
   2013/FEB/13  - First version (KTOWN)
*/
   
Adafruit_BMP085_Unified bmp = Adafruit_BMP085_Unified(12345);

/**************************************************************************/
/*
    Displays some basic information on this sensor from the unified
    sensor API sensor_t type (see Adafruit_Sensor for more information)
*/
/**************************************************************************/
void displaySensorDetails(void)
{
  sensor_t sensor;
  bmp.getSensor(&sensor);
  Serial.println("------------------------------------");
  Serial.print  ("Sensor:       "); Serial.println(sensor.name);
  Serial.print  ("Driver Ver:   "); Serial.println(sensor.version);
  Serial.print  ("Unique ID:    "); Serial.println(sensor.sensor_id);
  Serial.print  ("Max Value:    "); Serial.print(sensor.max_value); Serial.println(" hPa");
  Serial.print  ("Min Value:    "); Serial.print(sensor.min_value); Serial.println(" hPa");
  Serial.print  ("Resolution:   "); Serial.print(sensor.resolution); Serial.println(" hPa");  
  Serial.println("------------------------------------");
  Serial.println("");
  delay(500);
}

/**************************************************************************/
/*
    Arduino setup function (automatically called at startup)
*/
/**************************************************************************/
void setup(void) 
{
  Serial.begin(115200);
  Serial.println("Pressure Sensor Test"); Serial.println("");
  
  /* Initialise the sensor */
  if(!bmp.begin())
  {
    /* There was a problem detecting the BMP085 ... check your connections */
    Serial.print("Ooops, no BMP085 detected ... Check your wiring or I2C ADDR!");
    while(1);
  }
  
  /* Display some basic information on this sensor */
  displaySensorDetails();
}

/**************************************************************************/
/*
    Arduino loop function, called once 'setup' is complete (your own code
    should go here)
*/
/**************************************************************************/
void loop(void) 
{
  /* Get a new sensor event */ 
  sensors_event_t event;
  bmp.getEvent(&event);
 
  /* Display the results (barometric pressure is measure in hPa) */
  if (event.pressure)
  {
    /* Display atmospheric pressue in hPa */
    Serial.print("Pressure:    ");
    Serial.print(event.pressure);
    Serial.println(" hPa");
    
    /* Calculating altitude with reasonable accuracy requires pressure    *
     * sea level pressure for your position at the moment the data is     *
     * converted, as well as the ambient temperature in degress           *
     * celcius.  If you don't have these values, a 'generic' value of     *
     * 1013.25 hPa can be used (defined as SENSORS_PRESSURE_SEALEVELHPA   *
     * in sensors.h), but this isn't ideal and will give variable         *
     * results from one day to the next.                                  *
     *                                                                    *
     * You can usually find the current SLP value by looking at weather   *
     * websites or from environmental information centers near any major  *
     * airport.                                                           *
     *                                                                    *
     * For example, for Paris, France you can check the current mean      *
     * pressure and sea level at: http://bit.ly/16Au8ol                   */
     
    /* First we get the current temperature from the BMP085 */
    float temperature;
    bmp.getTemperature(&temperature);
    Serial.print("Temperature: ");
    Serial.print(temperature);
    Serial.println(" C");

    /* Then convert the atmospheric pressure, and SLP to altitude         */
    /* Update this next line with the current SLP for better results      */
    float seaLevelPressure = SENSORS_PRESSURE_SEALEVELHPA;
    Serial.print("Altitude:    "); 
    Serial.print(bmp.pressureToAltitude(seaLevelPressure,
                                        event.pressure)); 
    Serial.println(" m");
    Serial.println("");
  }
  else
  {
    Serial.println("Sensor error");
  }
  delay(1000);
}

An interesting point to note about this program is that it was clearly not written with the ESP8266 in mind. Take a close look at the setup() function.

I2C - BME280 Temperature/Pressure/Humidity Sensor


BME280 photo

The BME280 is a wonderful device. Unfortunately it's significantly more expensive than (eg) the BMP180, but it's the only single device I know of which offers temperature, pressure, and humidity.

Once again. This is an Adafruit example program. An annoying feature of their examples is that the I2C address of the device is specified in the library files rather than in the main program.

The example also has provision for using the SPI interface which is irrelevant here. We are strictly I2C.

/***************************************************************************
  This is a library for the BME280 humidity, temperature & pressure sensor

  Designed specifically to work with the Adafruit BME280 Breakout
  ----> http://www.adafruit.com/products/2650

  These sensors use I2C or SPI to communicate, 2 or 4 pins are required
  to interface. The device's I2C address is either 0x76 or 0x77.

  Adafruit invests time and resources providing this open source code,
  please support Adafruit andopen-source hardware by purchasing products
  from Adafruit!

  Written by Limor Fried & Kevin Townsend for Adafruit Industries.
  BSD license, all text above must be included in any redistribution
 ***************************************************************************/

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10

#define SEALEVELPRESSURE_HPA (1013.25)

Adafruit_BME280 bme; // I2C
//Adafruit_BME280 bme(BME_CS); // hardware SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI

unsigned long delayTime;

void setup() {
    Serial.begin(115200);
    Serial.println(F("BME280 test"));

    bool status;
    
    // default settings
    status = bme.begin();
    if (!status) {
        Serial.println("Could not find a valid BME280 sensor, check wiring!");
        while (1);
    }
    
    Serial.println("-- Default Test --");
    delayTime = 1000;

    Serial.println();

    delay(100); // let sensor boot up
}


void loop() { 
    printValues();
    delay(delayTime);
}


void printValues() {
    Serial.print("Temperature = ");
    Serial.print(bme.readTemperature());
    Serial.println(" *C");

    Serial.print("Pressure = ");

    Serial.print(bme.readPressure() / 100.0F);
    Serial.println(" hPa");

    Serial.print("Approx. Altitude = ");
    Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
    Serial.println(" m");

    Serial.print("Humidity = ");
    Serial.print(bme.readHumidity());
    Serial.println(" %");

    Serial.println();




}

I2C - BH1750 Light Sensor


BH1750 photo

Not a lot we can say about this. The BH1750 is a light level sensor. It works extremely well!

#include <BH1750FVI.h>

BH1750FVI LightSensor;

void setup() 
{
  Wire.begin();
  Serial.begin(115200);
  LightSensor.begin();
  LightSensor.SetAddress(Device_Address_L);//Address 0x23 - in library?
  LightSensor.SetMode(Continuous_H_resolution_Mode);
  Serial.println("\r\nRunning...");
}

void loop() 
{
  uint16_t lux = LightSensor.GetLightIntensity();// Get Lux value
  Serial.print("Light: ");
  Serial.print(lux);
  Serial.println(" lux");
  delay(1000);
}


I2C - VEML6070 UV Light Sensor


VEML6070 photo

Not a lot we can say about this either. The VEML6070 is a UV light level sensor. It works extremely well too!

It has the same footprint as the other I2C devices illustrated here - we don't use the 'ACK' pin.

The Adafruit library is used & this example is bundled with their library.

#include <Wire.h>
#include "Adafruit_VEML6070.h"

Adafruit_VEML6070 uv = Adafruit_VEML6070();

void setup() {
  Serial.begin(115200);
  Serial.println("VEML6070 Test");
  uv.begin(VEML6070_1_T);  // pass in the integration time constant
}


void loop() {
  Serial.print("UV light level: "); Serial.println(uv.readUV());
  delay(1000);
}


I2C - OLED

SSD1306 OLED working

There are several low cost OLED displays available which use the I2C bus. The vast majority of these use the SSD1306 driver IC. In the past I've had mixed results with many libraries for these displays.

Use the library referenced in the code. The code below works! It is identical to one of the examples bundled with the library except that the I2C pins have been changed to suit our board.

This program demonstrates many features of the library - don't be put off by its apparent complexity. If you strip it down to do just what you want it's actually quite simple.
The image above shows a 0.96" display which has a resolution of 128x64, this doesn't sound a lot but these displays are very crisp and only cost two or three pounds.
It's worth pointing out that not all OLEDs are equal! If you look for these displays on (eg) ebay (search for SSD1306) you will see loads which all look the same. There are two variants with DIFFERENT pinouts. The one to get is pinned: SDA,SCL,GND,Vcc to match our board. An easy way to spot the difference is the RIGHT displays have eliptical mounting holes in the four corners. The WRONG displays have round mounting holes.

/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 by Daniel Eichhorn
 * see https://github.com/squix78/esp8266-oled-ssd1306/tree/master/examples
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

// Include the correct display library
// For a connection via I2C using Wire include
#include <Wire.h>  // Only needed for Arduino 1.6.5 and earlier
#include "SSD1306.h" // alias for `#include "SSD1306Wire.h"`
// or #include "SH1106.h" alis for `#include "SH1106Wire.h"`
// For a connection via I2C using brzo_i2c (must be installed) include
// #include <brzo_i2c.h> // Only needed for Arduino 1.6.5 and earlier
// #include "SSD1306Brzo.h"
// #include "SH1106Brzo.h"
// For a connection via SPI include
// #include <SPI.h> // Only needed for Arduino 1.6.5 and earlier
// #include "SSD1306Spi.h"
// #include "SH1106SPi.h"

// Include custom images
#include "images.h"

// Initialize the OLED display using SPI
// D5 -> CLK
// D7 -> MOSI (DOUT)
// D0 -> RES
// D2 -> DC
// D8 -> CS
// SSD1306Spi        display(D0, D2, D8);
// or
// SH1106Spi         display(D0, D2);

// Initialize the OLED display using brzo_i2c
// D3 -> SDA
// D5 -> SCL
// SSD1306Brzo display(0x3c, D3, D5);
// or
// SH1106Brzo  display(0x3c, D3, D5);

// Initialize the OLED display using Wire library
SSD1306  display(0x3c, D2, D1);
// SH1106 display(0x3c, D3, D5);


#define DEMO_DURATION 3000
typedef void (*Demo)(void);

int demoMode = 0;
int counter = 1;

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


  // Initialising the UI will init the display too.
  display.init();

 // display.flipScreenVertically();
  display.setFont(ArialMT_Plain_10);

}

void drawFontFaceDemo() {
    // Font Demo1
    // create more fonts at http://oleddisplay.squix.ch/
    display.setTextAlignment(TEXT_ALIGN_LEFT);
    display.setFont(ArialMT_Plain_10);
    display.drawString(0, 0, "Hello world");
    display.setFont(ArialMT_Plain_16);
    display.drawString(0, 10, "Hello world");
    display.setFont(ArialMT_Plain_24);
    display.drawString(0, 26, "Hello world");
}

void drawTextFlowDemo() {
    display.setFont(ArialMT_Plain_10);
    display.setTextAlignment(TEXT_ALIGN_LEFT);
    display.drawStringMaxWidth(0, 0, 128,
      "Lorem ipsum\n dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore." );
}

void drawTextAlignmentDemo() {
    // Text alignment demo
  display.setFont(ArialMT_Plain_10);

  // The coordinates define the left starting point of the text
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString(0, 10, "Left aligned (0,10)");

  // The coordinates define the center of the text
  display.setTextAlignment(TEXT_ALIGN_CENTER);
  display.drawString(64, 22, "Center aligned (64,22)");

  // The coordinates define the right end of the text
  display.setTextAlignment(TEXT_ALIGN_RIGHT);
  display.drawString(128, 33, "Right aligned (128,33)");
}

void drawRectDemo() {
      // Draw a pixel at given position
    for (int i = 0; i < 10; i++) {
      display.setPixel(i, i);
      display.setPixel(10 - i, i);
    }
    display.drawRect(12, 12, 20, 20);

    // Fill the rectangle
    display.fillRect(14, 14, 17, 17);

    // Draw a line horizontally
    display.drawHorizontalLine(0, 40, 20);

    // Draw a line horizontally
    display.drawVerticalLine(40, 0, 20);
}

void drawCircleDemo() {
  for (int i=1; i < 8; i++) {
    display.setColor(WHITE);
    display.drawCircle(32, 32, i*3);
    if (i % 2 == 0) {
      display.setColor(BLACK);
    }
    display.fillCircle(96, 32, 32 - i* 3);
  }
}

void drawProgressBarDemo() {
  int progress = (counter / 5) % 100;
  // draw the progress bar
  display.drawProgressBar(0, 32, 120, 10, progress);

  // draw the percentage as String
  display.setTextAlignment(TEXT_ALIGN_CENTER);
  display.drawString(64, 15, String(progress) + "%");
}

void drawImageDemo() {
    // see http://blog.squix.org/2015/05/esp8266-nodemcu-how-to-create-xbm.html
    // on how to create xbm files
    display.drawXbm(34, 14, WiFi_Logo_width, WiFi_Logo_height, WiFi_Logo_bits);
}

Demo demos[] = {drawFontFaceDemo, drawTextFlowDemo, drawTextAlignmentDemo, drawRectDemo, drawCircleDemo, drawProgressBarDemo, drawImageDemo};
int demoLength = (sizeof(demos) / sizeof(Demo));
long timeSinceLastModeSwitch = 0;

void loop() {
  // clear the display
  display.clear();
  // draw the current demo method
  demos[demoMode]();

  display.setTextAlignment(TEXT_ALIGN_RIGHT);
  display.drawString(10, 128, String(millis()));
  // write the buffer to the display
  display.display();

  if (millis() - timeSinceLastModeSwitch > DEMO_DURATION) {
    demoMode = (demoMode + 1)  % demoLength;
    timeSinceLastModeSwitch = millis();
  }
  counter++;
  delay(10);
}

VL53L0X


Another
fun device to play with is the very catchily named VL53L0X 'LIDAR' chip.

VL53L0X LIDAR photo

This is a laser range finding device. It plugs directly into our I2C sockets using only the Vcc, GND, SCL, & SDA pins.
There are a couple of libraries available for this. The Adafruit library works well. The Pololu library doesn't.
The Adafruit example code below works nicely but their example which uses an OLED display does not! Despite it being 'seen' to work in their online tutorial it's full of bugs.

#include "Adafruit_VL53L0X.h"

Adafruit_VL53L0X lox = Adafruit_VL53L0X();

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

  // wait until serial port opens for native USB devices
  while (! Serial) {
    delay(1);
  }
  
  Serial.println("Adafruit VL53L0X test");
  if (!lox.begin()) {
    Serial.println(F("Failed to boot VL53L0X"));
    while(1);
  }
  // power 
  Serial.println(F("VL53L0X API Simple Ranging example\n\n")); 
}


void loop() {
  VL53L0X_RangingMeasurementData_t measure;
    
  Serial.print("Reading a measurement... ");
  lox.rangingTest(&measure, false); // pass in 'true' to get debug data printout!

  if (measure.RangeStatus != 4) {  // phase failures have incorrect data
    Serial.print("Distance (mm): "); Serial.println(measure.RangeMilliMeter);
  } else {
    Serial.println(" out of range ");
  }
    
  delay(100);
}


Using D1 & D2

We've tried to put every available pin on the Wemos board to good use. Sadly, this means that there are none left for you.

If you're not using I2C, pins D1 & D2 can be used for other things. By way of illustration I've connected a touch switch to the 'I2C' connector and used the pins as conventional I/O.

The switch I used is a TTP223 based device:
TTP223 touch switch
These are readily available for a few pence and work great. Check the code below. I wanted to plug the switch straight into the I2C sockets.

// Using the I2C pins (D1 & D2)
// Although we would normally use D1 & D2 for I2C they are not dedicated to this function
// The program below demonstrates the use of these pins with a cheap touch switch.
// The touch switch has 3 pins: GND,DATA,Vcc
// Unfortunately the pins don't match our sockets so I've connected GND-GND, DATA-D1, Vcc-D2
// By outputting '1' on D2 we power the switch (it needs a couple of mA so we're ok to do this)
// The switch is based on the TTP223 IC & is very popular (and cheap) on ebay.

#define DATA D1
#define VCC D2

void setup() {
  Serial.begin(115200);
  pinMode(DATA, INPUT);     //usually used for SCL
  pinMode(VCC, OUTPUT);     //usually used for SDA
  digitalWrite(VCC, 1);     //provides power to the touch switch

}

void loop() {
  Serial.println(digitalRead(DATA));
  delay(100);
}


DHT11 Temperature/Humidity Sensor

DHT11 sensor


The board has a DHT11 temperature & humidity sensor. This code is derived from the Adafruit example in their library.

The DHT11 is cheap & cheerful (and robust!) it is slow & its accuracy isn't great. The DHT22 is much better & is a drop in replacement.

// Example testing sketch for DHT11 humidity/temperature sensor
// Original code written by ladyada, public domain

#include "DHT.h"    //adafruit library
#define DHT_PIN D5
#define DHTTYPE DHT11   // DHT 11

DHT dht(DHT_PIN, DHTTYPE);  // Initialize DHT sensor.

void setup() {
  Serial.begin(115200);
  Serial.println("\r\nDHT11 test");
  dht.begin();
}

void loop() {
  delay(3000);  // Wait a few seconds between measurements.
  
  float h = dht.readHumidity();
  float t = dht.readTemperature();  // Read temperature as Celsius (the default)

  // Check if any reads failed and exit early (to try again).
  if (isnan(h) || isnan(t)) {
    Serial.println("Failed to read from DHT sensor!");
    return;
  }

  Serial.print("Humidity: ");
  Serial.print(h);
  Serial.print(" %\t");
  Serial.print("Temperature: ");
  Serial.print(t);
  Serial.println(" *C ");

}

One-Wire DS18B20 Temperature Sensor

DS18B20 sensor


Another sensor on the board is the Maxim/Dallas 18B20. This is included as it is a very popular device and it also uses the 'one-wire' protocol which is educational. The code below is taken directly from the examples included with the one-wire library.

As a point of interest, I have wired this sensor in 'parasitic' mode (see the datasheet). This caused lots of problems. The 4k7 pullup resistor shown on the original PCB is too high a value & should be 910R or thereabouts.

#include <OneWire.h>

// OneWire DS18S20, DS18B20, DS1822 Temperature Example
//
// http://www.pjrc.com/teensy/td_libs_OneWire.html
//
// The DallasTemperature library can do all this work for you!
// http://milesburton.com/Dallas_Temperature_Control_Library

OneWire  ds(D4);  // on pin 10 (a 4.7K resistor is necessary)

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

void loop(void) {
  byte i;
  byte present = 0;
  byte type_s;
  byte data[12];
  byte addr[8];
  float celsius, fahrenheit;
  
  if ( !ds.search(addr)) {
    Serial.println("No more addresses.");
    Serial.println();
    ds.reset_search();
    delay(250);
    return;
  }
  
  Serial.print("ROM =");
  for( i = 0; i < 8; i++) {
    Serial.write(' ');
    Serial.print(addr[i], HEX);
  }

  if (OneWire::crc8(addr, 7) != addr[7]) {
      Serial.println("CRC is not valid!");
      return;
  }
  Serial.println();
 
  // the first ROM byte indicates which chip
  switch (addr[0]) {
    case 0x10:
      Serial.println("  Chip = DS18S20");  // or old DS1820
      type_s = 1;
      break;
    case 0x28:
      Serial.println("  Chip = DS18B20");
      type_s = 0;
      break;
    case 0x22:
      Serial.println("  Chip = DS1822");
      type_s = 0;
      break;
    default:
      Serial.println("Device is not a DS18x20 family device.");
      return;
  } 

  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);        // start conversion, with parasite power on at the end
  
  delay(2000);     // maybe 750ms is enough, maybe not
  // we might do a ds.depower() here, but the reset will take care of it.
  
  present = ds.reset();
  ds.select(addr);    
  ds.write(0xBE);         // Read Scratchpad

  Serial.print("  Data = ");
  Serial.print(present, HEX);
  Serial.print(" ");
  for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds.read();
    Serial.print(data[i], HEX);
    Serial.print(" ");
  }
  Serial.print(" CRC=");
  Serial.print(OneWire::crc8(data, 8), HEX);
  Serial.println();

  // Convert the data to actual temperature
  // because the result is a 16 bit signed integer, it should
  // be stored to an "int16_t" type, which is always 16 bits
  // even when compiled on a 32 bit processor.
  int16_t raw = (data[1] << 8) | data[0];
  if (type_s) {
    raw = raw << 3; // 9 bit resolution default
    if (data[7] == 0x10) {
      // "count remain" gives full 12 bit resolution
      raw = (raw & 0xFFF0) + 12 - data[6];
    }
  } else {
    byte cfg = (data[4] & 0x60);
    // at lower res, the low bits are undefined, so let's zero them
    if (cfg == 0x00) raw = raw & ~7;  // 9 bit resolution, 93.75 ms
    else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
    else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
    //// default is 12 bit resolution, 750 ms conversion time
  }
  celsius = (float)raw / 16.0;
  fahrenheit = celsius * 1.8 + 32.0;
  Serial.print("  Temperature = ");
  Serial.print(celsius);
  Serial.print(" Celsius, ");
  Serial.print(fahrenheit);
  Serial.println(" Fahrenheit");
}

Neopixel

8 element neopixel

A fun feature of the board is the inclusion of a 8 element 'neopixel' LED array. These are RGB LEDs which are individually addressable yet require only one data line to control the array, irrespective of how many LEDs we have.

The DC socket on the board should be used to provide +5v power if you intend to use a longer array. Don't try very long arrays. The tracks on the current PCB are likely to fry.

As well as being fun, these LEDs are becoming ubiquitous. As LEDs are destined to replace incandescent lighting, there is an enormous volume of work concerned with driving these devices.

#include <Adafruit_NeoPixel.h>


#define NEO_PIN D6


Adafruit_NeoPixel pixels = Adafruit_NeoPixel(8, NEO_PIN, NEO_GRB + NEO_KHZ800); //8 pixels

void setup()
{
  pixels.begin(); // This initializes the NeoPixel library.
}

void loop()
{
  setColor(100, 0, 0, 100); //red
  setColor(0, 100, 0, 100); //green
  setColor(0, 0, 100, 100); //blue
  setColor(0, 0, 0, 100); //black
  setColor(50, 50, 50, 100); //white
  setColor(100, 0, 100, 100); //purple
}

//simple function which takes values for the red, green and blue led and also a delay
void setColor(int redValue, int greenValue, int blueValue, int delayValue)
{
  for (int i = 0; i < 8; ++i)
  {
    pixels.setPixelColor(i, pixels.Color(redValue, greenValue, blueValue));
    pixels.show();
    delay(delayValue);
  }
}

A video showing our neopixel array in action is available here.


Piezo Buzzer

piezo buzzer

The program below plays a little tune on the piezo buzzer. It's the same program as the Arduino Digital 'toneMelody' example but only the used notes are defined for clarity.
The piezo is a passive device, in other words it needs to be driven by a (square) wave at an appropriate frequency. The Arduino tone() function handles this for us.
The piezo is fairly quiet (thankfully!). It is connected to D8. You will spot this in the code.

/*
  Melody

  Plays a melody

  created 21 Jan 2010
  modified 30 Aug 2011
  by Tom Igoe

  This example code is in the public domain.

  http://www.arduino.cc/en/Tutorial/Tone


  /*************************************************
   Public Constants
 *************************************************/

#define NOTE_G3  196
#define NOTE_A3  220
#define NOTE_B3  247
#define NOTE_C4  262

// notes in the melody:
int melody[] = {
  NOTE_C4, NOTE_G3, NOTE_G3, NOTE_A3, NOTE_G3, 0, NOTE_B3, NOTE_C4
};

// note durations: 4 = quarter note, 8 = eighth note, etc.:
int noteDurations[] = {
  4, 8, 8, 4, 4, 4, 4, 4
};

void setup() {
  // iterate over the notes of the melody:
  for (int thisNote = 0; thisNote < 8; thisNote++) {

    // to calculate the note duration, take one second
    // divided by the note type.
    //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
    int noteDuration = 1000 / noteDurations[thisNote];
    tone(D8, melody[thisNote], noteDuration);

    // to distinguish the notes, set a minimum time between them.
    // the note's duration + 30% seems to work well:
    int pauseBetweenNotes = noteDuration * 1.30;
    delay(pauseBetweenNotes);
    // stop the tone playing:
    noTone(D8);
  }
}

void loop() {
  // no need to repeat the melody.
}

WiFi


So far we've not done anything at all which couldn't be achieved with a plain vanilla Arduino. The whole point of the Wemos & ESP8266 is that it can add WiFi functionality to our projects, so let's get on with it!

A Webserver (AP mode)


The ESP8266 can operate in two distinct modes. These are known as 'Access Point' (AP) or 'Station' (STA). It can work in both modes simultaneously but let's not confuse things yet!

In AP mode the device creates a new wireless network & we connect directly to it. In STA mode the device connects to an existing WiFi network & we access it through the network.

The first thing everyone wants to do is create a webserver, so let's begin with a AP based webserver. To make it a little more exciting we'll deliver a reading from the DHT11 sensor.

The program below was a demo for a simple device we were planning to give away to open day visitors. It's moved on a long way since this was written.

// Open day demonstration
//-----------------------------------------------
// This code is designed for a ESP8266 device connected to a DHT11/22 temperature & humidity sensor
// The device is configured as an access point with a specific name
// Power up the device (plug it in!) and wait a few seconds
// Locate the 'new' wireless access point (Wemos web) which appears on your PC/laptop/tablet
// Join this network - no password required
// Go to http://192.168.4.1 in a web browser
// You should see the temperature & humidity from the DHT11/22. The page should refresh every 60 seconds
// Ian Sexton 2015
//-----------------------------------------------
#include <DHT.h>            //sensor library
#include <ESP8266WiFi.h>    //wifi library
#include <ESP8266WebServer.h> //server library

#define DHTPIN D5               //configure sensor connection pin
#define DHTTYPE DHT11         //& type
DHT dht(DHTPIN, DHTTYPE);   

const char *ssid = "Wemos web";    //name of access point

ESP8266WebServer server(80);  //Go to http://192.168.4.1 in a web browser. Standard http port

//bits of a web page:
const String refresh = "<META HTTP-EQUIV=\"refresh\" CONTENT=\"60\">";  //reload after 60 seconds
// Pick a colour:
//const String format = "<body bgcolor=\"#cccccc\">"; //silver
//const String format = "<body bgcolor=\"#ccff99\">"; //pale green
//const String format = "<body bgcolor=\"#ffe6e6\">"; //pale pink
//const String format = "<body bgcolor=\"#ffffc2\">"; //pale yellow
const String format = "<body bgcolor=\"#ccffff\">";   //pale blue
// take a look at http://www.w3schools.com/tags/ref_colorpicker.asp for a full range of colours
const String h1 = "<h1>De Montfort University</h1>";    //heading 1
const String h2 = "<h2>Faculty of Technology</h2>";     //heading 2
//Put your name or something below
const String h3 = "<h2>Personalise it here</h2>";
const String msg1 = "We hope you enjoyed our Open Day, and continue to enjoy our gift to you.<br>";
const String msg2 = "This device measures temperature & relative humidity, the current values are:<br>";
//dynamic stuff inserted in here - see below
const String msg3 = "If you would like to know more about opportunities at DMU, please contact:<br><br>";
const String contact = "Joe Blogs<br>The Admissions Unit<br>DMU<br>The Gateway<br>blah<br>";
//I've deliberately separated this into parts to make it easier to modify

void setup()
{
  WiFi.softAP(ssid);          //start the access point
  server.on("/", webroot);    //we only have the root page
  server.begin();             //start the server
}

void loop() 
{
  server.handleClient();
}

void webroot() 
{
  float humidity = dht.readHumidity(); //read the sensor
  float temperature = dht.readTemperature();
  String webpage = refresh + format + h1 + h2 + h3 + msg1 + msg2; //build the webpage from parts above
  webpage += "<h2>Temperature: ";
  webpage += String(temperature);   //make it printable
  webpage += " C<br>";               //add the sensor readings in here
  webpage += "Humidity: ";
  webpage += String(humidity);      //make it printable
  webpage += " %<br></h2>";
  webpage += msg3 + contact;       //finish off the page - parts above too
  server.send(200, "text/html", webpage);  //and serve it...
}
The page served looks like this on my iPhone:

screen capture from iPhone



Almost  everything is handled by the ESP8266Webserver library. The most difficult part is constructing the web page! We'll discover easier ways of doing this later.



Connecting to an existing WiFi network (STA mode)

If we had a sensor like the BMP180 at home, it would probably be better to connect it to our home WiFi network and access it through that network rather than directly.
Let's see how to connect to a network.

// Connect to a WiFi network

#include <ESP8266WiFi.h>

//const char* ssid = "mynetwork";   //c method
//const char* password = "mypassword";
String ssid = "mynetwork";          //c++ method
String password = "mypassword";
void setup()
{
  Serial.begin(115200);
  //WiFi.begin(ssid, password);     //c method
  WiFi.begin(ssid.c_str(), password.c_str());    //c++ method
  Serial.println();

  // Wait for connection - this could keep trying forever

  while (WiFi.status() != WL_CONNECTED)   //meaning: not equal to 'wireless connected'
  {
    delay(500);
    Serial.print(".");      //progress bar
  }

  Serial.print("\r\nConnected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());   //IP address 'given' from access point.
}

void loop() 
{
  Serial.print("Now we are in loop() ");
  Serial.println(millis());    //so that we can see the program is running
  delay(1000);                //we'd usually do something useful now, eg serve web pages
}


In the program you would change the 'mynetwork' and 'mypassword' for your network name & password.

I've shown two different ways to declare & use these parameters. You are likely to encounter both when looking at other code.

The (serial) output from the program should look something like this:

output from serial port

My network is called 'cisco'. The junk on the top line will become familiar to you. The ESP8266 transmits diagnostic data at an obscure baudrate when it is reset. There is no way to stop this.
The dots are a progress bar which increases in length until a connection is established.



A Webserver (STA mode)

Here we connect to an existing WiFi network and the device can be accessed from anywhere on the same network. You need to use your network credentials for ssid & password.

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

#define LED D0

const char* ssid = "mynetwork";
const char* password = "mypassword";

ESP8266WebServer server(80);

void setup(void) {
  pinMode(LED, OUTPUT);
  digitalWrite(LED, 0);
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.println("\r\nConnecting");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.print("\r\nConnected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  server.on("/", handleRoot);
  server.begin();
  Serial.println("HTTP server started");
}

void loop(void) {
  server.handleClient();
}

void handleRoot() {
  digitalWrite(LED, 1);
  server.send(200, "text/plain", "Hello from your Wemos board!");
  digitalWrite(LED, 0);
}

This example is very like the previous (AP) example. The web page delivered is much simpler & the LED on our board flashes when a request is made.



Digression - SPIFFS


If you've ever used a real webserver to serve your own pages you might easily be disheartened by now. Constructing pages in a mixture of C and html is a nightmare!
Surely there's an easier way? Why can't we just upload a html file & have it served??? We can! The Wemos device has (at least) 4Mbytes of Flash memory. Only 1M is available for program code but we can use the remaining memory for a 'filesystem' called SPIFFS.

SPIFFS = Serial Peripheral Interface Flash File System. Consult the documentation if you want to learn more.

SPIFFS allows us to use files almost as if they were 'normal' files on a 'real' webserver.  To do this we need to create our web 'site' locally. We should have a 'index.htm' file along with any other files we want to use. These might include a stylesheet, JPEGs, Javascript, etc etc.
All of these files must be placed in a folder called 'data' inside the program folder. Under the 'Tools' menu in the Arduino IDE you should see an option: 'ESP8266 Sketch Data Upload' NB you must have previously installed this! See above re using the board. It can take quite a while to upload the data, be patient.

A real Webserver

/* Create a WiFi access point and provide a web server on it.
This program uses SPIFFS to hold files
Simply create a website and upload to SPIFFS
The root is 'index.htm' 
Use relative links so you can check your website before uploading
Use the 'ESP8266 Sketch Data Upload' from 'Tools' menu in Arduino IDE
AFTER putting your files in 'data' folder in the sketch folder
*/
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <FS.h>

const char *ssid = "Wemos_SPIFFS";
const char *password = "";    //no password

ESP8266WebServer server(80);  //port 80

//-----------------------------------------------------------------------------------------
void setup() {

  Serial.begin(115200);
  SPIFFS.begin();
  Serial.println();
  Serial.print("Configuring access point...");
  WiFi.softAP(ssid, password);
  IPAddress myIP = WiFi.softAPIP();
  Serial.print("Access Point IP address: ");
  Serial.println(myIP);
  server.onNotFound(handleOther);
  server.begin();
  Serial.println("HTTP server started");
}
//-----------------------------------------------------------------------------------------

void loop() {
  server.handleClient();
}
//-----------------------------------------------------------------------------------------


//tell the client what to do with data
bool loadFromSpiffs(String path)
{
  String dataType = "text/plain";
  if (path.endsWith("/")) path += "index.htm"; //this is where index.htm is created

  if (path.endsWith(".src")) path = path.substring(0, path.lastIndexOf("."));
  else if (path.endsWith(".htm")) dataType = "text/html";
  else if (path.endsWith(".css")) dataType = "text/css";
  else if (path.endsWith(".js")) dataType = "application/javascript";
  else if (path.endsWith(".png")) dataType = "image/png";
  else if (path.endsWith(".gif")) dataType = "image/gif";
  else if (path.endsWith(".jpg")) dataType = "image/jpeg";
  else if (path.endsWith(".ico")) dataType = "image/x-icon";
  else if (path.endsWith(".xml")) dataType = "text/xml";
  else if (path.endsWith(".pdf")) dataType = "application/pdf";
  else if (path.endsWith(".zip")) dataType = "application/zip";

  File dataFile = SPIFFS.open(path.c_str(), "r");   //open file to read
  if (!dataFile)  //unsuccesful open
  {
    Serial.print("Don't know this command and it's not a file in SPIFFS : ");
    Serial.println(path);
    return false;
  }
  if (server.hasArg("download")) dataType = "application/octet-stream";
  if (server.streamFile(dataFile, dataType) != dataFile.size()) {}    //a lot happening here

  dataFile.close();

  return true; //shouldn't always return true, Added false above
}
//-----------------------------------------------------------------------------------------
void handleOther() {   
  
  if (loadFromSpiffs(server.uri())) return;   //gotcha - it's a file in SPIFFS
  String message = "Not Found\n\n";           //or not...
  message += "URI: ";     //make a 404 response & provide debug information
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++)
  {
    message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
  Serial.println(message);
}


A potential issue with this approach is that it doen't lend itself well to the use of 'live' data. The program above simply spools a web page which is stored in SPIFFS. Generally, we want to display things like sensor readings which obviously are not known when the SPIFFS page is created. There are a few ways around this. Possibly the simplest is to use a bit of Javascript. A small Javascript routine can be embedded in the SPIFFS page, it will make a 'GET' request & our server can return html formatted data. I have another page which illustrates this concept.


OTA Programming

OTA (Over The Air) programming is a bonus feature which comes from having WiFi. Usually it's not a problem to connect your board to your PC with a USB cable, but there are plenty of instances when this isn't great. Your board might be miles away. It might be inconvenient to access physically. It could be connected to hazardous voltages etc.

I can remember the day when my iPhone needed connecting to my PC to perform updates. How ridiculous! My TV occasionally needs updates, thankfully it uses OTA as it's at the opposite end of the house to my PC!

The ESP8266 at the heart of our board can perform OTA programming a few different ways & these are all well explained in the documentation.

The key thing to remember is that this facility is not automatic! It's up to you to set it up!

This might sound obvious until you trip up... The program below does very little, it connects to your WiFi and flashes the LED. When you run this program your device is ready for OTA programming. If you look at Tools>Port in the Arduino IDE you should see that a network port is available (restart the IDE if there is nothing there).
Select the network port shown & program the device again with the same code, maybe change the rate the LED blinks just for proof that it works. Your program should load at lightning speed & run!

BUT if you load any other program from this page it will kill the OTA capability. Those lines which say, 'MUST be included in ALL programs.' Mean exactly that. Miss them out & you kill the OTA capability. This is very easy to do accidentally!

Imagine you are working with a BMP180 or some other I2C device. Your program uses OTA, you've uploaded it a dozen times with OTA but the I2C device refuses to work... So you upload the I2C scanner shown above & it tells you that you have the wrong address...

That's great to know, but you've just killed the OTA because the I2C scanner doesn't include the facility... Be careful!

Another potential issue is poor management. You may have a lot of devices on your network which are set up for OTA programming. Be sure to program the right one!

The moral of the story: OTA is extremely useful but don't be surprised if/when it bites your bum!

This topic is explained well in the documentation. Read it.

// OTA (Over The Air) programming
// this is a minimal example to show how it's done
// Ian Sexton 2016

#include <ESP8266WiFi.h>
#include <ArduinoOTA.h>   //MUST be included in ALL programs

#define LED D0

const char* ssid = "your_network";
const char* password = "your_password";

void setup()
{ //standard connect as station stuff
  pinMode(LED, OUTPUT);
  Serial.begin(115200);
  Serial.println("\r\nBooting");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED)
  {
    Serial.println("\r\nConnection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }
  ArduinoOTA.begin();    //MUST be included in ALL programs
}

void loop()
{
  ArduinoOTA.handle();   //MUST be included in ALL programs
  digitalWrite(LED, !digitalRead(LED));   //blink to show life
  delay(500);

}



Sleep

A sad fact of life is that WiFi (IEEE802.11) is not low power. The ESP8266 chip in our board can take up to 300mA of current. If we were to try running a device from batteries they wouldn't last very long (unless they were extremely large!). To put this in context, a typical rechargeable AA cell might have a capacity of around 2000mAH.  A battery pack made up of such cells might power a device for around 6 hours. That might be sufficient in some applications, it might not in others.

Many of the examples we have used so far concern reading of sensors. We might make the data available on a simple webserver. This approach means that our device is doing very little apart from when a client connects and requests the data.
If we approach the problem from another angle we can save enormous amounts of power & provide considerably extended battery life.

It is important to note from the outset that our Wemos board was never designed to be a true low power device and the best we can do here is to illustrate what can be done.

To implement a low power scheme we put the device to sleep (which uses less power). It wakes periodically, does something useful, and then returns to sleep.

This doesn't fit with our idea of presenting the data, on demand, via a web server. If our device is asleep it can't be checking for requests at the same time, however it might wake, send the data somewhere and then go back to sleep. Suppose we want to monitor the temperature in our living room. How often do we need to measure it? This will probably depend on why we are monitoring the temperature, but for the sake of illustration let's say we need to take a fresh reading every ten minutes. So we can sleep for ten minutes, wake, read the temperature, send the data somewhere, and sleep for another ten minutes. When the ESP8266 is in 'deep sleep' it requires around 20uA of current. At this rate our 2000mAH battery would last 100,000 hours or around 11 years. If it wakes every 10 minutes to do its stuff this obviously has an impact, but it only needs to be awake for a few seconds. I'm sure you get the idea!

As indicated, our board isn't designed for low power. The ESP8266 isn't the only device on our board which uses power. There is a USB to serial chip on the Wemos board, our sensors are wired to be permanently 'on', the pot is wired as a potential divider, we use LEDs without any care etc. The lowest current we can hope for on our board is around 40-50mA if we make the ESP8266 sleep.

Putting it to sleep is simple. We use a function: ESP.deepSleep(microseconds);

Waking it is a little more tricky and we need to use a hardware modification to reset the ESP8266 when it's time to wake up. The Wemos pin D0 goes low at the end of a sleep and by connecting this pin to the RST (reset) pin our board is woken with a reset at the end of the sleep time. Unfortunately that's not the end of the story, the Wemos board also uses the RST pin to start the serial bootloader so we have a conflict. If we connect a diode (preferably Schottky) between D0 & RST this will allow the RST or the bootloader to operate. The cathode of the diode should be connected to D0, anode to RST. I'm told that a resistor (~1k) between the pins will also work but I've not tried it. Also note that we use D0 to drive the white LED in normal use. This is out of bounds if you want to try sleeping!

The program below demonstrates deep sleep. It doesn't do anything clever but it can be instructive. Low power operation is almost an entire subect in itself but an overview is useful.

There are quite a few articles on the web which cover this topic but I don't know of anything definitive. This one is worth a read.

// deep sleep example
// D0 must be connected to RST on the Wemos board
// The best way to do this is to connect a Schottky diode between the pins
// with the cathode to D0, anode to RST.
// WiFi is switched on to make current measurements more realistic
// Ian Sexton 2017

#include <ESP8266WiFi.h>

#define buzzer D8
#define sleepSeconds 20   //Esp.deepSleep() expects microseconds
const char* ssid = "esp";

void setup() {
  Serial.begin(115200);
  tone(buzzer,1500,200); //beep the buzzer on waking
  Serial.println("\n\nRunning for 20 seconds");
  WiFi.softAP(ssid);          //start the access point
  delay(20000);               // stay awake for a while
  Serial.println("\n\nOff to sleep now\n");
  ESP.deepSleep(sleepSeconds * 1000000);  //time for bed
}

void loop() {
}


-----------------------------------------------------------------------------------------------------------------------------------------------------

MQTT

In an earlier example we used a DHT11 temperature & humidity sensor to create a web page. This is great fun but it isn't very practical. Install two or three of these sensors & you are faced with two or three web servers, each having different IP addresses. Put one in every room in the house & we could easily be in double figures. Apart from sensors, we might have actuators, eg we might want to control AC mains outlets &/or lighting in every room. It's not difficult to imagine that a truly 'smart' home could have 100+ devices competing for your attention.

Returning to our DHT11 example: Imagine it is telling you that your living room temperature is 27C in the middle of winter. You are on holiday & forgot to alter the heating - no problem, you can change the heating remotely. Then you notice that your house lights are all off 24/7. You can fix that remotely too.

You can work your way through all of the sensors & actuators. You will need another holiday when you are through!

Wouldn't it be useful if our devices could talk to each other? We could program one device as a client and another as a server.  We could potentially produce a mesh network where data could be shared across the network.

Or we could use MQTT! It's beyond our scope here to provide a detailed explanation of MQTT, for which you are referred to the literature.

Shamelessly lifted from the literature: Essentially, MQTT is a Client Server publish/subscribe messaging transport protocol. It is light weight, open, simple, and designed so as to be easy to implement. These characteristics make it ideal for use in many situations, including constrained environments such as for communication in Machine to Machine (M2M) and Internet of Things (IoT) contexts where a small code footprint is required and/or network bandwidth is at a premium.

Think of it like a Facebook for micros. You can post messages, others can see them. You can also see stuff that you are interested in. Your dialogue with other users (clients) is mediated by Facebook (server). So the MQTT server sits in the middle of all the devices/clients and manages the communication between them. Traditionally the MQTT server was referred to as a 'broker' but this seems to have given way to 'server' in the current terminology.

A problem for us is that there are several Arduino/ESP8266 MQTT (client)  libraries available. The program below uses the original Arduino library written by Nick O'Leary of IBM who created MQTT. The code is slightly modified from the ESP8266 example in the library. We will see why later.

What does this program do? It publishes a message, 'hello world' to the topic called 'outTopic' and it subscribes to a topic called 'inTopic' which hopefully tells us what to do with the LED.


/*
  Basic ESP8266 MQTT example

  This sketch demonstrates the capabilities of the pubsub library in combination
  with the ESP8266 board/library.

  It connects to an MQTT server then:
  - publishes "hello world" to the topic "outTopic" every two seconds
  - subscribes to the topic "inTopic", printing out any messages
    it receives. NB - it assumes the received payloads are strings not binary
 

  It will reconnect to the server if the connection is lost using a blocking
  reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
  achieve the same result without blocking the main loop.

  To install the ESP8266 board, (using Arduino 1.6.4+):
  - Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs":
       http://arduino.esp8266.com/stable/package_esp8266com_index.json
  - Open the "Tools -> Board -> Board Manager" and click install for the ESP8266"
  - Select your ESP8266 in "Tools -> Board"

*/
#define LED D0
#include <ESP8266WiFi.h>
#include <PubSubClient.h>

// Update these with values suitable for your network.

const char* ssid = "your_network";
const char* password = "your_password";
const char* mqtt_server = "10.0.0.101"; //IP address of YOUR server/broker

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;

void setup() {
  pinMode(LED, OUTPUT);     // Initialize the LED pin as an output
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
  String message;
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
    message += ((char)payload[i]);  //turn payload into string - easier to understand
  }
  Serial.println();

// act on a received message from the server
  if (message == "on")
    digitalWrite(LED, HIGH);   // Turn the LED on
  if (message == "off")
    digitalWrite(LED, LOW);   // Turn the LED off
  if (message == "toggle")
    digitalWrite(LED, !digitalRead(LED));  // Toggle the LED

}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client")) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic", "hello world");
      // ... and resubscribe
      client.subscribe("inTopic");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
void loop() {

  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  long now = millis();
  if (now - lastMsg > 2000) {
    lastMsg = now;
    ++value;
    snprintf (msg, 75, "hello world #%ld", value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish("outTopic", msg);
  }
}



Node-Red

It's faintly absurd to dedicate so few lines here to such an enormous topic but it's useful to provide a very brief introduction.

The example above requires the existence of a MQTT server/broker. Although there is such a server available for the ESP8266 platform we would generally use a more powerful device. The Raspberry Pi is very often used for this role. You don't actually need your own server at all. There are quite a few freely available MQTT servers on the interweb.

I prefer to keep things local. I use a rPi but tend to use a PC for development work. This is quicker & simpler

MQTT suddenly becomes more interesting with Node-Red!

If you want to use a rPi it's quite simple & is included with recent releases of the firmware. An even simpler route is to use The Thingbox which is a pre-configured SD card image with Node-Red & MQTT ready to go.

Installing Node-Red on a PC is a little more complex. I recommend you follow this video.

You also need to install the Mosquitto MQTT server if you want to work on a PC. The easiest way to do this is to download this zip file. Unzip the file & copy to a convenient location on your PC. Start the server by double clicking the file 'mosquitto.exe' contained in the folder.

Start Node-Red by opening a command window & typing 'node-red'

starting node-red from command prompt

Now open a web browser and enter 'localhost:1880' in the address bar. You should see something like this:

node-red environment

Let's create our first flow. Drag & drop the following nodes from the panel on the left:

Connect the nodes like this:

connecting nodes

In the picture my nodes are configured. You need to do this too.
Double click on an 'inject' node & you will be provided with a configuration menu:

edit node

Set the Payload to deliver a string. The payload itself is the string 'on' Click done.
Repeat this for the other two inject nodes with payloads 'off' and 'toggle'

Now let's configure the MQTT nodes. Double click & we get:

configure MQTT node

Node-Red needs to know the whereabouts of the MQTT server which in our example is our local machine. localhost:1883 is telling Node-Red that the server is at port 1883 of our local machine.

Our previous MQTT program publishes to 'outTopic' and subscribes to 'inTopic' We need to set the MQTT input and output nodes accordingly.

Now we are good to go. If we run the MQTT program above it should connect to our MQTT server and exchange messages.

Click on the 'debug' tab (top right) in Node-Red and you should see:

debug panel output

This is the message our device is publishing. Clicking any of the inject buttons should control the white LED on our board.



More Node-Red


Now let's make this a little more useful. Much earlier we looked at using a DHT11 sensor to measure temperature & humidity. You should be able to combine the example program with our MQTT example & publish the sensor data. I'm not going to do it for you!

You might see something like this:



The numbers indicate the temperature & humidity in my office. It's warm & fairly humid today.

The debug panel in Node-Red does exactly what it says on the tin. It's not a great way to present data & there are much better ways...



Node-Red Dashboard


So far we have used only four different nodes. The left hand panel in Node-Red shows that there are loads of ready built nodes, & we can create our own too.

Let's begin with a new flow. This time our board is publishing data to the topics 'temperature' and 'humidity'

simple flow from MQTT

The dubug panel looks like this:



Now let's add a 'gauge' node to the flow:

add a dashboard item

As before, we need to configure the node:

configure dashboard item

And then the clever bit! Open a new browser tab with the url: localhost:1880/ui and you should see:

view the dial we created



Links

Wemos github
Documentation for the Arduino/ESP8266
A Beginner's Guide to the ESP8266 - Excellent & very thorough article
OpenMQTTGateway - Acts as a gateway between RF, IR, & BLE devices. Very clever!
Amazon Echo & ESP8266 - use the ESP8266 to emulate a Belkin Wemo device
Calling Elvis - Node Red & Twillio for presence detection
My Register - A fairly big ESP8266 project of mine.
Message display
Node Red web server
Live data in static web page



Ian Sexton 2017