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); // 1秒ごとに更新
}

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


// コメント无,翻訳不要

WeatherIcons.cpp


#include "WeatherIcons.h"

const unsigned char WeatherIcons::icon_sunny[] PROGMEM = {
  ...
};

const unsigned char WeatherIcons::icon_cloudy[] PROGMEM = {
  ...
};

const unsigned char WeatherIcons::icon_rainy[] PROGMEM = {
  ...
};

const unsigned char WeatherIcons::icon_snowy[] PROGMEM = {
  ...
};

const unsigned char WeatherIcons::icon_foggy[] PROGMEM = {
  ...
};

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

发表评论

メールアドレスが公開されることはありません。 が付いている欄は必須項目です