BLOG

ブログ

【ラズパイ】札幌市のごみ出しカレンダーをLINEに通知

こちらは、Mavs Advent Calendar2023の3日目の記事です!🦌

こんにちは。
3年前にラズパイを買ったものの、初期設定だけ済ませて放置していたクワジマです。
この度、飼い殺しにしていたラズパイに札幌市のごみ収集日カレンダーの情報をもとに今日が何のごみの日かをLINEに通知させるという使命を与えましたので、その方法をご紹介します。

私と同じくラズパイにホコリを被せてしまっている方、そのままゴミに出しましょう。
ではなく、この記事を参考にラスパイを有効活用してみましょう!

※なお、ラズパイの初期設定の方法については解説致しませんのでご了承ください。
OSインストール〜ブラウザが使用できるところまで完了させておけば、この後の作業がスムーズになると思います。

完成形

↓こちらがラズパイからLINEへ送信されたメッセージです。

LINEのトーク画面-今日は「燃やせるごみ(有料)」/スプレー缶類(別袋無料)」の収集日だよ!

メッセージ送信用のプログラムを、平日の朝7:00にcronで実行しています。

作業内容

  • 環境構築(ラズパイにNode.jsとmicroエディタをインストール)
  • LINE Notifyのトークンを発行 ←2025年3月31日でサービスが終了しました。
  • LINE Messaging API の利用準備 ← LINE Notify終了後の代替手段となります!
  • メッセージ送信用プログラムの作成
  • crontabの設定

という流れでやっていきます。

使用機材

Raspberry Pi 4 model B
OS:Raspbian GNU/Linux 10 (buster)

使用言語

node v18.18.0

v18からはFetch APIが導入され、デフォルトでfetch()メソッドが使用可能となったため、node-fetchなどの外部パッケージをインストールすることなくネットワークリクエストを行えます。

ところで、札幌市のごみ収集日の情報ってどうやって準備するの?

札幌市のごみ収集日の情報ですが、実は取得用のAPIが提供されておりました…!
ごみの種別と収集日カレンダーをJSONで取得できます。

どこが提供しているAPIなのか調べてみると、「札幌市ICT活用プラットフォーム DATA-SMART CITY SAPPORO」というサイトで提供されているものでした。

以下、「札幌市ICT活用プラットフォーム DATA-SMART CITY SAPPORO」からの抜粋です。(引用元:https://data.pf-sapporo.jp/about_site/

「札幌市ICT活用プラットフォーム DATA-SMART CITY SAPPORO」について

一般財団法人さっぽろ産業振興財団は、札幌圏地域データ活用推進機構(SARD)の理念と機能を継承し、市民生活の利便性向上や、新たなサービス創出による経済の活性化、行政保有データの活用が容易になることによる行政の信頼性や透明性の向上に資するため、圏域で発生し官民が保有する様々なデータ(いわゆる「官民データ」)を協調して利活用できる環境を整備し、官民がデータ利活用を促進する「データの地産地消」の実現に向け事業を推進しています。

また、今回利用するAPIリソース(データカタログ)については以下のように記載されています。

(1)データカタログ
官民データを検索したり、ダウンロードしたりすることができるカタログサービスです。ダウンロードしたデータは、付与されているライセンス条件に沿って、誰でも閲覧し、活用することができます。

とのことでした。このような取り組みがあったとは全く知りませんでした…
ありがたく活用させて頂きます。

JSONのレスポンスのサンプルは下記から確認できます。

■ごみ種別・番号対応表
https://ckan.pf-sapporo.jp/api/3/action/datastore_search?resource_id=f13f6d71-1fde-433d-b5c5-c38631fde7ca&limit=5

■札幌市家庭ごみ収集日カレンダー(2023年10月1日~2024年9月30日)
https://ckan.pf-sapporo.jp/api/3/action/datastore_search?resource_id=c1c0f835-bbaf-42d2-8ea2-a71cae8d7389&limit=5

環境構築

ラズパイにNode.jsとmicroという軽量エディタをインストールします。
Node.jsについては、apt installによる方法だと古いバージョンが入ってしまうようでしたので、公式からバイナリをダウンロードする方法で進めます。

Node.jsのインストール

まずは画面左上のターミナルアイコンをクリックして「LXTerminal」を開き、

ラズパイのデスクトップ画面-画面上部のメニューバーにあるターミナルアイコンをクリックしてLXTerminalを展開

uname -m でCPUアーキテクチャを確認しておきます。

$ uname -m
armv7l

キャプチャでは見づらいですが、armv7lと表示されています。

続いて、ラズパイのブラウザでNode.jsのダウンロードページへアクセスし、最新のLTS版をダウンロードします。
先ほど確認した「ARMv7」のリンクをコピーしてください。

nodejs.orgにてARMv7用バイナリのボタンを右クリックして「リンクのアドレスをコピー」を選択する
ラズパイのブラウザ(Chronium)の画面

ターミナルに戻り、wgetコマンドでダウンロードします。URL部分はコピーしたリンクです。
(wgetコマンドを実行したディレクトリにファイルがダウンロードされます。)

$ cd ~
$ wget https://nodejs.org/dist/v18.18.0/node-v18.18.0-linux-armv7l.tar.xz

tarでファイルを解凍します。

$ tar Jxvf node-v18.18.0-linux-armv7l.tar.xz

動作確認します。

$ ~/node-v18.18.0-linux-armv7l/bin/node -v
v18.18.0

インストールしたバージョンが返ってきたらOKです。

パスを通します。

$ echo 'export PATH=$HOME/node-v18.18.0-linux-armv7l/bin:$PATH' >> ~/.bashrc
$ source ~/.bashrc

再確認します。

$ node -v
v18.18.0

npmも確認します。

$npm -v
9.8.1

以上で完了です!

microをインストール

この後のコーディング作業やcrontabの設定に使用するエディタとしてmicroをインストールします。crontabの設定くらいであれば、デフォルトでインストールされているnanoやViでも良いかもしれませんが、コーディング時は不便な点があると思います。

microはラズパイでも軽快に動作する軽量エディタで、コピーペーストなどの一般的なショートカットの使用や「Ctrl + /」でのコメントアウトも可能です。コードシンタックスハイライトもされるなど、わりと普段通りの感覚で使用できました。
最初はVSCodeを入れましたが、ラズパイでは動作が重かったためmicroに切り替えました。

それでは、ターミナルでcurlコマンドを実行してインストールします。

$ cd ~
$ curl https://getmic.ro | bash

「Micro Installed!」と表示されてターミナルにプロンプトが返ってきたら完了です。

Micro Installed!

動作確認します。

$ ./micro -version
Version: 2.0.13

バージョンが返ってきたらOKです。

microの実行ファイルを移動してパスを通します。

$ sudo mv micro /usr/local/bin

再確認します。

$ micro -version
Version: 2.0.13

バージョンが返ってきたらOKです。

デフォルトのエディタをmicroに設定

こちらは必須の作業ではありませんが、デフォルトのエディタを切り替えておくとcrontabの編集時にもmicroが起動するようになります。

$ select-editor

を実行すると、エディタの選択画面に候補が表示されます。
/usr/local/bin/microを使用したいので、その先頭のオプション数字を入力してEnterを押してください。

エディタの選択画面に「3. /usr/local/bin/micro」と表示されているので、「3」と入力

※エディタの候補にmicroが表示されていない場合は、下記のコマンドを実行後、改めて select-editor してみてください。

$ sudo update-alternatives --install /usr/bin/editor editor /usr/local/bin/micro 1

設定できたかを確認します。

$ editor

と入力し、microが起動すれば成功です。

ラズパイでmicroエディタが起動している

起動が確認できたら「Ctrl + Q」で閉じます。

LINE Messaging APIの利用準備

ラズパイからの通知をLINEで受信するために、LINE Messaging APIを利用します。
料金については、無料枠だと200通/月まで送信可能です!
消費数のカウントは「メッセージ通数×送付人数」となる点は注意ですが、
今回は平日に自分1人へ送信するだけなので、200通で十分です。

LINE Messaging APIを利用するための手順は下記です。

  1. LINE Official Account Manager でLINE公式アカウントを作成
  2. LINE Developers でLINE Messaging API の設定

LINE Official Account Manager でLINE公式アカウントを作成する

まずLINE Official Account Managerへアクセスし、「LINEアカウントでログイン」します。
(LINEに登録しているメールアドレスとパスワードでログインできます。)

ログインできたら「作成」をクリック

フォームに必要事項を入力し、「確認」をクリック

確認画面が表示されるので、「完了」をクリック

完了したら「LINE Official Account Managerへ」をクリック

規約についての同意確認画面が2画面ほど続きます。
いずれにも「同意」をクリックします。(キャプチャはありません)

ポップアップが出るので、「次へ」をクリック

「ホーム画面に移動」をクリック

画面右上の「設定」をクリック

サイドバーの 設定 > Messaging API をクリック →「Messaging APIを利用する」をクリック

プロバイダー選択のポップアップにて、プロバイダーを作成します。
→プロバイダー名を入力し、「同意する」をクリック

空欄でも問題ないので「OK」をクリック

アカウント名とプロバイダー名の対応を確認して「OK」をクリック

LINE Developers でLINE Messaging API を設定する

LINE Developers」のリンクをクリック

LINE Developers のコンソールにログインします

先ほど作成したプロバイダーの「Admin」をクリック

パネルをクリック

「Basic settings」の最下部までスクロール
※ 言語はヘッダー内の地球儀アイコンから変更可能です。
日本語へ変更した場合は「チャネル基本設定」というタブになります。

ユーザーIDをコピーして控えておきます。

続いて「Messaging API」のタブをクリックし、画面最下部へスクロールします。

チャネルアクセストークンを発行するため、「issue」をクリック

トークンが発行されたら、こちらもコピーして控えておきます。

以上で準備完了です!

メッセージ送信用のプログラムの作成

プロジェクト作成

任意のディレクトリへ移動し、好きな名前でプロジェクトを作成します。

$ cd ~
$ mkdir project_name
$ cd project_name
$ touch index.js

あとはmicroでindex.jsにコードを書いていきましょう!

$ micro index.js

コードを書くにあたって必要な情報

この後のセクションで私が書いたソースコードも貼りますが、ご自身でコードを書きたい方は以下の情報が必要になると思うので参考にしてください。

1. APIリソース

https://ckan.pf-sapporo.jp/dataset/garbage_collection_calendarへアクセスします。

探索 > プレビューをクリックします。

データとリソースのセクションにある「ごみ種別・番号対応表」の探索ボタンと「札幌市ごみ収集日カレンダー(2023年10月1~2024年9月30日)」の探索ボタン

画面の右上の「データAPI」をクリックします。

データAPIボタン

モーダルに”クエリ例(最初の5件)”とあります。

クエリ例(最初の5件)-https://ckan.pf-sapporo.jp/api/3/action/datastore_search?resource_id=c1c0f835-bbaf-42d2-8ea2-a71cae8d7389&limit=5

URLのクエリパラメーターのlimitを省略した場合はデフォルトで100件まで返ってきます。ごみ収集日カレンダーを1年分取得するにはlimit=366としてください。(2024年は閏年のため)

2. Messaging APIリファレンス

今回は任意のタイミング(平日の毎朝7:00)にプッシュ通知したいので、
こちらのAPIの使用方法を確認します。

ソースコード

私が書いたソースコードです。
コピペする場合は、
targetArea(収集区域名)、CHANNEL_ACCESS_TOKENACCOUNT_USER_IDの値は適宜変更してください。
ソースコード冒頭のコメントには、今回ブログに掲載するにあたって、利用したAPIのライセンス条件に沿って提供元のクレジットを記載しました。

/*
このプログラムで使用するデータセットは、
クリエイティブ・コモンズ・ライセンス(表示 4.0 国際)に従って提供されています。

クリエイティブ・コモンズ・ライセンス(表示 4.0 国際)の要約:
- 表示 (Attribution): 提供元を適切に表示すること
ライセンスの詳細については、以下のリンクを参照してください:
https://creativecommons.org/licenses/by/4.0/deed.ja

データセットのタイトル:
「ごみ種別・番号対応表」
「札幌市家庭ごみ収集日カレンダー(2023年10月1日~2024年9月30日)」

データセットの提供元:
一般財団法人さっぽろ産業振興財団
ホーム
*/

const API_COMMON_URL = "https://ckan.pf-sapporo.jp/api/3/action/datastore_search";

// 「ごみ種別・番号対応表」取得用URL
const GARBAGE_CATEGORIES_URL = `${API_COMMON_URL}?resource_id=f13f6d71-1fde-433d-b5c5-c38631fde7ca`;

// 「札幌市家庭ごみ収集日カレンダー(2023年10月1日~2024年9月30日)」取得用URL
const GARBAGE_COLLECTION_URL = `${API_COMMON_URL}?resource_id=c1c0f835-bbaf-42d2-8ea2-a71cae8d7389&limit=366`;

// 収集区域名
const targetArea = "XX区③";

// LINE Messaging API エンドポイント
const LINE_MESSAGING_API_URL = "https://api.line.me/v2/bot/message/push";

// LINE Messaging API へのリクエストに乗せるトークン
const CHANNEL_ACCESS_TOKEN = "発行したチャネルアクセストークン";

// LINE公式アカウントのUserID
const ACCOUNT_USER_ID = "作成したLINE公式アカウントのユーザーID"

/**
 * 現在の日付と時刻を "2023-10-01T00:00:00" 形式の文字列で返す関数
 * @returns {string} - "2023-10-01T00:00:00" 形式の日付文字列
 */
function getCurrentDate() {
  const today = new Date();
  const timeSuffix = "T00:00:00";
  const options = { year: "numeric", month: "2-digit", day: "2-digit" };
  const formattedDate = today
    .toLocaleDateString("ja-JP", options)
    .replaceAll("/", "-");
  return formattedDate + timeSuffix;
}

/**
 * 指定されたURLからJSONデータを取得する関数
 * @param {string} url - JSONデータを取得するURL
 * @returns {Array} - 取得したJSONデータのrecordsプロパティ
 * @throws {Error} - 失敗時はErrorオブジェクトをスロー
 */
async function fetchJSON(url) {
  try {
    const response = await fetch(url);
    const data = await response.json();
    return data.result.records;
  } catch (error) {
    throw new Error(`${url} からのJSON取得に失敗しました。 ${error.message}`);
  }
}

/**
 * 当日に収集されるごみの種別名称を取得する関数
 * @returns {string} - 当日に収集されるごみの種別名称
 */
async function getGarbageCategory() {
  const currentDate = getCurrentDate();
  const [garbageCategories, annualGarbageCollection] = await Promise.all([
    fetchJSON(GARBAGE_CATEGORIES_URL),
    fetchJSON(GARBAGE_COLLECTION_URL),
  ]);

  const todayData = annualGarbageCollection.find(
    (data) => data.日付 === currentDate
  );

  // 今日の日付(プログラム実行日)が収集日カレンダーに無い
  if (!todayData) {
    return "古いAPIを使い続ける君というごみ";
  }

  // 住んでいる区域のごみ種別
  const garbageCategorySymbol = todayData[targetArea];

  // 土日や年末年始はnull
  if (garbageCategorySymbol === null) {
    return "土日や年末年始にまで僕を働かせる君というごみ";
  }

  // 収集なし
  if (garbageCategorySymbol === 0) {
    return "なし";
  }

  const garbageCategory = garbageCategories.find(
    (category) => category.記号 === garbageCategorySymbol.toString()
  );

  return garbageCategory ? garbageCategory.ごみ種 : "!不明なごみ種別!";
}

/**
 * 送信するメッセージを生成する関数
 * @returns {string} - 送信用のテキストメッセージ
 */
async function generateMessage() {
  const garbageCategory = await getGarbageCategory();
  return garbageCategory === "なし"
    ? "今日はごみの収集はないよ!"
    : `今日は「${garbageCategory}」の収集日だよ!`;
}

/**
 * メッセージを送信する関数
 * @param {string} message - 送信するメッセージテキスト
 * @returns {Object} - 送信結果のHTTPステータスコード
 * @throws {Error} - 失敗時はErrorオブジェクトをスロー
 */
async function sendMessage(message) {
  const requestOptions = {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${CHANNEL_ACCESS_TOKEN}`,
    },
    body: JSON.stringify({
      to: `${ACCOUNT_USER_ID}`,
      messages: [
        {
          type: "text",
          text: `${message}`
        }
      ]
    })
  };

  try {
    const response = await fetch(LINE_MESSAGING_API_URL, requestOptions);
    return { status: response.status };
  } catch (error) {
    throw new Error(`メッセージの送信に失敗しました: ${error.message}`);
  }
}

/**
 * メインの実行関数
 */
async function main() {
  try {
    const message = await generateMessage();
    const result = await sendMessage(message);
    console.log(`HTTP ステータスコード: ${result.status}`);
  } catch (error) {
    console.error(error.message);
  }
}

main();

作者のこだわりポイント

土日や年末年始など、ごみ種別がnullの日にプログラムを実行するとラズパイの口が悪くなります。
また、収集日カレンダーのエンドポイントURLは年度ごとに resource_id= 以降が変わるので、過去の年度のカレンダーを参照した場合にも不必要に厳しい言葉で知らせてくれます。

作者の手抜きポイント

Gitに上げないので、ユーザーIDやチャネルアクセストークンはファイル内にベタ書きしてます。

動作確認

index.jsを配置しているディレクトリで、

$ node index.js

を実行して、LINEに通知が届けばOKです。

crontab設定

それでは、作成したプログラムを定期実行できるようにスケジューリングします。

予めnodeコマンドのフルパスを表示してコピーしておきます。

$ which node
/home/ユーザー名/node-v18.18.0-linux-armv7l/bin/node

crontabを編集します。

$ crontab -e

平日の朝7:00にindex.jsを実行する場合は、

# m h  dom mon dow   command
0 7 * * 1-5 /home/ユーザー名/node-v18.18.0-linux-armv7l/bin/node project_name/index.js

とします。
/home/ユーザー名/node-v18.18.0-linux-armv7l/bin/node“の部分には、先ほどコピーしたnodeコマンドのフルパスを貼り付けます。

cronの設定項目は左から

分 時 日 月 曜日 実行したいコマンド

となっています。

0から59で指定
0から23で指定
1から31で指定
1から12で指定
曜日0から7で指定 (0と7は日曜、1は月曜、6は土曜)

※月曜から金曜のように範囲指定する場合は”開始-終了”のようにハイフンで繋ぎます。

crontabをmicroで編集している場合は「Ctrl + S」で保存して「Ctrl + Q」で閉じます。
ターミナルも閉じてしまって大丈夫です。

以上で完了です!あとは朝を待つのみです(ラズパイの電源を切ってはいけません

翌日 AM 7:00

ちゃんと届きました!

LINEのトーク画面-今日は「燃やせないごみ(有料)」/加熱式たばこ・ライター(別袋無料)」の収集日だよ!

しかし、あまり家に溜まらないタイプのごみだったので無視しました。

さいごに

ラズパイが吹けば冷蔵庫屋が儲かる。

もう冷蔵庫にごみ収集日カレンダーを貼っておく必要はなくなりました。
これまで、オシャレな冷蔵庫が欲しいけど生活感のあるポスターでマスクされるから…と購入を控えていた消費者層の枷が取り外されることで、冷蔵庫の売上が2台くらい伸びそうです。

まだまだありそう。API活用の幅

札幌市ICT活用プラットフォーム DATA-SMART CITY SAPPOROでは、ごみ収集日カレンダーの他にも、札幌市の文化財一覧APIなど面白そうなものが提供されているので、またの機会に活用してみたいと思います。

  • この記事を書いた人
  • 最新の記事

Norihisa Kiwajima

営業職から転職したエンジニア。エンジニア転職のために無職となり、自宅に引きこもって独学していた半年間で、退職の餞別で貰ったうまい棒を500本食べて太る。