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…

sleepy pillow

Quite a while ago I had the idea to create a pillow with a Lilypad MP3 . The „Sleepy Pillow“ should play songs at random for approx. 45 minutes and then power down as good as possible to save battery.

The Lilypad MP3 is an Arduino board that takes an SD card with audible files and plays them with speakers attached.

This is how the Sleepy Pillow was realized:

components used

Lilypad MP3
speakers
conductive thread, needle for sewing
LiPo, additional JST connectors
Lilypad switch
fabric
pillow filling
for testing: alligator clips
mini SD card with MP3 songs

make it happen

First I set up the main components with alligator clips before sewing. That way the source code could be conveniently tested.

IMG_20151003_141823_sleepypillow_setup

I put something below the speakers to prevent them from vibrating too much while testing.

From my song collection I chose several songs in MP3 format and placed them on the mini SD card using an adapter. The filenames are consecutive numbers.

When the source code was working so far I started to sew the components on the fabric I selected as a pillowcase. For the LiPo I created a small bag.

I added a switch for turning the power completely off manually.

to consider

The Lilypad MP3 can not play songs when attached via USB to a computer. Only serial output can be seen! The player starts to play the files but the playback stops after a few seconds. The reason is that there is not enough power to drive the speakers via USB. An additional power supply such as a LiPo is required. The program can only be fully tested attaching a battery to the Lilypad.

The audio files on the SD card may be of the type MP3, WAV, MP4, FLA, OGG, WMA or AAC. The filenames should be in the format 8.3 which is up to 8 digits for the filename and 3 digits for the file extension. I went for consecutive numbers as filenames to ease random selection.

the source code

The source code is based on the examples that can be found for the Lilypad MP3. The sleep code originates from the Arduino playground.

SleepyPillow.ino

I used the latest Arduino Software from https://www.arduino.cc/en/Main/Software. Great tutorials to get started with the Lilypad MP3 are at the sparkfun website.

how it works

When the Sleepy Pillow is turned on it starts to play songs from the SD card at random. The music stops after the desired time limit is reached. Then the Lilypad MP3 is „put to sleep“ to save battery. Additionally it is possible to use the power switch…

The volume is automatically turned down approx. 10 s before the time limit is reached.

IMG_20151007_194819_sleepypillow

android cardboard app using camera and OpenGL ES

Some time ago I started to program Android apps for my smartphone. This year I got my hands on a cardboard. My first idea was to use the smartphones camera. Stereo vision with a single camera is a fake, but still it was tempting.

I was searching for examples of cardboard apps and stumbled across this example. For a start this was very useful. The example app shows how to create a stereo view and how to display a live camera image. The app is using Open GL ES which won’t work on all smartphones.

After having played around a little with the app I realized that if I want to change the live image only the fragment shader has to be replaced. This can happen  while clicking on the display (difficult when the smartphone is in the cardboard) or when using the magnet switch.

cardboardView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        switchShader();
    }
});


@Override
public void onCardboardTrigger() {
    vibrator.vibrate(50);
    switchShader();
}

Excerpt of switchShader():

private void switchShader() {
    shader_selection++;
    if (shader_selection > 31)
        shader_selection = 0;

    if (shader_selection == ORIG)
        overlayView.show3DToast("Shader: original. " +     shader_selection);
    else if (shader_selection == NEG)
        overlayView.show3DToast("Shader: negative. " +     shader_selection);
    ...
}

To display a negative image the fragment shader looks like this:

#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 textureCoordinate;
uniform samplerExternalOES s_texture;
void main() {
    vec4 color = texture2D( s_texture, textureCoordinate );
    float inverted = 1.0 - color.r;
    vec4 inverted_vec = vec4( vec3(inverted), 1.0);
    gl_FragColor = clamp(inverted_vec, 0.0, 1.0);
}

To invert RGB images:

#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 textureCoordinate;
uniform samplerExternalOES s_texture;
void main() {
	vec4 color = texture2D( s_texture, textureCoordinate );
    float invertedr = 1.0 - color.r;
    float invertedg = 1.0 - color.g;
    float invertedb = 1.0 - color.b;
    vec4 inverted_vec = vec4( vec3(invertedr, invertedg, invertedb), 1.0);
    gl_FragColor = clamp(inverted_vec, 0.0, 1.0);
}


A nice sepia effect:

#extension GL_OES_EGL_image_external : require
#ifdef GL_ES
precision mediump float;
#endif

varying vec2 textureCoordinate;
uniform samplerExternalOES s_texture;

void main()
{
    vec4 color = texture2D( s_texture, textureCoordinate );
    vec4 sepia = texture2D( s_texture, textureCoordinate );
    sepia.r = (color.r * .393) + (color.g *.769) + (color.b * .189);
    sepia.g = (color.r * .349) + (color.g *.686) + (color.b * .168);
    sepia.b = (color.r * .272) + (color.g *.534) + (color.b * .131);
    
    gl_FragColor = vec4(sepia.rgb, 1);
}

Several good examples for shaders which can be adapted I found here:

http://littlecheesecake.me/blog1/2013/08/15/pretty-geeky-codes.html
http://littlecheesecake.me/blog1/2013/04/19/more-filters.html
http://littlecheesecake.me/blog1/2013/01/31/image-processing.html

It is quite important to stop the camera when the app is paused. Otherwise other apps could not use the camera in the meantime.

    @Override
    public void onPause() {
        super.onPause();
        stopCamera();
    }

    private void stopCamera() {
        if( camera!=null ) {
            camera.stopPreview();
        }
    }

The result looks like this (computer mouse in negative mode):

androidcardboardnegativemouse

The smartphone gets pretty warm while using the app…