Dan FeinAdam WilliamsAmy Fischlin
Published

RGB LED Weather Map 

The Spark brings the wunderground.com API to life

Full instructions provided4,958
RGB LED Weather Map 

Things used in this project

Hardware components

Resistor 330 ohm
Resistor 330 ohm
To reduce NeoPixel burnout risk, add 300 - 500 Ohm resistor on first pixel's data input and minimize distance between Arduino and first pixel.
×1
Spark Core
Particle Spark Core
Fed data over wifi
×1
RGB addressable LEDs
×100
WS2801 controllers
In series
×100
5V 2A wall wart plug
Powering the whole installation
×1
1000 µf cap
To reduce NeoPixel burnout risk, add 1000 uF capacitor across pixel power leads,
×1

Story

Read more

Code

file_7505.txt

C/C++
Spark Core Code
#include "application.h"
//#include "spark_disable_wlan.h" (for faster local debugging only)
#include "neopixel/neopixel.h"

// IMPORTANT: Set pixel COUNT, PIN and TYPE
#define PIXEL_PIN D2
#define PIXEL_COUNT 100
#define PIXEL_TYPE WS2812B

Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);
Adafruit_NeoPixel rain = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);
Adafruit_NeoPixel heat = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

void setup() 
{
  strip.begin();
  Spark.function("rainbow", call_rainbow);
  Spark.function("heatmap", call_heatmap);
  Spark.function("rainmap", call_rainmap);
  strip.show(); // Initialize all pixels to 'off'
}
void loop() 
{
    //loop_rainbow(1);
    //loop_heatmap(3000);
    //loop_rainmap(3000);
    
}

int call_rainmap(String number) {
    
    
    rain.show();
    
    int delim_index = 0;
    int start_index = 0;
    int second_index = 0;
    //String loc = number.substring(0, delim_index);
    //String val = number.substring(delim_index+1, number.length());
    
    //uint16_t[] vals = new uint16_t[100];
    
    for(int i=0; i<5; i++) {
        
        delim_index = number.indexOf(':', start_index);
        second_index = number.indexOf(';', delim_index);
        
        String loc = number.substring(start_index, delim_index);
        String val = number.substring(delim_index+1, second_index);
        
        start_index = second_index+1;
        
        int pos = loc.toInt();
        int col = val.toInt();
        
        //uint16_t[i] = col;
        uint16_t num16 = pos;
        uint16_t col16 = col;
        rain.setPixelColor(num16, Wheel(col16 & 255));
    }
    
    rain.setBrightness(25);
    
    //rain.show();
    //rainbow(5);
    delay(20);
    return 0;
}

int call_heatmap(String number) {
    
    heat.show();
    
    int delim_index = 0;
    int start_index = 0;
    int second_index = 0;
    //String loc = number.substring(0, delim_index);
    //String val = number.substring(delim_index+1, number.length());
    
    //uint16_t[] vals = new uint16_t[100];
    
    for(int i=0; i<5; i++) {
        
        delim_index = number.indexOf(':', start_index);
        second_index = number.indexOf(';', delim_index);
        
        String loc = number.substring(start_index, delim_index);
        String val = number.substring(delim_index+1, second_index);
        
        start_index = second_index+1;
        
        int pos = loc.toInt();
        int temp = val.toInt();
        
        //uint32_t col = 0;
        uint16_t num16 = pos;
         
        if(temp < 30) {
            heat.setPixelColor(num16, 0, 164, 255);
        } else if (temp < 40) {
            heat.setPixelColor(num16, 255, 0, 255);
        } else if (temp < 45) {
            heat.setPixelColor(num16, 255, 127, 0);
        } else if (temp < 50) {
            heat.setPixelColor(num16, 255, 206, 0);
        } else if (temp < 55) {
            heat.setPixelColor(num16, 255, 254, 0);
        } else if (temp < 60) {
            heat.setPixelColor(num16, 230, 255, 1);
        } else if (temp < 65) {
            heat.setPixelColor(num16, 203, 255, 0);
        } else if (temp < 70) {
            heat.setPixelColor(num16, 174, 255, 0);
        } else if (temp < 75) {
            heat.setPixelColor(num16, 153, 255, 0);
        } else if (temp < 80) {
            heat.setPixelColor(num16, 127, 255, 0);
        } else {
            heat.setPixelColor(num16, 7, 255, 0);
        }
        
        //uint16_t[i] = col;
        //uint16_t num16 = pos;
        //uint16_t col16 = col;
        //heat.setPixelColor(num16, Wheel(col16 & 255));
        //heat.setPixelColor(num16, col);
    }

    heat.setBrightness(25);

    heat.show();
    //rainbow(5);
    delay(20);
    return 0;
}

int call_rainbow(String number) {
    rainbow(5);
}

void loop_rainbow(uint8_t wait) {
  uint16_t i, j, k;
  
  for(int k=0; k<wait; k++) {
    for(j=0; j<256; j++) {
        for(i=0; i<strip.numPixels(); i++) {
            strip.setPixelColor(i, Wheel((i+j) & 255));
        }
        strip.show();
        delay(20);
    }
    //delay(100);
  }
}

void loop_rainmap(int wait) {
    
    rain.show();
    delay(wait);
}

void loop_heatmap(int wait) {
    
    heat.show();
    delay(wait);
}

void rainbow(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i+j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
   return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } else if(WheelPos < 170) {
   WheelPos -= 85;
   return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else {
   WheelPos -= 170;
   return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}

file_7506.js

JavaScript
Node.js script to call the API, send if over to the spark core, saving memory in the device.
/*jslint node: true */
"use strict";

var _ = require('underscore');
var request = require('request');
var spark = require('spark');
var async = require('async');


var map = [];

// Login as usual
var promise = spark.login({ username: 'xxxxxxxxxx', password: 'xxxxxxx' });


var fetch = function(loc, cb) {

 request(loc.url, function(error, response, body) {
    if (!error && response.statusCode == 200) {
//            console.log(body) // Print the google web page.
      var data = JSON.parse(body);
//        console.log(data.current_observation.station.name);
//        console.log(data.current_observation.temperature);
      if(data && data.current_observation) {
        var temp = data.current_observation.temperature;
        var precip_today = data.current_observation.precip_today;
        var color;
        /*
        if(precip_today) {
          color = 155;
        } else {
          color = 65;
        }
        */
        /*
        if(temp >= 60) {
          color = '255';
        }
        if(temp < 60) {
          color = '155';
        }
        */
        color = Math.floor(temp);
//        var command = index + ':' + color;
//        map[index].command = command;
        cb(null, color);

//        console.log(command);
//        console.log(color);
      } else {
        cb(null, '001');
      }
    } else {
      cb("error 1");
    }
  });

};

var spark_device = null;

var batch_send = function(command, cb) {

  console.log('sending command: ' + command);
  spark_device.callFunction('heatmap', command, function(err, data) {
    if (err) {
      console.log('An error occurred:', err);
      cb(err);
    } else {
      console.log('Function called succesfully:', data);
      cb(null);
    //                rec_call(device, index+1);
    }
  });
}



promise.then(
  function(token){
    // If login is successful we get and accessToken,
    // we'll use that to call Spark API ListDevices
    var devicesPr = spark.listDevices();

    devicesPr.then(
      // We get an array with devices back and we list them
      function(devices){
        console.log('API call List Devices completed on promise resolve: ', devices);
        var device = devices[0];
        spark_device = device;

        async.map(map, fetch, function(err, results) {
          if(err) {
            console.log('async map error occurred:', err);
          } else {
            console.log('results found');
            console.log(results);
            var command = "";
            var count = 4;
            var it = 0;
            var command_set = [];
            _.each(results, function(el, index) {
              command += index + ':' + el + ';';
              if(it < count) {
                it++;
              } else {
                command_set.push(command);
                command = "";
                it = 0;
              }
            });
//            command_set.push(command);
            console.log(command_set);
            console.log(command);

            async.eachSeries(command_set, batch_send, function(err) {
              if(err) {
                console.log("error occurred: " + err);
              } else {
                console.log("no errors!");
              }
            });
            
            
          }
          //rec_call(device, 0);

         });

      },
      function(err) {
        console.log('API call List Devices completed on promise fail: ', err);
      }
    );
  },
  function(err) {
    console.log('API call completed on promise fail: ', err);
  }
);

function rec_call(device, index) {
  if(index > 99) return;
  var command = map[index].command;
  console.log("index: " + index);
  device.callFunction('rainbow', command, function(err, data) {
    if (err) {
      console.log('An error occurred:', err);
    } else {
      console.log('Function called succesfully:', data);
      rec_call(device, index+1);
    }
  });
}



function blah() {

  var locations = [
    {'lat':'34', 'lon': '-111'},
    {'lat':'37', 'lon': '-112'},
    {'lat':'41', 'lon': '-123'},
    {'lat':'45', 'lon': '-122'},
    {'lat':'47', 'lon': '-122'},
    {'lat':'45', 'lon': '-119'},
    {'lat':'42', 'lon': '-119'},
    {'lat':'40', 'lon': '-118'},
    {'lat':'38', 'lon': '-118'},
    {'lat':'33', 'lon': '-116'},
    {'lat':'35', 'lon': '-115'},
    {'lat':'33', 'lon': '-114'},
    {'lat':'34', 'lon': '-113'},
    {'lat':'36', 'lon': '-113'},
    {'lat':'39', 'lon': '-114'},
    {'lat':'41', 'lon': '-117'},
    {'lat':'45', 'lon': '-116'},
    {'lat':'47', 'lon': '-117'},
    {'lat':'47', 'lon': '-114'},
    {'lat':'46', 'lon': '-110'},
    {'lat':'42', 'lon': '-112'},
    {'lat':'40', 'lon': '-111'},
    {'lat':'39', 'lon': '-111'},
    {'lat':'35', 'lon': '-110'},
    {'lat':'32', 'lon': '-110'},
    {'lat':'31', 'lon': '-109'},
    {'lat':'33', 'lon': '-108'},
    {'lat':'36', 'lon': '-108'},
    {'lat':'39', 'lon': '-108'},
    {'lat':'41', 'lon': '-108'},
    {'lat':'45', 'lon': '-107'},
    {'lat':'46', 'lon': '-108'},
    {'lat':'47', 'lon': '-104'},
    {'lat':'46', 'lon': '-103'},
    {'lat':'42', 'lon': '-104'},
    {'lat':'40', 'lon': '-104'},
    {'lat':'38', 'lon': '-105'},
    {'lat':'34', 'lon': '-106'},
    {'lat':'32', 'lon': '-106'},
    {'lat':'30', 'lon': '-103'},
    {'lat':'33', 'lon': '-102'},
    {'lat':'35', 'lon': '-102'},
    {'lat':'39', 'lon': '-101'},
    {'lat':'40', 'lon': '-101'},
    {'lat':'43', 'lon': '-100'},
    {'lat':'47', 'lon': '-101'},
    {'lat':'48', 'lon': '-98'},
    {'lat':'45', 'lon': '-98'},
    {'lat':'41', 'lon': '-98'},
    {'lat':'40', 'lon': '-99'},
    {'lat':'36', 'lon': '-99'},
    {'lat':'34', 'lon': '-99'},
    {'lat':'33', 'lon': '-97'},
    {'lat':'30', 'lon': '-100'},
    {'lat':'28', 'lon': '-98'},
    {'lat':'29', 'lon': '-95'},
    {'lat':'33', 'lon': '-96'},
    {'lat':'35', 'lon': '-94'},
    {'lat':'37', 'lon': '-94'},
    {'lat':'40', 'lon': '-93 '},
    {'lat':'43', 'lon': '-94'},
    {'lat':'47', 'lon': '-94'},
    {'lat':'46', 'lon': '-90'},
    {'lat':'42', 'lon': '-91'},
    {'lat':'38', 'lon': '-90'},
    {'lat':'36', 'lon': '-91'},
    {'lat':'33', 'lon': '-92'},
    {'lat':'30', 'lon': '-93'},
    {'lat':'29', 'lon': '-90'},
    {'lat':'33', 'lon': '-88'},
    {'lat':'34', 'lon': '-88'},
    {'lat':'39', 'lon': '-87'},
    {'lat':'40', 'lon': '-87'},
    {'lat':'44', 'lon': '-86'},
    {'lat':'42', 'lon': '-86'},
    {'lat':'38', 'lon': '-86'},
    {'lat':'35', 'lon': '-86'},
    {'lat':'33', 'lon': '-86'},
    {'lat':'30', 'lon': '-85'},
    {'lat':'33', 'lon': '-85'},
    {'lat':'35', 'lon': '-85'},
    {'lat':'39', 'lon': '-85'},
    {'lat':'42', 'lon': '-84'},
    {'lat':'45', 'lon': '-83'},
    {'lat':'39', 'lon': '-82'},
    {'lat':'38', 'lon': '-82'},
    {'lat':'33', 'lon': '-82'},
    {'lat':'31', 'lon': '-81'},
    {'lat':'28', 'lon': '-82'},
    {'lat':'25', 'lon': '-80'},
    {'lat':'29', 'lon': '-79'},
    {'lat':'33', 'lon': '-80'},
    {'lat':'43', 'lon': '-80'},
    {'lat':'39', 'lon': '-80'},
    {'lat':'43', 'lon': '-80 '},
    {'lat':'45', 'lon': '-80'},
    {'lat':'46', 'lon': '-80'},
    {'lat':'46', 'lon': '-70'},
    {'lat':'42', 'lon': '-71'},
    {'lat':'33', 'lon': '-44'}
  ];


  var high = -999;
  var low = 999;
  for(var i=0; i<locations.length; i++) {
    console.log(i);
    var point = locations[i]
    map[i] = {}
    map[i].loc = point;
    map[i].url = 'http://api.wxug.com/api/xxxxxAPIKEYxxxx/conditions/v:2.0/lang:EN/units:english/q/' + point.lat + ',' + point.lon + '.json';
    //console.log(map[i]);
    /*
    request(map[i].url, function(error, response, body) {
      if (!error && response.statusCode == 200) {
//            console.log(body) // Print the google web page.
        var data = JSON.parse(body);
//        console.log(data.current_observation.station.name);
//        console.log(data.current_observation.temperature);
        if(data && data.current_observation) {
          var temp = data.current_observation.temperature;
          if(temp > 70) {
            high = temp;
          }
          if(temp < low) {
            low = temp;
          }
          console.log("high: " + high);
          console.log("low: " + low);
        }
      }
    });
    */
  }
}

blah();

Credits

Dan Fein

Dan Fein

6 projects • 52 followers
Information omnivore, Human potential optimist, Pathological Enabler.
Adam Williams

Adam Williams

1 project • 6 followers
Amy Fischlin

Amy Fischlin

2 projects • 13 followers
I am Amy.

Comments