🔲
ESP32 - MicroPythonで家電を制御しよう-その2(WEBリクエスト)
ESP32を使って、IFTTTサービスにリクエストを送り、家電を制御します。その仕組みとコードの部分について説明します。
全体像は、その1(コンセプト、スイッチ)を参照してください。
本記事は、WEBリクエスト(HTTPC管理スレッド)についての説明です。
関連記事
HTTPC管理スレッドの作成
HTTPC管理スレッドの作成と動作確認を行います。
開発環境の構築
開発環境の構築は、下記の記事を参考にしてください。
パーツ一覧
機材名 | 数量 | 備考 |
---|---|---|
ESP32評価ボード | 1 | ESP32-WROVER 開発ボード/ESP32-WROOM 開発ボードのどちらでも可 |
回路図
なし
事前準備(IFTTT)
Appletの登録
IFTTTの設定手順について説明します。
- https://ifttt.com/home へアクセスしてアカウントを作成 or ログイン
- Createにて、以下を追加
- This(Trigger)
- "Webhooks" - "Receive a web request"
- "Event Name" : sw1
- "Webhooks" - "Receive a web request"
- That(Action)
- "Nature Remo" - "Turn on air conditioner"
- Cool 26℃
- "Nature Remo" - "Turn on air conditioner"
- This(Trigger)
tokenの確認
webhooksをキックするためにtokenを確認します。
- Webhooks Settingsのページより確認する
- 下記画像のマスクされている箇所に記載されている
コード(HTTPC管理スレッド)
httpc.pyとutil.pyの2つのファイルで構成されています。
httpc.py
import _thread
from usocket import socket, AF_INET, SOCK_DGRAM
import network
import urequests as requests
import time
import gc
# from settings import SettingsFile
from util import *
SSID = "XXXXXXXXXXXXX"
PASSWORD = "YYYYYYYYYYYYY"
TOKEN = "ZZZZZZZZZZZZZZZZZ"
class HttpcProc():
def __init__(self, lock=None, snd_que=None, rcv_que=None):
# param
# settings_file = SettingsFile()
self._ssid = SSID
self._password = PASSWORD
self._token = TOKEN
self._lock = lock
self._snd_que = snd_que
self._rcv_que = rcv_que
return
def do_connect(self):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('connecting to network...')
# wlan.connect('essid', 'password')
wlan.connect(self._ssid, self._password)
while not wlan.isconnected():
pass
# time.sleep(1)
time.sleep(3)
print(".")
# send_que(self._lock, self._snd_que, ("dst:pre,src:httpc,cmd:dsp,type:clear"))
# send_que(self._lock, self._snd_que, ("dst:pre,src:httpc,cmd:led,type:off_all"))
print('network config:', wlan.ifconfig())
# send_que(self._lock, self._snd_que, ("dst:pre,src:httpc,cmd:led,type:blink,name:green"))
return
def _proc_httpc(self):
def act_httpc(msg):
def do_webhook(eventid, value1="dummy1", value2="dummy2", value3="dummy3"):
# care memory
gc.collect()
# valueに載せる情報
payload = "value1="
# payload += str(self._rtc.datetime())
payload += value1
payload += "&value2="
payload += value2
payload += "&value3="
payload += value3
url = "http://maker.ifttt.com/trigger/" + eventid + "/with/key/" + self._token
ret = True
try:
# response = requests.post(url, headers={"Content-Type": "application/x-www-form-urlencoded"}, data=payload)
# value指定が不要な場合は↓でok
response = requests.post(url)
# send_que(self._lock, self._snd_que, ("dst:pre,src:httpc,cmd:led,type:blink,name:blue"))
response.close()
except:
# send_que(self._lock, self._snd_que, ("dst:pre,src:httpc,cmd:led,type:blink,name:red"))
ret = False
return ret
print("act_httpc:run")
d = conv_msg2dict(msg)
print(d)
cmd, typ = d["cmd"], d["type"]
if "sw" in cmd:
how = d["how"]
evt_id = conv_typ2eventid(typ, how)
print(evt_id)
if evt_id is not None:
# send_que(self._lock, self._snd_que, ("dst:pre,src:httpc,cmd:dsp,type:ifttt,how:"+evt_id+",sts:sending...,tmr:3000"))
do_webhook(evt_id)
# send_que(self._lock, self._snd_que, ("dst:pre,src:httpc,cmd:dsp,type:ifttt,how:"+evt_id+",sts:sended.,tmr:3000"))
else:
print("act_httpc:error, ", msg)
return
is_connect = False
while True:
if is_connect is False:
self.do_connect()
is_connect = True
msg = recv_que(self._lock, self._rcv_que)
if msg is None:
time.sleep_ms(50)
continue
print("proc_httpc:msg - ", msg)
ret = act_httpc(msg)
if False == ret:
# reconnect
is_connect = False
return
def run(self):
_thread.start_new_thread(self._proc_httpc, ())
def main():
lock = _thread.allocate_lock()
que_pre2httpc = []
httpc_proc = HttpcProc(lock, snd_que=None, rcv_que=que_pre2httpc)
httpc_proc.run()
time.sleep_ms(5_000)
send_que(lock, que_pre2httpc, ("dst:httpc,src:pre,cmd:sw,type:no1,how:released"))
time.sleep_ms(10_000)
return
if __name__ == "__main__":
main()
util.py
# 通知とIFTTT-Webhookイベントの紐づけ
# - cmd:sw,type:no1,how:released" => sw1をイベントIDとしてIFTTTにリクエスト
KEY_TO_EVENTID = {
"no1" : {
"pressed" : None,
"released" : "sw1",
"long" : None,
},
}
def conv_msg2dict(msg):
# input : "a:b,c:d"
# output: {"a":"b","c":"d"}
d = {}
for item in msg.split(','):
key, val = item.split(':')
d[key] = val
return d
def conv_typ2eventid(typ, how):
if typ in KEY_TO_EVENTID:
return KEY_TO_EVENTID[typ][how]
return None
def send_que(lock, que, value):
if que is None:
print("Error:que is None.")
return
# lock
lock_p = lock.acquire(1, -1) #wait forever
if not lock_p:
print("Error:task can not get the lock.")
else:
que.append(value)
lock.release()
# unlock
return
def recv_que(lock, que):
if que is None:
print("Error:que is None.")
return
val = None
# lock
lock_p = lock.acquire(1, -1) #wait forever
if not lock_p:
print("Error:task can not get the lock.")
else:
if len(que) > 0:
val = que[0]
del que[0]
lock.release()
# unlock
return val
実行方法と結果
>>> import httpc
>>> httpc.main()
connecting to network...
.
network config: ('192.168.XX.xx', '255.255.255.0', '192.168.XX.1', '192.168.XX.1')
proc_httpc:msg - dst:httpc,src:pre,cmd:sw,type:no1,how:released
act_httpc:run
{'src': 'pre', 'dst': 'httpc', 'cmd': 'sw', 'how': 'released', 'type': 'no1'}
sw1
>>>
→エアコンをオンすることができました。
ポイント
SSID/PASSWORD/TOKENの指定
- SSID/PASSWORDには、使用するアクセスポイントの設定となります
- 2.4GHz帯のみ使用、5GHzは使用不可
- TOKEN
- 事前準備で確認したIFTTT-WebhooksのTokenを指定
WiFiの接続
- do_connect()でWiFiへの接続を行う
- 接続するまで本処理は抜けない
IFTTTへのリクエスト/メモリケア/例外
- do_webhook()にてリクエストを行う
- メモリケア
- gc.collect()にて能動的にガベレージコレクションを実施
- response.close()にてクローズ(メモリの開放)
- 評価ボードがSPI-RAMなし版の場合、メモリがプア
- HTTPリクエスト時はメモリが多く消費されるため行っている
- 例外処理 - try,except
- WiFiの切断している場合などは例外があがる
- 本例外は、切断していると判断しWiFiへの再接続を行う
- メモリケア
IFTTTへのリクエストでvalueを指定
- 今回は使用していないが、valueをのせる場合は、headerの指定が必要
- response = requests.post(url, headers={"Content-Type": "application/x-www-form-urlencoded"}, data=payload)
- urequestsモジュール内で、ヘッダが固定文字になっているため
スレッド間通信
- MicroPythonには、Queueモジュールが搭載されていない
- 代用として通常のリストをキューとして使用している
- キューの送信 : send_que()
- キューの受信 : recv_que()
参考URL
- MicroPython公式ドキュメント
- MicroPython - ESP32 用クイックリファレンス
さいごに
次は、LED/Displayの制御を記載する予定です。
Discussion