Last active
June 5, 2017 21:39
Star
You must be signed in to star a gist
Precise LED clock that can only be imprecisely read
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
/* | |
FuzzyClock is a precise LED clock that can only be imprecisely read. | |
Bogdan Radulescu <bogdan@nimblex.net> | |
License: Apache License v2 | |
*/ | |
#include <ESP8266WiFi.h> // This one comes with the boards manager import | |
#include <NTPtimeESP.h> // Get it from https://github.com/SensorsIot/NTPtimeESP | |
#include <Adafruit_NeoPixel.h> // Get it from https://github.com/adafruit/Adafruit_NeoPixel | |
#include <ArduinoJson.h> // Get it from https://github.com/bblanchon/ArduinoJson | |
NTPtime NTPch("ro.pool.ntp.org"); | |
const char* ssid = "Radulescu"; | |
const char* password = "replacewithyourown"; | |
#define analogOutPinGREEN 12 | |
#define analogOutPinBLUE 13 | |
#define analogOutPinRED 15 | |
// We use GPIO5 because GPIO0/GPIO2/GPIO15 are used to determine the boot mode that should be executed at reset. Because of this it causes all the LEDS to light up at reset. | |
#define LED_PIN 5 | |
#define LED_COUNT 30 | |
#define WU_API_KEY "replacewithyourown" | |
#define WU_LOCATION "Romania/Bucharest" | |
#define WUNDERGROUND "api.wunderground.com" | |
// Create an instance of the Adafruit_NeoPixel class called "leds". | |
Adafruit_NeoPixel leds = Adafruit_NeoPixel(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); | |
/* | |
* The structure contains following fields: | |
* struct strDateTime-- | |
{ | |
byte hour; | |
byte minute; | |
byte second; | |
int year; | |
byte month; | |
byte day; | |
byte dayofWeek; | |
boolean valid; | |
}; | |
*/ | |
strDateTime dateTime; | |
int LEDhour; | |
int LEDpressure; | |
// HTTP request | |
const char WUNDERGROUND_REQ[] = | |
"GET /api/" WU_API_KEY "/conditions/q/" WU_LOCATION ".json HTTP/1.1\r\n" | |
"User-Agent: ESP8266/0.1\r\n" | |
"Accept: */*\r\n" | |
"Host: " WUNDERGROUND "\r\n" | |
"Connection: close\r\n" | |
"\r\n"; | |
void setup() { | |
analogWrite(analogOutPinRED, 200); // Visually indicate that we started to boot. | |
leds.begin(); | |
//leds.show(); // Initialize all pixels to 'off'. If we do this it would make the light turn off for a little while when it comes back from sleep so it's commented out. | |
Serial.begin(115200); // We initialize the serial console for debugging purposes. | |
Serial.println(); | |
Serial.println("Booted"); | |
pinMode(A0, INPUT); // From this pin we read the ambiental light. | |
Serial.println("Connecting to Wi-Fi"); | |
WiFi.mode(WIFI_STA); | |
WiFi.begin (ssid, password); | |
while (WiFi.status() != WL_CONNECTED) { | |
Serial.print("."); | |
delay(500); | |
} | |
Serial.println("WiFi connected"); | |
analogWrite(analogOutPinRED, 0); | |
analogWrite(analogOutPinGREEN, 200); | |
WUAPI(); // Here we connecto the the WU API and get the data | |
getShowData(); // In this function we get the NTP time and we display the stuff on the LED strip | |
ESP.deepSleep(60000000, WAKE_RF_DEFAULT); // We are going to seep for 60 seconds | |
} | |
// Nothing happens here because we want to sleep most of the time. | |
void loop() { | |
} | |
void getShowData() { | |
analogWrite(analogOutPinBLUE, 100); | |
// first parameter: Time zone in floating point (for India); second parameter: 1 for European summer time; 2 for US daylight saving time (not implemented yet) | |
dateTime = NTPch.getNTPtime(2.0, 0); // +2GMT without summer time. | |
// NTPch.printDateTime(dateTime); | |
byte actualHour = dateTime.hour; | |
byte actualMinute = dateTime.minute; | |
// We split the day in half to essentially 12H clock so it would be easier to read. | |
if (actualHour > 11) | |
LEDhour = map(actualHour, 12, 23, 0, 29); | |
else | |
LEDhour = map(actualHour, 0, 11, 0, 29); | |
int LEDminute = map(actualMinute, 0, 60, 0, 29); | |
int lightVal = analogRead(0) * 2; // read the input pin and make the LEDs brighter. It depends on placement and personal preference | |
int LEDintensity = max(lightVal, 10); // The intensity will be at least 10 so the LEDs will always be on | |
LEDintensity = min(LEDintensity, 255);// The maximum intensity level is 255 because these chips are 8bit | |
Serial.print("LEDintensity "); Serial.print(LEDintensity); Serial.print(" LightVal "); Serial.println(lightVal); | |
leds.setPixelColor(LEDhour, LEDintensity, 0, 0); // Red | |
leds.setPixelColor(LEDminute, 0, LEDintensity, 0); // Green | |
leds.setPixelColor(LEDpressure, 0, 0, LEDintensity); // Blue | |
leds.show(); | |
// We make all leds off for the next time when we have to set them. | |
leds.setPixelColor(LEDhour, 0x000000); | |
leds.setPixelColor(LEDminute, 0x000000); | |
leds.setPixelColor(LEDpressure, 0x000000); | |
analogWrite(analogOutPinBLUE, 0); | |
} | |
bool showWeather(char *json) { | |
StaticJsonBuffer<3*1024> jsonBuffer; | |
// Skip characters until first '{' found | |
// Ignore chunked length, if present | |
char *jsonstart = strchr(json, '{'); | |
//Serial.print(F("jsonstart ")); Serial.println(jsonstart); | |
if (jsonstart == NULL) { | |
Serial.println(F("JSON data missing")); | |
return false; | |
} | |
json = jsonstart; | |
// Parse JSON | |
JsonObject& root = jsonBuffer.parseObject(json); | |
if (!root.success()) { | |
Serial.println(F("jsonBuffer.parseObject() failed")); | |
return false; | |
} | |
// Extract weather info from parsed JSON | |
JsonObject& current = root["current_observation"]; | |
const float temp_c = current["temp_c"]; | |
Serial.print(temp_c, 1); Serial.print(F(" C, ")); | |
const char *humi = current[F("relative_humidity")]; | |
Serial.print(humi); Serial.println(F(" RH")); | |
const char *weather = current["weather"]; | |
Serial.println(weather); | |
const char *pressure_mb = current["pressure_mb"]; | |
Serial.print("Pressure "); Serial.println(pressure_mb); | |
const char *observation_time = current["observation_time_rfc822"]; | |
Serial.println(observation_time); | |
int pressureVal = current["pressure_mb"]; | |
pressureVal = min(pressureVal, 1030); | |
pressureVal = max(pressureVal, 1001); | |
LEDpressure = map(pressureVal, 1001, 1030, 0, 29); | |
Serial.print("PressureLED "); Serial.println(LEDpressure); | |
} | |
void WUAPI() { | |
static char respBuf[4096]; | |
// Use WiFiClient class to create TCP connections | |
WiFiClient httpclient; | |
const int httpPort = 80; | |
if (!httpclient.connect(WUNDERGROUND, httpPort)) { | |
Serial.println(F("connection failed")); | |
return; | |
} | |
// This will send the http request to the server | |
Serial.print(WUNDERGROUND_REQ); | |
httpclient.print(WUNDERGROUND_REQ); | |
httpclient.flush(); | |
// Collect http response headers and content from Weather Underground | |
// HTTP headers are discarded. | |
// The content is formatted in JSON and is left in respBuf. | |
int respLen = 0; | |
bool skip_headers = true; | |
while (httpclient.connected() || httpclient.available()) { | |
if (skip_headers) { | |
String aLine = httpclient.readStringUntil('\n'); | |
//Serial.println(aLine); | |
// Blank line denotes end of headers | |
if (aLine.length() <= 1) { | |
skip_headers = false; | |
} | |
} | |
else { | |
int bytesIn; | |
bytesIn = httpclient.read((uint8_t *)&respBuf[respLen], sizeof(respBuf) - respLen); | |
Serial.print(F("bytesIn ")); Serial.println(bytesIn); | |
if (bytesIn > 0) { | |
respLen += bytesIn; | |
if (respLen > sizeof(respBuf)) respLen = sizeof(respBuf); | |
} | |
else if (bytesIn < 0) { | |
Serial.print(F("read error ")); | |
Serial.println(bytesIn); | |
} | |
} | |
delay(1); | |
} | |
httpclient.stop(); | |
if (respLen >= sizeof(respBuf)) { | |
Serial.print(F("respBuf overflow ")); | |
Serial.println(respLen); | |
return; | |
} | |
// Terminate the C string | |
respBuf[respLen++] = '\0'; | |
//Serial.print(F("respLen ")); | |
//Serial.println(respLen); | |
//Serial.println(respBuf); | |
showWeather(respBuf); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment