OLED天气时钟

本文演示通过OLED显示一个时钟和天气情况的例子,主要利用的是openweathermap的免费天气API。

一、软件和硬件

(1)硬件

零知ESP8266开发板 和 OLED SSD1306模块

(2)接线

接线很简单,I2C接口对应连接即可:

接好后实物图如下:

二、方法步骤

本次使用了OLED和WeatherStation相关的软件库,因此需要安装对应的库,可以下载或直接在库管理器中安装。 安装完成后,我们新建工程或打开附件的工程代码:


							
	/*
	
	2019年6月13日13:47:26
	
	by 零知实验室
	
	*/
	
	
	
	#include <ESPWiFi.h>
	
	#include <ESPHTTPClient.h>
	
	#include <JsonListener.h>
	
	
	
	// time
	
	#include <time.h>                       // time() ctime()
	
	#include <sys/time.h>                   // struct timeval
	
	#include <coredecls.h>                  // settimeofday_cb()
	
	
	
	#include "SSD1306Wire.h"
	
	#include "OLEDDisplayUi.h"
	
	#include "Wire.h"
	
	#include "OpenWeatherMapCurrent.h"
	
	#include "OpenWeatherMapForecast.h"
	
	#include "WeatherStationFonts.h"
	
	#include "WeatherStationImages.h"
	
	
	
	
	
	/***************************
	
	 * Begin Settings
	
	 **************************/
	
	
	
	// WIFI
	
	const char* WIFI_SSID = "xx";
	
	const char* WIFI_PWD = "xx";
	
	
	
	#define TZ              8       // (utc+) TZ in hours
	
	#define DST_MN          60      // use 60mn for summer time in some countries
	
	
	
	// Setup
	
	const int UPDATE_INTERVAL_SECS = 20 * 60; // Update every 20 minutes
	
	
	
	// Display Settings
	
	const int I2C_DISPLAY_ADDRESS = 0x3c;
	
	
	
	const int SDA_PIN = D3;
	
	const int SDC_PIN = D4;
	
	
	
	// OpenWeatherMap Settings
	
	// Sign up here to get an API key:
	
	// https://docs.thingpulse.com/how-tos/openweathermap-key/
	
	String OPEN_WEATHER_MAP_APP_ID = "xxx"; //你的API KEY
	
	/*
	
	Go to https://openweathermap.org/find?q= and search for a location. Go through the
	
	result set and select the entry closest to the actual location you want to display 
	
	data for. It'll be a URL like https://openweathermap.org/city/2657896. The number
	
	at the end is what you assign to the constant below.
	
	 */
	
	String OPEN_WEATHER_MAP_LOCATION_ID = "1795565"; //city:深圳
	
	
	
	// Pick a language code from this list:
	
	// Arabic - ar, Bulgarian - bg, Catalan - ca, Czech - cz, German - de, Greek - el,
	
	// English - en, Persian (Farsi) - fa, Finnish - fi, French - fr, Galician - gl,
	
	// Croatian - hr, Hungarian - hu, Italian - it, Japanese - ja, Korean - kr,
	
	// Latvian - la, Lithuanian - lt, Macedonian - mk, Dutch - nl, Polish - pl,
	
	// Portuguese - pt, Romanian - ro, Russian - ru, Swedish - se, Slovak - sk,
	
	// Slovenian - sl, Spanish - es, Turkish - tr, Ukrainian - ua, Vietnamese - vi,
	
	// Chinese Simplified - zh_cn, Chinese Traditional - zh_tw.
	
	String OPEN_WEATHER_MAP_LANGUAGE = "zh_cn";
	
	const uint8_t MAX_FORECASTS = 4;
	
	
	
	const boolean IS_METRIC = true;
	
	
	
	// Adjust according to your language
	
	const String WDAY_NAMES[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
	
	const String MONTH_NAMES[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
	
	
	
	/***************************
	
	 * End Settings
	
	 **************************/
	
	 // Initialize the oled display for address 0x3c
	
	 // sda-pin=14 and sdc-pin=12
	
	 SSD1306Wire     display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN);
	
	 OLEDDisplayUi   ui( &display );
	
	
	
	OpenWeatherMapCurrentData currentWeather;
	
	OpenWeatherMapCurrent currentWeatherClient;
	
	
	
	OpenWeatherMapForecastData forecasts[MAX_FORECASTS];
	
	OpenWeatherMapForecast forecastClient;
	
	
	
	#define TZ_MN           ((TZ)*60)
	
	#define TZ_SEC          ((TZ)*3600)
	
	#define DST_SEC         ((DST_MN)*60)
	
	time_t now;
	
	
	
	// flag changed in the ticker function every 10 minutes
	
	bool readyForWeatherUpdate = false;
	
	
	
	String lastUpdate = "--";
	
	
	
	long timeSinceLastWUpdate = 0;
	
	
	
	//declaring prototypes
	
	void drawProgress(OLEDDisplay *display, int percentage, String label);
	
	void updateData(OLEDDisplay *display);
	
	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 drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex);
	
	void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state);
	
	void setReadyForWeatherUpdate();
	
	
	
	
	
	// Add frames
	
	// this array keeps function pointers to all frames
	
	// frames are the single views that slide from right to left
	
	FrameCallback frames[] = { drawDateTime, drawCurrentWeather, drawForecast };
	
	int numberOfFrames = 3;
	
	
	
	OverlayCallback overlays[] = { drawHeaderOverlay };
	
	int numberOfOverlays = 1;
	
	
	
	void setup() {
	
	  Serial.begin(115200);
	
	  Serial.println();
	
	  Serial.println();
	
	
	
	  // initialize dispaly
	
	  display.init();
	
	  display.clear();
	
	  display.display();
	
	
	
	  //display.flipScreenVertically();
	
	  display.setFont(ArialMT_Plain_10);
	
	  display.setTextAlignment(TEXT_ALIGN_CENTER);
	
	  display.setContrast(255);
	
	
	
	  WiFi.begin(WIFI_SSID, WIFI_PWD);
	
	
	
	  int counter = 0;
	
	  while (WiFi.status() != WL_CONNECTED) {
	
	    delay(500);
	
	    Serial.print(".");
	
	    display.clear();
	
	    display.drawString(64, 10, "Connecting to WiFi");
	
	    display.drawXbm(46, 30, 8, 8, counter % 3 == 0 ? activeSymbole : inactiveSymbole);
	
	    display.drawXbm(60, 30, 8, 8, counter % 3 == 1 ? activeSymbole : inactiveSymbole);
	
	    display.drawXbm(74, 30, 8, 8, counter % 3 == 2 ? activeSymbole : inactiveSymbole);
	
	    display.display();
	
	
	
	    counter++;
	
	  }
	
	  // Get time from network time service
	
	  configTime(TZ_SEC, DST_SEC, "pool.ntp.org");
	
	
	
	  ui.setTargetFPS(30);
	
	
	
	  ui.setActiveSymbol(activeSymbole);
	
	  ui.setInactiveSymbol(inactiveSymbole);
	
	
	
	  // You can change this to
	
	  // TOP, LEFT, BOTTOM, RIGHT
	
	  ui.setIndicatorPosition(BOTTOM);
	
	
	
	  // Defines where the first frame is located in the bar.
	
	  ui.setIndicatorDirection(LEFT_RIGHT);
	
	
	
	  // You can change the transition that is used
	
	  // SLIDE_LEFT, SLIDE_RIGHT, SLIDE_TOP, SLIDE_DOWN
	
	  ui.setFrameAnimation(SLIDE_LEFT);
	
	
	
	  ui.setFrames(frames, numberOfFrames);
	
	
	
	  ui.setOverlays(overlays, numberOfOverlays);
	
	
	
	  // Inital UI takes care of initalising the display too.
	
	  ui.init();
	
	
	
	  Serial.println("");
	
	
	
	  updateData(&display);
	
	
	
	}
	
	
	
	void loop() {
	
	
	
	  if (millis() - timeSinceLastWUpdate > (1000L*UPDATE_INTERVAL_SECS)) {
	
	    setReadyForWeatherUpdate();
	
	    timeSinceLastWUpdate = millis();
	
	  }
	
	
	
	  if (readyForWeatherUpdate && ui.getUiState()->frameState == FIXED) {
	
	    updateData(&display);
	
	  }
	
	
	
	  int remainingTimeBudget = ui.update();
	
	
	
	  if (remainingTimeBudget > 0) {
	
	    // You can do some work here
	
	    // Don't do stuff if you are below your
	
	    // time budget.
	
	    delay(remainingTimeBudget);
	
	  }
	
	
	
	
	
	}
	
	
	
	void drawProgress(OLEDDisplay *display, int percentage, String label) {
	
	  display->clear();
	
	  display->setTextAlignment(TEXT_ALIGN_CENTER);
	
	  display->setFont(ArialMT_Plain_10);
	
	  display->drawString(64, 10, label);
	
	  display->drawProgressBar(2, 28, 124, 10, percentage);
	
	  display->display();
	
	}
	
	
	
	void updateData(OLEDDisplay *display) {
	
	  drawProgress(display, 10, "Updating time...");
	
	  drawProgress(display, 30, "Updating weather...");
	
	  currentWeatherClient.setMetric(IS_METRIC);
	
	  currentWeatherClient.setLanguage(OPEN_WEATHER_MAP_LANGUAGE);
	
	  currentWeatherClient.updateCurrentById(¤tWeather, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATION_ID);
	
	  drawProgress(display, 50, "Updating forecasts...");
	
	  forecastClient.setMetric(IS_METRIC);
	
	  forecastClient.setLanguage(OPEN_WEATHER_MAP_LANGUAGE);
	
	  uint8_t allowedHours[] = {12};
	
	  forecastClient.setAllowedHours(allowedHours, sizeof(allowedHours));
	
	  forecastClient.updateForecastsById(forecasts, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATION_ID, MAX_FORECASTS);
	
	
	
	  readyForWeatherUpdate = false;
	
	  drawProgress(display, 100, "Done...");
	
	  delay(1000);
	
	}
	
	
	
	
	
	
	
	void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
	
	  now = time(nullptr);
	
	  struct tm* timeInfo;
	
	  timeInfo = localtime(&now);
	
	  char buff[16];
	
	
	
	
	
	  display->setTextAlignment(TEXT_ALIGN_CENTER);
	
	  display->setFont(ArialMT_Plain_10);
	
	  String date = WDAY_NAMES[timeInfo->tm_wday];
	
	
	
	  sprintf_P(buff, PSTR("%s, %02d/%02d/%04d"), WDAY_NAMES[timeInfo->tm_wday].c_str(), timeInfo->tm_mday, timeInfo->tm_mon+1, timeInfo->tm_year + 1900);
	
	  display->drawString(64 + x, 5 + y, String(buff));
	
	  display->setFont(ArialMT_Plain_24);
	
	
	
	  sprintf_P(buff, PSTR("%02d:%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
	
	  display->drawString(64 + x, 15 + y, String(buff));
	
	  display->setTextAlignment(TEXT_ALIGN_LEFT);
	
	}
	
	
	
	void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
	
	  display->setFont(ArialMT_Plain_10);
	
	  display->setTextAlignment(TEXT_ALIGN_CENTER);
	
	  display->drawString(64 + x, 38 + y, currentWeather.description);
	
	
	
	  display->setFont(ArialMT_Plain_24);
	
	  display->setTextAlignment(TEXT_ALIGN_LEFT);
	
	  String temp = String(currentWeather.temp, 1) + (IS_METRIC ? "°C" : "°F");
	
	  display->drawString(60 + x, 5 + y, temp);
	
	
	
	  display->setFont(Meteocons_Plain_36);
	
	  display->setTextAlignment(TEXT_ALIGN_CENTER);
	
	  display->drawString(32 + x, 0 + y, currentWeather.iconMeteoCon);
	
	}
	
	
	
	
	
	void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
	
	  drawForecastDetails(display, x, y, 0);
	
	  drawForecastDetails(display, x + 44, y, 1);
	
	  drawForecastDetails(display, x + 88, y, 2);
	
	}
	
	
	
	void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex) {
	
	  time_t observationTimestamp = forecasts[dayIndex].observationTime;
	
	  struct tm* timeInfo;
	
	  timeInfo = localtime(&observationTimestamp);
	
	  display->setTextAlignment(TEXT_ALIGN_CENTER);
	
	  display->setFont(ArialMT_Plain_10);
	
	  display->drawString(x + 20, y, WDAY_NAMES[timeInfo->tm_wday]);
	
	
	
	  display->setFont(Meteocons_Plain_21);
	
	  display->drawString(x + 20, y + 12, forecasts[dayIndex].iconMeteoCon);
	
	  String temp = String(forecasts[dayIndex].temp, 0) + (IS_METRIC ? "°C" : "°F");
	
	  display->setFont(ArialMT_Plain_10);
	
	  display->drawString(x + 20, y + 34, temp);
	
	  display->setTextAlignment(TEXT_ALIGN_LEFT);
	
	}
	
	
	
	void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) {
	
	  now = time(nullptr);
	
	  struct tm* timeInfo;
	
	  timeInfo = localtime(&now);
	
	  char buff[14];
	
	  sprintf_P(buff, PSTR("%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min);
	
	
	
	  display->setColor(WHITE);
	
	  display->setFont(ArialMT_Plain_10);
	
	  display->setTextAlignment(TEXT_ALIGN_LEFT);
	
	  display->drawString(0, 54, String(buff));
	
	  display->setTextAlignment(TEXT_ALIGN_RIGHT);
	
	  String temp = String(currentWeather.temp, 1) + (IS_METRIC ? "°C" : "°F");
	
	  display->drawString(128, 54, temp);
	
	  display->drawHorizontalLine(0, 52, 128);
	
	}
	
	
	
	void setReadyForWeatherUpdate() {
	
	  Serial.println("Setting readyForUpdate to true");
	
	  readyForWeatherUpdate = true;
	
	}
							
						

注意代码里面填写自己的APIkey和要查询的城市名称,这里差的是深圳-shenzhen.

注:为了大家方便,这里提供一个测试账号,仅供测试噢

m13237003655@163.com 密码:lingzhilab

完整工程代码

将代码验证并上传到零知-ESP8266上面,打开串口调试工具,可以看到如下结果:

获取返回结果代码:200表示成功了,这里然后用

如果错误码,可以把地址粘贴到浏览器里看看是什么原因。



完整工程:weather_station1.7z(点击下载)

可能用到的库:Time.zip(点击下载)                                        json-streaming-parser.zip(点击下载)

                       esp8266-weather-station.zip(点击下载)        esp8266-oled-ssd1306.zip(点击下载)

                       Adafruit-GFX.zip(点击下载)                           Adafruit_SSD1306.zip(点击下载)