天気予報をM5Stackで表示してみた

はじめに

家を出るときは天気が良くて「今日は傘いらないな」と思っても夕方から雨が降ってしまうなんてことはよくある話だと思います。
朝のニュースやスマホのアプリで天気予報を見る習慣があればいいのですが、テレビを基本的に見ない、スマホを開いても他のことをついやっちゃう私としては、天気予報の機能だけを切り出してすぐに見られるようにしたいなと思いました。
そこで今回は天気予報を表示するガジェットをM5Stackを用いて実現したのでご紹介します。

完成品

天気予報の情報として日付や天気、最高気温、最低気温、そして6時間ごとの降水確率が表示されます。また、3つのボタンを左から今日、明日、明後日の天気予報に割り当て、それぞれのボタンを押すことで表示を切り替えます。

開発環境

実現方法

  • 全体の流れ

1. 起動時にWi-Fi接続し、APIを叩いて天気予報を取得、保持する。
2. 取得した天気予報を基にM5Stack上に今日の天気の情報を描画する。
3. ボタン押下に応じて、天気予報の表示を切り替える。

  • 描画について

以前に下記の記事で紹介した方法と同様に、表示する画面分の画像を用意して、処理に応じて画像を切り替えております。
kuracux.hatenablog.jp

今回であれば、天気の種類分の画像を用意します。が、フリーで手に入ったのがこれだけしかなかったので、今回表示できるのは下記の天気だけです。


f:id:kuracux:20190711224317j:plainf:id:kuracux:20190711224321j:plainf:id:kuracux:20190711224323j:plain


f:id:kuracux:20190711224326j:plainf:id:kuracux:20190711224331j:plainf:id:kuracux:20190711224334j:plain

www.irasutoya.com

そして、APIから取得した天気予報に応じて、表示する画像を決定し、描画。その後、日付や気温、降水確率を天気の画像の上から描画します。

  • 使用したAPIについて

今回の制作にあたり、表示する情報として必要なAPIの要件は下記の通りです。

  1. 3日分の天気が取得できること
  2. 降水確率が含まれていること
  3. レスポンスの形式がJSONであること
  4. (気温も取得できると嬉しい)

特に3つ目についてはXML or JSONという選択肢がありますが、JSONであればArduinoJSONというライブラリで簡単にパースできるため、今回はJSONで進めることにしました。

さて、これらの条件を満たすAPIを探したところ、今回はdrk7.jpさんのAPIを利用することにしました。しかし、レスポンスの形式がJSONPであるため、少し加工が必要です。
www.drk7.jp

スケッチ例

画像の種類が少ない関係で天気を完璧に表示することは出来ませんが、おおまかな天気は分かるようにしました。
使用時はmicroSDカードに変数pictureFolderと同じ名前のフォルダをルート直下に作り、その配下に適切なファイル名を設定した画像を格納し、ご利用ください。

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <M5Stack.h>
 
const char* ssid = "環境に応じて変更してください";
const char* password = "環境に応じて変更してください";

// 画像を入れるフォルダ名
const char* pictureFolder = "/pictures/";

// 愛知県西部の天気予報を取得
const char* endpoint = "https://www.drk7.jp/weather/json/23.js";
const char* region = "西部";
DynamicJsonDocument weatherInfo(20000);
 
void setup() {
    M5.begin();
    Wire.begin();
    M5.Lcd.setBrightness(100);
    Serial.begin(115200);
    WiFi.begin(ssid, password);
     
    while (WiFi.status() != WL_CONNECTED) {
        delay(1000);
        Serial.println("Connecting to WiFi..");
    }
    Serial.println("Connected to the WiFi network");
    weatherInfo = getJson();
    WiFi.disconnect();
    drawTodayWeather(); 
}
 
void loop() {
    delay(1);
    if (M5.BtnA.wasPressed()) {
        drawTodayWeather(); 
    }
    if (M5.BtnB.wasPressed()) {
        drawTomorrowWeather();
    }
    if (M5.BtnC.wasPressed()) {
        drawDayAfterTomorrowWeather();
    }
    M5.update();
}

DynamicJsonDocument getJson() {
    DynamicJsonDocument doc(20000);
  
    if ((WiFi.status() == WL_CONNECTED)) {
        HTTPClient http;
        http.begin(endpoint);
        int httpCode = http.GET();
        if (httpCode > 0) {
            //jsonオブジェクトの作成
            String jsonString = createJson(http.getString());
            deserializeJson(doc, jsonString);
        } else {
            Serial.println("Error on HTTP request");
        }
        http.end(); //リソースを解放
    }
    return doc;
}

// JSONP形式からJSON形式に変える
String createJson(String jsonString){
    jsonString.replace("drk7jpweather.callback(","");
    return jsonString.substring(0,jsonString.length()-2);
}

void drawTodayWeather() {
    String today = weatherInfo["pref"]["area"][region]["info"][0];
    drawWeather(today);
}

void drawTomorrowWeather() {
    String tomorrow = weatherInfo["pref"]["area"][region]["info"][1];
    drawWeather(tomorrow);
}

void drawDayAfterTomorrowWeather() {
    String dayAfterTomorrow = weatherInfo["pref"]["area"][region]["info"][2];
    drawWeather(dayAfterTomorrow);
}

void drawWeather(String infoWeather) {
    M5.Lcd.clear();
    DynamicJsonDocument doc(20000);
    deserializeJson(doc, infoWeather);
    String weather = doc["weather"];
    String filename = "";
    if (weather.indexOf("雨") != -1) {
        if (weather.indexOf("くもり") != -1) {
            filename = "rainyandcloudy.jpg";
        } else {
            filename = "rainy.jpg";
        }
    } else if (weather.indexOf("晴") != -1) {
        if (weather.indexOf("くもり") != -1) {
            filename = "sunnyandcloudy.jpg";
        } else {
            filename = "sunny.jpg";
        }
    } else if (weather.indexOf("雪") != -1) {
        filename = "snow.jpg";
    } else if (weather.indexOf("くもり") != -1) {
        filename = "cloudy.jpg";
    }
  
    if (filename.equals("")){ 
        return;
    }
  
    String filePath = pictureFolder+filename;
    M5.Lcd.drawJpgFile(SD,filePath.c_str());
  
    String maxTemperature = doc["temperature"]["range"][0]["content"];
    String minTemperature = doc["temperature"]["range"][1]["content"];
    drawTemperature(maxTemperature, minTemperature);
  
    String railfallchance0_6 = doc["rainfallchance"]["period"][0]["content"];
    String railfallchance6_12 = doc["rainfallchance"]["period"][1]["content"];
    String railfallchance12_18 = doc["rainfallchance"]["period"][2]["content"];
    String railfallchance18_24 = doc["rainfallchance"]["period"][3]["content"];
    drawRainfallChancce(railfallchance0_6, railfallchance6_12, railfallchance12_18, railfallchance18_24);
  
    drawDate(doc["date"]);
}

void drawTemperature(String maxTemperature, String minTemperature) {
    M5.Lcd.setTextColor(RED);
    M5.Lcd.setTextSize(4);
    M5.Lcd.setCursor(15,80);
    M5.Lcd.print(maxTemperature);
  
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setCursor(70,80);
    M5.Lcd.print("|");
  
    M5.Lcd.setTextColor(BLUE);
    M5.Lcd.setCursor(105,80);
    M5.Lcd.print(minTemperature);
}

void drawRainfallChancce(String rfc0_6, String rfc6_12, String rfc12_18, String rfc18_24) {
    M5.Lcd.setTextSize(3);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setCursor(27,200);
    M5.Lcd.print(rfc0_6);
    
    M5.Lcd.setCursor(92,200);
    M5.Lcd.print(rfc6_12);
  
    M5.Lcd.setCursor(165,200);
    M5.Lcd.print(rfc12_18);
  
    M5.Lcd.setCursor(245,200);
    M5.Lcd.print(rfc18_24);
}

void drawDate(String date) {
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setTextSize(3);
    M5.Lcd.setCursor(10,10);
    M5.Lcd.print(date);
}

おわりに

今回は天気予報をM5Stackで表示してみました。
画像の都合上、正確な天気を表示することは出来ませんが、大雑把に天気が分かって気温や降水確率が見られるので良いかなと思います。
ご参考になれば幸いです。