IoT
oled
ESP32

WEMOS の ESP32 で クラウドの気象データを表示

WiFiにつながる安いESP32のボードで、クラウドに接続して、今の気象データを表示するIoTをしてみました。

IMG_20181008_072437.jpg

ESP32 WEMOS Lolin32

AKBONE Release 5 に採用して、いろいろ展開しようとしてある程度まとめて2017年に仕入れました。
AKBONE というのは、「日本Androidの会秋葉原支部ロボット部」で作っている電子工作とプログラミングのトレーニングキットです。
WEMOS Lolin 32 は安かったのだけれど、継続的な入手性がネックになり、今年のバージョンの AKBONE Release 6 ではこれではないボード(アオノドン)に乗り換えています。

本家のサイトはこれ。
https://www.wemos.cc/

本家ではないサイトでは、OLEDがついていたり、18650バッテリのホルダーがついていたり、いろいろとバリエーションがあります。
しかしながら、これらはどうやらパチもんらしく、品質がアレだという話も聞こえています。

ESP32-DevKitC との違い

espressif 公式の ESP32-DevKitC と WEMOS の Lolin32 はよく似ています。
DAz9V_OVoAAWIdE (1).jpg

比較

  • ヘッダピン
    • ESP32-DevKitC はんだづけ済
    • Lolin32 ピンは自分ではんだづけ
    • ピン配列は微妙に異なる
  • 形状
    • ESP32-DevKitC はちょっと広い
    • Lolin32 は 2.54mm だけ狭く、400Pのブレッドボードに刺した時に少し余裕ができる
  • その他の付属機能
    • ESP32-DevKitC Bootスイッチ付いている
    • Lolin32 LiPo充電ポート付いている
  • 価格
    • ESP32-DevKitC ¥1480 (秋月電子)
    • Lolin32 約半額
  • USBシリアル
    • ESP32-DevKitC は CP2102
    • Lolin32は CP2104

DAz_v9RU0AIEoqx.jpg
DAz_ydEU0AEfys-.jpg

Arduino開発環境

普通のESP32開発の設定を行うと、ボード選択で「WEMOS LOLIN32」が選べるようになります。
image

動作確認

image

BlinkでLチカできます。内蔵LEDは5番に割り当てられています。

OLEDをつけます。

手元にSPIのOLEDがあったので、つなげてみました。

接続は
GND --  GND
VDD --  5V
SCK --  14
SDA -- 13
RES -- 4
DC -- 21
CS -- 16

IMG_20181001_022515.jpg

ライブラリとして、

  • Adafruit_GFX.h
  • Adafruit_SSD1306.h

を使っています。

ライブラリをインストールすると出てくる、SSD1306_128x64_SPI を使い、

// using software SPI
#define OLED_MOSI  13
#define OLED_DC    21
#define OLED_CS    16
#define OLED_CLK   14
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

だけ書き換えています。

Wi-Fiにつなぐ

「スケッチの例」の「WiFi」「WiFiClientBasic」で動きました。
ssid と passwd 、を書き換えるだけで Wi-Fiに接続しました。(接続後の!client.connect(host, port)))はうまく動かないみたい)

お天気情報を取得する

openweathermap.org

image

からTokyoの天気を取得しました。

サインアップし、APIキーを取得します。

APIを使って、

http://api.openweathermap.org/data/2.5/weather?APPID=eac・・・(APIキー)・・・2e2&id=1850147&units=metric

id=1850147 は東京のコードです。都市の指定は、Tokyoとかの指定でもいいですがコードで指定するほうが無難ぽいです。
http://bulk.openweathermap.org/sample/ の city.list.json.gz に都市のリストがあります。

units=metric とすると温度が摂氏で得られます。

取得したJSONは以下のようになっていました。


{"coord":{"lon":139.69,"lat":35.69} 
  ,"weather":[
   {"id":701,"main":"Mist","description":"mist","icon":"50n"}   
  ,{"id":721,"main":"Haze","description":"haze","icon":"50n"}
  ,{"id":520,"main":"Rain","description":"light intensity shower rain","icon":"09n"}
  ],"base":"stations"
  ,"main":{"temp":25.08,"pressure":980,"humidity":83,"temp_min":24,"temp_max":26},"visibility":3500     
  ,"clouds":{"all":75},"dt":1538331240
  ,"wind":{"speed":9.8,"deg":210,"gust":24.7}
  ,"sys":{"type":1,"id":7615,"message":0.0068,"country":"JP","sunrise":1538253334,"sunset":1538295968}
  ,"id":1850147,"name":"Tokyo","cod":200   
  }";

ArduinoJsonを使い、以下のようにパースしています。

  #include <ArduinoJson.h>
   ・・・
  Serial.println(json);  # "json"にデータが入っている
  JsonObject& root = jsonBuffer.parseObject(json);
  if (!root.success()) {
    Serial.println("parseObject() failed");
  }
  const char* weather = root["weather"][0]["main"];
  const char* description = root["weather"][0]["description"];
  long pressure = root["main"]["pressure"];
  double temp = root["main"]["temp"];

["weather"][0]だけ取得していますが、先ほど示したように複数の値がやってくることがあります。とりあえずは一番最初のものだけを取得するようにしています。

お天気情報を表示する

1分ごとにフェッチして、表示するようにしてみました。

IMG_20181008_072501.jpg

簡単に、まとめたプログラムコードが以下です。

#include <ArduinoJson.h>

// 接続先のSSIDとパスワード
const char ssid[] = "hogehoge";
const char passwd[] = "hagehage";

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <HTTPClient.h>
#include <WiFi.h> // This liblaly cause error "min". Replace to "_min".

// using software SPI
#define OLED_MOSI  13
#define OLED_DC    21
#define OLED_CS    16
#define OLED_CLK   14
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

String accessHttpAPI(char host[]) {
  HTTPClient http;
  http.begin(host);
  int httpCode = http.GET();
  String result = "";
  if (httpCode < 0) {
    result = http.errorToString(httpCode);
  } else if (http.getSize() < 0) {
    result =  "size is invalid";
  } else {
    result = http.getString();
  }
  http.end();
  return result;
}

void setup()   {                
  Serial.begin(115200);
  WiFi.begin(ssid, passwd);
  Serial.print("WiFi connecting...");
    while(WiFi.status() != WL_CONNECTED) {
        Serial.print(".");
        delay(100);
    }
    Serial.print(" connected. ");
    Serial.println(WiFi.localIP());
  display.begin(SSD1306_SWITCHCAPVCC);
  display.display();
  delay(2000);
  display.clearDisplay();
}

void loop() {
  StaticJsonBuffer<1500> jsonBuffer;
  String json = accessHttpAPI("http://api.openweathermap.org/data/2.5/weather?APPID=eac・・・(APIキー)・・・2e&id=1850147&units=metric");
  Serial.println(json);
  JsonObject& root = jsonBuffer.parseObject(json);
  if (!root.success()) {
    Serial.println("parseObject() failed");
  }
  const char* weather = root["weather"][0]["main"];
  const char* description = root["weather"][0]["description"];
  long pressure = root["main"]["pressure"];
  double temp = root["main"]["temp"];
  Serial.println(weather);
  Serial.println(description);
  Serial.println(pressure);
  Serial.println(temp);

  display.setTextSize(3);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println(weather);
  display.setTextColor(BLACK, WHITE); // 'inverted' text
  display.setTextSize(1);
  display.println(description);
  display.setTextColor(WHITE);
  display.setTextSize(2);
  display.print(pressure); display.println("hPa");
  display.print("temp:");display.print(temp);
  display.display();
  delay(60000);
  display.clearDisplay(); 
}