ESP8266: Get Date and Time from HTTP Header

Instead of sending NTP packets to remote time server as in a previous blog post the date and the time can also be extracted from any HTTP header returned after an HTTP GET request. Usually the HTTP header contains the date and the time from the router. This way the local network must not be left necessarily. For illustration purposes only a well-known URL is used in this example.

Components used

Adafruit Feather Huzzah
Monochrome OLED display

Wiring

Adafruit Feather Huzzah OLED Display
3,3 V 3,3 V
GND GND
SDA (4)  SDA
SCL (5)  SCL

Software

This Arduino sketch shows

  • how to connect to the WiFi network
  • how to perform an HTTP GET request on an URL (could be an IP address as well)
  • how to extract date and time from the HTTP response header
  • how to display date and time on an OLED display .

Code

#include <EEPROM.h>
#include <ESP8266WiFi.h>    // http://esp8266.github.io/Arduino/versions/2.0.0/doc/libraries.html
#include <WiFiClient.h>     // https://www.arduino.cc/en/Reference/WiFiClient

// OLED ESP_ssd1306_128x64_I2C
#include <ESP_SSD1306.h>    // Modification of Adafruit_SSD1306 for ESP8266 compatibility
#include <Adafruit_GFX.h>   // Needs a little change in original Adafruit library (See README.txt file)
#include <SPI.h>            // For SPI comm (needed for not getting compile error)
#include <Wire.h>           // For I2C comm, but needed for not getting compile error

boolean debug=true;

// WiFi connection data
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

const char* server = "www.google.de"; 	// an example URL
const int serverPort = 80;		// a port number
const char* resource = "/";       	// http resource

const unsigned long BAUD_RATE = 115200;	// serial connection speed
const unsigned long HTTP_TIMEOUT = 10000;   // max respone time from server

void initSerial();
void connectWiFi();
bool connect(const char* hostName, const int port);
void disconnect();
bool sendRequest(const char* host, const char* resource);
WiFiClient client;

#define OLED_RESET  16  // Pin 16 -RESET digital signal
ESP_SSD1306 display(OLED_RESET); // FOR I2C

void initOLEDDisplay();
void showDateAndTime();

bool findDateAndTimeInResponseHeaders();
String extractDayFromDateTimeString(String dateTime);
String extractMonthFromDateTimeString(String dateTime);
String extractYearFromDateTimeString(String dateTime);
String extractHourFromDateTimeString(String dateTime);
String extractMinuteFromDateTimeString(String dateTime);
String translateMonth(String monthStr);
String dateAndTime; // stores date and time from HTTP response header

unsigned long previousMillis = 0;
#define INTERVAL_MS 10000

void setup() {
  if( debug ) Serial.println("setup");
  initSerial();
  initOLEDDisplay();
  connectWiFi();
} // setup()

void loop() {

  unsigned long currentMillis = millis();
  // run every 10 seconds
  if (currentMillis - previousMillis >= INTERVAL_MS) {
    previousMillis = currentMillis;
	if( debug ) Serial.println("loop: get date and time");

	  if( connect(server, serverPort) ) {
		if( sendRequest(server, resource) ) {
		  if( findDateAndTimeInResponseHeaders() ) {
			if( debug ) {
			  Serial.print( "Date and Time from HTTP response header: " );
			  Serial.println( dateAndTime.c_str() );
			}
			showDateAndTime();
		  }
		}
		disconnect();
	  }
  }
} // loop()

// initialize serial port
void initSerial() {
  Serial.begin(BAUD_RATE);
  while (!Serial) {
    ;  // wait for serial port to initialize
  }
  if( debug ) Serial.println("Serial ready");
}

void initOLEDDisplay() {
  // SSD1306 Init
  display.begin(SSD1306_SWITCHCAPVCC);  // Switch OLED
  display.clearDisplay();
  // Show image buffer on the display hardware.
  // Since the buffer is intialized with an Adafruit splashscreen
  // internally, this will display the splashscreen.
  display.display();
  delay(2000);

  // Clear the buffer.
  display.clearDisplay();
}

// attempt to connect to WiFi
void connectWiFi() {
  WiFi.mode(WIFI_STA);
  // connect to the WiFi network
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    if( debug ) Serial.print(".");
  }
  if( debug ) {
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  }
}

// Open connection to the HTTP server
bool connect(const char* hostName, const int port) {
  if( debug ) {
    Serial.print("Connect to ");
    Serial.println(hostName);
  }
  bool ok = client.connect(hostName, port);
  if( debug ) Serial.println(ok ? "Connected" : "Connection Failed!");
  return ok;
}
/* example  curl -vv http://www.google.de
* About to connect() to www.google.de port 80 (#0)
*   Trying 172.217.17.227...
* Connected to www.google.de (172.217.17.227) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: www.google.de
> Accept: * / *
*/
/* example response:
< HTTP/1.1 200 OK
< Date: Sun, 29 May 2016 10:00:14 GMT */ // Send the HTTP GET request to the server bool sendRequest(const char* host, const char* resource) {      if( debug ) {         Serial.print("GET ");         Serial.println(resource);     }     client.print("GET ");     client.print(resource);     client.println(" HTTP/1.1");     client.print("Host: ");     client.println(host);     client.println("Accept: */*");     client.println("Connection: close");     client.println();     return true;  }  bool findDateAndTimeInResponseHeaders() {     // date and time string starts with Date: and ends with GMT     // example: Date: Sun, 29 May 2016 10:00:14 GMT     client.setTimeout(HTTP_TIMEOUT);     char header[85];     size_t length = client.readBytes(header, 85);     header[length] = 0;     String headerString = String(header);     int ds = headerString.indexOf("Date: ");     int de = headerString.indexOf("GMT");     dateAndTime = headerString.substring(ds+6, de);     // date and time: Sun, 29 May 2016 10:00:14     if( debug ) {       Serial.print("HTTP response header ");       Serial.println(headerString.c_str());       Serial.print("index start date ");       Serial.println(ds);       Serial.print("index end time ");       Serial.println(de);       Serial.println(  );       Serial.print("date and time: ");       Serial.println(dateAndTime.c_str());     }    return dateAndTime.length()&amp;gt;15;
}

// Close the connection with the HTTP server
void disconnect() {
  if( debug ) Serial.println("Disconnect from HTTP server");
  client.stop();
}

//-------- time+date code ----------
// example: Sun, 29 May 2016 10:00:14
String extractDayFromDateTimeString(String dateTime) {
  uint8_t firstSpace = dateTime.indexOf(' ');
  String dayStr = dateTime.substring(firstSpace+1, firstSpace+3);
  if( debug ) {
    Serial.print("Day: ");
    Serial.println(dayStr.c_str());
  }
  return dayStr;
}

String extractMonthFromDateTimeString(String dateTime) {
  uint8_t firstSpace = dateTime.indexOf(' ', 7);
  String monthStr = dateTime.substring(firstSpace+1, firstSpace+4);
  if( debug ) {
    Serial.print("Month: ");
    Serial.println(monthStr.c_str());
  }
  return monthStr;
}

String extractYearFromDateTimeString(String dateTime) {
  uint8_t firstSpace = dateTime.indexOf(' ', 10);
  String yearStr = dateTime.substring(firstSpace+1, firstSpace+5);
  if( debug ) {
    Serial.print("Year: ");
    Serial.println(yearStr.c_str());
  }
  return yearStr;
}

String extractHourFromDateTimeString(String dateTime) {
  uint8_t firstColon = dateTime.indexOf(':');
  String hourStr = dateTime.substring(firstColon, firstColon-2);
  if( debug ) {
    Serial.print("Hour (GMT): ");
    Serial.println(hourStr.c_str());
  }
  // adjust GMT time
  int h = hourStr.toInt();
  h += 2; // summertime
  //h += 1; // wintertime
  if( debug ) {
    Serial.print("Hour (adjusted for summertime): ");
    Serial.println(h);
  }
  return String(h);
}

String extractMinuteFromDateTimeString(String dateTime) {
  uint8_t secondColon = dateTime.lastIndexOf(':');
  String minuteStr = dateTime.substring(secondColon, secondColon-2);
  if( debug ) {
    Serial.print("Minute: ");
    Serial.println(minuteStr.c_str());
  }
  return minuteStr;
}

String extractDayFromCalendarDate(String date) {
  String dateStr = String(date);
  uint8_t firstDot = dateStr.indexOf('.');
  String dayStr = dateStr.substring(1, firstDot);
  if( debug ) {
    Serial.print("Day: ");
    Serial.println(dayStr.c_str());
  }
  return dayStr;
}

String translateMonth(String monthStr) {
  if(monthStr.equals(String("Jan"))) return String("01");
  if(monthStr.equals(String("Feb"))) return String("02");
  if(monthStr.equals(String("Mar"))) return String("03");
  if(monthStr.equals(String("Apr"))) return String("04");
  if(monthStr.equals(String("May"))) return String("05");
  if(monthStr.equals(String("Jun"))) return String("06");
  if(monthStr.equals(String("Jul"))) return String("07");
  if(monthStr.equals(String("Aug"))) return String("08");
  if(monthStr.equals(String("Sep"))) return String("09");
  if(monthStr.equals(String("Oct"))) return String("10");
  if(monthStr.equals(String("Nov"))) return String("11");
  if(monthStr.equals(String("Dec"))) return String("12");
}

void showDateAndTime() {
  display.clearDisplay();

  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  String date = extractDayFromDateTimeString(dateAndTime);
  date += ".";
  date += translateMonth(extractMonthFromDateTimeString(dateAndTime));
  date += ".";
  date += extractYearFromDateTimeString(dateAndTime);
  display.println(date.c_str());
  String timeStr = extractHourFromDateTimeString(dateAndTime);
  timeStr += ":";
  timeStr += extractMinuteFromDateTimeString(dateAndTime);
  display.setCursor(30, 35);
  display.println(timeStr.c_str());

  display.display();
}

Results

oled_blog
Date and time extracted from HTTP response header on OLED display

REST asleep – how to construct an HTTP server in Java

REST? What? An HTTP server to „communicate“ with via URLs? Yes, why not. In certain use cases a valid solution for various different tasks. Especially for the idea of displaying more or less ’static‘ information on a display.

In my case such a server handles tasks such as

  • fetching the next x entries in a (google) calendar
  • retrieving and preprocessing actual weather data from openweathermap
  • retrieving and preprocessing the weather forecast for the upcoming hours/days from openweathermap

Other examples of useful information could be financial data such as

  • exchange rates of different currencies or
  • stock market information.

Interesting could also be the information which dustbin should be standing outside next for garbage disposal in the morning.
Perhaps flight information for frequent flyers, train times (and delays) for rail travellers, even momentary fuel prices in the region (if only they were not changing so quickly as in Germany) is helpul.

For sure there are more ideas which data could be retrieved and prepared by a server for display! But back to the server itself.

HTTP Server Construction

An HTTP server in Java is basically a runnable jar file that is launched on a machine within a (local) network. The required libraries include http, httpclient, httpcore, … .

Functionality

When a GET request is received from a client the appropriate routine is launched. For each ‚digestible‘ URL a different Java class handles the request.
In case of fetching the next x entries from a Google calendar the specific Java class handles the authentication, the retrieval and the processing of the calendar entries into the JSON format. In Java this is more simple and faster than computing directly on an Arduino.

The entry point of the server may be constructed like this:

import com.sun.net.httpserver.HttpServer;
...
public static void main(String[] args) {
	try {
	    HttpServer server = HttpServer.create(new InetSocketAddress(12345), 0);
	    server.createContext("/calendar", new RequestCalendarHandler());
	    server.createContext("/weather", new RequestOWMWeatherHandler());
	    server.createContext("/forecast", new RequestOWMWeatherForecastHandler());
	    server.setExecutor(null); // creates a default executor
	    server.start();
	    System.out.println("Server running on host " + server.getAddress().getHostString());
	} catch (IOException e) {
	    System.err.println(e.getMessage());
	}
}

Openweathermap

For weather data/weather forecast retrieval from openweathermap it is not necessary to reimplement the wheel. An excellent Java library already exists: owm-japis. It can be used in a different Java class to process appropriate HTTP GET requests.

The handler for fetching weather data by openweathermap may look similar to this:

import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;

import net.aksingh.owmjapis.CurrentWeather;
import net.aksingh.owmjapis.OpenWeatherMap;
import net.aksingh.owmjapis.OpenWeatherMap.Units;
...
static class RequestOWMWeatherHandler implements HttpHandler {
	@Override
	public void handle(HttpExchange http) throws IOException {
		System.out.println("URI received: " + http.getRequestURI().toString());

	String[] request = http.getRequestURI().toString().split("/");
	if( request.length&gt;1 ) {
		// Handle read requests
		if( request[1].equals("weather") ) {
		try{
			OpenWeatherMap owm = new OpenWeatherMap("APIKEY");
			owm.setUnits(Units.METRIC);

			// getting current weather data for the location
			CurrentWeather cwd = owm.currentWeatherByCityCode(CITYCODE_OWM);

			// checking data retrieval was successful or not
			if (cwd.isValid()) {
				DateFormat dateFormatter = new SimpleDateFormat("dd.MM.yyyy");
				Date now = cwd.getDateTime();
				String dateNow = dateFormatter.format(now);
				DateFormat timeFormatter = new SimpleDateFormat("HH:mm");
				String timeNow = timeFormatter.format(now);

				System.out.println(cwd.getRawResponse());
				System.out.println("Post weather result in JSON format (raw)");
				// wrap parcels of byte size 4096
				int BUFFER_SIZE = 4096;
				http.sendResponseHeaders(200, 0);
				try (BufferedOutputStream out = new BufferedOutputStream(http.getResponseBody())) {
					try (ByteArrayInputStream bis = new ByteArrayInputStream(response.getBytes())) {
						byte [] buffer = new byte [BUFFER_SIZE];
						int count ;
						while ((count = bis.read(buffer)) != -1) {
							out.write(buffer, 0, count);
						}
					}
				}
			}
		} catch (Exception e) {
			System.err.println(e.getMessage());
		}
	}
}

Client to Server

A client requests data from the HTTP server by a GET request. An example URL to fetch the weather data looks like this:
http://localhost:12345/weather
Such an URL consisting of hostname:port can be tested in a browser on the machine where the server is running. Otherwise an IP or hostname must replace localhost.

Server to Clients

As soon as the HTTP server received a request it analyses the URL and launches the appropriate handler. In the example above the weather data will be retrieved from openweathermap (API key and location ID required).

How to read from a google calendar in Java is explained on google’s developer pages. Authentication for the desired calendar must be set up in advance following the manual.

The results can be preprocessed into a desired format to be posted for the client.
A common format is the JSON format. Data in this format can be read and processed further by a client.

Example Client: Arduino / ESP8266

The client I use is an Adafruit Huzzah microcontroller with an ESP8266 WiFi chip.
(Just in case: the hardware setup, the pin changes in the epd library and the code to parse weather data are described in previous blog posts) .
The source code in this blog post illustrates how to act as an HTTP client. To decode a reply in JSON format the library ArduinoJson is used.
Any display can be attached to the microcontroller to show the results from the HTTP GET request to the HTTP server. At the moment I strongly prefer an e-Ink display. 😉

calendar_currentweather

Links

https://developers.google.com/google-apps/calendar/quickstart/java

http://openweathermap.org/api

https://bitbucket.org/akapribot/owm-japis/overview

https://github.com/bblanchon/ArduinoJson

http://www.waveshare.com/4.3inch-e-paper.htm

http://www.waveshare.com/wiki/4.3inch_e-Paper

E-Ink Weather Display

An E-Ink display is perfect for displaying information that does not change quickly. Displaying weather data and perhaps even a weather forecast is something that usually does not require an update every second.

Where to find weather data

Weather data can be retrieved by openweathermap for example. All it needs is an account. The retrieval of current weather data and a five day / three hour forecast is for free within certain limits. Limit means that the calls per minute of the service may not exceed 60.

Openweathermap provides an API that can be used in various programming languages. In the end an URL is used to fetch the desired data. The format of the result can be selected. The default is the JSON format which can be relatively simple parsed on Arduino using libraries such as ArduinoJson. Still I found the summary of the typical pitfalls helpful.

The same principle can be used with a tailor made outdoor weather sensor in combination with a custom HTTP web server that delivers the requested data in the desired format. But that is a different blog post.

Example URL

To retrieve the weather data from openweathermap a simple URL is required. This URL may contain the city ID which can be found here, the desired unit system and the API key that can be generated after sign up on openweathermap. Other parameters to adjust the resulting weather data can be added optionally.

The weather data for the desired location can be accessed in several ways: by city name, by zip code, by geographic location, … . However, openweathermap recommends to use the ID of the location. This list contains the IDs for the available locations.

This example of an URL will work in a browser as well (using a valid API key!):

http://api.openweathermap.org/data/2.5/weather?id=2643743&units=metric&APPID=YourAPIKey

Example Result in JSON format

Below is an exemplary result string:

{"coord":{"lon":-0.13,"lat":51.51},
"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],
"base":"cmc stations",
"main":{"temp":15.25,"pressure":1017,"humidity":77,"temp_min":13,"temp_max":17},
"wind":{"speed":5.1,"deg":110},
"clouds":{"all":44},
"dt":1464380212,
"sys":{"type":1,"id":5091,"message":0.0065,"country":"GB",
"sunrise":1464321142,
"sunset":1464379442},
"id":2643743,"name":"London",
"cod":200}

How to retrieve the current weather data is explained in more detail on openweathermap/current.

Components used & Wiring

The hardware setup is the same as in the previous blog post.

Software

The Arduino sketch to retrieve and process weather data from openweathermap is based on an example from the ArduinoJson library. This example was extended to retrieve and process the weather data from openweathermap and to display the information on the E-Ink display.

// based on: https://github.com/bblanchon/ArduinoJson/blob/master/examples/JsonHttpClient/JsonHttpClient.ino

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ArduinoJson.h>
#include <epd.h>

const char* ssid = "SSID";
const char* password = "WIFIPASSWORD";

const char* server = "api.openweathermap.org"; // server's address
const int port = 80;
const char* resource = "/data/2.5/weather?id=2643743&units=metric&APPID=YourAPIKey"; // http resource

const unsigned long BAUD_RATE = 115200; // serial connection speed
const unsigned long HTTP_TIMEOUT = 10000; // max respone time from server
const size_t MAX_CONTENT_SIZE = 1024; // max size of the HTTP response

/* example URL
http://api.openweathermap.org/data/2.5/weather?id=2643743&units=metric&APPID=YourAPIKey
example result in JSON format:
{"coord":{"lon":-0.13,"lat":51.51},
"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],
"base":"cmc stations",
"main":{"temp":15.25,"pressure":1017,"humidity":77,"temp_min":13,"temp_max":17},
"wind":{"speed":5.1,"deg":110},
"clouds":{"all":44},
"dt":1464380212,
"sys":{"type":1,"id":5091,"message":0.0065,"country":"GB",
"sunrise":1464321142,
"sunset":1464379442},
"id":2643743,"name":"London",
"cod":200}*/

// weather data type
struct WeatherData {
char cityName[20];
char nowDescription[50];
char temperature[6];
char humidity[3];
char pressure[5];
char iconCode[4];
};

WiFiClient client;
boolean debug=true;

void initSerial();
void initializeEInkDisplay();
void connectWiFi();
bool connect(const char* hostName);
void disconnect();
void wait();
bool sendRequest(const char* host, const char* resource);
bool skipResponseHeaders();
void readReponseContent(char* content, size_t maxSize);
void printWeatherData(const struct WeatherData* weatherData);
bool parseWeatherData(char* content, struct WeatherData* weatherData);

void setSmallText(String text, int x, int y);
void setMediumText(String text, int x, int y);
void setLargeText(String text, int x, int y);

void updateDisplay(struct WeatherData* weatherData);
String selectWeatherIcon(struct WeatherData* weatherData);

void setup() {
initSerial();
initializeEInkDisplay();
connectWiFi();
}

void loop() {
if( connect(server) ) {
if( sendRequest(server, resource) && skipResponseHeaders() ) {

char jsonResult[MAX_CONTENT_SIZE];
readReponseContent(jsonResult, sizeof(jsonResult));

WeatherData weatherData;
if( parseWeatherData(jsonResult, &weatherData) ) {
printWeatherData(&weatherData);
updateDisplay(&weatherData);
}
}
disconnect();
}
wait();
}

void initSerial() {
Serial.begin(BAUD_RATE);
while (!Serial) {
; // wait for serial port to initialize
}
if( debug ) Serial.println("Serial ready");
}

void initializeEInkDisplay() {
epd_init();
epd_wakeup();
epd_set_memory(MEM_TF); // MEM_NAND=internal memory; MEM_TF=sd card
epd_set_color(BLACK, WHITE);
}

void connectWiFi() {
WiFi.mode(WIFI_STA);
// connect to the WiFi network
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
if( debug ) Serial.print(".");
}
if( debug ) {
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
}

bool connect(const char* hostName) {
if( debug ) {
Serial.print("Connect to ");
Serial.println(hostName);
}
bool ok = client.connect(hostName, port);
if( debug ) Serial.println(ok ? "Connected" : "Connection Failed!");
return ok;
}

bool sendRequest(const char* host, const char* resource) {
if( debug ) {
Serial.print("GET ");
Serial.println(resource);
}
client.print("GET ");
client.print(resource);
client.println(" HTTP/1.1");
client.print("Host: ");
client.println(server);
client.println("Connection: close");
client.println();
return true;
}

bool skipResponseHeaders() {
// HTTP headers end with an empty line
char endOfHeaders[] = "\r\n\r\n";
client.setTimeout(HTTP_TIMEOUT);
bool ok = client.find(endOfHeaders);
if (!ok) {
if( debug ) Serial.println("No response or invalid response!");
}
return ok;
}

void readReponseContent(char* content, size_t maxSize) {
size_t length = client.readBytes(content, maxSize);
content[length] = 0;
if( debug ) {
Serial.println("readReponseContent");
Serial.println(content);
}
}

bool parseWeatherData(char* content, struct WeatherData* weatherData) {
// find first and last curly bracket of JSON result
String cStr = String(content);
int firstCurlyBracket = cStr.indexOf('{');
int lastCurlyBracket = cStr.lastIndexOf('}');
String c = cStr.substring(firstCurlyBracket,lastCurlyBracket+1);

/*if( debug ) {
Serial.println("parseWeatherData: ");
Serial.print("content length: ");
Serial.println(c.length());
Serial.println(c.c_str());
}*/

c.toCharArray(content, MAX_CONTENT_SIZE);

/*if( debug ) {
Serial.println("parseWeatherData: ");
Serial.println(content);
}*/

StaticJsonBuffer<MAX_CONTENT_SIZE> jsonBuffer;

// find fields in JSON object
JsonObject& root = jsonBuffer.parseObject(content);
if (!root.success()) {
if( debug ) Serial.println("parsing JSON Object() failed");
return false;
}

strcpy(weatherData->cityName, root["name"]);
strcpy(weatherData->nowDescription, root["weather"][0]["description"]);
strcpy(weatherData->temperature, root["main"]["temp"]);
strcpy(weatherData->humidity, root["main"]["humidity"]);
strcpy(weatherData->pressure, root["main"]["pressure"]);
strcpy(weatherData->iconCode, root["weather"][0]["icon"]);
return true;
}

void printWeatherData(const struct WeatherData* weatherData) {
if( debug ) {
Serial.print("city name = ");
Serial.println(weatherData->cityName);
Serial.print("temperature = ");
Serial.print(weatherData->temperature);
Serial.println(" *C");
Serial.print("humidity = ");
Serial.print(weatherData->humidity);
Serial.println(" %");
Serial.print("pressure = ");
Serial.print(weatherData->pressure);
Serial.println(" hPa");
}
}

void disconnect() {
if( debug ) Serial.println("Disconnect from HTTP server");
client.stop();
}

void wait() {
if( debug ) Serial.println("Wait 30 seconds");
delay(30000);
}

//-------- e-ink display code ----------
void setSmallText(String text, int x, int y) {
epd_set_ch_font(GBK32);
epd_set_en_font(ASCII32);
epd_disp_string(text.c_str(), x, y);
}

void setMediumText(String text, int x, int y) {
epd_set_ch_font(GBK48);
epd_set_en_font(ASCII48);
epd_disp_string(text.c_str(), x, y);
}

void setLargeText(String text, int x, int y) {
epd_set_ch_font(GBK64);
epd_set_en_font(ASCII64);
epd_disp_string(text.c_str(), x, y);
}

void updateDisplay(struct WeatherData* weatherData) {
epd_clear();
int distStart=50;
setLargeText(String(weatherData->cityName), 50, distStart);
distStart += 100;
epd_draw_line(10, distStart, 500, distStart); // horizontal line
distStart += 20;
setMediumText(String(weatherData->nowDescription), 50, distStart);
distStart += 60;
epd_draw_line(10, distStart, 500, distStart); // horizontal line

distStart += 50;
setMediumText(String("temperature: ") + String(weatherData->temperature) + String(" *C"), 50, distStart);
distStart += 60;
setMediumText(String("humidity: ") + String(weatherData->humidity) + String(" %"), 50, distStart);
distStart += 60;
setMediumText(String("pressure: ") + String(weatherData->pressure) + String(" hPa"), 50, distStart);
distStart += 60;

epd_draw_line(10, distStart, 500, distStart); // horizontal line

epd_draw_line(510, 10, 510, 590); // vertical line

// display weather icon
String weatherIcon = selectWeatherIcon(weatherData);
epd_disp_bitmap(weatherIcon.c_str(), 560, 100);

epd_udpate();
}

String selectWeatherIcon(struct WeatherData* weatherData) {
String iconCode = String(weatherData->iconCode);
if( debug ) {
Serial.print("selectWeatherIcon: ");
Serial.println(iconCode.c_str());
}
if( iconCode.startsWith("01") ){
if( debug ) Serial.println("SUN.BMP");
return String("SUN.BMP");
} else if( iconCode.startsWith("02") ){
if( debug ) Serial.println("CLOUDY.BMP");
return String("CLOUDY.BMP");
} else if( iconCode.startsWith("03") ){
if( debug ) Serial.println("CLOUD.BMP");
return String("CLOUD.BMP");
} else if( iconCode.startsWith("04") ){
if( debug ) Serial.println("CLOUD.BMP");
return String("CLOUD.BMP");
} else if( iconCode.startsWith("09") ){
if( debug ) Serial.println("RAIN.BMP");
return String("RAIN.BMP");
} else if( iconCode.startsWith("10") ){
if( debug ) Serial.println("UNSETTLED.BMP");
return String("UNSET.BMP");
} else if( iconCode.startsWith("11") ){
if( debug ) Serial.println("THUNDER.BMP");
return String("THUND.BMP");
} else if( iconCode.startsWith("23") ){
if( debug ) Serial.println("SNOW.BMP");
return String("SNOW.BMP");
} else {
if( debug ) Serial.println("UNSETTLED.BMP");
return String("UNSET.BMP");
}
}

Beautifying

To get a quick overview off the weather or the forecast an icon does the trick better than pure text. One can design own weather icons or search thenounproject for beautiful examples. The icons have to be uploaded first to the micro SD card of the E-Ink display in the appropriate format. The manufacturer of the display explains in his wiki how to prepare the images and how to upload them on the micro SD card.

The weather icon codes returned by openweathermap are listed in this table. To display the appropriate image the codes only need to be translated.

The result can look like this:

e-ink weather display

e-ink weather display

In the end this weather display hack is a prototype which can be easily extended.

Links

http://openweathermap.org/api

http://openweathermap.org/weather-conditions

https://github.com/bblanchon/ArduinoJson

https://thenounproject.com/

http://www.waveshare.com/4.3inch-e-paper.htm

http://www.waveshare.com/wiki/4.3inch_e-Paper

Network Time synchronized Clock with E-Ink Display

In my previus post I connected an E-Ink display to an Arduino Uno.
Since it is possible to drive the E-Ink display with 3,3 V logic it is easily connected to an ESP8266 module. This time I used an Adafruit Huzzah ESP8266 breakout.

The very first test of this module is a typical example: a simple digital clock. Every minute an NTP packet will be requested by a time server. A well known one is time.nist.gov, but some WiFi routers run time servers too. The time returned will be in UTC format.
The UTC time will be adjusted to the local time using the TimeZone library. This way it is easily possible to define daylight saving time rules for the different time zones.

Components used

Adafruit Huzzah ESP8266 breakout
Waveshare 4,3″inch e-Paper 800×600
some cables
FTDI programmer

Wiring

e-Ink Display Adafruit Huzzah
VCC (red) 3,3 V
GND (black) GND
DOUT (white) RX
DIN (green) TX
WAKE_UP (yellow) 12
RST (blue) 14

Software

Two small changes are required in the epd library’s cpp file to use different pins on the Adafruit Huzzah for the reset and wake up lines of the E-Ink display:

//const int wake_up = 2;// Arduino Uno/Pro Mini
//const int reset = 3; // Arduino Uno/Pro Mini
const int wake_up = 12; // Adafruit huzzah
const int reset = 14; // Adafruit huzzah

The sketch is based on the example showing time synchronization with an NTP time source from Arduino’s Time library.

To upload the sketch to the Adafruit Huzzah hold the GPIO0 button, push the reset button, release the reset button and at last the GPIO0 button. This procedure brings the Adafruit Huzzah into bootloader mode which allows to upload sketches using the Arduino IDE.

/*
 * Simple digital clock with e-Ink display.
 * The time in UTC format is adjusted to local time
 * with the TimeZone library.
 */

#include <ESP8266WiFi.h> // https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WiFi
#include <WiFiUdp.h> // https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WiFi
#include <epd.h> // demo in http://www.waveshare.com/wiki/File:4.3inch-e-Paper-Code.7z
#include <TimeLib.h> // https://github.com/PaulStoffregen/Time
#include <Timezone.h> // https://github.com/JChristensen/Timezone

boolean debug = false;
char ssid[] = "SSID";
char pass[] = "WIFIPASSWORD";

unsigned int localPort = 8989; // local port to listen for UDP packets

const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

// UDP instance to send and receive packets via UDP
WiFiUDP udp;

// Don't hardwire the IP address or we won't get the benefits of the pool.
// Lookup the IP address for the host name instead.
IPAddress timeServerIP; // NTP server address
const char* ntpServerName = "time.nist.gov"; // or any other NTP server

// Central European Time (Paris, Berlin)
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; // Central European Summer Time
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; // Central European Standard Time
Timezone CE(CEST, CET);
TimeChangeRule *tcr; // pointer to the time change rule, used to get the TZ abbrev

time_t prevDisplay = 0; // last time the digital clock was displayed

// function declarations
void setMediumText(String text, int x, int y);
void setLargeText(String text, int x, int y);
unsigned long sendNTPpacket(IPAddress& address);
time_t getNtpTime();
String digits(int number);
String getTime(time_t dt);
String getDate(time_t dt);

void setup() {
  // initialize e-ink display
  epd_init();
  epd_wakeup();
  epd_set_memory(MEM_NAND);
  epd_set_color(BLACK, WHITE);

  if( debug ) {
    Serial.begin(115200);
    Serial.println();
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);
  }
  // connect to the WiFi network
  WiFi.begin(ssid, pass);
 
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    if( debug ) Serial.print(".");
  }
  if( debug ) {
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    Serial.println("Starting UDP");
  }
  udp.begin(localPort);

  if( debug ) {
    Serial.print("Local port: ");
    Serial.println(udp.localPort());
    Serial.println("waiting for sync");
  }
 
  setSyncProvider(getNtpTime);
  setSyncInterval(60); // set the number of seconds between re-sync
} //setup()

void loop() {
  if (timeStatus() != timeNotSet) {
    // update the display only if time has changed
    if (now() != prevDisplay) {
      epd_clear();
      prevDisplay = now();
      // adjust daylight saving time
      time_t dt = CE.toLocal(prevDisplay, &tcr);
      // print time
      setLargeText(getTime(dt), 315, 100 );
      // print date
      setMediumText(getDate(dt), 270, 250 );
      epd_udpate();
      delay(60000); // wait a minute
    }
  }
} // loop()

//-------- NTP code ----------
// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& address) {
  if( debug ) Serial.println("Sending NTP packet...");
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011; // LI, Version, Mode
  packetBuffer[1] = 0; // Stratum, or type of clock
  packetBuffer[2] = 6; // Polling Interval
  packetBuffer[3] = 0xEC; // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  udp.beginPacket(address, 123); //NTP requests are to port 123
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();
} // sendNTPpacket()

time_t getNtpTime() {
  IPAddress ntpServerIP; // NTP server's ip address

  while (udp.parsePacket() > 0) ; // discard any previously received packets
    if( debug ) Serial.println("Transmit NTP Request");
    // get a random server from the pool
    WiFi.hostByName(ntpServerName, ntpServerIP);
    if( debug ) {
      Serial.print(ntpServerName);
      Serial.print(": ");
      Serial.println(ntpServerIP);
  }
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 = (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - (2208988800UL+0 * SECS_PER_HOUR);
    }
  }
  if( debug ) Serial.println("No NTP Response");
  return 0; // return 0 if unable to get the time
} // getNtpTime()

//-------- e-ink display code ----------
void setMediumText(String text, int x, int y) {
  epd_set_ch_font(GBK48);
  epd_set_en_font(ASCII48);
  epd_disp_string(text.c_str(), x, y);
} // setMediumText()

void setLargeText(String text, int x, int y) {
  epd_set_ch_font(GBK64);
  epd_set_en_font(ASCII64);
  epd_disp_string(text.c_str(), x, y);
} // setLargeText()

//-------- time+date display code ----------
String digits(int number) {
  // utility for digital clock display: leading 0
  String nr = String("");
  if (number < 10) {
    if( debug ) Serial.print('0');
    nr = "0";
  }
  if( debug ) Serial.print(number);
  nr += String(number);
  return nr;
} // digits()

String getTime(time_t dt) {
  String time_clock = String("");
  time_clock = digits(hour(dt));
  time_clock += " : ";
  time_clock += digits(minute(dt));
  //time_clock += " : ";
  //time_clock += digits(second(dt));
  return time_clock;
} // getTime()

String getDate(time_t dt) {
  String date_clock = String("");
  date_clock = digits(day(dt));
  date_clock += ".";
  date_clock += digits(month(dt));
  date_clock += ".";
  date_clock += year();
  return date_clock;
} // getDate()

Result

The final result of this test looks like this:

simple digital clock on e-ink display

Sources

https://learn.adafruit.com/adafruit-huzzah-esp8266-breakout/overview
http://www.waveshare.com/wiki/4.3inch_e-Paper
https://github.com/PaulStoffregen/Time
https://github.com/JChristensen/Timezone

 

E-Ink Display driven by Arduino

Recently I discovered an 4,3 “ E-Ink display (Waveshare 4,3″inch e-Paper 800×600) that can be used with an Arduino. E-Ink displays (or E-Paper) are low-energy displays that keep what was printed even when the power is turned off. This feature makes E-Ink displays not only perfect for E-Readers, but also interesting for IoT projects. Such a display could show information that does not require animations (E-Ink displays take their time to update).
For IoT projects I can immediately think of examples such as displaying weather data (and the time), daily appointments from an electronic calendar, cooking recipes… .

Hardware

Waveshare 4,3″inch e-Paper 800×600
Arduino (Uno)
some cables

Wiring

e-Ink Display Arduino
VCC 5V
GND GND
DOUT RX
DIN TX
WAKE_UP D2
RST A3

Software

In this post I discovered the epd library for usage in Arduino sketches. Originally this library was found here and repackaged. This library can be directly imported into the Arduino IDE.

Arduino Sketch

This sketch is based on the example from here. It was immediateley working with little changes only.
To upload the sketch the RX/TX lines have to be removed from the Arduino. If the RX/TX lines are put back the Arduino can be reset to refresh the display.

#include <epd.h>

void fillPage(void) {
  char buff[] = {'G', 'B', 'K', '3', '2', ':', ' ', 0xc4, 0xe3, 0xba, 0xc3, 0xca, 0xc0, 0xbd, 0xe7, 0};

  epd_set_color(BLACK, WHITE);  // text=black, background=white
 
  epd_clear();

  // setting the font type and size
  epd_set_ch_font(GBK32);
  epd_set_en_font(ASCII32);

  epd_disp_string(buff, 0, 0);
  epd_disp_string("Test of the length of characters that can be printed in a line #1", 0, 0);
  epd_disp_string("Test of the length of characters that can be printed in a line #2", 0, 50);
  epd_disp_string("Test of the length of characters that can be printed in a line #3", 0, 100);
  
  // increasing the font type and size
  epd_set_ch_font(GBK48);
  epd_set_en_font(ASCII48);

  buff[3] = '4';
  buff[4] = '8';

  // print larger text
  epd_disp_string(buff, -10, -10);
  epd_disp_string("thisisyetanotherblog.wordpress.com", 25, 200);

  epd_set_ch_font(GBK64);
  epd_set_en_font(ASCII64);

  buff[3] = '6';
  buff[4] = '4';

  // drawing some forms
  epd_draw_circle(80, 350, 55);
  epd_draw_triangle(100, 450, 170, 400, 250, 450);
  epd_fill_rect(400, 500, 335, 470);
  epd_draw_line(100, 550, 700, 550);
 
  // update the display
  epd_udpate();
}

void setup(void) {
 // initialize the display
 epd_init();
 epd_wakeup();
 epd_set_memory(MEM_NAND);
}

void loop(void) {
 fillPage(); // fill the display once
 while (1) {} // do nothing more in the loop
}

Result

This is a base to start from with the next IoT project… Hopefully E-Ink displays become more affordable sooner than later!

e-ink display test, powered off

Sources

http://www.waveshare.com/4.3inch-e-paper.htm
http://www.waveshare.com/wiki/4.3inch_e-Paper

http://www.jarzebski.pl/arduino/komponenty/e-papier-waveshare-4-3.html
http://www.zoobab.com/waveshare-e-paper-and-arduino
http://zoobab.wdfiles.com/local–files/waveshare-e-paper-and-arduino/ArduinoEpd.zip

IoT: Collecting Weather Data

Collecting data from temperature, humidity and barometric sensors with an
Arduino is compelling when observing the weather. The idea is to create an outdoor sensor module. This outdoor module sends the measured data via WiFi to a server which stores the measurement data in a local database.

Components used

ESP-ADC DIL 18 with ESP8266 module
BMP085 Barometric Pressure & Temp Sensor
DHT22 Temperature and Humidity Sensor
220 uF 16V capacitor
Single color LED
220; 4,7k, 10k resistors
wires, switches, breadboard, breadboard power supply, 3,3 V FTDI programmer

Make it happen

Wiring

wiring of esp8266, dht22, bmp085

 

ESP8266 Parts Notes
GND GND DHT22 / GND BMP085 / GND ESP-ADC / LED – via 220 resistor
VCC 3,3 V VCC DHT22 / VCC BMP085 / VCC ESP8266
Reset switch 1 / GPIO_16 Connection to GPIO_16 required for deep sleep mode of ESP8266
GPIO_0 switch 2
GPIO_4 SDA BMP085 On ESP8266 I2C is implemented in software, could be soft-wired to any other GPIO, default for I2C clock is GPIO_4
GPIO_5 SDL BMP085 On ESP8266 I2C is implemented in software, could be soft-wired to any other GPIO, default for I2C data is GPIO_5
GPIO_7 Pin 2 DHT22, pull to 3,3 V VCC via 4,7 k resistor
GPIO_13 LED +
GPIO_16 RST ESP8266
CHPD 3,3 V VCC via 10k resistor

The capacitor is used to stabilize the electric power supply for the two sensors and the ESP8266. This is especially useful when the ESP8266 sets up a WiFi connection and its power consumption increases.

Uploading an Arduino Sketch to the ESP8266

Wiring

  • Adjust the FTDI programmer for 3,3 V logic
  • Connect the FTDI programmer’s RX and TX lines to the ESP8266.
  • Connect the FTDI programmer’s GND line to GND of the breadboard power supply.

Arduino IDE settings

Adjust the upload settings in the Arduino IDE:

Arduino IDE upload settings

Uploading

To upload an Arduino sketch to the ESP8266 module push the two buttons. Release the switch connected to Reset approx. a second before the switch connected to GPIO_0 and trigger the upload in the Arduino IDE. The timing is relevant. It may happen that this procedure has to be repeated several times until the upload succeeds.

After the successful upload of the sketch the output in the Arduino IDE looks similar to this:

...
upload message on success:
setting serial port timeouts to 1000 ms
 espcomm_send_command: receiving 2 bytes of data
 writing flash
............................................................................................................................................................................................................................
starting app without reboot
 espcomm_send_command: sending command header
 espcomm_send_command: sending command payload
 espcomm_send_command: receiving 2 bytes of data
closing bootloader
 flush start
 setting serial port timeouts to 1 ms
 setting serial port timeouts to 1000 ms
 flush complete

Sketch

Installing Adafruit Libraries

The Arduino sketch based on the examples for the unified BMP085 and DHT libraries.
The required libraries for Adafruid BMP085 Unified, Sensor and DHT Unified can be installed for the Arduino IDE via Sketch > Libraries > Manage Libraries… .

Some Code

#include <ESP8266WiFi.h> // http://esp8266.github.io/Arduino/versions/2.0.0/doc/libraries.html
#include <WiFiClient.h>

#include <Adafruit_Sensor.h>
#include <Adafruit_BMP085_U.h> // https://github.com/adafruit/Adafruit_BMP085_Unified

#include <DHT.h>
#include <DHT_U.h> // https://github.com/adafruit/Adafruit_DHT_Unified
#define DHTTYPE DHT22
#define DHTPIN 7

// WiFi network credentials
const char* ssid = "<SSID>";
const char* password = "<PASSWD>";

// IP / port weather station server
#define IPWS "192.168.145.244"
#define PORT 20016

// times for deep sleep mode
unsigned long MIN_1 = 60000000;
unsigned long MIN_15 = 900000000;
unsigned long MIN_30 = 1800000000;
unsigned long S_30 = 30000000;
unsigned long DEEP_SLEEP = MIN_30;
RFMode MODE = WAKE_RF_DEFAULT;

// Initialize DHT sensor 
// NOTE: For working with a faster than ATmega328p 16 MHz Arduino chip, like an ESP8266,
// you need to increase the threshold for cycle counts considered a 1 or 0.
// You can do this by passing a 3rd parameter for this threshold. It's a bit
// of fiddling to find the right value, but in general the faster the CPU the
// higher the value. The default for a 16mhz AVR is a value of 6. For an
// Arduino Due that runs at 84mhz a value of 30 works.
// This is for the ESP8266 processor on ESP-01 
DHT_Unified dht(DHTPIN, DHTTYPE, 11); // 11 works fine for ESP8266

Adafruit_BMP085_Unified bmp;

#define STATUSLED 13

boolean debugging = false; // enable / disable debug output

String float2String(float value) {
  char v[10];
  dtostrf(value,4,1,v);
  return String(v);
}

String readSensors() {
  if( debugging ) Serial.println("Read sensor data...");
  String str = "";
  String str2Send = "wData;";

  if( debugging ) Serial.println("DHT22");
  sensors_event_t event;
  float temperature = 0.0;
  dht.temperature().getEvent(&event);
  temperature = event.temperature;
  if( isnan(temperature) ) {
    if( debugging ) Serial.println("Error reading temperature!");
  } else {
    if( debugging ) {
      Serial.print("Temperature: ");
      Serial.print(temperature);
      Serial.println(" *C");
    }
 }
 // Get humidity event and print its value.
 dht.humidity().getEvent(&event);
 float humidity = 0.0;
 humidity = event.relative_humidity;
 if( isnan(humidity) ) {
   if( debugging ) Serial.println("Error reading humidity!");
 } else {
   if( debugging ) {
     Serial.print("Humidity: ");
     Serial.print(humidity);
     Serial.println("%");
   }
 }

 float temperature2 = 0.0;
 bmp.getTemperature(&temperature2);
 str = "Temperature: " + float2String(temperature2) + " *C\n";
 str += "Humidity: " + float2String(humidity) +" %\n";

 float pressure = 0.0;
 bmp.getPressure(&pressure);
 pressure = pressure/100.0;
 if( debugging ) {
   Serial.print("Pressure: ");
   Serial.print(pressure);
   Serial.println(" hPa");
 }
 str += "Pressure: " + float2String(pressure) + " hPa\n";

 if( debugging ) {
   Serial.print("Collected sensor data: ");
   Serial.println(str);
 } 
 str2Send += temperature;
 str2Send += ";";
 str2Send += humidity;
 str2Send += ";";
 str2Send += pressure;
 str2Send += "\0";

 if( debugging ) {
   Serial.print("Sensor data for sending to server: ");
   Serial.println(str2Send);
 }
 return str2Send;
}

// expected string: e.g. weatherdata;14.3;60.7;1018.0
void sendSensorData(String str2Send) {
 WiFiClient client;
 client.connect(IPWS, PORT);
 if( debugging ) {
   Serial.print("Sending: ");
   Serial.println(str2Send);
 }
 client.write(str2Send.c_str(), str2Send.length());
 if( client.connected() ) {
   client.stop();
 }
}

void setup(void) {
 // You can open the Arduino IDE Serial Monitor window to see what the code is doing
 Serial.begin(115200); // Serial connection from ESP-01 via 3.3v console cable

 dht.begin(); // initialize temperature sensor

 if (!bmp.begin()) {
   if( debugging ) Serial.println("Could not find a valid BMP085 sensor, check wiring!");
   while (1) {}
 }
 
 // set as station
 WiFi.mode(WIFI_STA);
 
 // connect to WiFi network
 WiFi.begin(ssid, password);
 if( debugging ) Serial.print("\n\r \n\rWorking to connect");

 // wait for connection
 while (WiFi.status() != WL_CONNECTED) {
   delay(500);
   if( debugging ) Serial.print(".");
 }

 pinMode(STATUSLED, OUTPUT);
 digitalWrite(STATUSLED, LOW);
} // setup()

void loop(void) {

 // read data from sensors
 String str = readSensors();

 if( debugging ) {
   Serial.print("Sending sensor data to server: ");
   Serial.println(str);
 }
 sendSensorData(str);

 // indicate data acquisition and sending
 digitalWrite(STATUSLED, HIGH);
 delay(1000);

 // enter deep sleep mode for x minutes
 // GPIO16 needs to be tied to RST to wake ESP8266 from deep sleep mode
 // http://russ.russmathis.com/esp8266-power-modes/
 //ESP.deepSleep(DEEP_SLEEP, MODE);
 // 30000000 ms = 30 s
 ESP.deepSleep(MIN_30, WAKE_RF_DEFAULT);
} 

Output

In debug mode the output on the serial console is similar to this:

Working to connect….
Read sensor data…
DHT22
Temperature: 22.10 *C
Humidity: 53.60%
BMP085
Temperature: 22.10 *C
Pressure: 1009.2 hPa
Collected sensor data:
Temperature: 22.10 *C
Humidity: 53.60%
Pressure: 1009.2 hPa
Sending sensor data to server: …

Notes

After measurement the ESP8266 establishes a WiFi connection. The collected data is sent as a string to a dedicated server of the desired IP within the local network.
Another possibility would be to send the data to thingspeak.com for quick visualization.

Deep sleep – saving power

After having sent the measurement data the ESP8266 enters „deep sleep“ mode until it is woken up again after the desired time. For this feature GPIO_16 has to be wired with Reset on the ESP8266.

Configuration

The ESP8266 module should be configured as a a station, not as an accesspoint (WIFI_AP). In this setup the module is used for sending data only.

Barometric sensor

Usually the barometric pressure is the ‚raw‘ value. It is calculated to match the barometric pressure at sea level. Depending on the actual level the measured value differs from the barometric values close to the location. In this case the measured barometric pressure needs to be adapted.

Temperature sensor data

Small deviations of the measured temperature are possible between the two sensors. The causes for such deviations are usually production tolerances of the sensor’s chips or even a close local heat source.

ESP8266 vs. Arduino

The inexpensive ESP8266 module looks promising for realizing own IoT projects.
However there are a couple of stumbling blocks to be found when trying to integrate this module. These are the ones I found when I tried to connect an Arduino Pro Mini (3,3 V) to the local WiFi network with this module.

Stumbling block #1

During development the ESP8266 module requires its own 3.3 V (!) power supply. The power consumption increases when a WiFi connection is established. Powering the ESP8266 module via FTDI/USB is not sufficient as soon as WiFi connections are planned.

 

ESP8266 on FTDI programmer

Stumbling block #2

To check whether the ESP8266 is flashed with firmware and understands AT commands first connect it directly to an FTDI programmer:

FTDI programmer
GND GND 3.3V power supply
VCC not connected
RX TX ESP8266
TX RX ESP8266
DTR  not connected
RST  not connected
ESP8266 module
GND GND 3.3V power supply
VCC VCC 3.3V power supply
RX TX FTDI
TX RX FTDI
CHPD VCC 3.3V power supply via 10k resistor
REST VCC 3.3V power supply via 10k resistor

esp8266onftdi_breadboard

With a terminal (e.g. from the Arduino IDE) it is now possible to send AT commands to the ESP8266. A comprehensive description of the AT command set for ESP8266 modules is found on  http://www.esp8266.com/wiki/doku.php?id=at_commands .

Stumbling block #3

New line and carriage return must be set in the terminal and the proper baud rate (115200) must be selected.

Basic AT commands

command expected result description
AT OK Test whether AT commands will work
AT+GMR e.g. AT version:0.60.0.0(Jan 29 2016 15:10:17)
SDK veróion:1.5.2(80914727)
compile time:Jan 29 2016 19:06:34
OK
Print version information
AT+RST OK Reset ESP8266 module
AT+RESTORE OK Configuration will be reset to factory defaults.
AT+CWLAP list of WIFI local networks Available WIFI access points will be displayed.
AT+CWJAP=“<SSID>“,“<WIFIpassword>“ WIFI CONNECTED
WIFI GOT IP
OK
Connects to the WIFI network with SSID + WIFIpassword
AT+CWDHCP_DEF=1,1 OK Enable DHCP, set ESP8266 as station
AT+PING=“<IPv4address>“ ping response Pings an IP address

Stumbling block #4

If the module appears to have no firmware it is necessary to flash the firmware.

ESP8266 on Arduino

An Arduino can be used to program the ESP8266. Assuming that this simple Arduino sketch was uploaded to the Arduino first:

#define SSID "<SSID>"
#define PASS "<WIFIpassword>"
#define IP "<IPv4adress>"

void setup() { 
  Serial.begin(115200);  // set baud rate
  
  Serial.println("AT");
  delay(5000);
  if( Serial.find("OK") ){
    connectToWiFi();
  }
}

void loop(){
  // generate some traffic
  ping();
  delay(10000);
}

void connectToWiFi(){
  // enable DHCP and set ESP8266 as station
  Serial.println("AT+CWDHCP_CUR=1,1");
  delay(2000);
  // connect to the WIFI network
  String cmd="AT+CWJAP=\"";
  cmd+=SSID;
  cmd+="\",\"";
  cmd+=PASS;
  cmd+="\"";
  Serial.println(cmd);
}

void ping() {
    String cmd="AT+PING=\"";
    cmd += IP;
    cmd += "\"";
    Serial.println(cmd);
}

The program sets up a WiFi connection to the desired network and generates some network traffic using ping. The network traffic can be observed using tcpdump (Linux) for example.

Stumbling block #5/6

ESP8266 must be powered off and RX/TX lines must be connected to the Arduino everytime the sketch is uploaded. 😉

Stumbling block #7

Once the Arduino is generating the AT commands for the ESP8266 the setup has to be slightly changed:

Arduino Pro Mini, 3,3 V
GND in GND 5 V power supply
VCC in VCC 5 V power supply
RX TX ESP8266
TX RX ESP8266
DTR not connected
RST not connected
ESP8266 module
GND GND 3.3V power supply
VCC VCC 3.3V power supply
RX TX Arduino
TX RX Arduino
CHPD VCC 3.3V power supply via 10k resistor
REST VCC 3.3V power supply via 10k resistor

esp8266onarduino_breadboard

Now the Arduino should be able to connect to the local WiFi network and should ping the desired IP every couple of seconds.

How to keep a Powerbank awake

Using a powerbank as a battery for an Arduino project is usually a good idea. Powerbanks are available for little money, have large capacities, can be charged easily via USB and could be used for charging mobile devices as well.

When powering an Arduino it may happen that the powerbank simply turns off after a while. Powerbanks are just way too smart sometimes.

But why? Some clever circuit inside the powerbank detects how much power is drawn. An Arduino usually does not need so much power compared to a charging mobile device. If the amount of power drawn is too small the powerbank turns off.

Searching the internet for enlightenment on this issue leads to the following pages:
http://www.instructables.com/id/USB-External-Battery-Packs-on-Arduino-turns-OFF/
http://dorkbotpdx.org/blog/paul/battery_pack_load
http://held-im-ruhestand.de/hardware/powerbank-autooff.html

To keep such a clever powerbank awake when running an Arduino it is sufficient to trigger a load pulse every few seconds. The time interval depends on the powerbank.
A simple hardware setup of two resistors and a transistor in combination with a pulse generating software is sufficient to create a pulsed load on the powerbank. This way it does not turn off automatically after its time has passed.

The simple piece of software used to trigger the data pin works without the delay-function:

int USBBATTERYDATAPIN=8;
bool usbPinState = false;
int tweakcounter=0;
unsigned long previousMillis;
bool debugging=true;
void triggerUSBBatteryPin() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= 1000) {
    previousMillis = currentMillis;

    if( tweakcounter<=1 ) {
      if( debugging ) {
        Serial.print(F("Toggle USB battery pin: "));
        Serial.println(usbPinState?"low":"high");
      }
      digitalWrite(USBBATTERYDATAPIN, usbPinState?LOW:HIGH);
      usbPinState = !usbPinState;
    } else if( tweakcounter==9 ){ // start over again 8s later
      tweakcounter = 0;
      if( debugging ) {
        Serial.println(F("Reset tweak counter"));
      }
      return;
    }
    tweakcounter++;
    if( debugging ) {
        Serial.print(F("tweakcounter: "));
        Serial.println(tweakcounter);
      }
  }
}

bluetooth sunrise alarm clock + iRise smartphone app

idea

This time I had the idea of creating a sunrise alarm clock. I know they are available for buying, but still I wanted to create my very own version.

My idea was to use an Arduino microcontroller and some RGB LEDs. The sunrise alarm clock should work without a display. A smartphone app should be used to connect via bluetooth to the alarm clock. The smartphone app should be able to set the time, the alarm, steer the LEDs, etc. .

make it happen

With an example I figured out how to create a ’normal‘ Arduino alarm clock using an RTC. This example was easily adapted to steer the RGB LEDs and to integrate the HC-06 bluetooth controller.

principal scheme of hardware setup + working mode

IMG_20151128_114853_principalscheme

I left out the on/off switch and the battery in the basic scheme. It is obvious.

It is definitely recommended to change the name and the PIN of the bluetooth module as described in the bluetooth cookbook.

Switching the alarm on at the time desired was easy. But how to turn the alarm (light) off again? Using the smartphone might be tricky in the morning. I was also thinking of a proximity or a light sensor but this could be error-prone. The next best thing I thought of was a capacitative touch sensor instead of a switch. In the end I realized three possibilities to turn the alarm off:

  • using a simple capacitative touch sensor
  • using the iRise app to send the off command
  • an automatic timeout

The bluetooth controller is connceted to the Arduino as a serial device. A simple Serial.read() does the trick and reads the characters sent via bluetooth. Sending data back is possible using the Serial.write() function.
Before creating a dedicated smartphone app the bluetooth connection can be tested with a bluetooth terminal app from the play store.

protocol

I invented some codes for communication via bluetooth to steer the basic functionality of the alarm clock such as

  • setting the actual time: i12:34z
  • setting the alarm time: s07:30z
  • stopping the alarm: tz
  • turning the RGB LEDs on: c0..9z
  • sending back the current time of the clock: bz

The character ‚z‘ marks the end of a command.

Since I do not want to give anyone the possibility to launch „Denial-of-Sleep“-attacks I will not publish the codes I really use. 🙂

libraries

To avoid a long and confusing Arduino program it is possible to create C++ libraries. For the alarm clock functionality and driving the RGB LEDs I created two simple Arduino libraries.

hardware

In the end I transferred the project to a clone of an Arduino Pro Mini. This small plate has all the required connections and has a minimal size for a reasonable prize.

iRise smartphone app

The smartphone app ‚iRise‘ is pretty straightforward. I decided to go for plain Material design. On http://www.androidhive.info/2015/04/android-getting-started-with-material-design/ I found a good example to start with.
Numerous examples explain how to find and pair a bluetooth device. E.g. http://www.tutorialspoint.com/android/android_bluetooth.htm or http://examples.javacodegeeks.com/android/core/bluetooth/bluetoothadapter/android-bluetooth-example/ explain everything.

rainbow and mood light

The bluetooth sunrise alarm clock can do some more: it also serves as a mood light. Several single colors can be selected in the app. Alternatively a rainbow color change or blueish night mode can be switched on.

The bluetooth state is displayed on another fragment of the app.

extending iRise

Another idea for extension is to capture smartphone notifications such as calendar reminders or incoming messages. Such notifications could trigger the sunrise alarm clock to blink in a dedicated color. This could be solved using a service and a user activated listener as well as the smartphone to be near the alarm clock for a working bluetooth connection.

Another possible extension is to measure the battery voltage to estimate the remaining battery time.

components used

Clone of Arduino Pro Mini 
Lilypad RGB LEDs
RTC – real time clock module
HC-06 bluetooth module
capacitative touch sensor
rechargeable 3.7 V battery
slide switch
buzzer
cables
FTDI cable
for development and testing: Arduino Uno
a box for the clock

For simplicity I used the Lilypad RGB LEDs. They bring the required resistance along. But any other RGB LED or even a RGB LED strip (5V!) will do as well.

software development

Arduino IDE from https://www.arduino.cc/en/Main/Software
Android Studio from http://developer.android.com/sdk/index.html

good to know

Whenever the Arduino sketch is transferred to the board the bluetooth adapter may not be running because it is using the serial connections. Simply remove the power of the HC-06 before the software upload starts and put it back afterwards if required. A reversible connection between the VCC input of the bluetooth module and the power source is a good solution for software updates.

During development it might help a lot to attach an LDC display the the Arduino to display the current settings of the alarm clock.

some pictures and a screenshot

 

source code examples

sunriseclock.ino

AlarmClock

RGBLED

BluetoothUtils.java

construction of a box

This was not simple. One of the first ideas was to construct a paper box. But this is not a durable solution. So the box will be -under construction- for a while! Maybe it will be a wooden box with an acrylic glas top in the end.

references

https://github.com/pAIgn10/AlarmClock

http://arduino-hannover.de/2013/07/20/bluetooth-kochbuch-fur-arduino/

http://lilypadarduino.org/?page_id=548

https://www.arduino.cc/

 

FTDI troubleshooting

While working on an Arduino based project I was suddenly not able to upload sketches using an FTDI anymore. From one minute to another I received errors such as

avrdude: stk500_getsync(): not in sync: resp=0x00
avrdude: stk500_disable(): protocol error, expect=0x14, resp=0x51

when trying to upload a sketch to an Arduino Pro Mini or Lilypad.

I checked several possible causes such as

 

But the error persisted. None of the possibles causes I checked was responsible.

With the tips in this forum entry I found out that my (original) FTDI chip was the problem, somehow the TX line went dead. Further tests on Linux confirmed this. How and why this happened is unclear.

My final solution? I had to buy a new one…