1. はじめに
M5stackシリーズとUIFlowで直接Line NotifyにPOSTする方法について、参考になる記事と簡単なサンプルを挙げました。
(2023.12/29追記:UIFlowが用いているmicroPythonのurequestsはリダイレクトに対応してないため、お使いの回線によっては、この方法は利用できない場合があります)
(2025.3/16追記:LineNotifyが2025.3/31でサービス終了し、代替サービスとしてLINE Messagingがあります。Qiita上に有用な記事が沢山あります)
2. 準備
2.1. Line Notifyの準備
多くの資料がwebにあります。「Line notify 準備」などで検索しましょう。次が判り易いと思います。
「LINE Notifyを使いたい」nattyan_tv氏
https://qiita.com/nattyan_tv/items/33ac7a7269fe12e49198
注意点を追加するとしたら次があります。
- Lineのアカウントが必要です(自分のものや職場の電話で登録するなど)
- Line Notifyの登録はPCで行う方が良いです(Lineはスマホで使いますがLine Notify登録はPCで)
2.2. M5stackシリーズでUIFlowと連携
多くの資料がwebにあります。「M5stack UIFlow」などで検索しましょう。次が判り易いと思います。
「M5Stack UIFlowのはじめかた」 田中正幸 氏
https://lang-ship.com/blog/work/m5stack-uiflow/
注意点を追加するとしたら次があります。
- UIFlowにバージョン1 と 2 があり、ここではバージョン1を使うことを前提にしています
3. UIFlowでプログラム
今回利用したのはM5stick c で、真ん中のボタンを押すとLineに通知が行くようにします。LineNotifyとの通信が成功すると画面が水色に、失敗するとオレンジになるようにします。
作成したブロックは以下です。少し冗長ですので、判る方はもっとシンプルにしても良いと思います。
こちらがそのファイルです。三角のマークをクリックして、表示されたテキストをコピーしてメモ帳などにペーストしm5line.m5f
などのファイル名で保存し、UIFlowに読み込ませます。(2023.11/27 修正)
m5linenotify.m5f
{"components":[{"id":"_stickscreen","createTime":1692080457941,"name":"screen","x":0,"y":0,"width":120,"height":240,"backgroundColor":"#111111","backgroundImage":"","size":0,"screenType":"default","type":"screen"}],"type":"stick-C","versions":"Beta","units":[],"hats":[],"stamps":[],"blockly":"<variables><variable id=\".~GQ?Sx@?wW}Z1Hh`6#$\">hostAddress</variable><variable id=\"|V)9^ezI1cP3830Q~_6e\">token</variable><variable id=\"Y7Vhv4hjyxGfbJ2-axn-\">tokenBear</variable><variable id=\"wK[o)2By.X``bl^~p}3d\">mess</variable><variable id=\"-fcaWw)=19l}M83*xg?)\">messSend</variable></variables><block type=\"basic_on_setup\" id=\"setup_block\" deletable=\"false\" x=\"-330\" y=\"-310\"><next><block type=\"wifi_doConnect\" id=\"S,6[Y)x#4?`$Ht-,hZmo\"><value name=\"apiKey\"><shadow type=\"text\" id=\"o6WDwp~tpMRE({r5nA8s\"><field name=\"TEXT\">SSID</field></shadow></value><value name=\"Msg\"><shadow type=\"text_password\" id=\"HfkxMI(17rUh+`|Y%VfJ\"><field name=\"TEXT\">password</field></shadow></value><next><block type=\"screen_set_brightness\" id=\"Qe-8+1x(f0n^KA0IzosV\"><value name=\"BRIGHTNESS\"><shadow type=\"math_slider\" id=\"f-%,O#:4{[m!kc3dGYuV\"><field name=\"NUM\" max=\"100\" step=\"1\">30</field></shadow></value></block></next></block></next></block><block type=\"button_callback\" id=\"72fb=5D2*I322CM})q12\" x=\"-330\" y=\"-190\"><field name=\"BUTTON\">A</field><field name=\"EVENT\">wasPressed</field><statement name=\"FUNC\"><block type=\"variables_set\" id=\"2qR~,GTqJ^@RsKlxX{q@\"><field name=\"VAR\" id=\".~GQ?Sx@?wW}Z1Hh`6#$\">hostAddress</field><value name=\"VALUE\"><block type=\"text\" id=\"SR;rE9R`KUkVl-F-u.dq\"><field name=\"TEXT\">https://notify-api.line.me/api/notify</field></block></value><next><block type=\"variables_set\" id=\"Y#Q|vLbIVufEY9$|V_y~\"><field name=\"VAR\" id=\"|V)9^ezI1cP3830Q~_6e\">token</field><value name=\"VALUE\"><block type=\"text\" id=\"N@4/G2zp_z+E-XqY;w0h\"><field name=\"TEXT\">abcdefghijklmnopqrstuvwxyz01234567890123456</field></block></value><next><block type=\"variables_set\" id=\"AH9Z+9*pbc8?9sk_L}DZ\"><field name=\"VAR\" id=\"Y7Vhv4hjyxGfbJ2-axn-\">tokenBear</field><value name=\"VALUE\"><block type=\"text\" id=\"SUv`L$1,^,1^psq/Lkz{\"><field name=\"TEXT\">Bearer </field></block></value><next><block type=\"variables_set\" id=\"pd%K0sgQHBtW?vH,J;mB\"><field name=\"VAR\" id=\"Y7Vhv4hjyxGfbJ2-axn-\">tokenBear</field><value name=\"VALUE\"><block type=\"text_add\" id=\"J;prv[l]EBC2#+-dNn9K\"><value name=\"arg0\"><shadow type=\"text\" disabled=\"true\"><field name=\"TEXT\"></field></shadow><block type=\"variables_get\" id=\"aOJMp/gCm,`unnu`cvUe\"><field name=\"VAR\" id=\"Y7Vhv4hjyxGfbJ2-axn-\">tokenBear</field></block></value><value name=\"arg1\"><block type=\"variables_get\" id=\"SY#UFUFMEw6)ov+m4ktw\"><field name=\"VAR\" id=\"|V)9^ezI1cP3830Q~_6e\">token</field></block></value></block></value><next><block type=\"variables_set\" id=\"~u;H9J}[$!m9Cl}!6|/H\"><field name=\"VAR\" id=\"wK[o)2By.X``bl^~p}3d\">mess</field><value name=\"VALUE\"><block type=\"text\" id=\"S1`-dnJ26(xwdb)Pzv.Z\"><field name=\"TEXT\">\"hello from m5stick\"</field></block></value><next><block type=\"variables_set\" id=\"@H(qeYeQXoRiO9W|rvwL\"><field name=\"VAR\" id=\"-fcaWw)=19l}M83*xg?)\">messSend</field><value name=\"VALUE\"><block type=\"text_add\" id=\"-r`lWt~0r;*;+52808X*\"><value name=\"arg0\"><shadow type=\"text\" id=\":,Z,Q-`yukjCw,A^}JnX\"><field name=\"TEXT\">message= </field></shadow></value><value name=\"arg1\"><block type=\"variables_get\" id=\"IZTf#uGuuwi8-:|7k`{p\"><field name=\"VAR\" id=\"wK[o)2By.X``bl^~p}3d\">mess</field></block></value></block></value><next><block type=\"screen_set_bgcolor\" id=\"zpeW4O,=w5cLQy+}H$T1\"><field name=\"COLOR\">#cc66cc</field><next><block type=\"http_request\" id=\"Y,k{)[Woj4mRRRInczmJ\"><field name=\"method\">POST</field><value name=\"url\"><shadow type=\"text\" id=\"p#s[#{W.UYsXJ|jy_L{~\"><field name=\"TEXT\"></field></shadow><block type=\"variables_get\" id=\"tHW8~nATJbDaBcLQT/h-\"><field name=\"VAR\" id=\".~GQ?Sx@?wW}Z1Hh`6#$\">hostAddress</field></block></value><value name=\"headers\"><block type=\"map_on_loop\" id=\"O8#+ea|+7VA8-*-T?d|S\"><statement name=\"LOOP\"><block type=\"create_json_key\" id=\"N}r[r]|FLWtjls?X{uVY\"><value name=\"key\"><shadow type=\"text\" id=\"A9[!RFjxzvGo$IEl4+;=\"><field name=\"TEXT\">Authorization</field></shadow></value><value name=\"value\"><shadow type=\"text\" disabled=\"true\"><field name=\"TEXT\"></field></shadow><block type=\"variables_get\" id=\"se;+H_.!e.s:Ky}r9/T3\"><field name=\"VAR\" id=\"Y7Vhv4hjyxGfbJ2-axn-\">tokenBear</field></block></value><next><block type=\"create_json_key\" id=\"jcKP#5pHj03hW]zrbPW8\"><value name=\"key\"><shadow type=\"text\" id=\"!|?D2%.P?(X|`7z_|+D8\"><field name=\"TEXT\">Content-Type</field></shadow></value><value name=\"value\"><shadow type=\"text\" id=\"yGPBJxBs%*7F#z7yh%vI\"><field name=\"TEXT\">application/x-www-form-urlencoded</field></shadow></value></block></next></block></statement></block></value><value name=\"data\"><block type=\"variables_get\" id=\")a/CCvLeTHKdQA@WVm%~\"><field name=\"VAR\" id=\"-fcaWw)=19l}M83*xg?)\">messSend</field></block></value><statement name=\"success\"><block type=\"screen_set_bgcolor\" id=\"5M9SFhCwH!GqKIu@Kn#0\"><field name=\"COLOR\">#33ccff</field></block></statement><statement name=\"fail\"><block type=\"screen_set_bgcolor\" id=\"M21A}157J2!U;dqq{I]{\"><field name=\"COLOR\">#ff0000</field></block></statement></block></next></block></next></block></next></block></next></block></next></block></next></block></next></block></statement></block>","Blockly.Remotes":[],"Blockly.RemotePlus":[{"id":"__title","blockId":"","createTime":1693542612886,"name":"M5RemoteTitle","dragAndDrop":false,"resizable":false,"options":{"minWidth":1,"minHeight":1,"maxWidth":6,"maxHeight":10,"defaultWidth":2,"defaultHeight":1},"w":2,"h":1,"bgColor":"#0080FF","color":"#fff","fontsize":"M","label":"M5Remote","interval":3000,"code":"","event":"","dataSource":"none","ezdataToken":"","topic":"","needShadow":false,"type":"title","x":0,"y":0}],"modules":[],"cbIdList_":[],"eventCBIdList_":[],"apikey":"AAAAAAAA","uuid":"6666666a-2225-444f-aaaa-fffffffffff4"}
このBlocklyコードをpythonで表示したものが次になります。何かうまくいかない時などに、このコードと比較すると良いでしょう。
m5linenotify.py
from m5stack import *
from m5ui import *
from uiflow import *
import wifiCfg
import urequests
setScreenColor(0x111111)
hostAddress = None
token = None
tokenBear = None
mess = None
messSend = None
def buttonA_wasPressed():
global hostAddress, token, tokenBear, mess, messSend
hostAddress = 'https://notify-api.line.me/api/notify'
token = 'abcdefghijklmnopqrstuvwxyz01234567890123456'
tokenBear = 'Bearer '
tokenBear = (str(tokenBear) + str(token))
mess = '"hello from m5stick"'
messSend = (str('message= ') + str(mess))
setScreenColor(0xcc66cc)
try:
req = urequests.request(method='POST', url=hostAddress,json=messSend, headers={'Authorization':tokenBear,'Content-Type':'application/x-www-form-urlencoded'})
setScreenColor(0x33ccff)
gc.collect()
req.close()
except:
setScreenColor(0xff0000)
pass
btnA.wasPressed(buttonA_wasPressed)
wifiCfg.doConnect('SSID', 'password')
axp.setLcdBrightness(30)
別のM5stackシリーズに置き換える場合は、まずはm5fを読み込んだ後に、右上の三本線のアイコンをクリックし、「設定」(Setting)をクリックすると、デバイス選択する画面になります。
なお、スクリーンが無い機種は、画面設定のブロックをLED点灯のブロックに置き換えるなど、お使いのM5シリーズに合わせて編集してお使いください。
実は、UIFlowでプログラムも、webに沢山あります。が、「M5stack Line Notify」と検索しても、IFTTT経由のものが上位にきます。Qiitaでもその傾向があります。
今回の例にマッチした情報が載っている記事が次でした。
「LINE Notify を利用して UIFlow のプログラムで LINE に通知を送る(日本語テキストも送信) #M5Stack」 youtoy 氏
https://qiita.com/youtoy/items/76586479c2d4c5893c5b
4. トラブルシューティング
うまくいかない時は、常に正しく動作するプログラムに戻り、変更点を確認するようにしましょう。
それでも変更するとうまく行かない場合は、次を重点的に確認すると問題が見つかることがあります。
- Wi-Fiが正しく設定され、繋がっているか?
- メッセージはダブルクォーテーションでくくってあるか?
- トークンは正しく記載できているか?
4.1. ボタン押下すると失敗する
2通り考えられます。
- Wi-Fiが接続できてないので、ボタン押下後に失敗側の色になる
- Wi-Fiは接続しているけど、ボタン押下後に失敗側の色になる
前者は、Wi-Fiの設定を確認します。UIFlowを接続する場合は、Wi-Fiに接続していることが前提ですので、このケースはあまり遭遇しないと思います。
後者は、URLを間違っているなどが考えられます。LINE 公式サイトに書かれているURLがきちんと入力されているかを確認しましょう。
(2023.12/29追記): httpsをリダイレクトするような回線では、UIFlowでサーバに接続ができません。テザリングでも回線によっては接続ができませんでした。
4.2. ボタン押下してもLINEに届かない
ボタン押下後に成功側の色になっているのにもかかわらず、LINEに届かない場合についてです。
HTTP Requestの処理が成功していても、サーバからエラーを返答されている場合にこのような現象になります。これは、HTTP Request処理の記述が問題があることが多いです。
後述しますが、Http Requestのブロックでは、サーバからの返答値を取得することができます。
ありがちなのは次の2つですので、それぞれ勘所を述べます。
- 400 : Bad Request : ヘッダなどの情報が正しくない
- 401 : Unauthorized : 認証資格が不足
4.2.1. 400 Bad Request
まず、400番 Bad Request エラーについてです。
- 400 Bad Request
-
message: must not be empty
: メッセージが空
-
メッセージに関して必要なヘッダ情報は次です。
{'content-type':'application/x-www-form-urlencoded'}
UIFlowでは、キーにcontent-type
、値にapplication/x-www-form-urlencoded
を入れたmapをHeadersに入れることで送信してくれます。
「メッセージが空」の問題については、次のように送りたいメッセージをダブルクォーテーションでくくることで解決しました。
入力するのは文字列だからダブルクォーテーションは不要と思ったのですが、これを付けないと空のメッセージとみなされるようでした。
"hello from M5"
よって、UIFlowで送信するメッセージとして用意する文字列は次の通りです。
'message= "hello from M5"'
上記は、message:
ではなくmessage=
(コロンではなくイコール)です。これを間違えるとやはり同じく400エラーになります。
このメッセージ本体を Dataの部分に入れることで送信してくれます。
4.2.2. 401 Unauthorized
次に 401番 Unauthorized エラーについてです。これは認証できなかった時に返ってくるエラーです。
例えば、LINEから取得したアクセストークンが次だったとします。
abcdefghijklmnopqrstuvwxyz01234567890123456
認証に関して必要なヘッダ情報は次です。
{'Authorization':'Bearer abcdefghijklmnopqrstuvwxyz01234567890123456'}
UIFlowでは、
- キーに
Authorization
、 - 値に
Bearer abcdefghijklmnopqrstuvwxyz01234567890123456
を入れたmapをHeadersに入れることで送信してくれます。
(2023.11/27追記)
この値は、Bearer
とトークンの間はスペース1個です。2個以上スペースがあると、401エラーになります。
上は正しい例、下は間違った例です。
Bearer abcdefghijklmnopqrstuvwxyz01234567890123456
Bearer abcdefghijklmnopqrstuvwxyz01234567890123456
(修正前のm5fファイルは2個スペースが入っており、401エラーになっていました)
4.3. UIFlowでのデバッグ
どうしても問題が解消できない場合は、一つずつ確認する処理を入れると良いでしょう。
- Wi-Fi接続している場合は画面をフラッシュさせる
これは、「Wi-Fi接続と接続している」( Wi-Fi is connected )とifを使い、その後に画面フラッシュを入れることで実装できます。
- Http Requestsの返答値を表示する
これは、画面表示ができる機種限定になりますが、「ステータスコードを取得」( Get Status Code (return int) )をLabelに表示することで実装できます。
(追記:2023.12/29)
UIFlowに(beta版の)ターミナルモードがあります。これを使うとデバッグが劇的楽になります。
まずは、M5stack等とPCをUSBで接続し、chromeブラウザなどを起動します。(operaは利用可能なようでした。Firefoxは利用できませんでした)
- オレンジ色のTerminal (beta) をクリックします
- 現れたターミナルウインドウの左上にあるチェーンのようなアイコンをクリックします
- M5が繋がっているcom番号を選択します
- 接続をクリックします
- ターミナルウインドウの三角マークをクリック
- 別途用意したpythonのスクリプトを1行ずつペーストしてい、エラーが出るかを確認していきます
この方法を用いると、Wi-Fi接続せずともM5に指示を与えることができるので、とても便利です。
4.4. micro Pythonでデバッグ
新規のコードをUIFlowでデバッグするのは結構面倒臭いと思います。
UIFlowは、googleのblocklyを利用してmicro Pythonに変換してM5stack類で動かしています。なので、micro Pythonで確認するのが無難なことがあります。ただし、micro python環境は別途構築が必要で、初心者にはお勧めできませんが、私の場合はthonyを使い、次のようなコードで確認しました。
esp32linenotify.py
#from m5stack import *
#from m5ui import *
#from uiflow import *
#import wifiCfg
import urequests
import network
import utime
#setScreenColor(0x111111)
hostAddress = None
token = None
tokenBear = None
reqhead = None
mess = None
mess = 'hello from M5'
messSend = None
MYSSID='SSID'
MYPASS='PASS'
MYHostAddress = 'https://notify-api.line.me/api/notify'
#wifiCfg.doConnect(MYSSID,MYPASS)
#axp.setLcdBrightness(30)
wifi= network.WLAN(network.STA_IF)
if wifi.isconnected() :
print('connected')
else:
wifi.active(True)
wifi.connect(MYSSID, MYPASS)
TIMEOUT=10
timeout=0
while not wifi.isconnected() and TIMEOUT > timeout:
print('.')
utime.sleep(1)
timeout -= 1
def buttonA_wasPressed():
global hostAddress, token, tokenBear, reqhead, mess, messSend
hostAddress = MYHostAddress
#print('send to'+hostAddress)
token = 'abcdefghijklmnopqrstuwvxyz01234567890123456'
tokenBear = 'Bearer '
tokenBear = (str(tokenBear) + str(token))
mess = 'hello from M5'
messSend = (str('message:') + str(mess))
mess2 = 'message="hello from M5"'
# messSend = (str(messSend) + str('\\r\\n'))
# messSend = (str(messSend) + str('\\r\\n'))
#setScreenColor(0xcc66cc)
#print('tokenBear: '+tokenBear)
#print('messSend: '+messSend)
myHead ={'content-type':'application/x-www-form-urlencoded', 'Authorization':tokenBear }
try:
#print('try reqest')
#req = urequests.get(hostAddress)
#req = urequests.request(method='POST', url=hostAddress,json=messSend, headers={'Authorization':tokenBear, 'Content-Type':'application/x-www-form-urlencoded', 'Content-Length':str(len(mess)) })
req = urequests.post(url=hostAddress, headers=myHead,data=mess2 ) #{"status":200,"message":"ok"}
#setScreenColor(0x33ccff)
print(req.text)
gc.collect()
req.close()
print('http sent')
except :
print('http failure ')
#setScreenColor(0xff0000)
#setScreenColor(0x000000)
#pass
#print('call function')
buttonA_wasPressed()
print('end of script')
これらを対処するために参考にした記事を紹介します。
まずは、悩ましい エラー400 bad request の解決策を判り易く記載されているのが次でした。
「【ESP32・MicroPython】ドアホンが鳴ったらLINEに通知する」 nak435 氏
https://qiita.com/nak435/items/879b647aa3d7343ba0be
また、Arduino-IDEで組む場合については次が判り易かったです。
「【ESP32】ボタンを押したらLINE通知」 コダマ 氏
https://www.ekit-tech.com/?p=3434
5. おわりに
Line NotifyとUIFlowの組み合わせは、話としてよく聞きます。
Line Notifyについては随分長く継続されているサービスのようで、比較的安心感があります。
一方、UIFlowはバージョンアップが重ねられた後アプリ版の更新が止まり、更新されているのはクラウド版のみになりました。
正直なところ私は、IoTなどの入門として無料のサービスを勧めるのはリスキーだと感じています。無料であるが故に、突然の機能制限や停止の可能性がゼロではないためです。
その観点から、IoT入門の王道は、Arduino-IDEにESP32やRP-picoW、ラズパイにnode-redではないかと思います。
そこへのきっかけとして、Line NotifyとUIFlowの組み合わせは悪くはないとは思いますが、Line NotifyはLineが使えなければ利用できないことを理解しておくべきでしょうし、UIFlowは接続先など様々なリスクを考えて利用すべきだろうと、私は考えます。
A. 修正履歴
(2023.11/27):文章を修正。m5fファイルの認証部分の余計な空白を削除。デバイス変更方法を追加。
(2023.12/29):この方法が使えない場合について追記。デバッグ方法を追記。