概要
スマホの LINE から、自宅の Raspberry Pi Pico W につないだエアコン(赤外線リモコン)を操作する構成の作り方です。LINE の Webhook は公開 URL を必要としますが、家庭内の Pico W は直接受けられません。そこで Google Apps Script(GAS)を「命令の一時置き場」にして、Pico W 側から定期的に取りに行くポーリング構成にしました。この記事では中核の GAS 側コードと Pico W 側の取得処理だけを抜き出します。自作の温湿度見守り装置を外出先から操作したくて作ったものです。
なぜ直接受信ではなく「GAS中継 + ポーリング」なのか
- LINE の Webhook 送信先は HTTPS の公開エンドポイントである必要がある。Pico W にグローバル IP やポート開放を割り当てるのは家庭では手間もセキュリティリスクも大きい
- GAS の
doPost/doGetなら無料で公開 HTTPS をもらえる。「LINE → GAS(保存)」と「Pico W → GAS(取得)」の一方向通信2つに分解できる - Pico W は「取りに行く」側なので、ルーターの内側にいるままでよい
中継サーバー(GAS)側: 命令を預かって渡す
ScriptProperties を簡易 KVS として使い、doPost で命令を保存、doGet で返して消費(NONE に戻す)します。
const KEY = "AIR_CON_SIGNAL";
// LINE の Webhook。メッセージ本文から命令を決めて保存するだけ
function doPost(e) {
const event = JSON.parse(e.postData.contents).events[0];
if (event && event.type === "message" && event.message.type === "text") {
const text = event.message.text;
const props = PropertiesService.getScriptProperties();
if (text.includes("冷房")) props.setProperty(KEY, "COOL_ON");
else if (text.includes("停止")) props.setProperty(KEY, "COOL_OFF");
}
return ContentService.createTextOutput(JSON.stringify({ status: "ok" }))
.setMimeType(ContentService.MimeType.JSON);
}
// Pico W が取りに来る。返したら NONE に戻す(命令を消費する)
function doGet() {
const props = PropertiesService.getScriptProperties();
const signal = props.getProperty(KEY) || "NONE";
if (signal !== "NONE") props.setProperty(KEY, "NONE");
return ContentService.createTextOutput(JSON.stringify({ signal: signal }))
.setMimeType(ContentService.MimeType.JSON);
}
デプロイ時は「アクセスできるユーザー」を 「全員」 にします(「Google アカウントを持つ全員」ではありません)。ここが「匿名」でないと、次の GET で JSON ではなくログイン画面の HTML が返ってきます。
Pico W側: GASにポーリングし、302リダイレクトを追う
urequests は リダイレクトを自動で追いません。script.google.com/.../exec への GET は script.googleusercontent.com への 302 を返すため、Location ヘッダーを自分で取って再度 GET します。
import urequests
import json
GAS_URL = "https://script.google.com/macros/s/XXXXX/exec"
def check_gas_command():
res = urequests.get(GAS_URL)
# urequests は 302 を自動で追わないので手動でリダイレクト
if res.status_code in (301, 302, 303, 307, 308):
location = res.headers.get("location")
res.close()
if not location:
return "NONE"
res = urequests.get(location)
text = res.text
res.close()
if text.startswith("<"): # ログインHTMLが返ってきた = アクセス権の設定ミス
return "NONE"
return json.loads(text).get("signal", "NONE")
あとはメインループで check_gas_command() の戻り値(COOL_ON など)に応じて赤外線を送るだけです。
完全なソースコードと手順はブログにまとめています
GAS プロジェクトの作成・デプロイ手順、LINE Developers 側の Webhook URL 設定と応答設定、Pico W の main.py 全文(スタンドアロン運用で print を1つも入れない理由、gc.collect()、連続エラー時の machine.reset() による自動復帰)、動かないときの定番トラブルまで、以下の記事に書いています。
