M5stackシリーズとUIFlowでLineNotify 2023年度版

# 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との通信が成功すると画面が水色に、失敗するとオレンジになるようにします。

作成したブロックは以下です。少し冗長ですので、判る方はもっとシンプルにしても良いと思います。

![m5ui12_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698531/4acf65b5-9ad3-bda0-1ab9-3f00bec444d1.png)


こちらがそのファイルです。三角のマークをクリックして、表示されたテキストをコピーしてメモ帳などにペーストし`m5line.m5f`などのファイル名で保存し、UIFlowに読み込ませます。(2023.11/27 修正)

<details><summary>m5linenotify.m5f</summary><div>

```
{"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"}
```

</div></details>


このBlocklyコードをpythonで表示したものが次になります。何かうまくいかない時などに、このコードと比較すると良いでしょう。

<details><summary>m5linenotify.py</summary><div>

```python
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)

```

</div></details>


別のM5stackシリーズに置き換える場合は、まずはm5fを読み込んだ後に、右上の三本線のアイコンをクリックし、「設定」(Setting)をクリックすると、デバイス選択する画面になります。

![selectDevice36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698531/3fd30963-a092-9166-7b92-95efd05c355e.png)


なお、スクリーンが無い機種は、画面設定のブロックを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は利用できませんでした）

1. オレンジ色のTerminal (beta) をクリックします
2. 現れたターミナルウインドウの左上にあるチェーンのようなアイコンをクリックします
3. M5が繋がっているcom番号を選択します
4. 接続をクリックします
5. ターミナルウインドウの三角マークをクリック
6. 別途用意したpythonのスクリプトを1行ずつペーストしてい、エラーが出るかを確認していきます

![terminal_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698531/2ad36485-aa5c-95a0-9a5f-649e4db06156.png)

この方法を用いると、Wi-Fi接続せずともM5に指示を与えることができるので、とても便利です。


## 4.4. micro Pythonでデバッグ

新規のコードをUIFlowでデバッグするのは結構面倒臭いと思います。
UIFlowは、googleのblocklyを利用してmicro Pythonに変換してM5stack類で動かしています。なので、micro Pythonで確認するのが無難なことがあります。ただし、micro python環境は別途構築が必要で、初心者にはお勧めできませんが、私の場合はthonyを使い、次のようなコードで確認しました。

<details><summary>esp32linenotify.py</summary><div>

```python
#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')


```

</div></details>


これらを対処するために参考にした記事を紹介します。
まずは、悩ましい エラー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)：この方法が使えない場合について追記。デバッグ方法を追記。