Web-based Weather Station

This Arduino project displays information on a 4-line LCD of local weather conditions. Using an Ethernet shield on an Arduino UNO, the unit requests data from openweathermap.org (using their api) and parses the information to obtain temperature (current, min and max), humidity, pressure, wind speed and direction, and textual description of current conditions. It is intended that this project will migrate from using the current Ethernet shield / UNO combination to an Nano and ESP8266 WiFi module for simple WiFi connectivity, However, I thought that this current version – naked and unhoused – may be on interest to some of you.

Web Weather Station

Web Weather Station

Upon request, the API sends a JSON file (see below, with line breaks added) that contains lots of weather and related information for the location.

{"coord":{"lon":-75.67,"lat":45.65},"sys":{"type":1,"id":3783,"message":0.0139,"country":"CA","sunrise":1427022052,"sunset":1427066277},
"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"base":"cmc stations",
"main":{"temp":257.77,"pressure":1023,"humidity":49,"temp_min":257.15,"temp_max":258.15},"wind":{"speed":3.1,"deg":310},
"clouds":{"all":20},"dt":1427024794,"id":6173012,"name":"Val-des-Monts","cod":200}

However as much of this data was superfluous to the project, I initially intended to use the ThingHTTP application on thingspeak.com to parse out the data. While this worked OK for obtaining temperature data, I found the parsing ‘conditions’ were too limiting. So I ended up doing all of the text parsing of the long string in software.

The software requests data from the website every 10 minutes and updates the I2C-connected display accordingly. A couple of tests catch bad or incomplete responses but in reality the request – response appears quite robust. Now that the format is established, it would be quite easy to cycle through weather conditions in several different locals.

For the weather conditions display, I have incorporated some pieces of prior work. For the current temperature I have added code from the big font numerals work to display the data in 3-line high numerals. In addition, I have taken the work from the gpSKY CLOCK to convert the wind direction in degrees to a simple cardinal position.

Anyway, here’s the code… You will need to sign up to obtain your own API key (APPID) from openweathermap.org and, of course, select the location whose weather you want to display. However, the rest of the code is easily hackable and I hope that it gets your own creative juices flowing.

Here is the Arduino code of the Web Weather Station. If you have any thoughts, suggestions or can suggest any improvements to the code, please comment away!

<

p style=”text-align: justify;”>[codesyntax lang=”php” title=”Web Weather Station” blockstate=”collapsed”]

//**************************************************************************************************
//                                     WEB WEATHER MONITOR
//                                   Adrian Jones, March 2015
//
//**************************************************************************************************

// Build 1
//   r1 150320 - initial build with I2C 4x20 display
//   r2 150321 - add weather conditions
//   r4 150323 - rearrange lcd screen and add large fonts
//********************************************************************************************
#define build 1
#define revision 4
//********************************************************************************************

#include <avr/pgmspace.h>            // for memory storage in program space

#include <Wire.h> 
#include <LCD.h>                     // Standard lcd library
#include <LiquidCrystal_I2C.h>       // library for I@C interface

#define I2C_ADDR  0x27               // address found from I2C scanner
#define RS_pin    0                  // pin configuration for LCM1602 interface module
#define RW_pin    1
#define EN_pin    2
#define BACK_pin  3
#define D4_pin    4
#define D5_pin    5
#define D6_pin    6
#define D7_pin    7

LiquidCrystal_I2C lcd(I2C_ADDR, EN_pin, RW_pin, RS_pin, D4_pin, D5_pin, D6_pin, D7_pin, BACK_pin, POSITIVE);
//Pins for the LCD are SCL A5, SDA A4

#include <SPI.h>
#include <Ethernet.h>

byte mac[] = { [** YOUR MAC ADDRESS **] };
IPAddress ip([** YOUR IP ADDRESS **]);            // LAN IP address (http://192.168.1.178:8081)
IPAddress gateway(192,168,1,1);                   // internet access via router
IPAddress subnet(255,255,255,0);                  // subnet mask

EthernetClient client;

char server[]   = "api.openweathermap.org";                  // name for server
String sendReq  = "/data/2.5/weather?id=6173012&APPID=[** YOUR APPID **]";     // send request
// 5959974 - Gatineau, 6094817 - Ottawa, 6173012 - Val-des-Monts, 6175108 - Wakefield

String inString = "",conditions;
char inChar;
int start;
float temp_cur, temp_min, temp_max, pressure, humidity, wspeed, dir;
#define kelvin 273.15                              // adjust from Kelvin to Centigrade

#define longDelay  600000                          // long delay
#define shortDelay  30000                          // short delay 
unsigned long loopTime;                            // update loop time
unsigned long timer;                               // interrupt timer
boolean first=true;

const char custom[][8] PROGMEM = {
     { 0x01, 0x07, 0x0F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F },      // char 1: bottom right triangle
     { 0x00, 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x1F, 0x1F },      // char 2: bottom block
     { 0x10, 0x1C, 0x1E, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F },      // char 3: bottom left triangle
     { 0x1F, 0x0F, 0x07, 0x01, 0x00, 0x00, 0x00, 0x00 },      // char 4: top right triangle
     { 0x1F, 0x1E, 0x1C, 0x10, 0x00, 0x00, 0x00, 0x00 },      // char 5: top left triangle
     { 0x1F, 0x1F, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x00 },      // char 6: upper block
     { 0x0C, 0x12, 0x12, 0x0C, 0x00, 0x00, 0x00, 0x00 },      // char 7: full top right triangle
     { 0x01, 0x07, 0x0F, 0x1F, 0x00, 0x00, 0x00, 0x00 }       // char 8: top right triangle
};

const char bn3[][30] PROGMEM = {                            // 3-line numbers
//         0               1               2               3               4              5               6                7               8               9
    { 0x01,0x06,0x03, 0x08,0xFF,0xFE, 0x08,0x06,0x03, 0x08,0x06,0x03, 0xFF,0xFE,0xFF, 0xFF,0x06,0x06, 0x01,0x06,0xFE, 0x06,0x06,0xFF, 0x01,0x06,0x03, 0x01,0x06,0x03 },
    { 0xFF,0xFE,0xFF, 0xFE,0xFF,0xFE, 0x01,0x06,0x05, 0xFE,0x06,0xFF, 0x06,0x06,0xFF, 0x06,0x06,0x03, 0xFF,0x06,0x03, 0xFE,0x01,0x05, 0xFF,0x06,0xFF, 0x04,0x06,0xFF },
    { 0x04,0x06,0x05, 0xFE,0x06,0xFE, 0x06,0x06,0x06, 0x04,0x06,0x05, 0xFE,0xFE,0x06, 0x04,0x06,0x05, 0x04,0x06,0x05, 0xFE,0x06,0xFE, 0x04,0x06,0x05, 0x04,0x06,0x05 }
};

byte col,row,nb=0,bc=0, bb[8];

// cardinal directions
const char card0[]  PROGMEM = "N";
const char card1[]  PROGMEM = "NNE";
const char card2[]  PROGMEM = "NE";
const char card3[]  PROGMEM = "ENE";
const char card4[]  PROGMEM = "E";
const char card5[]  PROGMEM = "ESE";
const char card6[]  PROGMEM = "SE";
const char card7[]  PROGMEM = "SSE";
const char card8[]  PROGMEM = "S";
const char card9[]  PROGMEM = "SSW";
const char cardA[]  PROGMEM = "SW";
const char cardB[]  PROGMEM = "WSW";
const char cardC[]  PROGMEM = "W";
const char cardD[]  PROGMEM = "WNW";
const char cardE[]  PROGMEM = "NW";
const char cardF[]  PROGMEM = "NNW";
const char * const cards[] PROGMEM = { card0,card1,card2,card3,card4,card5,card6,card7,card8,card9,cardA,cardB,cardC,cardD,cardE,cardF };

char cb[4];                                        // text buffer for cardinal directions


#define serEn true                                 // serial port
#define lcdEn true                                 // lcd

//*****************************************************************************************//
//                                      Initial Setup
//*****************************************************************************************//
void setup() {
  Ethernet.begin(mac, ip, gateway, gateway, subnet);                  // set up connection
  
  if(serEn) {
    Serial.begin(57600);                                             // Open serial port
    Serial.println(F("WEB WEATHER MONITOR"));
    Serial.print(F("Build ")); Serial.print(build); Serial.print(F(".")); Serial.println(revision);
    Serial.print(F("Free RAM: "));  Serial.print(freeRam()); Serial.println(F("B"));
  }
  
  if(lcdEn) {
    lcd.begin(20, 4);
    for (nb=0; nb<8; nb++ ) {                     // create 8 custom characters
      for (bc=0; bc<8; bc++) bb[bc]= pgm_read_byte( &custom[nb][bc] );
      lcd.createChar ( nb+1, bb );
    }
    lcd.clear();
    lcd.setCursor(0, 0); lcd.print(F("WEB WEATHER STATION")); 
    lcd.setCursor(5, 1); lcd.print(F("V")); lcd.print(build); lcd.print(F(".")); lcd.print(revision); lcd.print(F(" "));  lcd.print(freeRam()); lcd.print(F("B"));
    lcd.setCursor(2, 2); lcd.print(F("... waiting ..."));
 }
  delay(1000);
  timer=millis();
  loopTime = shortDelay;                        // initially
}

//*****************************************************************************************//
//                                      MAIN LOOP
//*****************************************************************************************//
void loop() {
  if (timer > millis())  timer = millis();      // if millis() or timer wraps around, we'll just reset it
  if ( (millis() - timer > loopTime) || first) {            // 
    timer = millis();                           // reset the timer
    first=false;
  
    doConnect();                                // connect to server
    
    inString = "";
    while (client.available()) {  
      inChar = client.read();  // read incoming bytes if available
      if (inChar != '\n') inString += inChar;
    }
   
   if(inString.length()>350) {                  // process line
// approximate format of expected return from server
// 0         1         2         3         4         5         6         7         8         9         0         1         2         3         4         5         6         7         8         9         0         1         2         3         4         5         6         7         8         9
// 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
// {"coord":{"lon":-75.67,"lat":45.65},"sys":{"type":1,"id":3783,"message":0.0139,"country":"CA","sunrise":1427022052,"sunset":1427066277},"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"base":"cmc stations","main":{"temp":257.77,"pressure":1023,"humidity":49,"temp_min":257.15,"temp_max":258.15},"wind":{"speed":3.1,"deg":310},"clouds":{"all":20},"dt":1427024794,"id":6173012,"name":"Val-des-Monts","cod":200}


      start = inString.indexOf("temp\":", 0) + 6;
      temp_cur = inString.substring(start, (inString.indexOf(',',start)) ).toFloat() - kelvin ;
    
      start = inString.indexOf("pressure\":", 0) + 10; 
      pressure = inString.substring(start, (inString.indexOf(',',start)) ).toFloat();
    
      start = inString.indexOf("humidity\":", 0) + 10;
      humidity = inString.substring(start, (inString.indexOf(',',start)) ).toFloat();
    
      start = inString.indexOf("temp_min\":", 0) + 10;
      temp_min = inString.substring(start, (inString.indexOf(',',start)) ).toFloat()- kelvin ;
    
      start = inString.indexOf("temp_max\":", 0) + 10;
      temp_max = inString.substring(start, (inString.indexOf(',',start)) ).toFloat()- kelvin ;

      start = inString.indexOf("description\":", 0) + 14;
      conditions = inString.substring(start, (inString.indexOf(',',start)-1 ) );
      
      start = inString.indexOf("speed\":", 0) + 7;
      wspeed = inString.substring(start, (inString.indexOf(',',start)-1) ).toFloat() * 3.6;
      
      start = inString.indexOf("deg\":", 0) + 5;
      dir = inString.substring(start, (inString.indexOf('}',start)-1) ).toFloat();

      boolean goodStr = (inString.indexOf("coord\":", 0) == -1)? false : true;        // ensure the string is valid

      if(serEn) Serial.println(inString);
          
      if( (humidity>0 || pressure >0) && goodStr) {
        if(serEn) {
          Serial.print(F("Temperature:\t"));
          Serial.print(temp_cur,1);
          Serial.print(F("C\t ["));
          Serial.print(temp_min,1);
          Serial.print(F("C to "));
          Serial.print(temp_max,1);
          Serial.println(F("C]"));     
          Serial.print(F("Humidity:\t")); Serial.print(humidity,0); Serial.println(F("%"));
          Serial.print(F("Pressure:\t")); Serial.print(pressure,0); Serial.println(F(" hpa"));
          Serial.print(F("Wind speed:\t")); Serial.print(wspeed,0); Serial.println(F(" kph"));
          Serial.print(F("Wind Direction:\t")); Serial.print(dir,0); Serial.print(F(" deg [")); Serial.print(getCardinal(dir)); Serial.println(F("]"));
          Serial.print(F("Conditions:\t")); Serial.println(conditions);
         }
        if(lcdEn) {
          lcd.clear();
          int temp = int(temp_cur);
          printNum3(abs(temp)/10, 13, 0);
          printNum3(abs(temp)%10, 16, 0);
          lcd.setCursor(19, 0); lcd.write(0x07); lcd.setCursor(19, 1); lcd.print(F("C"));
          if(temp<0) {lcd.setCursor(12, 1); lcd.write(0x06);}
          lcd.setCursor(0, 0); lcd.print(temp_min,0); lcd.print(F(" - ")); lcd.print(temp_max,0); lcd.write(0x07); lcd.print(F("C")); 
          lcd.setCursor(0, 1); lcd.print(humidity,0); lcd.print(F("% ")); lcd.print(pressure,0); lcd.print(F("mb")); 
          lcd.setCursor(0, 2); lcd.print(wspeed,1);   lcd.print(F("kph "));  lcd.print(getCardinal(dir));
          if(conditions.length()>20) conditions = conditions.substring(0,19);          // truncate string to no more than 20 characters
          lcd.setCursor(0, 3); lcd.print(conditions);
        }
      loopTime = longDelay;
      }
    }
    
    if (!client.connected()) {  // if the server's disconnected, stop the client:
      Serial.println(F("-> Disconnected"));
      Serial.println();
      client.stop();
    }
  }
  delay(50);  
}


// ********************************************************************************** //
//                                  CONNECTION SUBROUTINES
// ********************************************************************************** //

boolean doConnect() {
  Serial.println(F("Connecting to server"));
  if (client.connect(server, 80)) {
    Serial.println(F("-> Connected"));             // if you get a connection, report back via serial:
    client.print(F("GET "));
    client.println(sendReq);
    client.println(F("Host: api.openweathermap.org"));
    client.println(F("Connection: close")); 
    client.println();
    return true;  
  } else {
    Serial.println(F("-> Connection failed"));    // if you didn't get a connection to the server:
    return false;
  }
}

// ********************************************************************************** //
//                                      OPERATION ROUTINES
// ********************************************************************************** //
// FREERAM: Returns the number of bytes currently free in RAM  
int freeRam(void) {
  extern int  __bss_end, *__brkval; 
  int free_memory; 
  if((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__bss_end); 
  } 
  else {
    free_memory = ((int)&free_memory) - ((int)__brkval); 
  }
  return free_memory; 
}

// ********************************************************************************** //
//                               GENERAL SUBROUTINES
// ********************************************************************************** //
// getCardinal: converts the headsing (in degrees) to a text-based cardinal heading ("ENE", for example)
String getCardinal( float h ) { 
  h += 11.25; if (h > 360.0) h = h-360.0;
  strcpy_P(cb, (char*) pgm_read_word(&(cards[(int) (h / 22.5)]) ) );
  return cb;  
}

// ********************************************************************************** //
//                             LCD PRINT SUBROUTINES
// ********************************************************************************** //
void printNum3(byte digit, byte leftAdjust, byte topAdjust) {
   for(row=0; row<3; row++) {
     lcd.setCursor(leftAdjust,row+topAdjust);               
     for(byte num=digit*3; num <digit*3+3; num++) {
       lcd.write(pgm_read_byte( &bn3[row][num]) );
     }
   }
}

[/codesyntax]

 

Maybe the “few clouds” should read “It’s frigging cold – where the hell is spring?”

Stay cool and stay warm!

 

3 thoughts on “Web-based Weather Station

  1. mazur

    Arduino IDE 1.6.2 gives me following errors:

    web_based_weather_station_arduino.ino:38:20: error: ‘YOUR’ was not declared in this scope
    web_based_weather_station_arduino.ino:38:17: error: expected identifier before ‘*’ token
    web_based_weather_station_arduino.ino:38:39: error: type ‘__lambda0’ with no linkage used to declare function ‘auto __lambda0::operator()() const’ with linkage [-fpermissive]
    web_based_weather_station_arduino.ino: In lambda function:
    web_based_weather_station_arduino.ino:38:41: error: expected ‘{‘ before ‘}’ token
    web_based_weather_station_arduino.ino: At global scope:
    web_based_weather_station_arduino.ino:38:41: error: invalid user-defined conversion from ‘__lambda0’ to ‘byte {aka unsigned char}’ [-fpermissive]
    web_based_weather_station_arduino.ino:38:39: note: candidate is: __lambda0::operator void (*)()() const
    web_based_weather_station_arduino.ino:38:39: note: no known conversion for implicit ‘this’ parameter from ‘void (*)()’ to ‘byte {aka unsigned char}’
    web_based_weather_station_arduino.ino:39:15: error: expected identifier before ‘*’ token
    web_based_weather_station_arduino.ino:39:36: error: type ‘__lambda1’ with no linkage used to declare function ‘auto __lambda1::operator()() const’ with linkage [-fpermissive]
    web_based_weather_station_arduino.ino: In lambda function:
    web_based_weather_station_arduino.ino:39:37: error: expected ‘{‘ before ‘)’ token
    web_based_weather_station_arduino.ino: At global scope:
    web_based_weather_station_arduino.ino:39:37: error: invalid user-defined conversion from ‘__lambda1’ to ‘uint32_t {aka long unsigned int}’ [-fpermissive]
    web_based_weather_station_arduino.ino:39:36: note: candidate is: __lambda1::operator void (*)()() const
    web_based_weather_station_arduino.ino:39:36: note: no known conversion for implicit ‘this’ parameter from ‘void (*)()’ to ‘uint32_t {aka long unsigned int}’
    Error compiling.

    Reply
    1. Adrian Post author

      Yes… Not surprisingly.
      You have to edit in your own Ethernet shield MAC address, IP address etc.
      Then get your own APPID from openweathermap.org.
      OK?

      Reply

Come on... leave a comment...