ゆるく楽しむプログラミング

M5Stamp PICOで天気予報を表示 ~OpenWathe編~

M5Stamp PICOで天気予報サイト OpenWatherにアクセスして、天気予報データを取得しお天気アイコンを表示します。


OpenWatherからの天気予報データ取得方法

OpenWatherのAPIを使用するにはAPI-KEYが必要なので、アカウントを作成してAPI-KEYを取得します。
アカウントは無料と35GBP(約6千円/月)~700GBP(約12万円/月)の有料コースがあります。
無料アカウントでは利用でるAPIに制限があるほか、利用回数も60回/分、100回/日に制限されます。
取りあえずは、近い時間のお天気アイコンを表示するだけなので、無料アカウントで事足ります。

今回は、5 day / 3 hour forecastのAPIを利用します。
利用方法は API解説で説明されています。
api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={API key}

観測地の緯度{lat}、経度{lon} の値は、Googlemapで観測地点を右クリックすれば表示されます。
表示された値をコピペすればOKです。


JSONとアイコン

APIにアクセスするとJSON形式で天気予報データが返ってきます
JSONの読み取りにArduinoJSONライブラリを使用します。


ストリームから入力したテキストを渡すとJSONに展開してくれるので、使い方がとても簡単です。
 基本、▼の操作だけで時刻ごとの天気IDが取り出せます。
  HTTPClient http;
  http.begin(endpoint + key); 	// URLを指定
  int httpCode = http.GET();  	// GETリクエストを送信
  if (httpCode == 200) { 	// Request Result 正常終了
    String watherData = http.getString();  //返答(JSON形式)を取得
    deserializeJson(doc, watherDataStr);
  }

  int count = doc["cnt"];
  for (int i=0;i<count;i++) {
    time_t dateTime = doc["list"][i]["dt"];		// 予報時刻を取得
    int watheId = doc["list"][i]["weather"][0]["id"];	// WatherIDを取得
  }
天気ID(WatherID)とお天気アイコンの対応は OpenWatherのヘルプページに記載されています。

お天気アイコンの作成

晴と、曇り以外は表示時にパーツを合成する事で実現しました。※作るのが面倒なので。


作成したアイコン画像を lcd-image-converter で1ドット/1ByteのBITMAPデータに変換します。



ガジェットの立体配線図


SPIのCSとMISOは省略
CSはSSD1331のRSTに使用
MISOは入力専用ピンなので出力には使えません。
DHT-11のプルアップ抵抗は4.7KΩを使用

スケッチの構造


setup( )
 起動時の初期後にNTPサーバから時刻を取得してRTCに設定

loop( )
 1分毎に温湿度を取得して表示

 毎時00分にOpwnWatherMapから天気予報を取得

 3時間ごとの天気をアイコン表示


OpwnWather.zip
※ <>を全角<>に置き換えています。コピペの際は注意してください。
  ▼に表示しているのは、プログラムの一部です。全体はZIPファイルを解凍して参照してください。
/*
 * M5Stamp PICO IoT実験
 * 天気予報を取得
 */
#include <WiFi.h>
#include <SPI.h>
#include <Wire.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

#define DEBUG true
#define BAUDRATE 9600
char str[120] ;

#include "ssd1331.h"
#include "sd1307.h"
#include "openWather.h"

// --- OLED ---
SSD1331 ssd1331 ;

// --- RTC 000
SD1307 sd1307 ;

// --- DHT-11
#define DHT_PIN 0
struct dht11_s {
  int8_t humidityN ;   // 湿度の整数部
  int8_t humidityD ;   // 湿度の小数部 ※は常に0
  int8_t temperatureN; // 温度の整数部
  int8_t temperatureD; // 温度の小数部
} dht11 ;


// --- OpenWatheMap ---
OPENWATHER openWather ;

// ----- NetWork Data ---
const char* ssid     = "****";
const char* password = "********";

// --- 時刻調整用
#define SPANTIME 60 // データ取得間隔
unsigned long passTime;
int sendTime = 0 ;
struct tm timeInfo;

// --------------
// ----- NTP ----
// --------------
void connectWiFi( ) {
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.reconnect( ) ;
    int i = 0 ;
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
#if DEBUG
      Serial.print(".");
#endif
      i++ ;
      if (i > 20) {
        delay(30000);
        return  ;
      }
    }
#if DEBUG      
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
#endif
  }
}

void getNTP( ) {
  connectWiFi() ;
  configTime(9 * 3600L, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp");
  getLocalTime(&timeInfo) ;
  
  sd1307.yer = timeInfo.tm_year ;       // 年
  sd1307.mon = timeInfo.tm_mon + 1 ;    // 月
  sd1307.day = timeInfo.tm_mday ;       // 日
  sd1307.sec = timeInfo.tm_sec;         // 秒
  sd1307.mnt = timeInfo.tm_min ;        // 分
  sd1307.how = timeInfo.tm_hour ;       // 時
  sd1307.weekday = timeInfo.tm_wday + 1; // 曜日(1~7)
  sd1307.setTime( ) ;

#if DEBUG
  sprintf(str,"NEW %04d/%02d/%02d(%d) %02d:%02d:%02d",
        timeInfo.tm_year + 1900 , timeInfo.tm_mon + 1 ,  timeInfo.tm_mday, timeInfo.tm_wday + 1 ,
        timeInfo.tm_hour , timeInfo.tm_min , timeInfo.tm_sec) ;
  Serial.println(str) ;
  sd1307.getTime( ) ;
  sprintf(str,"RTC 20%02d/%02d/%02d(%d) %02d:%02d:%02d",
        sd1307.yer , sd1307.mon ,  sd1307.day, sd1307.weekday ,
        sd1307.how , sd1307.mnt , sd1307.sec) ;
  Serial.println(str) ;

#endif
}

// ----------------
// ----- DHT11 ----
// ----------------
bool readDHT11( ) {
  uint8_t readData[5] ;
  uint8_t checkSum = 0 ;
  int setPos = 0 ;
  int bitLen = 0 ;
  int stat = HIGH ;
  uint8_t bitData ;
  unsigned long beginTime ;
  unsigned long passTime ;
  unsigned long zeroTime = 35 ; // 35μ秒以下なら0 以上なら1と判定 ※仕様では0は26~28μ秒
  bool flagStart = false;

  for (int i=0;i<5;i++) {
    readData[i] = 0 ;
  }

  // ----- データ送信要求 -----
  pinMode(DHT_PIN, OUTPUT); //開始信号
  delay(20);
  pinMode(DHT_PIN, INPUT);
  while(digitalRead(DHT_PIN) == LOW) {
    delayMicroseconds(5) ;
  }

  // ----- ACSEPT -----
  flagStart = false;
  passTime = 0 ;
  beginTime = micros();
  while (passTime < 1000) {
    passTime = micros() - beginTime ;
    if (digitalRead(DHT_PIN) !=  stat) {
      if (stat == HIGH) {
        stat = LOW ;
        if (flagStart) {
           break ; 
        }
      } else {
        beginTime = micros();
        flagStart = true ;
        stat = HIGH ;
      }
    }
  }
  
  // ----- データ受信 -----
  bitData = 0x80 ;
  flagStart = false;
  passTime = 0 ;
  beginTime = micros();
  while (bitLen < 40 && passTime < 1000) {
    passTime = micros() - beginTime ;
    if (digitalRead(DHT_PIN) !=  stat) {
      if (stat == HIGH) {
        stat = LOW ;
        if (flagStart) {
          // ----- 1Bit 受信 -----
          if (passTime > zeroTime) {
            readData[setPos] |= bitData ;
          }
          if (bitData != 0x01) {
            bitData >>= 1 ;
          } else {
            bitData = 0x80 ;
            setPos ++ ;
          }

          // ----- ステート変数リセット -----
          beginTime = micros();
          flagStart = false;
          bitLen ++ ;
        }
      } else {
        beginTime = micros();
        flagStart = true ;
        stat = HIGH ;
      }
    }
  }

  // ----- 取得データ格納 -----
  dht11.humidityN = readData[0] ;   // 湿度の整数部 Range:5~95
  dht11.humidityD = readData[1] ;   // 湿度の小数部 ※は常に0
  dht11.temperatureN = readData[2]; // 温度の整数部 -20 ~ 
  dht11.temperatureD = readData[3]; // 温度の小数部
  checkSum = readData[0] + readData[1] + readData[2] + readData[3] ;

  return (bitLen == 40 && checkSum == readData[4]) ;
}

// ----------------------
// ----- セットアップ -----
// ----------------------
void setup() {
  // --- 初期化 ---
#if DEBUG
  Serial.begin(BAUDRATE);
  Serial.println("\nWakeUp") ;
#endif
  Wire.begin() ;
  SPI.begin();//SPIを初期化、SCK、MOSI、SSの各ピンの動作は出力、SCK、MOSIはLOW、SSはHIGH
  ssd1331.init( ) ;

  // --- Netwaorkに接続
  Serial.println("Networkに接続") ;
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
#if DEBUG
    Serial.print(".");
#endif
  }

#if DEBUG
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
#endif

  // ----- 起動時 初期読み取り -----
  getNTP( ) ;
}

// ----------------------
// ----- ループ-----
// ----------------------
void loop() {
  sd1307.getTime( ) ;
  sprintf(str,"%02d/%02d %02d:%02d", sd1307.mon ,  sd1307.day , sd1307.how , sd1307.mnt ) ;
  ssd1331.dispStr(str,4,0) ;

  if (sd1307.sec == 0 || openWather.watheData[0].dateTime == 0) {
    if (readDHT11( )) {
      sprintf(str,"%2d.%dC %2d%%", dht11.temperatureN ,dht11.temperatureD,dht11.humidityN ) ;
    } else {
      sprintf(str,"--.--C --%%") ;
    }
    ssd1331.dispStr(str,4,16) ;
  }

  if ((sd1307.mnt == 0 && sd1307.sec == 10) || openWather.watheData[0].dateTime == 0) {
    if (sd1307.how == 0) {
        getNTP( ) ;
    }
    openWather.getWatherData(sd1307.how) ;

    struct tm tim = *localtime( &openWather.watheData[0].dateTime );
    sprintf(str,"%02d:%02d",tim.tm_hour,tim.tm_min) ;
    ssd1331.dispWatherIcon(openWather.watheData[0].watheId, 0,32,str) ;
  
    tim = *localtime( &openWather.watheData[1].dateTime );
    sprintf(str,"%02d:%02d",tim.tm_hour,tim.tm_min) ;
    ssd1331.dispWatherIcon(openWather.watheData[1].watheId,32,32,str) ;
  
    tim = *localtime( &openWather.watheData[2].dateTime );
    sprintf(str,"%02d:%02d",tim.tm_hour,tim.tm_min) ;
    ssd1331.dispWatherIcon(openWather.watheData[2].watheId,64,32,str) ;
  }

  // ループ間隔 10秒
  delay(1000) ;
}

解説動画