Các thông tin được lập trình để hiển thị lên Oled theo dạng từng khung hình (frame). Mỗi khung hình sẽ được hiển thị 10 giây trước khi chuyển sang khung hình khác bằng hiệu ứng trượt sang trái như các khung ảnh điện tử. Sẽ có 4 khung hình bao gồm:
Ta cần khai báo thư viện cho Oled
#include "SSD1306Wire.h" #include "OLEDDisplayUi.h" #include "Wire.h"
khai báo địa chỉ I2C của Oled và chân nối với Oled cùng đối tượng display dùng để vẽ hình lên Oled
/* OLED Settings*/ const int I2C_DISPLAY_ADDRESS = 0x3c; const int SDA_PIN = 4; const int SCL_PIN = 5; SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SCL_PIN); OLEDDisplayUi ui( &display );
khai báo 4 khung hình và hàm hiển thị thanh trạng thái khi đang cập nhật dữ liệu từ Internet và các hàm hiển thị thông tin cho từng khung hình:
void drawProgress(OLEDDisplay *display, int percentage, String label); void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); void drawDHT(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex); void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state); /* frames */ FrameCallback frames[] = {drawDateTime, drawDHT, drawCurrentWeather, drawForecast}; int numberOfFrames = 4; OverlayCallback overlays[] = { drawHeaderOverlay }; int numberOfOverlays = 1;
Trong các hàm này, ta sử dụng đối tượng display và các hàm hỗ trợ hiển thị text (display.drawString) và icon (display.drawXbm) để hiện thị lên Oled.
Việc chuyển các khung hình đã được hỗ trợ tự động bởi thư viện.
Để hiển thị đồng hồ lấy từ Internet thì ta dùng thư viện NTPClient. Khai báo timezone của Việt Nam là 7 trong UTC_OFFSET và khở tạo thư viện NTPClient
const float UTC_OFFSET = 7; const int UPDATE_INTERVAL_SECS = 5 * 60; // Update every 5min // flag changed in the ticker function every 10 minutes bool readyForWeatherUpdate = false; String lastUpdate = "--"; WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, UTC_OFFSET*3600);
Và yêu cầu cập nhật thời gian từ Internet trong hàm setup() thông qua hàm timeClient.begin();
Khi cần hiển thị đồng hồ thì ta dùng các hàm của NTPClient để lấy ngày giờ.
void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(ArialMT_Plain_10); /* String date = wunderground.getDate(); int textWidth = display->getStringWidth(date); display->drawString(64 + x, 5 + y, date); */ String date = "12/25/2018"; int textWidth = display->getStringWidth(date); display->drawString(64 + x, 5 + y, date); display->setFont(ArialMT_Plain_24); String time = timeClient.getFormattedTime(); textWidth = display->getStringWidth(time); display->drawString(64 + x, 15 + y, time); display->setTextAlignment(TEXT_ALIGN_LEFT); }
Để hiển thị nhiệt độ và độ ẩm trong phòng thì ta dùng các hàm của thư viện DHT để lấy thông tin trong hàm getDHT và hiển thị lên Oled trong hàm drawDHT
void getDHT() { float tempIni = localTemp; float humIni = localHum; localTemp = dht.readTemperature(); localHum = dht.readHumidity(); if (isnan(localHum) || isnan(localTemp) || localTemp<=0 || localTemp>=100) // Check if any reads failed and exit early (to try again). { Serial.println("Failed to read from DHT sensor!"); localTemp = tempIni; localHum = humIni; return; } } void drawDHT(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { display->setFont(ArialMT_Plain_10); display->setTextAlignment(TEXT_ALIGN_LEFT); display->drawString(0 + x, 5 + y, "Hum"); display->setFont(ArialMT_Plain_10); display->setTextAlignment(TEXT_ALIGN_LEFT); display->drawString(43 + x, y, "INDOOR"); display->setFont(ArialMT_Plain_24); String hum = String(localHum) + "%"; display->drawString(0 + x, 15 + y, hum); int humWidth = display->getStringWidth(hum); display->setFont(ArialMT_Plain_10); display->setTextAlignment(TEXT_ALIGN_LEFT); display->drawString(95 + x, 5 + y, "Temp"); display->setFont(ArialMT_Plain_24); String temp = String(localTemp) + "°C"; display->drawString(70 + x, 15 + y, temp); int tempWidth = display->getStringWidth(temp); }
Phần phức tạp nhất là hàm cập nhật thông tin thời tiết từ Internet.
Để lấy thời tiết hiện tại thì ra dùng gọi 1 request tới server của openweathermap với url là http://api.openweathermap.org/data/2.5/weather?id=location_id&cnt=3&appid=app_key&units=metric và đọc kết quả thông qua json
void getCurrentWeather() { WiFiClient client; Serial.println("\nStarting connection to server..."); // if you get a connection, report back via serial: if (client.connect(server, 80)) { Serial.println("connected to server"); // Make a HTTP request: client.print("GET /data/2.5/weather?"); client.print("id="+OPEN_WEATHER_MAP_LOCATION_ID); client.print("&appid="+OPEN_WEATHER_MAP_APP_ID); client.println("&units=metric"); client.println("Host: api.openweathermap.org"); client.println("Connection: close"); client.println(); } else { Serial.println("unable to connect"); } delay(1000); String line = ""; while (client.available()) { line = client.readStringUntil('\n'); Serial.println(line); } Serial.println("parsingValues"); //create a json buffer where to store the json data StaticJsonBuffer<5000> jsonBuffer; JsonObject& root = jsonBuffer.parseObject(line); if (!root.success()) { Serial.println("parseObject() failed"); return; } //get the data from the json tree String main = root["weather"][0]["main"]; String temp = root["main"]["temp"]; String temp_min = root["main"]["temp_min"]; String temp_max = root["main"]["temp_max"]; currentWeatherIcon = root["weather"][0]["icon"].as<String>(); currentWeatherTitle = root["weather"][0]["main"].as<String>(); currentWeatherTemp = root["main"]["temp"].as<String>(); // Print values. Serial.println(main); Serial.println(temp); Serial.println(temp_min); Serial.println(temp_max); Serial.println(currentWeatherIcon); }
Kết quả trả về từ server của openweathermap có cấu trúc như sau:
"coord": { "lon": 106.65, "lat": 10.83 }, "weather": [ { "id": 803, "main": "Clouds", "description": "broken clouds", "icon": "04d" } ], "base": "stations", "main": { "temp": 31, "pressure": 1007, "humidity": 66, "temp_min": 31, "temp_max": 31 }, "visibility": 10000, "wind": { "speed": 2.6, "deg": 360 }, "clouds": { "all": 75 }, "dt": 1545888600, "sys": { "type": 1, "id": 9314, "message": 0.0045, "country": "VN", "sunrise": 1545865770, "sunset": 1545907159 }, "id": 1565593, "name": "Thông Tây Hội", "cod": 200 }
Tương tự cho phần lấy dự báo 3 ngày tới với url là http://api.openweathermap.org/data/2.5/forecast?id=location_id&appid=app_key&cnt=3&units=metric
void getWeatherForecast() { WiFiClient client; Serial.println("\nStarting connection to server..."); // if you get a connection, report back via serial: if (client.connect(server, 80)) { Serial.println("connected to server"); // Make a HTTP request: client.print("GET /data/2.5/forecast?"); client.print("id="+OPEN_WEATHER_MAP_LOCATION_ID); client.print("&appid="+OPEN_WEATHER_MAP_APP_ID); client.print("&cnt=3"); client.println("&units=metric"); client.println("Host: api.openweathermap.org"); client.println("Connection: close"); client.println(); } else { Serial.println("unable to connect"); } delay(1000); String line = ""; while (client.available()) { line = client.readStringUntil('\n'); Serial.println(line); Serial.println("parsingValues"); //create a json buffer where to store the json data StaticJsonBuffer<30000> jsonBuffer; JsonObject& root = jsonBuffer.parseObject(line); if (!root.success()) { Serial.println("parseObject() failed"); return; } //get the data from the json tree for(int i=0; i<MAX_FORECASTS; i++) { forecastTitle[i] = root["list"][i]["weather"][0]["main"].as<String>(); forecastWeatherIcon[i] = root["list"][i]["weather"][0]["icon"].as<String>(); forecastLowTemp[i] = root["list"][i]["main"]["temp_min"].as<String>().substring(0, 2); forecastHighTemp[i] = root["list"][i]["main"]["temp_max"].as<String>().substring(0, 2); forecastDate[i] = WDAY_NAMES[weekday(root["list"][i]["dt"].as<String>().toInt())]; } } }
Nếu các đoạn code này quá phức tạp với bạn thì bạn có thể bỏ qua không cần hiểu rõ và có thể quay lại tìm hiểu kỹ hơn khi cần thay đổi theo ý muốn.