- はじめに:IoTスマートリモコンの総仕上げ!ラズパイPico WとLINEを連携させよう
- はじめに:スマホのLINEからエアコンを遠隔操作する
- Pico Wを外から操作できない理由と、GASを使った中継のしくみ
- 中継サーバーの構築:Google Apps Scriptの作成とデプロイ
- LINE BotとGASを紐付ける:Webhook URLと応答設定
- Pico Wに最終プログラム(main.py)を書き込む
- スマホからエアコンを遠隔操作するテスト
- うまく動かないときの定番トラブルと解決策
- PC不要にする:Pico Wにmain.pyを保存して独立稼働させる
- おわりに:ハードからクラウドまで一気通貫で作った自作IoT
- よくある質問(FAQ)
- 自作スマートリモコンシリーズ(全5回)
- シリーズ記事一覧
はじめに:IoTスマートリモコンの総仕上げ!ラズパイPico WとLINEを連携させよう
自作スマートリモコン 第5回
はじめに:スマホのLINEからエアコンを遠隔操作する
こんにちは、ニンジンです🥕
シリーズ最終回のこの記事では、これまで作ってきた「温湿度を測ってLINEに通知するPico W」を、スマホのLINEから『冷房ON』と送るとエアコンが動くスマートリモコンに仕上げます。カギになるのは、自宅ルーターのファイアウォールを越えて外から命令を届けるしくみです。無料のGoogle Apps Script(GAS)を中継サーバーにして、Webhookとポーリングで疑似的な双方向通信を実現します。
前提として、第1回〜第4回までが終わっている必要があります。特に第4回で取得した「エアコンの赤外線データ(数字のリスト)」を手元に用意してください。
この記事で分かること
- Pico Wを外から操作できない理由(ルーターのファイアウォール)とGAS中継の考え方
- Google Apps Scriptの作成・デプロイ手順(ウェブアプリ/アクセス=全員)
- doPost(LINE受信)とdoGet(Pico Wからのポーリング)のGASコード
- LINE DevelopersのWebhook URL設定と応答設定
- スタンドアロン運用でフリーズを防ぐため、本番main.pyからprint文を消す理由
Pico Wを外から操作できない理由と、GASを使った中継のしくみ
現状、Pico WからLINEへ「暑いよ」と通知を送ることはできています。しかし逆に、スマホのLINEから「冷房をつけて」とPico Wへ直接命令を送ることは簡単にはできません。自宅のWi-Fiルーターがファイアウォールで、外部からの見知らぬアクセスをすべて遮断するためです。
VPNやWebサーバーを立てる方法もありますが手間がかかります。そこで、無料で使えるGoogle Apps Script(GAS)を「インターネット上の私書箱(中継サーバー)」として使い、疑似的な双方向通信を実現します。
命令が届くまでの流れ
- スマホからLINEに「冷房ON」と送る
- LINEのサーバーが、私書箱のGASへ「冷房ONの命令が来た」とメモを残す(Webhook)
- 家の中のPico Wが10秒ごとにGASを見に行き(ポーリング)、命令があれば赤外線を発射する
このバケツリレー方式なら、ルーターの設定を一切変えずに、安全に遠隔操作ができます。
中継サーバーの構築:Google Apps Scriptの作成とデプロイ
1. GASプロジェクトを作る
Googleドライブ にアクセスします。
// スクリプトプロパティ(GASの簡易データベース)を使って状態を管理します
const PROPERTY_KEY = 'AIR_CON_SIGNAL';
// 1. LINEからメッセージを受け取る関数 (Webhook)
function doPost(e) {
try {
const jsonString = e.postData.contents;
const data = JSON.parse(jsonString);
// LINEからのイベントを取得
const event = data.events[0];
if (event && event.type === 'message' && event.message.type === 'text') {
const userMessage = event.message.text;
const properties = PropertiesService.getScriptProperties();
// 送られてきたメッセージの内容によって、保存する命令(ステータス)を変える
if (userMessage.includes("冷房")) {
properties.setProperty(PROPERTY_KEY, "COOL_ON");
} else if (userMessage.includes("停止")) {
properties.setProperty(PROPERTY_KEY, "COOL_OFF");
} else if (userMessage.includes("現在の温度は")) {
properties.setProperty(PROPERTY_KEY, "GET_TEMP");
}
}
return ContentService.createTextOutput(JSON.stringify({status: 'post_success'}))
.setMimeType(ContentService.MimeType.JSON);
} catch(error) {
return ContentService.createTextOutput(JSON.stringify({status: 'error', message: error.toString()}))
.setMimeType(ContentService.MimeType.JSON);
}
}
// 2. Pico Wから「命令ある?」と確認しにくる関数
function doGet(e) {
const properties = PropertiesService.getScriptProperties();
const currentSignal = properties.getProperty(PROPERTY_KEY) || "NONE";
// Pico Wに現在のステータスを返す
const responseData = { signal: currentSignal };
// 何かしらの命令(NONE以外)があったら、確認されたのでリセット(消去)する
if (currentSignal !== "NONE") {
properties.setProperty(PROPERTY_KEY, "NONE");
}
return ContentService.createTextOutput(JSON.stringify(responseData))
.setMimeType(ContentService.MimeType.JSON);
}doPost がLINEからのメッセージを受け取り、内容(冷房・停止・現在の温度は)に応じて命令をスクリプトプロパティに保存します。doGet はPico Wが取りに来たときに現在の命令を返し、返したらリセットします。
2. GASをデプロイ(ウェブアプリとして公開)
「次のユーザーとして実行:自分」「アクセスできるユーザー:全員」に設定して「デプロイ」を押します。「全員」でないとPico WやLINEがアクセスできません(「Googleアカウントを持つ全員」ではない点に注意)。
LINE BotとGASを紐付ける:Webhook URLと応答設定
3. LINE DevelopersでWebhook URLを設定
LINE Developers のコンソールにログインします。
4. LINE Official Account Managerで応答設定
LINE Official Account Manager にログインし、「温湿度自動アラート」をクリックします。
Pico Wに最終プログラム(main.py)を書き込む
いよいよ大詰めです。下のコードを貼り付け、上部の設定(Wi-Fi情報・LINEトークン・USER_ID・取得したGASのURL)を自分の値に書き換えます。さらに第4回で取得したエアコンの赤外線データを COOL_ON_DATA / COOL_OFF_DATA に貼り付けます。
#main.py
import machine
import dht
import network
import time
import urequests
import json
import gc
# 起動時のパーツ立ち上がりを待つ(超重要!)
time.sleep(3)
# --- 1. ご自身の環境に書き換えてください ---
WIFI_SSID = "あなたのWi-Fi_SSID"
WIFI_PASSWORD = "あなたのWi-Fi_パスワード"
LINE_TOKEN = "あなたのLINE_チャンネルアクセストークン"
USER_ID = "あなたのLINE_ユーザーID"
# 🔴 先ほど取得したGASのウェブアプリURL
GAS_URL = "https://script.google.com/macros/s/XXXXX/exec"
# --- 2. 見守りとピンの設定 ---
ALERT_TEMP = 28 # 警告温度
CHECK_INTERVAL = 10 # チェック間隔(秒)
TX_PIN = 17 # 赤外線LEDピン
# --- 3. エアコン信号データ(※前回取得した数字のリストに書き換えてください) ---
COOL_ON_DATA = [4307, 4535, 480, 1667, ...]
COOL_OFF_DATA = [4368, 4507, 476, 1605, ...]
sensor = dht.DHT11(machine.Pin(15))
wlan = network.WLAN(network.STA_IF)
def connect_wifi():
wlan.active(True)
if not wlan.isconnected():
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
timeout = 15
while not wlan.isconnected() and timeout > 0:
time.sleep(1)
timeout -= 1
if not wlan.isconnected():
machine.reset()
def send_ir(data):
pwm = machine.PWM(machine.Pin(TX_PIN))
pwm.freq(38000)
def pwm_on(): pwm.duty_u16(21845)
def pwm_off(): pwm.duty_u16(0)
is_on = True
for us in data:
if is_on: pwm_on()
else: pwm_off()
time.sleep_us(us)
is_on = not is_on
pwm_off()
pwm.deinit()
def send_line(message):
response = None
try:
connect_wifi()
url = "https://api.line.me/v2/bot/message/push"
headers = {"Content-Type": "application/json; charset=UTF-8", "Authorization": f"Bearer {LINE_TOKEN}"}
payload = {"to": USER_ID, "messages": [{"type": "text", "text": message}]}
response = urequests.post(url, data=json.dumps(payload).encode('utf-8'), headers=headers)
except Exception:
pass
finally:
if response:
try: response.close()
except: pass
def check_gas_command():
response = None
try:
connect_wifi()
response = urequests.get(GAS_URL)
if response.status_code in [301, 302, 303, 307, 308]:
redirect_url = response.headers.get('location') if hasattr(response, 'headers') else None
response.close()
response = None
if redirect_url: response = urequests.get(redirect_url)
else: return "NONE"
text_data = response.text
if text_data.startswith('<'): return "NONE"
return json.loads(text_data).get("signal", "NONE")
except Exception:
return "NONE"
finally:
if response:
try: response.close()
except: pass
# --- メイン処理 ---
send_line("遠隔エアコン操作システムが起動しました!監視と命令待ちを開始します。")
alert_sent = False
error_count = 0
while True:
try:
sensor.measure()
temp = sensor.temperature()
humidity = sensor.humidity()
if temp >= ALERT_TEMP:
if not alert_sent:
send_line(f"【警告】室温が{temp}°Cになりました!エアコンを確認してください。(湿度: {humidity}%)")
alert_sent = True
else:
if alert_sent:
send_line(f"室温が{temp}°Cに下がりました。安全圏内です。")
alert_sent = False
cmd = check_gas_command()
if cmd == "COOL_ON":
send_ir(COOL_ON_DATA)
send_line(f"エアコンに冷房ON信号を送信しました。(現在の室温: {temp}°C)")
elif cmd == "COOL_OFF":
send_ir(COOL_OFF_DATA)
send_line(f"冷房OFFの信号を発信しました。(現在の室温: {temp}°C)")
elif cmd == "GET_TEMP":
send_line(f"現在のお部屋の状況です。\n温度: {temp}°C\n湿度: {humidity}%")
error_count = 0
except Exception:
error_count += 1
time.sleep(5)
if error_count >= 5: machine.reset() # 連続エラー時はシステム再起動
finally:
gc.collect() # メモリ解放
time.sleep(CHECK_INTERVAL)check_gas_command() がGASにポーリングし、返ってきた命令(COOL_ON / COOL_OFF / GET_TEMP)に応じて send_ir() で赤外線を発射します。script.google.comは302リダイレクトを返すことがあるため、リダイレクト先を追う処理も入れてあります。
なぜ本番のmain.pyには「print文」が1つも無いのか
テスト用コードには print("冷房ONを検知") のような出力がたくさんありましたが、本番の main.py ではあえて全部消しています。これはスタンドアロン運用でのフリーズを防ぐためです。
PC(Thonny)につないでいる間は、print の出力をPCが受け取ってくれます。しかしPCから抜いてコンセント給電にすると、出力の受け取り先がいなくなります。受け取り先が無いまま print し続けると、Pico W内部のバッファに送信待ちのテキストが溜まり続け、やがてメモリを圧迫してシステムごと停止します。
print を無くし、except は pass で流し、gc.collect() でこまめにメモリを解放します。連続エラー時は machine.reset() で自動再起動させることで、長期間ほったらかしでも安定して動きます。スマホからエアコンを遠隔操作するテスト
まずThonnyの実行ボタンでエラーが出ないか確認します。LINEに起動メッセージが届いたら、スマホから「冷房ON」「停止」「現在の温度は」と送ってみます。
うまく動かないときの定番トラブルと解決策
Q1LINEから「冷房」と送ってもPico Wが反応しない
A原因の大半はWebhook URLの更新忘れです。GASのコードを少しでも変えたら「新しいデプロイ」でURLを再発行し、Pico Wの GAS_URL とLINE DevelopersのWebhook URLの両方を貼り直します。URLを据え置きたい場合は「デプロイの管理」から既存デプロイを編集します。
Q2Thonnyに「HTMLを受信」「JSONエラー」と出る
APico WがGASにアクセスしたとき、JSONではなくGoogleのログイン画面(HTML)が返っています。GASのデプロイ設定で「アクセスできるユーザー」が「全員」になっているか確認してください(「Googleアカウントを持つ全員」ではありません)。
Q3SyntaxError: invalid syntax で止まる
APythonはインデント(行頭の空白)でブロックを判断します。コピペで if / elif の縦位置がずれた、行末の : が消えた、が典型例です。エラー行の周辺の空白と記号を見直してください。
よくある質問(FAQ)
QGASのウェブアプリは無料ですか?制限はありますか?
A無料です。ただしGASには1日あたりの実行回数やUrlFetchの上限(クォータ)があります。10秒間隔のポーリングとたまの操作程度なら通常は上限に達しません。反応が不要な夜間はPico W側で間隔を延ばすと余裕が出ます。
Qポーリング間隔(CHECK_INTERVAL)は変えられますか?
Aコード上部のCHECK_INTERVALで変更できます。短くするとLINEを送ってからエアコンが動くまでが速くなりますが、GASの1日あたり実行回数の上限に近づきます。10〜15秒が実用的なバランスです。
Q「アクセスできるユーザー:全員」はセキュリティ的に問題ないですか?
AウェブアプリのURLは推測が困難な長い文字列です。doGetは現在の命令を返すだけなので大きなリスクは小さいですが、気になる場合はdoPostでLINEの署名(X-Line-Signature)を検証する処理を追加してください。
QGASのコードを修正したら必ずやることは何ですか?
A「新しいデプロイ」でURLを再発行し、そのURLをPico WのGAS_URLとLINE DevelopersのWebhook URLの両方に貼り替えます。URLを変えたくない場合は「デプロイの管理」から既存デプロイを編集して上書きします。
QLINEの通知は来るのに、エアコンが反応しません。
ACOOL_ON_DATA / COOL_OFF_DATA が第4回で取得した実データに置き換わっているか、赤外線LEDの配線(TX_PIN=GP17)と向き、給電が足りているか(USB給電より2A以上のACアダプタ)を確認してください。
Q数時間でPico Wがフリーズします。
Aprint文が残っていないか、exceptをpassで流しているか、gc.collect()が入っているかを確認してください。本記事のmain.pyはこれらの対策を最初から入れてあります。
自作スマートリモコンシリーズ(全5回)
- ラズパイPico WとDHT11で温湿度を測定する方法|配線図とMicroPythonコード
- LINE Messaging APIの始め方|チャネル作成とアクセストークン・ユーザーIDの取得手順
- Pico WからLINEへ通知を送るMicroPythonコード|urequestsでプッシュ送信・日本語エラー400対策
- ラズパイPico Wで赤外線リモコンを自作|受信・送信回路と38kHz PWM制御
- GASを中継サーバーにLINEからPico Wを遠隔操作する方法|Webhookとポーリングで双方向通信(この記事)
Follow me on X
本ブログの中の人「ニンジン🥕」はXやってます。新着記事はいち早くポストでお知らせしているので、見逃したくない方は @ninjin_py_vba をフォローしてみてください🥕
@ninjin_py_vba をフォローシリーズ記事一覧
電子工作・自動化ツールの製作記録をシリーズごとにまとめています。気になるテーマからお読みください。
-
FILE.01 — IoT
Raspberry Pi Pico W × GASラズパイPico W×GASで自作!「LINEで動く温湿度&スマートリモコン」完全ロードマップ
「外出中のペットの室温が心配…」そんな思いからスタートしたIoT自作プロジェクトの総集編。温湿度監視からエアコン遠隔操作まで、仕組みをゼロから作りたい方のための全5回の開発記録です。
シリーズを読む -
FILE.02 — CNC
Arduino × レーザー刻印CNCCNC自作シリーズ
高精度な加工を目指し、本格的な自作CNC製作に挑戦中。現在は基幹パーツであるオリエンタルモーターの納品を待つ「設計・準備編」を公開。ハードとソフトの両面から、理想のマシンを形にする過程をリアルタイムにお届けします。
シリーズを読む -
FILE.03 — XY軸制御
Raspberry Pi × ステッピングモーターXYテーブルシリーズ
Raspberry Piとステッパモーターを使い、ゼロから2軸制御に挑む記録。OS設定から回路設計、多軸制御のPythonコードまで、躓きやすいポイントを徹底図解。電子工作初心者が「動く感動」を味わうための実戦ガイドです。
シリーズを読む -
FILE.04 — 業務自動化
Python × Excel × CustomTkinter【Python開発記】NINJIN Mail制作記
PythonとExcelを連携させ、実務で即戦力となるメール送信ツールを開発。CustomTkinterによるUI構築や、ミスを防ぐテンプレート活用術など、現場の「痒い所に手が届く」自動化ノウハウを細部まで丁寧に解説します。
シリーズを読む -
FILE.05 — 業務自動化
Python × Excel × GUIアプリ化Invoice Maker(請求書・見積書自動作成)
手作業の請求書作成から卒業!PythonでExcelデータを読み込み一括PDF化する「Invoice_Maker」の作り方を全3回で解説。基本ロジックからGUIアプリ化まで、実務で役立つ自動化ノウハウが満載です。
シリーズを読む































コメント