// 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.");
}
