Enchanted WiFi Weather Clock
WIZFI360 Based weather clock that uses ntp server for time and realtime/predicted weather displayed on Enchanted clock using led and speaker
Enchanted Wifi Weather Clock is made using WizFi360 EVB Pico
Led color mapping and push button function mapping
BLUE LED - HOUR
RED LED - MINUTE
GREEN LED - SECOND
BUTTON W - PLAYS WEATHER FORECAST ON SPEAKER
BUTTON T - PLAYS CURRENT TIME ON SPEAKER
Features
Update Weather every 15 minutes using API of openweathermap.org
sync with network time every 1 hour using API of worldtimeapi.org
Timer interrupt used so time keeping and Neopixel update runs in background and no other task interrupt it so no need to use dedicated RTC Hardware for time keeping
button press can play current time / weather when required
Automatically play weather condition every 15 minutes
Automatically play time on speaker every hour'
Play time in 24 hour time format on Speaker default press both button at same time makes it 12 hour format
As flexible NEOPIXEL LED Strip used to display enchanted time, It can be fixed on wall in any shape Circle, oval, square, heart shape etc
By using neopixel led with different LED distance, smaller to very bigger clock can be made
As this project made in limited time taken out of my free time will add more function in future like play date and day with time, automatic LED brightness adjustment based on ambient light by feedback from external LDR sensor, find more intuitive and simple way to update weather/weather forecast on same led ring etc
Hardware setup
Modules used in project
60 PIXEL 2 METER NEOPIXEL STRIPE
Hardware Connection
Arduino Code for Wizfi360-evb-pico
Serial Terminal log showing setting up wifi and network time and weather fetched from server using api
one can just copy paste this code in arduino ide or download from attachment, DF Mini mp3 player functions are included in code so no library needed for DF Mini Mp3 player, All MP3 files are also in attachment.
Rest API from openweathermap.org with Json response used to get current weather and forecast - https://api.openweathermap.org/data/2.5/weather?lat=23.2167&lon=72.6833&appid=cd6370f2d6a5912dbb5b2bae7594c0.
find above line and change latitude longitude and API key before use, to get API key create account with openweathermap.org
Rest API from worldtimeapi.org with Json response used to get current time - http://worldtimeapi.org/api/timezone/Asia/Kolkata
find above line and change timezone before use.
one need to change atleast wifi credential and api key of openweathermap before uploading this code
One need to install Raspberry Pi Pico Board package for Arduino from https://github.com/earlephilhower/arduino-pico
one also need to install following library to make this code work
Weather forecast and current weather derived from icon id and correspondingly MP3 file play weather report description. mapping of icon ID to description in next pic,
SD Card Setup
All MP3 Files used in project attached at last of this blog, Download it unzip it and use attached python file to copy mp3 files to SD card one by one automatically with small delay.
Note : normal copy paste can not work as it messed up with DF player mini's file indexing and it will play wrong file randomly so use this python file and change source destination path to copy paste mp3 files to sd card root one by one automatically.
Arduino Code
// WizFi360 EVB PICO Based Enchanted WiFi Weather Clock
// Developer - N.J. Chhasatia
// Wiznet Contest project 2022
#include <ArduinoJson.h>
#include "WizFi360.h"
#include "RPi_Pico_TimerInterrupt.h"
#include <Adafruit_NeoPixel.h>
#define PIXEL_PIN 28 // pico pin
#define PIXEL_NUM 60 // NeoPixel clock size
#define MP3_TXPIN 12
#define MP3_RXPIN 13
#define PB_TIME 0 // Push button for play time
#define PB_WETH 1 // Push button for play weather
// MP3 number to name mapping
#define NWCH 102
#define NWOK 101
#define TMOK 113
#define HOUR 111
#define MINT 110
#define TIME 105
#define TEMP 108
#define HUMD 107
#define TMUN 104
#define HDUN 103
#define WLCM 112
#define WETH 106
#define FCST 109
#define BRIGHTNESS 90
uint8_t mp3_cmd[] = {0x7E,0xFF,0x06,0x03,0x00,0x00,0x03,0xFE,0xF5,0xEF};
uint8_t mp3_cmd_vol[] = {0x7E,0xFF,0x06,0x06,0x01,0x00,0x15,0xFE,0xDF,0xEF};
int status = WL_IDLE_STATUS; // the Wifi radio's status
static const char* ssid = "LAPTOP_NJC"; // Edit AP Credential
static const char* password = "password";
unsigned long epoche = 1666081881;
unsigned long premillis = 0;
static uint8_t sec;
static uint8_t mint;
static uint8_t hor;
static uint8_t hor_12;
static uint8_t flasher = 0;
float main_temp = 0;
int main_pressure = 0;
int main_humidity = 0;
uint8_t time_flag = 0;
uint8_t cweather_flag = 0;
StaticJsonDocument<16> filter_time;
StaticJsonDocument<250> filter_weather;
StaticJsonDocument<48> doc_time;
StaticJsonDocument<250> doc_weather;
WiFiClient client;
RPI_PICO_Timer ITimer0(0);
Adafruit_NeoPixel pixels(PIXEL_NUM, PIXEL_PIN, NEO_GRB + NEO_KHZ800);
void setup() {
Serial.begin(115200); // Initialize Serial port
pinMode(PB_TIME,INPUT_PULLUP);
pinMode(PB_WETH,INPUT_PULLUP);
init_mp3_player(); // Initlize MP3 Player
setup_neopixel(); // Initlize NeoPixel strip object (REQUIRED)
play_mp3(WLCM); // Play welcome msg
delay(1000);
play_mp3(NWCH); // Play connecting to AP
setup_wifi(); // Setup Wifi
delay(1000);
play_mp3(NWOK); // Play connected to AP
while(time_flag == 0){check_time_nw();neopixel_wave();} // Wait until Time available
setup_timer();
delay(20);
while(cweather_flag == 0){check_current_weather_nw();delay(5000);}
play_mp3(TMOK); // Play Time and weather available
delay(2000);
play_mp3_many(TIME,hor,HOUR,mint,MINT,0); // Play Time
delay(2000);
play_mp3_many(FCST,TEMP,(int)main_temp-273,TMUN,HUMD,(int)main_humidity); // Play forcast
play_mp3_many(HDUN,WETH,0,0,0,0); // Play forcast
}
void loop() {
if(mint%60 == 0 && sec == 0)
{
play_mp3_many(TIME,hor,HOUR,mint,MINT,0); // Play Time
delay(100);
check_time_nw();
delay(100);
}
if(mint%20 == 0 && sec == 0)
{
delay(100);
check_current_weather_nw();
delay(100);
}
if(digitalRead(PB_TIME) == 0)
{
play_mp3_many(TIME,hor,HOUR,mint,MINT,0); // Play Time
}
if(digitalRead(PB_WETH) == 0)
{
play_mp3_many(FCST,TEMP,(int)main_temp-273,TMUN,HUMD,(int)main_humidity); // Play forcast
play_mp3_many(HDUN,WETH,0,0,0,0); // Play forcast
}
//Debug Messages print time
//Serial.print(hor);Serial.print(":");
//Serial.print(mint);Serial.print(":");
//Serial.println(sec);
delay(10);
}
// ================ convert unix to local time ===========
void epoche2dt(unsigned long ep)
{
sec = ep%60;
ep = ep/60;
mint = ep%60;
ep = ep/60;
hor = ep%24;
epoche = ep/24;
Serial.print("Network_Time = ");
Serial.print(hor);Serial.print(":");
Serial.print(mint);Serial.print(":");
Serial.println(sec);
}
// ================ Setup Wifi connection ===========
void setup_wifi(void)
{
Serial2.setTX(4);
Serial2.setRX(5);
Serial2.begin(115200);
// initialize WizFi360 module
WiFi.init(&Serial2);
WiFi.begin(ssid, password);
// check for the presence of the shield
if (WiFi.status() == WL_NO_SHIELD) {
Serial.println("WiFi shield not present");
// don't continue
while (true);
}
// attempt to connect to WiFi network
while ( status != WL_CONNECTED) {
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
// Connect to WPA/WPA2 network
status = WiFi.begin(ssid, password);
}
// you're connected now, so print out the data
Serial.println("You're connected to the network");
}
// ================ Update latest time from network and convert unix to local time ===========
void check_time_nw(void)
{
client.setTimeout(15000);
//http://worldtimeapi.org/api/timezone/Asia/Kolkata
if (!client.connect("worldtimeapi.org", 80)) {
Serial.println("Connection failed");
return;
}
Serial.println("Connected!");
// Send HTTP request
client.println("GET /api/timezone/Asia/Kolkata HTTP/1.0");
client.println("Host: arduinojson.org");
client.println("Connection: close");
if (client.println() == 0) {
Serial.println("Failed to send request");
client.stop();
return;
}
// Check HTTP status
char status[32] = {0};
client.readBytesUntil('\r', status, sizeof(status));
// It should be "HTTP/1.0 200 OK" or "HTTP/1.1 200 OK"
if (strcmp(status + 9, "200 OK") != 0) {
Serial.print("Unexpected response: ");
Serial.println(status);
client.stop();
return;
}
// Skip HTTP headers
char endOfHeaders[] = "\r\n\r\n";
if (!client.find(endOfHeaders)) {
Serial.println("Invalid response");
client.stop();
return;
}
// Allocate the JSON document
// Use https://arduinojson.org/v6/assistant to compute the capacity.
filter_time["unixtime"] = true;
DeserializationError error = deserializeJson(doc_time, client, DeserializationOption::Filter(filter_time));
if (error) {
Serial.print("deserializeJson() failed: ");
return;
}
time_flag = 1;
epoche = doc_time["unixtime"];
client.stop();
delay(100);
//client.flush();
epoche2dt(epoche + 19800);
}
// ================ Update current weather from network ===========
void check_current_weather_nw(void)
{
client.setTimeout(10000);
//https://api.openweathermap.org/data/2.5/weather?lat=23.2167&lon=72.6833&appid=cd6370f2d6a5912dbb5b2bae7594c0
if (!client.connect("api.openweathermap.org", 80)) {
Serial.println("Connection failed");
return;
}
Serial.println("Connected!");
// Send HTTP request
client.println("GET /data/2.5/weather?lat=23.2167&lon=72.6833&appid=cd6370f2d6a5912dbb5b2bae7594c0 HTTP/1.0");
client.println("Host: arduinojson.org");
client.println("Connection: close");
if (client.println() == 0) {
Serial.println("Failed to send request");
client.stop();
return;
}
// Check HTTP status
char status[32] = {0};
client.readBytesUntil('\r', status, sizeof(status));
// It should be "HTTP/1.0 200 OK" or "HTTP/1.1 200 OK"
if (strcmp(status + 9, "200 OK") != 0) {
Serial.print("Unexpected response: ");
Serial.println(status);
client.stop();
return;
}
// Skip HTTP headers
char endOfHeaders[] = "\r\n\r\n";
if (!client.find(endOfHeaders)) {
Serial.println("Invalid response");
client.stop();
return;
}
// Allocate the JSON document
// Use https://arduinojson.org/v6/assistant to compute the capacity.
filter_weather["weather"][0]["icon"] = true;
filter_weather["main"]["temp"] = true;
filter_weather["main"]["pressure"] = true;
filter_weather["main"]["humidity"] = true;
DeserializationError error = deserializeJson(doc_weather, client, DeserializationOption::Filter(filter_weather));
if (error) {
Serial.print("deserializeJson() failed: ");
return;
}
const char* weather_0_icon = doc_weather["weather"][0]["icon"]; // "01d"
main_temp = doc_weather["main"]["temp"]; // 309.14
main_pressure = doc_weather["main"]["pressure"]; // 1009
main_humidity = doc_weather["main"]["humidity"]; // 34
client.stop();
cweather_flag = 1;
delay(100);
//client.flush();
Serial.println("Current Weather : ");
Serial.print("-->temp = ");Serial.println(main_temp);
Serial.print("-->humd = ");Serial.println(main_humidity);
Serial.print("-->pressure = ");Serial.println(main_pressure);
Serial.print("-->icon = ");Serial.println(weather_0_icon);
}
// ================ ISR Handler for Timer triggered at every 1 second ================
bool TimerHandler0(struct repeating_timer *t)
{
sec=sec+1;
if(sec>59){sec = 0; mint = mint+1;}
if(mint>59){mint = 0; hor = hor+1;}
if(hor>23){hor = 0;}
if(hor>11)hor_12 = hor-12;
else hor_12 = hor;
pixels.clear();
pixels.setPixelColor(sec, pixels.Color(0, BRIGHTNESS, 0)); // Set pixel's color (in RAM)
if((hor_12*5)+(mint/12) == mint)
{
if(flasher%2 == 1)pixels.setPixelColor(mint, pixels.Color(BRIGHTNESS, 0, 0));
else pixels.setPixelColor(mint, pixels.Color(0, 0, BRIGHTNESS));
flasher++;
}
else
{
pixels.setPixelColor(mint, pixels.Color(BRIGHTNESS, 0, 0)); // Set pixel's color (in RAM)
pixels.setPixelColor(((hor_12*5)+(mint/12)), pixels.Color(0, 0, BRIGHTNESS)); // Set pixel's color (in RAM)
}
pixels.show();
return true;
}
// ================ initlize timer 0 pico ==================
void setup_timer(void)
{
delay(100);
Serial.print(F("CPU Frequency = ")); Serial.print(F_CPU / 1000000); Serial.println(F(" MHz"));
// Interval in microsecs
if (ITimer0.attachInterruptInterval(1000000, TimerHandler0))
Serial.print(F("Starting ITimer0 OK"));
else
Serial.println(F("Can't set ITimer0. Select another freq. or timer"));
}
// ================ Neo pixel clock ring setup ==================
void setup_neopixel(void)
{
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
pixels.clear(); // Set all pixel colors to 'off'
for(int i=0; i<pixels.numPixels(); i++) { // For each pixel in strip...
pixels.setPixelColor(i, pixels.Color(0, BRIGHTNESS/2, 0)); // Set pixel's color (in RAM)
pixels.show(); // Update strip to match
delay(20); // Pause for a moment
}
for(int i=pixels.numPixels()-1; i>=0; i--) { // For each pixel in strip...
pixels.setPixelColor(i, pixels.Color(0, 0, 0)); // Set pixel's color (in RAM)
pixels.show(); // Update strip to match
delay(20); // Pause for a moment
}
}
// ================ Neo pixel wave red ==================
void neopixel_wave(void)
{
pixels.clear(); // Set all pixel colors to 'off'
for(int i=0; i<pixels.numPixels(); i++) { // For each pixel in strip...
pixels.setPixelColor(i, pixels.Color(BRIGHTNESS/2, 0, 0)); // Set pixel's color (in RAM)
pixels.show(); // Update strip to match
delay(20); // Pause for a moment
}
for(int i=pixels.numPixels()-1; i>=0; i--) { // For each pixel in strip...
pixels.setPixelColor(i, pixels.Color(0, 0, 0)); // Set pixel's color (in RAM)
pixels.show(); // Update strip to match
delay(20); // Pause for a moment
}
}
// =========== init MP3 Player ========
void init_mp3_player(void)
{
Serial1.setTX(MP3_TXPIN); // TX PIN for Serial1 MP3
Serial1.setRX(MP3_RXPIN); // RX PIN for Serial1 MP3
Serial1.begin(9600); // MP3
delay(1000);
Serial1.write(mp3_cmd_vol,10);
delay(100);
}
//========== Play Track ==========
void play_mp3(uint8_t track)
{
uint8_t btx;
mp3_cmd[6] = track;
mp3_cmd[8] = 0xF8 - track;
for(btx=0;btx<=9;btx++)
{
Serial1.write(mp3_cmd[btx]);
}
}
//========== Play multiple Track upto 6 ==========
void play_mp3_many(uint8_t track1,uint8_t track2,uint8_t track3,uint8_t track4,uint8_t track5,uint8_t track6)
{
char dummy;
Serial.println("Playing tracks..");
while(Serial1.available()>0) dummy = Serial1.read();
if(track1 != 0){play_mp3(track1);
while(Serial1.available()==0);
delay(20);
while(Serial1.available()>0)dummy = Serial1.read();}
Serial.println();
delay(20);
if(track2 != 0){play_mp3(track2);
while(Serial1.available()==0);
delay(20);
while(Serial1.available()>0)dummy = Serial1.read();}
Serial.println();
delay(20);
if(track3 != 0){play_mp3(track3);
while(Serial1.available()==0);
delay(20);
while(Serial1.available()>0)dummy = Serial1.read();}
Serial.println();
delay(20);
if(track4 != 0){play_mp3(track4);
while(Serial1.available()==0);
delay(20);
while(Serial1.available()>0)dummy = Serial1.read();}
Serial.println();
delay(20);
if(track5 != 0){play_mp3(track5);
while(Serial1.available()==0);
delay(20);
while(Serial1.available()>0)dummy = Serial1.read();}
Serial.println();
delay(20);
if(track6 != 0){play_mp3(track6);
while(Serial1.available()==0);
delay(20);
while(Serial1.available()>0)dummy = Serial1.read();}
Serial.println("Playing tracks done.");
}
-
mp3 files used in project
All mp3 files used in project
-
python code to copy mp3 files to sd card
normal copy paste can messed up with DF player mini's file indexing so use this python file and change source destination path to copy paste mp3 files to sd card one by one automatically
-
Wiznet Weather clock code
Arduino ino code of wiznet weather clock
-
Modules Connections
Modules connections