poniedziałek, 20 kwietnia 2020

Zegar. Kalendarz. Stacja pogodowa. Wyświetlacz T6963 + Arduino + ESP8266



Stacja pogodowa na wyświetlaczu 240x128 ze sterownikiem T6963, Arduino Nano jako "sterownikiem" wyświetlacza i ESP8266 jako źródłem.

Uzyskałem wyświetlacz z lekkim uszkodzeniem w postaci nieczynnych poziomych linii, wyeliminowałem te linie wykorzystując odpowiednie graficzne ułożenie (przerwy między ramkami w grafice wyświetlacza widocznym na zdjęciu).

Sterownik wyświetlacza działa na bibliotece u8glib i Timelib.h dla Arduino. Sterownik jest tak zaprogramowany aby przyjmował czas w postaci czasu unixowego (czas w sekundach od początku epoki) i wiadomości tekstowe które wyświetli w dwóch ramkach pod datą. Informacje te są przesyłane z ESP8266 poprzez UART.
0x02 to znak początku wiadomości,
0x03 to znak końca wiadomości
Można przesłać 3 typy informacji w postaci:
        znak początku"0x02"  parametr,  informacja   znak końca"0x03"
 linia pierwsza: 0x02 1, ramka pierwsza 0x03
 linia druga:      0x02 2, ramka druga 0x03
 i czas w postaci czasu unixowego.

ESP8266 poprzez WiFi pobiera czas z wskazanego serwera czasu (biblioteka Timelib.h), wykorzystuję również bibliotekę Timezone.h - aby dostosować czas do obowiązującego w Polsce (czas letni/zimowy), ponieważ czas z serwera to UTC 0. Informacje pogodowe pobierane są z serwera Open Weather Maps - aby z niego korzystać należy założyć darmowe konto i wygenerować klucz API potrzebny do obsługi zapytań tego serwera.

Początkowo próbowałem skorzystać z biblioteki JSON dla Arduino do odczytu informacji pogodowych, lecz zrezygnowałem z tego na rzecz własnego 'parsowania' danych. Dane zwracane przez bibliotekę miały własny format danych, które ciężko konwersować do innych typów.

[Przy próbie załadowania tych kodów do mikrokontrolerów upewnij się, że masz załadowane potrzebne biblioteki użyte w kodzie.]

Kod dla Arduino Nano jako sterownik wyświetlacza

#include "U8glib.h"
U8GLIB_T6963_240X128 u8g(7, 8, 9, 10, 11, 12, 14, 15, 4, 5, 2, 3, 6); // 8Bit Com: D0..D7: 7,8,9,10,11,12,14,15, cs=4, a0=5, wr=2, rd=3, reset=6

#include <TimeLib.h>

short DN;   //Returns the number of day in the year
short WN;   //Returns the number of the week in the year


int Hour = 0, Min = 0, Sec = 0, lastSecond = 0;
char Hh[] = "00", Mm[] = "00", Ss[] = "00";

String stringHour, stringMinute, stringSecond, stringTime, stringDate, stringWeek;
String info_1 = "Infomation line 1", info_2 = "Infomation line 2";

// SERRIAL

#define numChars 255
char tempChars[numChars];
char messageFromPC[numChars] = {0};
int integerFromPC = 0;

//chars for serial:
char startChar = 0x02;
char stopChar = 0x03;

void draw(void) {

  u8g.setFont(u8g_font_fub42n);
  u8g.drawStr( 0, 47, stringTime.c_str() );

  u8g.drawRFrame(0, 0, 240, 55, 1); //ramka na godzinę
  u8g.drawBox(0, 50, (second() * 4), 4); // pasek sekund pod godzina

  u8g.setFont(u8g_font_fub30n);
  u8g.drawStr( 2, 90, stringDate.c_str() );

  u8g.drawRFrame(0, 56, 214, 36, 1); //ramka na datę

  u8g.drawRFrame(215, 56, 25, 36, 1); //ramka na tydzień
  u8g.setFont(u8g_font_profont10);
  u8g.drawStr( 218, 66, "WEEK" );

  u8g.setFont(u8g_font_fub14);
  u8g.drawStr( 216, 87, stringWeek.c_str() );

  u8g.drawRFrame(0, 93, 240, 15, 1); // pierwsza ramka informacyjna
  u8g.setFont(u8g_font_profont12);
  u8g.drawStr( 3, 104, info_1.c_str() );

  u8g.drawRFrame(0, 110, 240, 18, 1); // pierwsza ramka informacyjna
  u8g.drawStr( 3, 124, info_2.c_str() );



}

void recvWithStartEndMarkers() {
  bool starting, transmitting;
  byte len = 0, pointer = 0;
  char rc;
  char buffer_1[numChars];

  while (Serial.available() > 0) {
    rc = Serial.read();
    if (rc == startChar) {
      starting = true;
    } else if (transmitting && pointer < len) {
      buffer_1[pointer++] = rc;
    } else if (starting) {
      len = rc;
      pointer = 0;
      starting = false;
      transmitting = true;
    } else if (transmitting && pointer == len) {
      transmitting = false;
      if (rc == stopChar) {
        while (pointer < numChars) {
          buffer_1[pointer++] = 0;
        }

        char * strtokIndx = 0; // this is used by strtok() as an index

        strtokIndx = strtok(buffer_1, ",");     // get the first part - the integer
        integerFromPC = atoi(strtokIndx);     // convert this part to an integer

        Serial.println(strtokIndx);

        strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
        strcpy(messageFromPC, strtokIndx); // copy it to messageFromPC

        switch (integerFromPC) {
          case 1:
            info_1 = messageFromPC;
            break;
          case 2:
            info_2 = messageFromPC;
            break;
          case 3:
            setTime( ((String)messageFromPC).toInt() );
            break;
          default:
            //Serial.println("Incorrect number");
            break;
        }
      }
    }
  }

}

//============

void setup(void) {

  Serial.begin(9600);
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);
  //setSyncProvider( requestSync);  //set function to call when sync required
  Serial.println("Data Format to send: <number,massage> ");
  Serial.println("line 1: 0x02 1, line 1 massage 0x03 ");
  Serial.println("line 2: 0x02 2, line 2 massage 0x03 ");
  Serial.println("set time: 0x02 3,unix stamp 0x03 ");
  // flip screen, if required
  // u8g.setRot180();
  setTime(1586858400);
}

void loop(void) {

  recvWithStartEndMarkers();

  date_to_string();
  time_to_string();
  DayWeekNumber(year(), month(), day(), weekday());
  u8g.firstPage();
  do
  {
    draw();
  }
  while ( u8g.nextPage() );

  delay(500);
}


void time_to_string()
{
  stringHour   = "";  stringMinute = "";  stringSecond = "";  stringTime   = "";
  // adding 0 if number if less than 0
  if (hour() < 10) {
    stringHour =  stringHour + "0" + hour();
  }
  else stringHour =  stringHour + hour();

  if (minute() < 10) {
    stringMinute =  stringMinute + "0" + minute();
  }
  else stringMinute =  stringMinute + minute();

  if (second() < 10) {
    stringSecond =  stringSecond + "0" + second();
  }
  else stringSecond =  stringSecond + second();
  // time as a one string
  stringTime = stringHour + ":" + stringMinute + ":" + stringSecond;
}

void date_to_string()
{
  String stringYear = "", stringMonth = "", stringDay = "";  stringYear = year();

  if (month() < 10) {
    stringMonth =  stringMonth + "0" + month();
  }
  else stringMonth = month();

  if (day() < 10) {
    stringDay =  stringDay + "0" + day();
  }
  else stringDay = day();

  // date as a one string
  stringDate = stringYear + "-" + stringMonth + "-" + stringDay;
}

void DayWeekNumber(unsigned int y, unsigned int m, unsigned int d, unsigned int w) {
  int days[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // Number of days at the beginning of the month in a not leap year.
  stringWeek = "";
  //Start to calculate the number of day
  if (m == 1 || m == 2) {
    DN = days[(m - 1)] + d;                 //for any type of year, it calculate the number of days for January or february
  }                        // Now, try to calculate for the other months
  else if ((y % 4 == 0 && y % 100 != 0) ||  y % 400 == 0) { //those are the conditions to have a leap year
    DN = days[(m - 1)] + d + 1; // if leap year, calculate in the same way but increasing one day
  }
  else {                                //if not a leap year, calculate in the normal way, such as January or February
    DN = days[(m - 1)] + d;
  }
  // Now start to calculate Week number
  if (w == 0) {
    WN = (DN - 7 + 10) / 7;       //if it is sunday (time library returns 0)
  }
  else {
    WN = (DN - w + 10) / 7;  // for the other days of week
  }
  stringWeek = WN;
}

Kod dla ESP8266

#include <ESP8266WiFi.h>
#include <TimeLib.h>
#include <WiFiUdp.h>
#include <Timezone.h>

const char* ssid = "ssid";                 // SSID of local network
const char* password = "pass";                  // Password on network
String APIKEY = "APIKEY";
String CityID = "YourCity";                    //Your City

WiFiClient client;
char servername[] = "api.openweathermap.org";              // remote server we will connect to
String result;

int  counter = 10;
long Timer1 = 0;

String weatherDescription = "";
String weatherLocation = "";
float temperature, temp_max, temp_min;
int  humidity;
int  pressure;
long Sunrise;
long Sunset;
double Speed;
long Temp_min;
long Temp_max;
long Visibility;

// TIME:
static const char ntpServerName[] = "time.nist.gov";
const int timeZone = 0;     // Central European Time //=0 because use TimeZone lib
WiFiUDP Udp;
unsigned int localPort = 8888;  // local port to listen for UDP packets
time_t getNtpTime();
void digitalClockDisplay();
void printDigits(int digits);
void sendNTPpacket(IPAddress &address);

TimeChangeRule myDST = {"EDT", Last, Sun, Mar, 2, 120};    // Daylight time = UTC - 4 hours
TimeChangeRule mySTD = {"EST", Last, Sun, Nov, 2, 60};     // Standard time = UTC - 5 hours
Timezone myTZ(myDST, mySTD);

TimeChangeRule *tcr;

time_t prevDisplay = 0; // when the digital clock was displayed
time_t utc;
time_t local;



void setup()
{
  pinMode(2, OUTPUT);
  digitalWrite(2,LOW);
  Serial.begin(9600);
  WiFi.begin(ssid, password);

  //Serial.println();Serial.println();
  //Serial.println("connecting: ");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    //Serial.print(".");
  }
  //Serial.println("WiFi Connected");
  //Serial.println(WiFi.localIP());
  Udp.begin(localPort);
  //Serial.println(Udp.localPort());
  setSyncProvider(getNtpTime);
  setSyncInterval(300);

  utc = now();
  local = myTZ.toLocal(utc, &tcr);
  //Serial.println();
  //printDateTime(utc, "UTC");
  //printDateTime(local, tcr -> abbrev);

  SendData("3," + (String)local);
  delay(1000);
  
  getWeatherData();
}

// ############## MAIN LOOP ##############
int tick = 0;

void loop() {
  if (millis() - Timer1 > 60000) {
    Timer1 = millis();
    tick++;
    if(tick > 9){
      getWeatherData();
      tick = 0;
    }
  }
}
// ######## END OF MAIN LOOP ############

//client function to send/receive GET request data.
void getWeatherData()
{
  //starts client connection, checks for connection
  if (client.connect(servername, 80)) {
    client.println("GET /data/2.5/weather?q=" + CityID + "&lang=pl&units=metric&APPID=" + APIKEY);
    client.println("Host: api.openweathermap.org");
    client.println("User-Agent: ArduinoWiFi/1.1");
    client.println("Connection: close");
    client.println();
  } else {
    //error message if no client connect
    Serial.println("connection failed");
    Serial.println();
  }

  while (client.connected() && !client.available()) {
    //waits for data
    delay(1);
  }
  result = "";
  //connected or data available
  while (client.connected() && client.available()) {
    //gets byte from ethernet buffer
    char c = client.read();
    if (c != 0) {
      result = result + c;
    }
  }
  client.stop();//stop client
  //Serial.println("<2,Try>");

  if ( result.indexOf("\"temp\":") > 1 ) {
    temperature =  ParseDataOpenWeatherMaps(result, "temp").toFloat();
    humidity =  ParseDataOpenWeatherMaps(result, "humidity").toFloat();
    pressure =  ParseDataOpenWeatherMaps(result, "pressure").toFloat();
    temp_max =  ParseDataOpenWeatherMaps(result, "temp_max").toFloat();
    temp_min = ParseDataOpenWeatherMaps(result, "temp_min").toFloat();
    weatherDescription = ParseDataOpenWeatherMaps(result, "description");
    weatherDescription.remove(0, 1); //delete first char "
    weatherDescription.remove(weatherDescription.length() - 1);  // delete last char "

    SendData("1,T:" + roundAndString(temperature) + "(" + roundAndString(temp_min) + "/" +
             roundAndString(temp_max) + ")'C H:" + (String)humidity + " P:" + (String)pressure + "hPa" );

    delay(2000);
    SendData("2," + weatherDescription);
    delay(2000);
    local = myTZ.toLocal(now(), &tcr);
    SendData("3," + (String)local);
  }


  //Serial.println();
  //printDateTime(utc, "UTC");
  //printDateTime(local, tcr -> abbrev);


  delay(1000);
}

String ParseDataOpenWeatherMaps(String input, String parameter) {
  parameter = "\"" + parameter + "\":";
  String workingPhraseString = input.substring(input.indexOf(parameter), input.indexOf(parameter) + 56);
  char endMarker = ','; char rc;
  byte i = 0;
  while ( rc != endMarker ) {
    rc = workingPhraseString[i];
    i++;
  }
  byte whereStart = input.indexOf(parameter) + parameter.length(); byte whereEnd = input.indexOf(parameter) + i - 1;
  return input.substring(whereStart, whereEnd);
}

// ################# SEND DATA #####################
void SendData(String input) {
  Serial.write(0x02); Serial.write((byte)input.length()); Serial.print(input); Serial.write(0x03); Serial.println(input.length());
}

String roundAndString(float input) {
  input = round(input * 10) / 10;
  String output = (String)(input);
  output.remove(output.length()-1, 1);
  return output;
}



void printDigits(int digits)
{
  // utility for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if (digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  IPAddress ntpServerIP; // NTP server's ip address

  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  //Serial.println("Transmit NTP Request");
  // get a random server from the pool
  WiFi.hostByName(ntpServerName, ntpServerIP);
  //Serial.print(ntpServerName);
  //Serial.print(": ");
  //Serial.println(ntpServerIP);
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      //Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

void printDateTime(time_t t, const char *tz)
{
  char buf[32];
  char m[4];    // temporary storage for month string (DateStrings.cpp uses shared buffer)
  strcpy(m, monthShortStr(month(t)));
  sprintf(buf, "%.2d:%.2d:%.2d %s %.2d %s %d %s",
          hour(t), minute(t), second(t), dayShortStr(weekday(t)), day(t), m, year(t), tz);
  Serial.println(buf);
}



Pomocne linki:
wątek na forum Arduino: Topic: Serial Input Basics - updated - jak działa komunikacja UART i przykładowe kody (bazowałem głównie na przykładzie nr 5)

Open Weather Maps API - stąd można pozyskać dane środowiskowe (temperatura, wilgotność, itd...)

Kod pokolorowałem tutaj: hilite.me

Jak dodać płytkę ESP w Arduino opisałem w innym poście

Brak komentarzy:

Prześlij komentarz