Wio LTEのMicroPythonの通信処理を使いやすくする (未完)

# はじめに

1年半前くらいにMicroPythonをWio LTEに移植して使ってきましたが、LTEの通信機能は一応使えるものの、使いやすいものとは言えませんでした。

そこで、M5Stack 3G 拡張ボードをMicroPythonで動かしたときの知見をもとに、Wio LTEのLTE通信を使いやすく方法を検討しました。

# 現状の問題点

現状、Wio LTE上のMicroPythonからLTEの通信機能を使うためには、MicroPythonで実装したLTE通信モジュール・ドライバを使って通信処理を記述する必要があります。
このLTE通信モジュール・ドライバには、基地局への接続機能と、TCP,UDPでの通信機能があります。

一方、MicroPython用のHTTP通信ライブラリやMQTT通信ライブラリは、socketモジュールに依存しているため、これらのライブラリを使うためにはsocketモジュール経由で通信できる必要があります。

よって、現状のLTE通信モジュールのドライバの実装では、既存の通信ライブラリを使用できず、一から実装しなおす必要があります。

そこで、socketモジュールから扱えるネットワーク・インターフェースとしてLTE通信モジュールを扱えるようにすることを考えます。

# 実装方法

以下の2つの方法があります。

1. EC21Jを使うためのMicroPythonのネットワーク・インターフェース・モジュールを実装する。
2. PPP通信モジュールを実装し、EC21JをPPPモードで使用する。


(1)の方法は、MicroPythonのnetworkモジュールから使うネットワーク・インターフェースとして、新たにEC21Jを追加する方法です。既存のネットワーク・インターフェースの実装としては、WIZnetのハードウェアTCP/IPモジュールを使う実装があります。networkモジュール管理下のネットワーク・インターフェースは、socketモジュール経由での通信処理で使うことができます。

(2)の方法は、MicroPythonの通信処理を組み込み用TCP/IPスタックであるlwIPを使うように設定し、lwIPのPPP通信機能を使って通信します。lwIPのPPP通信機能は、lwIPの管理下のネットワーク・インターフェースとして扱われるため、socketモジュール経由で通信処理を行うことができます。

今回は、M5Stack 3G 拡張ボードでもPPP通信機能を扱った経験から、(2)のlwIPのPPP通信機能を使う方針で実装を試みました。

# MicroPythonの変更内容

MicroPythonのESP32ポートには、pppモジュールがすでに実装されていますので、このpppモジュールの実装をSTM32に移植することにしました。

ESP32では、OSとしてFreeRTOSが使われるため、ESP32ポートのpppモジュールにもFreeRTOSに依存している部分があります。
これらの依存部分をFreeRTOSを使わない実装に変更しました。

# EC21Jの初期化処理

EC21Jの初期化処理は、基本的にEC21Jで直接TCP/IPの通信を扱う場合と同じですが、通信コンテキストの初期化処理だけ異なります。

EC21JのTCP/IP通信機能を使う場合、通信コンテキストの初期化には、`+QI`で始まるQuectel独自のTCP/IP通信用独自コマンドを用います。

例として`beam.soracom.io`のポート80版に接続する場合は次のようになります。

```
AT+QICSGP=1,1,"soracom.io","sora","sora",1    // TCP/IP通信コンテキストを初期化
AT+QIOPEN=1,TCP,"beam.soracom.io",80,0,0      // TCP接続開始
```

代わりに、PPP通信を開始するには、`+CGDCONT`コマンドでPPP上のプロトコルに`IP`を指定して通信コンテキストを作成したのち、`+CGDATA`でPPP通信を開始します。

```
AT+CGDCONT=1,"IP","soracom.io"
AT+CGDATA="PPP",1
```

`+CGDATA`の代わりに`ATD*99***1#`などでも良いようです。

これ以降は、PPPに則った通信を行えるようになります。

# 結果

残念ながら、PPPでの通信処理はうまくいきませんでした。

以下に実験時の通信ログを示します。

```
[DEBUG] initialize
[DEBUG] Waiting busy...
[DEBUG] wait_response: target=b'RDY'
[DEBUG] wait_response: response=bytearray(b'RDY')
[DEBUG] <- b'AT'
[DEBUG] wait_response: target=b'OK'
[DEBUG] wait_response: response=bytearray(b'OK')
[DEBUG] <- b'ATZ'
[DEBUG] wait_response: target=b'OK'
[DEBUG] wait_response: response=bytearray(b'OK')
[DEBUG] <- b'ATE0'
[DEBUG] wait_response: target=b'OK'
[DEBUG] wait_response: response=bytearray(b'+CFUN: 1')
[DEBUG] wait_response: response=bytearray(b'OK')
[DEBUG] <- b'AT+CFUN=1'
[DEBUG] wait_response: target=b'OK'
[DEBUG] wait_response: response=bytearray(b'OK')
[DEBUG] <- b'AT+QSCLK=1'
[INFO] Waiting SIM goes active...
[DEBUG] <- b'AT+CPIN?'
[INFO] AT+CPIN result=True, response=2
[INFO] Activating network...
[DEBUG] <- b'AT+CGREG?'
[DEBUG] -> b'+CGREG: 0,2'
[DEBUG] AT+CGREG?:+CGREG: 0,2
[DEBUG] <- b'AT+CGREG?'
[DEBUG] -> b'+CGREG: 0,2'
(...省略...)
[DEBUG] AT+CGREG?:+CGREG: 0,1
[DEBUG] <- b'AT+CEREG?'
[DEBUG] -> b'+CEREG: 0,1'
[DEBUG] AT+CEREG?:+CEREG: 0,1
[DEBUG] <- b'AT+CNMI: 0,0,0,0,0'
[DEBUG] wait_response: target=b'OK'
[DEBUG] wait_response: response=bytearray(b'OK')
[DEBUG] <- b'AT&D0'
[DEBUG] wait_response: target=b'OK'
[DEBUG] wait_response: response=bytearray(b'ERROR')
[DEBUG] wait_response: response=bytearray(b'OK')
[DEBUG] <- b'AT+CMEE=2'
[DEBUG] wait_response: target=b'OK'
[DEBUG] wait_response: response=bytearray(b'OK')
[DEBUG] <- b'AT+CGDCONT=1,"IP","soracom.io"'
[DEBUG] wait_response: target=b'OK'
[DEBUG] wait_response: response=bytearray(b'OK')
[DEBUG] <- b'ATD*99***1#'
[DEBUG] -> b'CONNECT 150000000'
[INFO] creating PPP
[INFO] activate PPP
ppp phase changed[2]: phase=0
[INFO] connect PPP
ppp_connect[2]: holdoff=0
ppp phase changed[2]: phase=3
pppos_connect: unit 2: connecting
ppp_start[2]
ppp phase changed[2]: phase=6
pppos_send_config[2]: out_accm=FF FF FF FF
ppp_send_config[2]
pppos_recv_config[2]: in_accm=FF FF FF FF
ppp_recv_config[2]
ppp: auth protocols: PAP=0 CHAP=1 CHAP_MD5=1
output: 7e ff 7d 23 c0 21 7d 21 7d 21 7d 20 7d 34 7d 22 7d 26 7d 20 7d 20 7d 20 7d 20 7d 25 7d 26 ea ef fc 5f 7d 27 7d 22 7d 28 7d 22 ba fa 7e
pppos_write[2]: len=24
ppp_start[2]: finished
>>> input: 7e ff 7d 23 c0 21 7d 21 7d 20 7d 20 7d 39 7d 22 7d 26 7d 20 7d 20 7d 20 7d 20 7d 23 7d 25 c2 23 7d 25 7d 25 7d 26 74 21 7d 36 d5 7d 27 7d 22 7d 28 7d 22 7d 32 ef 7e 7e ff 7d 23 c0 21 7d 22 7d 21 7d 20 7d 34 7d 22 7d 26 7d 20 7d 20 7d 20 7d 20 7d 25 7d 26 ea ef fc 5f 7d 27 7d 22 7d 28 7d 22 51 93 7e
pppos_input[2]: got 100 bytes
output: ff 7d 23 c0 21 7d 22 7d 20 7d 20 7d 39 7d 22 7d 26 7d 20 7d 20 7d 20 7d 20 7d 23 7d 25 c2 23 7d 25 7d 25 7d 26 74 21 7d 36 d5 7d 27 7d 22 7d 28 7d 22 9f e3 7e
pppos_write[2]: len=29
netif_set_mtu[2]: mtu=1500
pppos_send_config[2]: out_accm=0 0 0 0
ppp_send_config[2]
pppos_recv_config[2]: in_accm=0 0 0 0
ppp_recv_config[2]
ppp phase changed[2]: phase=7
input: 7e ff 7d 23 c0 21 7d 2b 7d 21 7d 20 7d 28 74 21 7d 36 d5 2e cb 7e 7e c2 23 01 01 00 23 10 89 c6 1f 37 f2 7d 5d df 79 6a da ef 32 7d 5d 19 e7 cc 55 4d 54 53 5f 43 48 41 50 5f 53 52 56 52 2a d9 7e
pppos_input[2]: got 65 bytes
output: ff 03 c2 23 02 01 00 19 10 33 40 e7 b6 d6 2a ce cf 1e 44 f4 c7 6d f5 d5 aa 73 6f 72 61 15 95 7e
pppos_write[2]: len=29
input: 7e c2 23 03 01 00 04 c2 bc 7e
pppos_input[2]: got 10 bytes
CHAP authentication succeeded
CHAP authentication succeeded
ppp phase changed[2]: phase=9
output: ff 03 80 21 01 01 00 16 03 06 00 00 00 00 81 06 00 00 00 00 83 06 00 00 00 00 6e db 7e
pppos_write[2]: len=26
input: 0d 0a 4e 4f 20 43 41 52 52 49 45 52 0d 0a
pppos_input[2]: got 14 bytes
input: 0d 0a 2b 51 49 4e 44 3a 20 50 42 20 44 4f 4e 45 0d 0a
pppos_input[2]: got 18 bytes
output: 7e ff 03 80 21 01 01 00 16 03 06 00 00 00 00 81 06 00 00 00 00 83 06 00 00 00 00 6e db 7e
pppos_write[2]: len=26
output: 7e ff 03 80 21 01 01 00 16 03 06 00 00 00 00 81 06 00 00 00 00 83 06 00 00 00 00 6e db 7e
pppos_write[2]: len=26
output: 7e ff 03 80 21 01 01 00 16 03 06 00 00 00 00 81 06 00 00 00 00 83 06 00 00 00 00 6e db 7e
pppos_write[2]: len=26
output: 7e ff 03 80 21 01 01 00 16 03 06 00 00 00 00 81 06 00 00 00 00 83 06 00 00 00 00 6e db 7e
pppos_write[2]: len=26
output: 7e ff 03 80 21 01 01 00 16 03 06 00 00 00 00 81 06 00 00 00 00 83 06 00 00 00 00 6e db 7e
pppos_write[2]: len=26
output: 7e ff 03 80 21 01 01 00 16 03 06 00 00 00 00 81 06 00 00 00 00 83 06 00 00 00 00 6e db 7e
pppos_write[2]: len=26
output: 7e ff 03 80 21 01 01 00 16 03 06 00 00 00 00 81 06 00 00 00 00 83 06 00 00 00 00 6e db 7e
pppos_write[2]: len=26
output: 7e ff 03 80 21 01 01 00 16 03 06 00 00 00 00 81 06 00 00 00 00 83 06 00 00 00 00 6e db 7e
pppos_write[2]: len=26
output: 7e ff 03 80 21 01 01 00 16 03 06 00 00 00 00 81 06 00 00 00 00 83 06 00 00 00 00 6e db 7e
pppos_write[2]: len=26
IPCP: timeout sending Config-Requests
ppp phase changed[2]: phase=11
ppp phase changed[2]: phase=6
pppos_send_config[2]: out_accm=FF FF FF FF
ppp_send_config[2]
pppos_recv_config[2]: in_accm=0 0 0 0
ppp_recv_config[2]
output: 7e ff 7d 23 c0 21 7d 25 7d 22 7d 20 20 4e 6f 20 6e 65 74 77 6f 72 6b 20 70 72 6f 74 6f 63 6f 6c 73 20 72 75 6e 6e 69 6e 67 7d 37 7d 38 7e
pppos_write[2]: len=36
output: 7e ff 7d 23 c0 21 7d 25 7d 23 7d 20 20 4e 6f 20 6e 65 74 77 6f 72 6b 20 70 72 6f 74 6f 63 6f 6c 73 20 72 75 6e 6e 69 6e 67 23 dd 7e
pppos_write[2]: len=36
ppp phase changed[2]: phase=12
Connection terminated.
ppp_link_terminated[2]
ppp_link_end[2]
ppp phase changed[2]: phase=0
ppp_link_terminated[2]: finished.
```

`activate PPP`以降、lwIPの通信ログが出力されています。`output:`の行は送信したPPPのパケットの内容、`input:`の行は受信したPPPのパケットの内容を表します。

`CHAP authentication succeeded` と表示されて、CHAPによる認証が完了後、IPCPでIPアドレスを取得するため、IPCPのConfigure-Requestを送信しています。

```
output: ff 03 80 21 01 01 00 16 03 06 00 00 00 00 81 06 00 00 00 00 83 06 00 00 00 00 6e db 7e
```

このパケットの内容は以下の通り、IPCPにて基地局側にIPの割り当てを要求しています。

```
ff 03 80 21 // IPCP
01 01 00 16 // Configure-Request, ID=0x01, Length=0x0016
03 06 00 00 00 00 // Self IP address = 0.0.0.0 (Request to assign IP)
81 06 00 00 00 00 // 
83 06 00 00 00 00 
6e db 7e // FCS+Flag
```

この直後、基地局側から以下の内容が返信されています。

```
input: 0d 0a 4e 4f 20 43 41 52 52 49 45 52 0d 0a
```

これは、文字列に直すと `\r\nNO CARRIER\r\n`となり、通信が切断されたことを表しています。
これ以降、基地局側はPPPのパケットに応答しなくなります。

現在のところ、なぜ通信が切断されるのか分からず解決できていません。詳しい方がいましたら教えていただけるとありがたいです。

# おわりに

Advent Calendarの担当日までに通信処理を実装して、通信できた！という内容を書ければいいなという予定でしたが、残念ながら間に合いませんでした。

この後しばらくPPPで通信できない原因を調査して、解決出来なければ、(1)の方針にてEC21のネットワーク･ドライバを実装しようと思います。