2025/05/06 oxxo Arduino, featured 无标签

ESP8266实现时钟+天气状况表示

功能
可同步NTP服务器的时钟
外部天气实况API调用
文末有实现后的效果视频

主程序


#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
#include <ArduinoJson.h>
#include <U8g2lib.h>
#include "WeatherIcons.h"

// OLED 初始化(D1 -> SCL, D2 -> SDA)
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, D1, D2, U8X8_PIN_NONE);

// WiFi 信息
const char* ssid = "SSID";
const char* password = "PASSWORD";

// 天气信息
String weatherInfo = "";
WeatherIcons weatherIcons;

// NTP 时间客户端
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "ntp.nict.jp", 9 * 3600, 60000); // 日本时间 UTC+9

// 更新时间和天气的定时器
unsigned long lastWeatherUpdate = 0;
const unsigned long weatherInterval = 10 * 60 * 1000; // 每10分钟更新天气

void setup() {
  Serial.begin(115200);
  u8g2.begin();
  WiFi.begin(ssid, password);
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_ncenB08_tr);
  u8g2.drawStr(0, 15, "Connecting WiFi...");
  u8g2.sendBuffer();

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  timeClient.begin();
  getWeather(); // 开始先拉一次天气
}

void loop() {
  timeClient.update();

  unsigned long now = millis();
  if (now - lastWeatherUpdate > weatherInterval) {
    getWeather();
    lastWeatherUpdate = now;
  }

  // 获取当前时间
  String formattedTime = timeClient.getFormattedTime();
  time_t rawTime = timeClient.getEpochTime();
  struct tm* timeinfo = localtime(&rawTime);
  char dateBuffer[16];
  strftime(dateBuffer, sizeof(dateBuffer), "%Y-%m-%d", timeinfo);

  // 显示内容
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_wqy16_t_gb2312b);
  u8g2.drawStr(10, 12, formattedTime.c_str());     // 时间
  u8g2.drawStr(10, 27, dateBuffer);               // 日期

  displayWeather(weatherInfo);  // 只在这里调用一次天气显示(包括图标和文字)

  u8g2.sendBuffer();
  delay(1000); // 每秒刷新一次时间
}

void getWeather() {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    WiFiClient client;
    String url = "http://api.weatherapi.com/v1/current.json?key=**********APIKEY**********&q=Tokyo&lang=zh";
    
    http.begin(client, url);
    int httpCode = http.GET();

    if (httpCode == HTTP_CODE_OK) {
      String payload = http.getString();

      DynamicJsonDocument doc(1024);
      DeserializationError error = deserializeJson(doc, payload);

      if (!error) {
        String location = doc["location"]["name"].as();
        String condition = doc["current"]["condition"]["text"].as();
        float temp = doc["current"]["temp_c"].as();
        weatherInfo = location + " " + condition + " " + String(temp, 1) + "°C";
      } else {
        weatherInfo = "天气解析错误";
      }
    } else {
      weatherInfo = "天气请求失败";
    }

    http.end();
  } else {
    weatherInfo = "WiFi未连接";
  }
}
void displayWeather(const String &weatherInfo) {
  u8g2.setFont(u8g2_font_wqy12_t_gb2312b); // 设置中文字体
  u8g2.drawUTF8(10, 60, weatherInfo.c_str()); // 正确显示中文
  weatherIcons.draw(u8g2, weatherInfo, 100, 20); // 图标
}

WeatherIcons.h


#ifndef WEATHER_ICONS_H
#define WEATHER_ICONS_H

#include <Arduino.h>
#include <U8g2lib.h>

class WeatherIcons {
public:
    void draw(U8G2 &u8g2, const String &weatherDesc, int x = 0, int y = 32);

private:
    static const unsigned char icon_sunny[];
    static const unsigned char icon_cloudy[];
    static const unsigned char icon_rainy[];
    static const unsigned char icon_snowy[];
    static const unsigned char icon_foggy[];
};

#endif

WeatherIcons.cpp


#include "WeatherIcons.h"

const unsigned char WeatherIcons::icon_sunny[] PROGMEM = {
  0x10, 0x80, 0x00, 0x00, 0x10, 0x80, 0x00, 0x00,
  0x38, 0xE0, 0x7C, 0xF0, 0xFE, 0xF8, 0xFF, 0xF8,
  0xFF, 0xF8, 0xFE, 0xF8, 0x7C, 0xF0, 0x38, 0xE0,
  0x00, 0x00, 0x10, 0x80, 0x00, 0x00, 0x10, 0x80
};

const unsigned char WeatherIcons::icon_cloudy[] PROGMEM = {
  0x00, 0x00, 0x0C, 0x00, 0x1E, 0x00, 0x3F, 0x00,
  0x7F, 0x80, 0xFF, 0xC0, 0x7F, 0x80, 0x3F, 0x00,
  0x1E, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x3C, 0x00,
  0x7E, 0x00, 0xFF, 0x00, 0x7E, 0x00, 0x3C, 0x00
};

const unsigned char WeatherIcons::icon_rainy[] PROGMEM = {
  0x00, 0x00, 0x0E, 0x00, 0x1F, 0x00, 0x3F, 0x80,
  0x7F, 0xC0, 0x3F, 0x80, 0x1F, 0x00, 0x0E, 0x00,
  0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x24, 0x00,
  0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x24, 0x00
};

const unsigned char WeatherIcons::icon_snowy[] PROGMEM = {
  0x00, 0x00, 0x1C, 0x00, 0x3E, 0x00, 0x7F, 0x00,
  0xFF, 0x80, 0x7F, 0x00, 0x3E, 0x00, 0x1C, 0x00,
  0x2A, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00,
  0x2A, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00
};

const unsigned char WeatherIcons::icon_foggy[] PROGMEM = {
  0x00, 0x00, 0x7E, 0x00, 0x81, 0x00, 0xBD, 0x00,
  0x81, 0x00, 0xBD, 0x00, 0x81, 0x00, 0x7E, 0x00,
  0x18, 0x00, 0x18, 0x00, 0x7E, 0x00, 0x81, 0x00,
  0x99, 0x00, 0x81, 0x00, 0x7E, 0x00, 0x00, 0x00
};

void WeatherIcons::draw(U8G2 &u8g2, const String &weatherDesc, int x, int y) {
  if (weatherDesc.indexOf("晴") != -1) {
    u8g2.drawXBMP(x, y, 16, 16, icon_sunny);
  } else if (weatherDesc.indexOf("雨") != -1) {
    u8g2.drawXBMP(x, y, 16, 16, icon_rainy);
  } else if (weatherDesc.indexOf("雪") != -1) {
    u8g2.drawXBMP(x, y, 16, 16, icon_snowy);
  } else if (weatherDesc.indexOf("云") != -1 || weatherDesc.indexOf("阴") != -1) {
    u8g2.drawXBMP(x, y, 16, 16, icon_cloudy);
  } else if (weatherDesc.indexOf("雾") != -1) {
    u8g2.drawXBMP(x, y, 16, 16, icon_foggy);
  }
}

oxxo

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注