Friday, March 27, 2015

Arduino Day 2015 Project - Air Quality Display

Arduino Day gives folks the opportunity to showcase their work with these easy to use microcontrollers.  Today's project is something I've wanted for awhile: an Internet connected air quality display.  Folks looking to go outside want a quick glance look at how the air quality is that day.  This includes runners, those with breathing problems, and young children.


Components

There are several blocks to the project, both hardware and software:
  • Arduino Uno Microcontroller
  • Arduino Ethernet Shield
  • Adafruit LCD Display (select one that does not require hardware SPI that conflicts with the select line).
Software used:

How it works

The ethernet shield is connected to your home network.  The software sketch ensures all the pieces are communicating, then it obtains the air quality based on the latitude and longitude of the project.  This is available in the United States for free from the U.S. Environmental Protection Agency (EPA) via their free AirNow API.  In other countries, there are some data service providers, the code would need to be written to read the other service's API and data, see the list at http://www.airnow.gov/index.cfm?action=airnow.international 

Once the data is collected, the specific data desired is parsed out of the HTTP code and API output.  Then the data is written to an LCD display.  A color display is important as the U.S. EPA has specific colors for specific air quality levels:

AQI NumbersAQI Category (Descriptor)AQI Color  Hexadecimal Color ValueCategory Number
0 - 50GoodGreen  (00e400)1
51 - 100ModerateYellow  (ffff00)2
101 - 150Unhealthy for Sensitive GroupsOrange  (ff7e00)3
151 - 200UnhealthyRed  (ff0000)4
201 - 300Very UnhealthyPurple  (99004c)5
301 - 500HazardousMaroon  (7e0023)6
So the display should be clear seen from a distance using the EPA colors what the status is.  For a 24 bit (3 byte RGB) color display, the color values specified above can be sent.  I tried to use two other displays: a large touchscreen (conflicted with the Ethernet SPI control) and a display shield (I probably have a wiring issue with it) so I jumped to a trusty display I have written about used with the Arduino Esplora.  The problem is it is discontinued and it is only 16 bit color.  SO I had to tweak the Adafruit driver library code to compile under the Arduino IDE Version 1.6.x and to use 16 bit colors.  I have tried to get similar colors but I suggest you select colors that you find are closest to the colors above.  If you can find a color picker for 16 bits, place a comment below.

You will need to get an API key from www.airnow.gov.  The key is free but you need to sign up.  Replace my key in the code (please!) with your own.

Code

I'll admit the code is not as elegant as that in many of my Adafruit tutorials.  I had to change displays late in the project.  And I did not use a GPS shield to keep the project simple but that does not help in portability.  Also I've selected the US service with a link to other countries' air quality services.  Each will probably have their own data format which will require the correct code to parse the data, that has not been done here except for the US formatting.

This code is freely available open source on Github at https://github.com/TheKitty/AirQuality/tree/master

/***************************************************
  Air Quality Monitoring
  Uses the Arduino Uno, Ethernet Shield, and Adafruit 1.8"
  display shield http://www.adafruit.com/products/802

  Check out the links above for our tutorials and wiring diagrams

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

  Based on code written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/
#include <Ethernet.h>
#include <SPI.h>      

#include <Adafruit_GFX.h>      // Core graphics library
#include <Adafruit_HX8340B.h>

// Display Color definitions
#define BLACK           0x0000
#define BLUE            0x001F
#define RED             0xF800
#define GREEN           0x07E0
#define CYAN            0x07FF
#define MAGENTA         0xF81F
#define YELLOW          0xFFE0  
#define WHITE           0xFFFF
#define ORANGE          0xF500

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // Ethernet shield MAC address

char server[] = "www.airnowapi.org";    
IPAddress ip(192, 168, 0, 178);  // select for your home network - it will try DHCP first though

EthernetClient client;

uint8_t lineCount = 0;

// TFT display will NOT use hardware SPI due to the SPI of the Ethernet shield
// SO other pins are used.  This makes the display a bit slow
#define SD_CS     4   // Chip select line for SD card
#define TFT_CS    9   // Chip select line for TFT display
#define TFT_SCLK  6   // set these to be whatever pins you like!
#define TFT_MOSI  7   // set these to be whatever pins you like!
#define TFT_RST   8   // Reset line for TFT (0 = reset on Arduino reset)
Adafruit_HX8340B tft(TFT_MOSI, TFT_SCLK, TFT_RST, TFT_CS);

void setup(void) {

  Serial.begin(115200);
  Serial.println(F("Air Quality Monitor"));
    
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // no point in carrying on, so do nothing forevermore:
    // try to congifure using IP address instead of DHCP:
    Ethernet.begin(mac, ip);
  }
  // Give the Ethernet shield a second to initialize:
  delay(1000);
  Serial.println("Ethernet connecting...");
  // if you get a connection, report back via serial:
  if (client.connect(server, 80)) {
    Serial.println("Ethernet connected");
    // Make a HTTP request
    // Change the latitude/longitude for your location
    // You need your own API key from www.airnow.org pasted into the string below
    // If you will go mobile, get lat/long from GPS or from your outward IP address
    //   from a service like http://www.freegeoip.org/
    client.println("GET /aq/forecast/latLong/?format=text/csv&latitude=38.8&longitude=-77.3&distance=25&API_KEY=FE62B688-EE27-4214-8BD8-9903E1AC5881 HTTP/1.1");
    client.println("Host: www.airnowapi.org");
    client.println("Connection: close");
    client.println();
  }
  else {
    // If you didn't get a connection to the server:
    Serial.println("Ethernet connection failed");
  }
  client.setTimeout(1000);
  
  tft.begin();           // Initialize  TFT
  tft.setRotation(1);    // Landscape display
}

void loop() {
  char buffer[160];      // The HTTP read buffer - the maximum line returned by the API is 158 chars
  char partInfo[4][10];  // The air quality values are placed in 4 strings of length 10 characters
  uint8_t tokenCount, valueCount;
  char *bufValue;
  char *bufPtr;

  if (client.available()) {
    byte numChar = client.readBytesUntil((char)0x0a,buffer,159);  // Read until a line feed character
    buffer[numChar]='\0';
    lineCount = lineCount + 1;
    if(lineCount == 11) {  // Parse first record
      Serial.print("-> ");
      Serial.println(buffer);
      tokenCount=0;
      valueCount=0;
      bufPtr = strtok(buffer,",");
      while(bufPtr != NULL && valueCount < 10) {
        if(valueCount > 5) {
          strcpy(partInfo[tokenCount], bufPtr+1);
          tokenCount++;
        }
        bufPtr = strtok(NULL,",");
        valueCount++;
      }
      for( uint8_t i=0; i<4; i++) {
        for( uint8_t j=0; j<10; j++) {
          if(partInfo[i][j] == '"') {  // the second quotes is the end of the string we want
            partInfo[i][j] = '\0';     //   so replace it with the C null end of string character
          }
        }
        Serial.println(partInfo[i]);
      }
    } else {
      Serial.print(lineCount);
      Serial.print(": ");
      Serial.println(buffer);
    }
  }

  if (!client.connected()) {
    Serial.println();
    Serial.println("Ethernet disconnecting.");
    client.stop();
    /* process the values */
    uint32_t colorAQI = AQI2hex(atoi(partInfo[1]));
    tft.fillScreen(colorAQI);
    //Serial.print("Color set: ");
    //Serial.println(colorAQI,HEX);
    drawtext("Air Quality Today", BLACK, 2, 10, 10);
    drawtext(partInfo[3], WHITE, 3, 40, 40);
    drawtext(partInfo[1], WHITE, 3, 85, 80);
    drawtext("Type:", WHITE, 3, 20, 120);
    drawtext(partInfo[0], WHITE, 3, 110, 120);
    for(;;)
      ; // Infinite loop, press reset button to get daily reading
        // It's best to have a real time clock to pull the value at midnight
        // Perhaps a GPS shield would get the time and the lat/long but be sure 
        // to deconflict the data pins if necessary.
  }
}

uint32_t AQI2hex(uint16_t AQI) {   // see color tablee for mandated color coding
  if(AQI <=  50) return(GREEN); // Green
  if(AQI <= 100) return(YELLOW); // Yellow
  if(AQI <= 150) return(ORANGE); // Orange
  if(AQI <= 200) return(RED); // Red
  if(AQI <= 300) return(MAGENTA); // Purple
  return(0x8000); // Maroon
}

void drawtext(char *text, uint16_t color, uint8_t tsize, uint8_t x, uint8_t y) {
  tft.setCursor(x, y);
  tft.setTextSize(tsize);
  tft.setTextColor(color);
  tft.print(text);
}
   




Saturday, March 14, 2015

The Wonder of Pi Day

Pi Day, the day in the calendar that comes closest to the transcendental number Pi denoted by the Greek letter π, with a value of 3.14159265......  The circumference of a circle is π times the diameter. Anyone reveling in the fact that March 14, 2015 can be written 3/14/15, like the numbers of Pi, must be rather special.

The number of articles discussing folks reveling in the science and fun of celebrating Pi on March 14th has exploded. People of all ages are going gaga with those who like science or just some fun coming out to celebrate in many way.  Celebrations often involve making and eating Pie the dessert, due to the similarity in pronunciation between Pi and Pie.

Wikipedia public domain by  GJ_on_Wiki 
More permanent celebrations of Pi are placed around the world such as this depiction at the Mathematics Building at the Technical University of Berlin.

Wikipedia CC BY-SA 3.0 Holger Motzkau
As my focus has been with the Maker movement, specifically mainly with electronics, I love projects incorporating information about Pi day.  So my project uses a very nice Adafruit 8x32 programmable LED grid as a marquee to scroll "Happy Pi Day 3.14.15" in red, green, and blue text.  A video of the project is below:

 The code for the project:

// Program for Flexible 8x32 NeoPixel RGB LED Matrix
// Scrolls text across the matrix 
//
// Mike Barela 3/12/2015  MIT License, attribution appreciated

#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>
#define PIN 12 // NeoPixel data pin on Uno

// MATRIX DECLARATION:
// Parameter 1 = width of NeoPixel matrix
// Parameter 2 = height of matrix
// Parameter 3 = pin number (most are valid)
// Parameter 4 = matrix layout flags, add together as needed:
//   NEO_MATRIX_TOP, NEO_MATRIX_BOTTOM, NEO_MATRIX_LEFT, NEO_MATRIX_RIGHT:
//     Position of the FIRST LED in the matrix; pick two, e.g.
//     NEO_MATRIX_TOP + NEO_MATRIX_LEFT for the top-left corner.
//   NEO_MATRIX_ROWS, NEO_MATRIX_COLUMNS: LEDs are arranged in horizontal
//     rows or in vertical columns, respectively; pick one or the other.
//   NEO_MATRIX_PROGRESSIVE, NEO_MATRIX_ZIGZAG: all rows/columns proceed
//     in the same order, or alternate lines reverse direction; pick one.
//   See example below for these values in action.
// Parameter 5 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)


// Example for Flexible 8x32 NeoPixel RGB LED Matrix.  In this application we'd like to use it
// as a 32x8 tall matrix.  When held that way, the first pixel is at the top right, and
// lines are arranged in columns, progressive order.  The shield uses
// 800 KHz (v2) pixels that expect GRB color data.
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(32, 8, PIN,  
   NEO_MATRIX_TOP + NEO_MATRIX_LEFT + NEO_MATRIX_COLUMNS + NEO_MATRIX_ZIGZAG, NEO_GRB + NEO_KHZ800);

const uint16_t colors[] = {
  matrix.Color(255, 0, 0), matrix.Color(0, 255, 0), matrix.Color(0, 0, 255) };

void setup() {
  matrix.begin();
  matrix.setTextWrap(false);
  matrix.setBrightness(35);
}

int x    = matrix.width();

void loop() {
  matrix.fillScreen(0);
  matrix.setCursor(x, 0);
  matrix.setTextColor(colors[1]);
  matrix.print(F("Happy "));
  matrix.setTextColor(colors[0]);
  matrix.print(F("Pi "));
  matrix.setTextColor(colors[1]);
  matrix.print(F("Day "));
  matrix.setTextColor(colors[2]);
  matrix.print(F("3.14.15  "));
  if(--x < -(22*5)) {  // Scrolling code - 22 characters, 5 pixels wide
    x = matrix.width();
  }
  matrix.show();
  delay(100);
}

Be sure you have a power supply big enough to power that many Neopixels.  I used Adafruit's 5V 10 amp supply but a 2 amp supply at the lower brightness might suffice, 5 amps might be a good size.

If you cannot get an Adafruit matrix, a couple (or more!) Wyolum TiM LED matrices would also be very good for a similar marquee. 

Have a happy and safe Pi Day!