M5Stick-Cに天気を画像で表示する

今回は以下の画像のように、APIで取得した天気情報を使ってM5Stick-Cに天気を画像として表示させる方法を紹介します。天気以外にも、APIで取得した情報に応じた画像表示もできるようになります。

前提知識

この記事はすでに投稿済みの以下の2記事の情報を使っています。疑問等ありましたらこれらの記事も参考にしながら作ってみてください。

必須ライブラリ

  • Arduino Json
  • SPIFFS Uploader
    • ESP32のファイル領域SPIFFSに画像を送付するのに必要です。mgo-tecさんの記事を参考にインストールしましょう

画像を適当に作ろう!

まず天気の画像を適当に作りましょう。M5Stick-Cの画面の大きさは160×80なので、それに収まるように作ってください。ドット絵をpiskel等のサービスで作るのが便利です。

今回使うのはSPIFFS領域に画像を直接アップロードして表示する方法です。表示するにはビットマップ画像にしなければなりません。画像を作った後、ペイントで24bitの画像として保存します。

今回デモ用として用意した画像がこちら。

コード及びゆるい画像などのセットはgithubに置いてありますので、ダウンロードしてご利用ください

画像をアップロード

  1. プロジェクトフォルダを適当な場所に作り、中にdataという名前のフォルダを作り、その中に先程の.bmpファイルを入れます。※このフォルダ内が丸ごとM5Stick-Cにアップロードされます。

  2. mgo-tecさんのSPIFFS uploaderの導入方法に従いSPIFFS uploaderを入れる
  3. ESP32 Sketch Data Uploadを選択してM5Stick-Cにデータをアップロードする。

これでM5Stick-Cにdata内の画像が送信されました。

天気API(OpenWeatherMap)のキーを取得しよう

好きな場所の現在の天気や予報が取得できるOpenWeatherMapというサービスを使います。その登録方法などはこちらにあります。
この方法に従って、API KEYを取得しましょう。

コード(ほぼコピペで動く)

以下のWi-Fiの情報とopenweathermapの情報(以下コードの大文字で書かれている部分)を自分のデータに書き換えてから書き込んでください。

#include "FS.h"
#include "SPIFFS.h"
#include "M5StickC.h"
#include "M5Display.h"

#include <HTTPClient.h>
#include <ArduinoJson.h> 
char str[100];
const char* ssid = "YOURSSID";
const char* password = "YOURPASS";

const String endpoint = "http://api.openweathermap.org/data/2.5/weather?q=tokyo,jp&APPID=";
const String key = "APIKEY";

String getWeather(){
  HTTPClient http;
  http.begin(endpoint + key); //URLを指定
  int httpCode = http.GET();  //GETリクエストを送信
  String weather;
  if (httpCode > 0) { //返答がある場合

      String payload = http.getString();  //返答(JSON形式)を取得
      Serial.println(httpCode);
      Serial.println(payload);

      //jsonオブジェクトの作成
      DynamicJsonBuffer jsonBuffer;
      String json = payload;
      JsonObject& weatherdata = jsonBuffer.parseObject(json);

      //パースが成功したかどうかを確認
      if(!weatherdata.success()){
        Serial.println("parseObject() failed");
      }

      //各データを抜き出し
       weather = weatherdata["weather"][0]["main"].as<char*>();
      }
  return weather;
}
double getTemperature(){
  HTTPClient http;
  http.begin(endpoint + key); //URLを指定
  int httpCode = http.GET();  //GETリクエストを送信
  if (httpCode > 0) { //返答がある場合

      String payload = http.getString();  //返答(JSON形式)を取得
      Serial.println(httpCode);
      Serial.println(payload);

      //jsonオブジェクトの作成
      DynamicJsonBuffer jsonBuffer;
      String json = payload;
      JsonObject& weatherdata = jsonBuffer.parseObject(json);

      //パースが成功したかどうかを確認
      if(!weatherdata.success()){
        Serial.println("parseObject() failed");
      }
      const double temp = weatherdata["main"]["temp"].as<double>();
      return temp;
  }else{
    return -88.88;
  }
}
//ここから参考にしたM5stackの関数群
uint16_t read16(fs::File &f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(fs::File &f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}
void drawBmpFile(fs::FS &fs, const char *path, uint16_t x, uint16_t y) {
  if ((x >= 160) || (y >= 80)) return;

  // Open requested file on SD card
  File bmpFS = fs.open(path, "r");

  if (!bmpFS) {
    Serial.print("File not found");
    return;
  }

  uint32_t seekOffset;
  uint16_t w, h, row, col;
  uint8_t  r, g, b;

  uint32_t startTime = millis();

  if (read16(bmpFS) == 0x4D42) {
    read32(bmpFS);
    read32(bmpFS);
    seekOffset = read32(bmpFS);
    read32(bmpFS);
    w = read32(bmpFS);
    h = read32(bmpFS);

    if ((read16(bmpFS) == 1) && (read16(bmpFS) == 24) && (read32(bmpFS) == 0)) {
      y += h - 1;

      M5.Lcd.setSwapBytes(true);
      bmpFS.seek(seekOffset);

      uint16_t padding = (4 - ((w * 3) & 3)) & 3;
      uint8_t lineBuffer[w * 3 + padding];

      for (row = 0; row < h; row++) {
        bmpFS.read(lineBuffer, sizeof(lineBuffer));
        uint8_t*  bptr = lineBuffer;
        uint16_t* tptr = (uint16_t*)lineBuffer;
        // Convert 24 to 16 bit colours
        for (col = 0; col < w; col++) {
          b = *bptr++;
          g = *bptr++;
          r = *bptr++;
          *tptr++ = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
        }

        // Push the pixel row to screen, pushImage will crop the line if needed
        // y is decremented as the BMP image is drawn bottom up
        M5.Lcd.pushImage(x, y--, w, 1, (uint16_t*)lineBuffer);
      }
      Serial.print("Loaded in "); Serial.print(millis() - startTime);
      Serial.println(" ms");
    }
    else Serial.println("BMP format not recognized.");
  }
  bmpFS.close();
}
//参考にした関数終わり
void showWeather(){//天気情報を取得し、それに応じて画像を表示
  String weather = getWeather();
  Serial.println(weather);
  M5.Lcd.fillScreen(BLACK);
  if (weather=="Clear"){
    drawBmpFile(SPIFFS, "/sunny.bmp", 50, 20);
  }else if(weather=="Clouds"){
    drawBmpFile(SPIFFS, "/cloudy.bmp", 50, 20);
  }else if(weather=="Rain"){
    drawBmpFile(SPIFFS, "/rainy.bmp", 50, 20);
  }else{
    M5.Lcd.print(weather);
  }
  String temp = String(getTemperature()-273.15) + " C";
  M5.Lcd.setCursor(0,0);
  M5.Lcd.print(temp);
}

void setup(){
    M5.begin();
    M5.Axp.ScreenBreath(10);          // 7-12で明るさ設定
    M5.Lcd.setRotation(3);            // 0-3で画面の向き  
    M5.Lcd.setTextSize(2);
    if(!SPIFFS.begin(true)){
        Serial.println("SPIFFS Mount Failed");
        return;
    }
    WiFi.begin(ssid, password);
  
    while (WiFi.status() != WL_CONNECTED) {
      delay(1000);
      Serial.println("Connecting to WiFi..");
    }
    Serial.println("Connected to the WiFi network");
    delay(1000);
    drawBmpFile(SPIFFS, "/logo.bmp", 0, 0);
}

void loop(){
  showWeather();
  delay(1000*600);
}

プチ解説

今回実装したのは、以下の天気に応じて表示する画像を変える部分です。

void showWeather(){//天気情報を取得し、それに応じて画像を表示
  String weather = getWeather();
  Serial.println(weather);
  M5.Lcd.fillScreen(BLACK);
  if (weather=="Clear"){
    drawBmpFile(SPIFFS, "/sunny.bmp", 50, 20);
  }else if(weather=="Clouds"){
    drawBmpFile(SPIFFS, "/cloudy.bmp", 50, 20);
  }else if(weather=="Rain"){
    drawBmpFile(SPIFFS, "/rainy.bmp", 50, 20);
  }else{
    M5.Lcd.print(weather);
  }
  String temp = String(getTemperature()-273.15) + " C";
  M5.Lcd.setCursor(0,0);
  M5.Lcd.print(temp);
}

OpenWeatherMapは様々な種類の天気の名称を文字列で返します。晴れの時はClear、曇りはClouds、雨はRainですね。これをweatherという変数に入れて、その名前でどの画像にするか決定しています。

SPIFFSの画像はdrawBmpFile(SPIFFS,path,x,y);で表示できるような関数を書きましたので、簡単に画像名で指定できます。

現在の気温に関しては、そのままの値をM5.Lcd.print(temp)で表示しています。

コメントする

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

Exit mobile version