LoginSignup
1
0

More than 1 year has passed since last update.

Google Calendarから予定を取得してESP32のLCDに表示する

Last updated at Posted at 2021-10-31

前回の投稿 Google Photosからランダムな1枚の画像を取得できるようにする にて、Google Photosに登録しておいた画像をランダムに表示されるフォトフレームを作りました。
今回は、少し拡張して、Google Calendarに登録しておいたイベントも合わせて表示するようにします。

Google PhotosのAPIの呼び出しの時には、REST呼び出しでしたが、Google CalendarはNode.js用のライブラリが用意されているのでそれを使います。
ですが、Googleアカウントログイン部分は、REST呼び出しのものを流用したいので、REST呼び出しで認証したGoogleアカウントの認証結果(アクセストークン)をGoogle Calendarライブラリに引き継ぐようにします。

ソースコードもろもろは、以下に上書きしています。

poruruba/GooglePhotosGallery

Google Calendar APIを利用できるようにする

Google Cloud Platformのコンソールから、Google Calendar APIを使えるように有効化します。

GCP:APIとライブラリ
https://console.cloud.google.com/apis/library

image.png

検索のところに、Calendarと入力すると、Google Calendar APIが出てきますので、選択してEnableにします。

認証結果をGoogleライブラリに引き継ぐ

以前の投稿 GoogleAPIライブラリを使わずにGoogleアカウントでログインできるようにする で認証結果(アクセストークン・IDトークン・リフレッシュトークン)をファイル出力していました。
それを使います。

node.js/api/controllers/googlecalendar-api/index.js
function get_calendar(token){
  const oAuth2Client = new google.auth.OAuth2(CLIENT_ID);
  oAuth2Client.setCredentials(token);
  return google.calendar({ version: 'v3', auth: oAuth2Client });
}

上記のtokenには、access_tokenがあれば大丈夫です。
ただし、このトークンを取得したときのGoogleアカウント認証時のScopeに、以下が含まれている必要があります。

https://www.googleapis.com/auth/calendar.readonly

CLIENT_IDは、認証時に利用していたクライアントIDを指定してください。

あとは、以下のように呼び出すだけです。

node.js/api/controllers/googlecalendar-api/index.js
  var result = await new Promise((resolve, reject) => {
    calendar.events.list(params, (err, res) => {
      if (err)
        return reject(err);
      resolve(res);
    });
  });

paramsには、以下で示されるパラメータを指定します。

取得結果には、nextPageTokenが含まれている場合があります。その場合には、すべてのイベントを取得しきれていないので、pageTokenにnextPageTokenを指定して、続けて呼び出す必要があります。

結局、こんな感じです。

node.js/api/controllers/googlecalendar-api/index.js
async function get_event_list(calendar, date){
  var startTime = date.toISOString();
  var endDate = new Date(date.getTime());
  endDate.setHours(23);
  endDate.setMinutes(59);
  endDate.setSeconds(59);
  endDate.setMilliseconds(999);
  var endTime = endDate.toISOString();

  var params = {
    calendarId: 'primary',
    timeMin: startTime,
    timeMax: endTime,
    singleEvents: true,
    orderBy: 'startTime',
  };
  var result = await new Promise((resolve, reject) => {
    calendar.events.list(params, (err, res) => {
      if (err)
        return reject(err);
      resolve(res);
    });
  });
  var items = result.data.items || [];
  while (result.nextPageToken) {
    params.pageToken = result.nextPageToken;
    result = await new Promise((resolve, reject) => {
      calendar.events.list(params, (err, res) => {
        if (err)
          return reject(err);
        resolve(res);
      });
    });
    items = items.concat(result.data.items);
  }

  var list = [];
  for (const item of items) {
    var term = convert_date(item.start, item.end);
    list.push({
      summary: item.summary,
      term: term
    });
  }

  return list;
}

イベントの変更通知を受け取る

Google Calendarのイベントは、他のGoogleアプリから編集されるため、イベントリスト取得後にも変更されている場合があります。
そこで、変更されたことをNotificationとして通知してくれる機能があります。以下のように登録します。

(参考)
https://developers.google.com/calendar/api/guides/push?hl=ja

node.js/api/controllers/googlecalendar-api/index.js
const CALENDAR_WEBHOOK_URL = 'https://【Node.jsサーバのURL】/googlecalendar-webhooks';
・・・
      var params = {
        id: uuidv4(),
        type: "web_hook",
        address: CALENDAR_WEBHOOK_URL
      };
      var result = await do_post_with_token('https://www.googleapis.com/calendar/v3/calendars/primary/events/watch', params, token.access_token); 

URLのところで「primary」に指定することで、ログインユーザのプライマリカレンダーが変更検知対象になります。
idには、毎回異なる値を指定します。今回はuuidにしました。
addressには、通知を受けたいWebAPIのURLを指定します。

戻り値はJsonです。戻り値のresultには、idとresourceIdが含まれており、登録解除時に必要ですので、ファイルに保存しておきます。

解除は以下の通りです。

node.js/api/controllers/googlecalendar-api/index.js
      var params = {
        id: json.notification.id,
        resourceId: json.notification.resourceId
      };
      var result = await do_post_text_with_token('https://www.googleapis.com/calendar/v3/channels/stop', params, token.access_token);
      console.log(result);

戻り値は、Jsonではなくテキストです。

以下は、通知を受けた時の処理の例です。

node.js/api/controllers/googlecalendar-api/index.js
    case '/googlecalendar-webhooks': {
      console.log(event);
      if( event.headers['x-goog-resource-state'] == 'exists'){
        var token = await read_token();
        const calendar = get_calendar(token);
        var list = await read_event_list(calendar, true);

        client.publish(TOPIC_CMD, "1");
      }
      return new Response({});
    }

登録したURLに届く通知は、Google Calendarのイベントの変更時以外にも、いくつかのタイミングで届きます。HTTPヘッダー「x-goog-resource-state」がexistsの時が、イベント変更時の通知です。

上記の例では、イベントリストを更新したのち、MQTTにパブリッシュして他のデバイス(今回の場合は、LCD付のESP32がSubscribeしている)に通知しています。

ついでにGoogle Tasks(toDo)のタスクリストも取得してみます

こちらも、Googleライブラリ化されていますので、それを使います。
毎度の通り、以下で、Tasks APIを有効にします。

GCP:APIとライブラリ
https://console.cloud.google.com/apis/library

また、Googleアカウント認証時には、以下をScopeに含めます。

https://www.googleapis.com/auth/tasks.readonly

あとは、以下のようにtasksのインスタンスを生成して、呼び出すだけです。

node.js/api/controllers/googlecalendar-api/index.js
    case '/googletasks-list': {
      var token = await read_token();
      const tasks = get_tasks(token);
      var list = await get_tasklist_list(tasks);
      console.log(list);
      for( var i = 0 ; i < list.length ; i++ ){
        list[i].list = await get_task_list(tasks, list[i].id);
      }
      return new Response({ list: list });
    }
・・・
function get_tasks(token) {
  const oAuth2Client = new google.auth.OAuth2(CLIENT_ID);
  oAuth2Client.setCredentials(token);
  return google.tasks({ version: 'v1', auth: oAuth2Client });
}

async function get_tasklist_list(tasks){
  var params = {};
  var result = await new Promise((resolve, reject) => {
    tasks.tasklists.list(params, (err, res) => {
      if (err)
        return reject(err);
      resolve(res);
    });
  });
  var items = result.data.items;
  while (result.nextPageToken) {
    params.pageToken = result.nextPageToken;
    result = await new Promise((resolve, reject) => {
      tasks.tasklists.list(params, (err, res) => {
        if (err)
          return reject(err);
        resolve(res);
      });
    });
    items = items.concat(result.data.items);
  }

  var list = [];
  for (const item of items) {
    list.push({
      title: item.title,
      id: item.id
    });
  }

  return list;
}

async function get_task_list(tasks, id) {
  var params = {
    tasklist: id
  };
  var result = await new Promise((resolve, reject) => {
    tasks.tasks.list(params, (err, res) => {
      if (err)
        return reject(err);
      resolve(res);
    });
  });
  var items = result.data.items || [];
  while (result.nextPageToken) {
    params.pageToken = result.nextPageToken;
    result = await new Promise((resolve, reject) => {
      tasks.tasks.list(params, (err, res) => {
        if (err)
          return reject(err);
        resolve(res);
      });
    });
    items = items.concat(result.data.items);
  }

  var list = [];
  for (const item of items) {
    list.push({
      title: item.title,
      notes: item.notes,
      id: item.id,
      parent: item.parent || null,
      due: item.due ? new Date(item.due).getTime() : null
    });
  }

  return list;
}

流れとしては、タスクリストというタスクを束ねたもののリストを取得したのち、タスクリストごとに含まれるタスクのリストを取得する、という流れです。

流れさえわかれば、おおよそ理解できると思いますので、詳しくは、GitHubのソースコード参照してください。

poruruba/GooglePhotosGallery
 https://github.com/poruruba/GooglePhotosGallery

LCD付ESP32の実装

Google Calendarのイベントの取得は、以下のURLに対してJSON-POST呼び出ししているだけです。

https://【Node.jsサーバのホスト名】/googlecalendar-list

一方で、イベントの変更検知するために、MQTTでトピック「calendar/notify」でSubscribeして待ち受けています。通知を受けたら、Google Calendarイベントの再取得を含むLCD再描画処理を実行しています。

詳しくは、GitHubのソースコード参照してください。

poruruba/GooglePhotosGallery
 https://github.com/poruruba/GooglePhotosGallery

以上

1
0
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
1
0