Last active
February 14, 2022 15:10
-
-
Save oveddan/4fddf79bc2a030d2bfaeab27a04242e8 to your computer and use it in GitHub Desktop.
Japanese Weather Diorama Arduino and Node.js code - oveddan.github.io/blog/posts/physical_computing/weather-diorama/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const SerialPort = require('serialport'); | |
const request = require('request'); | |
const myKey = '12312314'; | |
const moment = require('moment') | |
const Readline = SerialPort.parsers.Readline; | |
const thirtyMinutesInSeconds = 60 * 60 | |
const SUNRISE = 0 | |
const SUNSET = 1 | |
const MIDDAY = 2 | |
const NIGHTTIME = 3 | |
const getTimeOfDayCode = weatherResult => { | |
const { dt, sys: { sunset, sunrise } } = weatherResult | |
const currentTime = moment(dt * 1000) | |
const sunriseTime = moment(sunrise * 1000) | |
const sunsetTime = moment(sunset * 1000) | |
if (currentTime.isBefore(sunriseTime.subtract(30, 'minutes'))) | |
return NIGHTTIME | |
if (currentTime.isBefore(sunriseTime.add(30, 'minutes'))) | |
return SUNRISE | |
if (currentTime.isBefore(sunsetTime.subtract(30, 'minutes'))) | |
return MIDDAY | |
if (currentTime.isBefore(sunsetTime.add(30, 'minutes'))) | |
return SUNSET | |
return NIGHTTIME | |
} | |
const getWeather = weatherResult => weatherResult.weather[0].main.toLowerCase() | |
const CLEARSKIES = 0 | |
const OVERCAST = 1 | |
const RAIN = 2 | |
const getWeatherCode = weatherResult => { | |
switch(getWeather(weatherResult)) { | |
case "smoke": | |
case "fog": | |
case "cloudy": | |
return OVERCAST | |
case "rain": | |
case "snow": | |
return RAIN | |
default: | |
return CLEARSKIES | |
} | |
} | |
SerialPort.list((err, ports) => { | |
const lastPort = ports[ports.length - 1]; | |
var port = new SerialPort(lastPort.comName, { | |
baudRate: 9600 | |
}); | |
port.on('error', function(err) { | |
console.log('Error: ', err.message); | |
}) | |
const parser = new Readline(); | |
// Switches the port into "flowing mode" | |
port.pipe(parser); | |
parser.on('data', data => { | |
console.log('got data:', data); | |
if (data === 'getWeather\r') { | |
// request('http://api.openweathermap.org/data/2.5/weather?id=5128581&appid=85bbe8e9824e89a038ea3aa9ab31a0eb', (error, response, body) => { | |
request('http://api.openweathermap.org/data/2.5/weather?id=1864134&appid=85bbe8e9824e89a038ea3aa9ab31a0eb', (error, response, body) => { | |
const weatherResult = JSON.parse(body); | |
const timeOfDayCode = getTimeOfDayCode(weatherResult) | |
const weatherCode = getWeatherCode(weatherResult) | |
console.log('result:', timeOfDayCode, weatherCode) | |
port.write(`${weatherCode},${timeOfDayCode}\r`); | |
}) | |
} | |
}); | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "weather", | |
"version": "1.0.0", | |
"description": "", | |
"main": "index.js", | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1" | |
}, | |
"author": "", | |
"license": "ISC", | |
"dependencies": { | |
"moment": "^2.19.1", | |
"request": "^2.83.0", | |
"serialport": "^6.0.3" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "FastLED.h" | |
#define NUM_LEDS 60 | |
#define TIME_BUTTON_PIN 3 | |
#define WEATHER_BUTTON_PIN 2 | |
#define BUTTON_PINC 4 | |
#define DATA_PIN 6 | |
#define LATCH_PIN 8 | |
//Pin connected to SH_CP of 74HC595 | |
#define CLOCK_PIN 12 | |
////Pin connected to DS of 74HC595 | |
#define SHIFT_DATA_PIN 11 | |
CRGB leds[NUM_LEDS]; | |
CRGB lastLeds[NUM_LEDS]; | |
CRGB targetLeds[NUM_LEDS]; | |
void setup() { | |
FastLED.addLeds<WS2812, 6, GRB>(leds, NUM_LEDS); | |
FastLED.setBrightness(255); | |
pinMode(TIME_BUTTON_PIN, INPUT); | |
pinMode(WEATHER_BUTTON_PIN, INPUT); | |
pinMode(BUTTON_PINC, INPUT); | |
Serial.begin(9600); | |
for(int i = 0; i < NUM_LEDS; i++) { | |
leds[i] = CRGB::Red; | |
lastLeds[i] = CRGB::Red; | |
targetLeds[i] = CRGB::Red; | |
} | |
pinMode(LATCH_PIN, OUTPUT); | |
pinMode(CLOCK_PIN, OUTPUT); | |
pinMode(SHIFT_DATA_PIN, OUTPUT); | |
} | |
int lightState = 0; | |
int lastButtonAState = LOW; | |
int lastButtonBState = LOW; | |
int lastButtonCState = LOW; | |
int maximumState = 1; | |
void setLed(int ledIndex, CRGB targetColor) { | |
targetLeds[ledIndex] = CRGB(0, 0, 255); | |
} | |
long lastChange = millis(); | |
void setLastLeds() { | |
for(int i = 0; i < NUM_LEDS; i++) { | |
lastLeds[i] = leds[i]; | |
} | |
} | |
#define SUNRISE 0 | |
#define MIDDAY 1 | |
#define SUNSET 2 | |
#define NIGHTTIME 3 | |
int timeOfDay = SUNRISE; | |
#define CLEARSKIES 0 | |
#define OVERCAST 1 | |
#define RAIN 2 | |
int weather = CLEARSKIES; | |
int layerLeds[][2] = {{0, 9}, {10, 19}, {20, 29}, {30, 39}, {40, 49}, {50, 59}, {60, 60}}; | |
#define SUN 5 | |
#define MOUNTAIN1 0 | |
#define MOUNTAIN2 1 | |
#define MOUNTAIN3 2 | |
#define MOUNTAIN4 3 | |
#define SKY 4 | |
CRGB mountain1Color = CRGB::White; | |
CRGB mountain2Color = CRGB::Chartreuse;//;// CRGB(28, 148, 173); | |
CRGB mountain3Color = CRGB(90, 166, 181); | |
CRGB mountain4Color = CRGB::Coral;//CRGB(228, 176, 168); | |
CRGB skyColor = CRGB::DeepSkyBlue; | |
CRGB sunColor = CRGB(249, 171, 136); | |
CRGB fadeColorByWeather(CRGB color, int weather) { | |
switch(weather) { | |
case OVERCAST: | |
return color.fadeLightBy(255/2); | |
case RAIN: | |
return color.fadeLightBy(255*3/4); | |
default: | |
return color; | |
} | |
} | |
CRGB paintColorByTimeOfDay(CRGB color, int timeOfDay) { | |
switch(timeOfDay) { | |
case SUNSET: | |
return CRGB::Red; | |
case SUNRISE: | |
return CRGB::OrangeRed; | |
case NIGHTTIME: | |
return CRGB::Blue; | |
default: | |
return color; | |
} | |
} | |
CRGB getLayerColor(int layer, int timeOfDay, int weather) { | |
//Serial.println(layer); | |
if(layer == SUN) { | |
CRGB color; | |
switch(timeOfDay) { | |
case SUNRISE: | |
color = CRGB::Orange; | |
case MIDDAY: | |
color = CRGB::Yellow; | |
case SUNSET: | |
color = CRGB::Yellow; | |
default: | |
color = CRGB::MintCream; | |
break; | |
} | |
color.fadeLightBy(200); | |
return color; | |
} else if(layer == MOUNTAIN1) { | |
CRGB color = mountain1Color; | |
color = paintColorByTimeOfDay(color, timeOfDay); | |
color = fadeColorByWeather(color, weather); | |
return color; | |
} else if(layer == MOUNTAIN2) { | |
if (timeOfDay == NIGHTTIME) | |
return CRGB::Black; | |
CRGB color = mountain2Color; | |
color = paintColorByTimeOfDay(color, timeOfDay); | |
color = fadeColorByWeather(color, weather); | |
return color; | |
} else if(layer == MOUNTAIN3) { | |
if (timeOfDay == NIGHTTIME) | |
return CRGB::Black; | |
CRGB color = mountain3Color; | |
color = paintColorByTimeOfDay(color, timeOfDay); | |
color = fadeColorByWeather(color, weather); | |
return color; | |
} else if(layer == MOUNTAIN4) { | |
if (timeOfDay == NIGHTTIME) | |
return CRGB::Black; | |
CRGB color = mountain4Color; | |
color = paintColorByTimeOfDay(color, timeOfDay); | |
color = fadeColorByWeather(color, weather); | |
return color; | |
} else if (layer == SKY) { | |
CRGB color; | |
switch(timeOfDay) { | |
case SUNRISE: | |
return CRGB::DeepPink; | |
case MIDDAY: | |
color = CRGB::SkyBlue; | |
color.fadeLightBy(100); | |
return color; | |
case SUNSET: | |
return CRGB::Red; | |
default: | |
return CRGB::LightGoldenrodYellow; | |
} | |
} | |
else { | |
return CRGB::White; | |
} | |
} | |
void setConditions(int timeOfDay, int weather) { | |
setLastLeds(); | |
lastChange = millis(); | |
byte statusData = 0; | |
bitSet(statusData, weather); | |
bitSet(statusData, timeOfDay + 3); | |
digitalWrite(LATCH_PIN, LOW); | |
// shift out the bits: | |
shiftOut(SHIFT_DATA_PIN, CLOCK_PIN, MSBFIRST, statusData); | |
//take the latch pin high so the LEDs will light up: | |
digitalWrite(LATCH_PIN, HIGH); | |
for(int i = 0; i <= 5; i++) { | |
CRGB color = getLayerColor(i, timeOfDay, weather); | |
for(int j = layerLeds[i][0]; j <= layerLeds[i][1]; j++) { | |
targetLeds[j] = color; | |
} | |
} | |
} | |
#define TRANSITION_DURATION 1000.0 | |
void advanceAnimation() { | |
float percentage = constrain((millis() - lastChange * 1.) / TRANSITION_DURATION, 0., 0.9999); | |
fract8 fract = floor(percentage * 256); | |
if (fract <= 256) { | |
for(int i = 0; i < NUM_LEDS; i++) { | |
leds[i] = blend(lastLeds[i], targetLeds[i], fract); | |
} | |
} | |
if (timeOfDay == NIGHTTIME) { | |
animateNightStars(); | |
} | |
FastLED.show(); | |
delay(20); | |
} | |
#define SPARKLE_DURATION 1000 | |
long lastStarChange = millis(); | |
void animateNightStars() { | |
int firstSkyLed = layerLeds[MOUNTAIN3][0]; | |
int lastSkyLed = layerLeds[SKY][1]; | |
if(millis() - lastStarChange > SPARKLE_DURATION) { | |
Serial.println("sparkling"); | |
lastStarChange = millis(); | |
for(int i = firstSkyLed; i <= lastSkyLed; i++) { | |
lastLeds[i] = leds[i]; | |
targetLeds[i] = CRGB::Blue; | |
} | |
for(int i = 0; i < 10; i++) { | |
int randomStarOn = random(firstSkyLed, lastSkyLed + 1); | |
Serial.print(randomStarOn); | |
Serial.print(" "); | |
targetLeds[randomStarOn] = CRGB::White; | |
targetLeds[randomStarOn].fadeLightBy(200); | |
} | |
Serial.println(""); | |
lastStarChange = millis(); | |
} | |
float percentage = constrain((millis() - lastStarChange * 1.) / SPARKLE_DURATION, 0., 0.9999); | |
fract8 fract = floor(percentage * 256); | |
for(int i = firstSkyLed; i <= lastSkyLed; i++) { | |
leds[i] = blend(lastLeds[i], targetLeds[i], fract); | |
} | |
} | |
void loop() { | |
// read the state of the pushbutton value: | |
int currentButtonAState = digitalRead(TIME_BUTTON_PIN); | |
int currentButtonBState = digitalRead(WEATHER_BUTTON_PIN); | |
int currentButtonCState = digitalRead(BUTTON_PINC); | |
if (currentButtonAState == HIGH && lastButtonAState == LOW) { | |
timeOfDay++; | |
if (timeOfDay >NIGHTTIME) | |
timeOfDay = 0; | |
Serial.print("time of day: "); | |
Serial.println(timeOfDay); | |
setConditions(timeOfDay, weather); | |
// for(int i = 0; i < NUM_LEDS; i++) { | |
// targetLeds[i] = CRGB::Green; | |
// } | |
} | |
if (currentButtonBState == HIGH && lastButtonBState == LOW) { | |
weather++; | |
if (weather > RAIN) | |
weather = 0; | |
Serial.print("weather: "); | |
Serial.println(weather); | |
setConditions(timeOfDay, weather); | |
// for(int i = 0; i < NUM_LEDS; i++) { | |
// targetLeds[i] = CRGB::Blue; | |
// } | |
} | |
if (currentButtonCState == HIGH && lastButtonCState == LOW) { | |
Serial.println("getWeather"); | |
} | |
lastButtonAState = currentButtonAState; | |
lastButtonBState = currentButtonBState; | |
lastButtonCState = currentButtonCState; | |
advanceAnimation(); | |
if (Serial.available() > 0) { | |
// read the incoming byte: | |
String incomingMessage = Serial.readString(); | |
int commaIndex = incomingMessage.indexOf(','); | |
timeOfDay = incomingMessage.substring(0, commaIndex).toInt(); | |
weather = incomingMessage.substring(commaIndex + 1, incomingMessage.length()).toInt(); | |
setConditions(timeOfDay, weather); | |
Serial.print("weather received: "); | |
Serial.print(timeOfDay); | |
Serial.print(" "); | |
Serial.println(weather); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment