13
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CoconeAdvent Calendar 2023

Day 3

M5Stackにごみの日を表示する

Last updated at Posted at 2023-12-02

はじめに

  • いつも今日は何のごみが出せるのか迷います
  • 今現在何のごみが出せるか表示するアプリを作れば良いじゃん
  • 使ってない M5Stack Core2 が自宅にころがってるのでこれを使おう
m5stack.png

ということで、コレに表示するようにしましょう。

方針

  • Googleカレンダーにゴミの日情報を登録しておきます
  • Googleカレンダーからゴミの日を取得するGoogle Apps Scriptを用意します
  • M5Stackから定期的にGoogle Apps Scriptを叩いてごみの日を画面に表示します

開発環境構築

VSCode + PlatformIO を使っている人が多そうなのでこれにします。
手順は https://kumabelog.com/m5stack-development-environment-for-vscode/ こちらを参考にさせていただきました。

  1. VSCodeのインストール
  2. VSCodeの拡張機能のC/C++のインストール
  3. VSCodeの拡張機能のPlatformIO IDE(VSCodeの再起動が必要)
  4. PlatformIOのプロジェクト作成(私の場合、BoardはM5StackCore2を選択)
  5. ライブラリを追加(私の場合はM5Core2とArduinoJsonを追加)

Googleカレンダーに登録

Googleカレンダーにごみの日を登録しておきます。
IDで識別できるようにGoogleカレンダーに登録するイベント名には先頭にIDをつけておきます。

ID ゴミ種別 イベント名 登録日
T00 ごみが出せない日
T01 可燃ごみ T01_可燃ごみ 毎週火曜日と金曜日
T02 不燃ごみ T02_不燃ごみ 第一水曜日と第三水曜日
T03 資源ごみ T03_資源ごみ 毎週月曜日
T04 ペットボトル T04_ペットボトル 第二水曜日と第四水曜日
2023-11-29_21h52_41.png

アイコンを準備

いらすとやの素材を利用させていただきました。
適当に縮小してmicroSDに保存してM5Stackに挿します。
保存場所と名前は、/images/[ID].jpgとしました。

ファイル名 画像
T00.jpg T00.jpg
T01.jpg T01.jpg
T02.jpg T02.jpg
T03.jpg T03.jpg
T04.jpg T04.jpg

Google Apps Scriptを準備

https://script.google.com/home ここから新しいプロジェクトを作ります。
gas0.png

Googleカレンダーに登録してあるごみの日を取得するGoogle Apps Scriptを準備します。
一応一週間分のイベントを取得して、先頭にIDがついているイベントだけを抽出してjsonで返すようにします。

getEvent.gs
/// 一週間のごみの日の予定を取得します。
function doGet() {
  // カレンダーID
  const calendarId = 'カレンダー登録しているGmailアドレス';
  // カレンダー
  const calendar = CalendarApp.getCalendarById(calendarId);
  // ごみの日の正規表現
  const regex = /^T\d{2}_.*$/;
  // イベント取得開始日
  const startTime = new Date();
  // イベント取得終了日
  const endTime = new Date();
  endTime.setDate(startTime.getDate() + 7);
  // イベント(ごみの日)取得
  const events = calendar.getEvents(startTime, endTime);
  
  if (events.length === 0) {
    console.log("event not found.");
  }
  else
  {
    const response = [];
    for (event in events) {
      let title = events[event].getTitle();
      let time = Utilities.formatDate(events[event].getStartTime(), "JST", "yyyy/MM/dd HH:mm:ss");;
      if (title.match(regex)) {
        response.push({time: time, title: title});
        console.log(time + ' ' + title);
      }
    }
    return ContentService.createTextOutput(JSON.stringify(response));
  }
}

動作確認できたらデプロイします。(今回は雑に全員アクセスにしてます)
gas1.png

デプロイが終わるとウェブアプリのURLが発行されるのでコピーしておきます。
gas3.png

M5Stack側のプログラムを準備

M5Stackに書き込むプログラムを準備します。
Wifi接続してウェブアプリからごみの日を取得して今日と明日の分を画面表示します。
10分置きに再取得するようにしています。

main.cpp
#include <M5Core2.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
/// @brief Api URL
const String api_url = "GoogleAppsScriptで用意したウェブアプリのURL";
/// @brief Wi-Fi SSID
const char* wifi_ssid = "WifiのSSID";
/// @brief Wi-Fi PASSWORD
const char* wifi_passwd = "Wifiのパスワード";
/// @brief Day of week
const char* week[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
// NTP設定
#define TIMEZONE_JST (3600 * 9)           // 日本標準時はUTC+9時間
#define DAYLIGHTOFFSET_JST (0)            // サマータイム時差(無し)
#define NTP_SERVER1 "ntp.nict.jp"         // NTPサーバー
#define NTP_SERVER2 "ntp.jst.mfeed.ad.jp" // NTPサーバー
/// @brief ごみの日情報
const size_t capacity = JSON_OBJECT_SIZE(3) + 2*JSON_OBJECT_SIZE(10) + 170;
StaticJsonDocument<capacity> doc;
JsonObject trash;
/// @brief ごみの日情報を取得します。
/// @param url ApiのURL
/// @return ごみの日のjson
JsonObject getTrash(String url) {
  HTTPClient http;
  const char* headers[] = {"Location"};
  http.collectHeaders(headers, 1);
  http.begin(url);
  http.addHeader("Content-Type", "application/json");
  int httpCode = http.GET();
  if (httpCode == HTTP_CODE_OK) {
    Serial.println("HTTP_CODE_OK");
    String payload = "";
    payload = http.getString();
    deserializeJson(doc, payload);
    http.end();
    Serial.println(payload);
    return doc.as<JsonObject>();
  }else if(httpCode == 302){
    Serial.println("HTTP_CODE_302");
    http.end();
    return getTrash(http.header(headers[0]));
  } else {
    http.end();
    return doc.as<JsonObject>();
  }
}
/// @brief 特定の日のごみの日を表示します。
/// @param day 日付
void displayOneDay(char* day, char* wday, int index) {
  // 日付曜日描画
  std::string str_day = day;
  std::string str_wday = wday;
  str_day = str_day.substr(5, 5) + wday;
  M5.Lcd.setTextFont(4);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setCursor(index * 160 + 12, 32);
  M5.Lcd.println(str_day.c_str());
  // 画像描画
  char image[50];
  bool enabled = false;
  for (JsonObject elem : doc.as<JsonArray>()) {
    std::string time = elem["time"];
    std::string string_title = elem["title"];
    std::string title_code = string_title.substr(0, 3);
    bool found = time.find(day) != std::string::npos;
    if (found) {
      sprintf(image, "/images/%s.jpg", title_code.c_str());
      M5.Lcd.drawJpgFile(SD, image, index * 160 + 16, 70);
      enabled = true;
    }
  }
  if (!enabled) {
    sprintf(image, "/images/%s.jpg", "T00");
    M5.Lcd.drawJpgFile(SD, image, index * 160 + 16, 70);
  }
  M5.Lcd.println("");
}
/// @brief ごみの日情報を表示します。
/// @param json ごみの日情報
void displayTrash(JsonObject json) {
  // 画面初期化
  M5.Lcd.fillScreen(WHITE);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.setTextColor(BLACK);
  M5.Lcd.setTextSize(2);
  // 現在時間表示
  struct tm t;
  if (getLocalTime(&t)) {
    // 当日
    char today[50];
    char today_wday[50];
    char tomorrow[50];
    char tomorrow_wday[50];
    sprintf(today, "%04d/%02d/%02d", (1900 + t.tm_year), (t.tm_mon + 1), t.tm_mday);
    sprintf(today_wday, "(%s)",  week[t.tm_wday]);
    // 翌日
    t.tm_mday += 1;
    if (t.tm_mday > 31) {
        t.tm_mday = 1;
        t.tm_mon += 1;
    }
    if (t.tm_mon > 11) {
      t.tm_mon = 0;
      t.tm_year += 1;
    }
    t.tm_wday = (t.tm_wday + 1) % 7;
    sprintf(tomorrow, "%04d/%02d/%02d", (1900 + t.tm_year), (t.tm_mon + 1), t.tm_mday);
    sprintf(tomorrow_wday, "(%s)",  week[t.tm_wday]);
    displayOneDay(today, today_wday, 0);
    displayOneDay(tomorrow, tomorrow_wday, 1);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setTextFont(1);
    M5.Lcd.setCursor(0, 220);
    M5.Lcd.printf("%04d/%02d/%02d %02d:%02d:%02d UPDATE", (1900 + t.tm_year), (t.tm_mon + 1), t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
  }
}
/// @brief 起動時に一回実行されます。
void setup() {
  M5.begin();
  Serial.begin(9600);
  // WiFi接続
  M5.Lcd.printf("Connecting to %s", wifi_ssid);
  WiFi.begin(wifi_ssid, wifi_passwd);
  while (WiFi.status() != WL_CONNECTED) {
    M5.Lcd.print(".");
    delay(100);
  }
  M5.Lcd.println(".");
  M5.Lcd.println("Success to connect");
  M5.Lcd.println(WiFi.localIP());
  // 時刻取得
  configTime(TIMEZONE_JST, DAYLIGHTOFFSET_JST, NTP_SERVER1, NTP_SERVER2);
}
/// @brief setup完了後に繰り返し実行されます。
void loop() {
  // ごみの日取得
  trash = getTrash(api_url);
  // ごみの日表示
  displayTrash(trash);
  // 600秒待ち
  delay(1000*600);
}

準備できたらビルドして、M5StackをUSB接続して書き込みます。

ビルドはVSCode下部のこのボタンでできます。
vscode0.png

M5Stackへの書き込みはこのボタンでできます。
vscode1.png

実行結果

今日と明日のごみの日がわかるようになりました。

m5stack2.png
13
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?