Pico Bot, PukiWikiでRaspberry Pi Pico Wを制御する

# はじめに

<iframe width="560" height="315" src="https://www.youtube.com/embed/755hV683Y5w?si=1kf3yAtnGmH_obyg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## IoT演習の授業
[IoT演習シラバス案その１](https://qiita.com/takashiyamanoue/items/280b428f6ea804ad9202)に書いたように、2024年9月より、IoT演習という授業を担当する予定です。この授業は、マイコンボード(Raspberry Pi Pico W, 以後 Pico W)を使って、Visual Studio Code (以後 VSCode)でのPython (Micro Python)のプログラミング及び、Lチカのような、簡単な物の電子工作から始めて、簡単なIoTシステムを作成する方法まで、授業と演習を行い、最後に自由課題として、受講者が作りたいIoTシステムを作ってもらおう、というものです。
## Wiki IoT/Bot  Computing
[この授業](https://qiita.com/takashiyamanoue/items/280b428f6ea804ad9202)では、IoTシステムで使うサーバとして、[PukiWiki](https://en.wikipedia.org/wiki/PukiWiki)を使おうと思っています。既存のIoTシステム用のサーバ(IoTサーバと呼ぶことにします)として[Ambient](https://ambidata.io/)やAWSのIoT Core など、様々な既存のサーバがあります。既存のIoTサーバを使えば、簡単にIoTシステムを作ることができそうですし、本来、既存の標準的なIoTサーバを使ったIoTシステムの開発を学生に体験させるべきです。しかしながら、今までそれらのシステムを使っていて、途中で仕様変更が発生して、最初の年は動いていたけど、次の年には動かなくなってしまった、ということがしばしば発生します（私の経験です）。それであれば、できるだけ、自分（教員）が制御可能で、良く知っているものを組み合わせて作られたIoTサーバを利用しよう、と思いました。そこで、私のところで作っている、Wiki IoT/Bot Computing を使うことにしました。これであれば、少々意地悪な質問が学生から出ても答えることができそうです。
(Wiki IoT/Bot Computing に関する文献 : https://www.jstage.jst.go.jp/article/ipsjjip/30/0/30_251/_article/-char/ja/ , https://www.iaiai.org/journals/index.php/IEE/article/view/420 , https://jglobal.jst.go.jp/detail?JGLOBAL_ID=201902203552279112 )

（ただ、MQTTがIoTのサーバ-エッジ間通信のプロトコルの標準になりそうな気配もあるので、将来はそちらに変更した方が良いかもしれません。 Raspberry Pi Pico Wで使うことができる、[MQTT client](https://tech-and-investment.com/raspberrypi-picow-6-mqtt/) も存在するようです）

Wikiは人間同士の共同作業を推進するのによく使われるツールです。Wiki IoT/Bot Computing は、Wikiを人間だけでなく、人間と機械(Bot)との共同作業に使おうとするものです(図1)。
![man-machine-collabo-using-wiki-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/b768134d-9d75-57a2-6719-9ac2f8a175f1.png)
図1. Wiki IoT/Bot Computing を使った人間と機械の共同作業

Wiki IoTはインターネット上のWikiページと、センサが接続されたWiki Botや、 センサネットワークと通信が可能なWiki Bot/Gateway や、センサ及びセンサネットワークが接続されていない Wiki Botから構成されています。
センサだけでなく, アクチュエータを使うこともできます(図2)。
![wiki_iot_bot_computing_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/f967345f-0808-bbdb-8d8c-34e0beb662a9.png)
図2. Wiki IoT

# Pico Bot
Wiki Botはスクリプトインタープリタであり、Wiki Botの振る舞いは、Wiki ページに記述されたスクリプトによって定まります。Wiki ページのスクリプトは、コマンドの列とMicro Pythonプログラムで構成されます。
Wiki Bot のハードウェアとして、Android 端末にセンサやアクチュエータを付けたArduino ボードを接続したもの、Raspberry Pi のGPIOにセンサやアクチュエータを接続したもの、GPIOには何も接続していないRaspberry Pi、Windows パソコン、などが利用できますが、ここでは、Pico W をWiki Botのハードウェアとして利用することについて述べます。
このBotを Pico Bot と呼ぶことにします。 また、従来のRaspberry Pi で動作させていたBotのことをPi Bot と呼ぶことにします。

## Pico Bot の動作
Wiki IoT/Bot Computingでは、Pico Bot (Wiki Bot) が、定期的に、そのスクリプトが記述されたWiki ページを読みに行くため、多くの場合は、Pico BotとWikiページの間にNATやファイヤーウォールが存在しても、Wikiページに書かれたスクリプトによって、Pico Botを制御することができます。Pico BotがWikiページを読みに行く間隔も、スクリプトで変更することができます。
Pico Botが起動した後, Pico Botは以下の手順を繰り返します(図3)。
1.	指定されたWikiページに書かれたスクリプトを読み込みます。このWikiページのことを, Object ページと呼びます。現在、最初に読むObjectページのURLは、Pico Wのプログラムに記述しています
2.	スクリプトを実行します。 このとき, Pico Wに取り付けられたセンサの設定を行ったり, センサからデータを入力したり、入力したデータを処理したり Pico Wに取り付けられたアクチュエータへデータを送って動かしたりすることができます
3.	スクリプトの実行結果などを、スクリプトに従って、指定されたObjectページに書き込みます（書き込まない場合もあります）

![pico_bot_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/0fc01680-3969-15d2-8730-eea069b94456.png)
図3. Pico Botの動作

## Pico Wiki Driver
Pico Bot は、Puki Wiki のページに書かれたスクリプトとデータを読み込み、スクリプトに従って、センサからの入力やアクチュエータへの出力や計算を行い、その結果をPuki Wikiのページのデータ部分に書き戻します。この、Puki Wiki のページの読み書きを実現するため、[Pico Wiki Driver](https://qiita.com/takashiyamanoue/items/03d068a37b79d77bd0e0) と名付けたプログラム(Python のclass, class pico_wiki_driver)を作成しました。Pico Wiki Driverは、PukiWiki のAPIになるわけです。

## class pico_wiki_bot
Pico Wiki Driver を使って、Pico Botを実現するプログラム、class pico_wiki_bot を作成しました。このclass の中に、method `def read_eval_print_loop(self):`　が定義されていて、このmethod を実行することで、スクリプトの読み込み、スクリプトの実行、実行結果の描き戻し、を繰り返し行います。[このページ](https://www2.yama-lab.org/IoT/pico_wiki/index.php?pico_wiki_driver_ex05.py)の、pico_wiki_driver_ex05.py に class pico_wiki_bot を含んだプログラム例を示します。

## Pico Bot の使い方
Pico Botは、以下のようにして作成・設定し、実行を開始します。
1. Pico Wに、入出力素子を接続し、Pico Botのハードウェアを作成します
2. Pico Bot を制御し, Pico Botの実行結果を格納するPukiWiki ページを作成します。このページの書き方についてはのちほど詳しく述べます
3.  [このサイト](https://www2.yama-lab.org/IoT/pico_wiki/index.php?pico_wiki_driver_ex05.py)から、pico_wiki_driver_ex05.py のプログラムをコピーします
4. [MicroPico](https://github.com/paulober/MicroPico)([参考](https://qiita.com/IoriGunji/items/ebe6cefd3562639f7ca2))をインストールしたVSCode や [Thonny](https://thonny.org) などのIDE等で、コピーしたプログラムをプログラム編集領域にペーストし、このプログラムを以下の様に編集します

4.1  プログラムの先頭近くにある、以下の部分を書き換えて、使えるWi-Fi アクセスポイントのSSIDとパスワードを設定します

```Python
ssid = 'Wi-Fi_Access_Point_SSID'
password = 'Wi_Fi_Access_Point_Password' 
```

4.2  プログラムの先頭近くにある、以下の部分を書き換えて、2のPukiWikiのURLとページを設定します
 
```Python
init_url="http://192.168.1.16/pukiwiki/pico_wiki/"
init_page="20240131-02"
```
 
5\.  編集したプログラムを1で作ったPico Botのハードウェアに書き込んで実行します

## Pico Bot の作成・実行の例
Pico Botを使った、Wiki IoT/Bot Computing システムの例として、Pico WのボードのLEDを点滅させると同時に、Pico Wに接続したタクトスイッチが押されているか否かを検出し、その状態をPukiWikiのページに表示するシステムの作成・実行例を示します。

1. PukiWikiのサーバを用意しておきます。読み書きの権限があれば、既存のPukiWikiを利用することができます(httpsで利用できるかどうかはまだ未検証です。現時点では認証があると利用できません)
(PukiWikiのインストール方法には以下の他, 色々あります。[インストール方法その１](https://qiita.com/donnma777/items/08e6b17985f5d28f6222),  [インストール方法その２](https://qiita.com/nanbuwks/items/4933c1df88967fd028bd), [インストール方法その３](https://qiita.com/nanbuwks/items/4933c1df88967fd028bd))
2. Pico WのGPIOの14番ピンとGNDの間にタクトスイッチを接続したのものを作成します(図4)

![DSC_1711.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/d5e88f97-44be-3855-43d6-5b167782b4e9.jpeg)
図4. Raspberry Pi Pico Wの14番ピンとGNDの間にタクトスイッチを接続して作成した、Pico Botのハードウェアの例

3\. 2で作成したハードウェアを動作させるスクリプトとその動作結果を格納する領域を書いたPukiWikiのページの例として、図5 のWikiページを書きます。 このページが、http://192.168.1.16/pukiwiki/pico_wiki/ の PukiWiki サーバの、20240131-02 という名前のページにあったとします。このとき、このページのURIは、http://192.168.1.16/pukiwiki/pico_wiki/?20240131-02 になります（PUkiWiki 1.5.4の場合）。また、このPukiWikiを誰でも読み書き可能な状態に設定しておきます

![20240208-wiki-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/c374d95f-4d7f-16ea-4ec8-dac3a23c844c.png)
図5. Pico Bot を動かし、その実行結果を格納するPuki Wikiのページの例

4\. [このサイト](https://www2.yama-lab.org/IoT/pico_wiki/index.php?pico_wiki_driver_ex05.py)から、pico_wiki_driver_ex05.py のプログラムをコピーします
5\. (MicroPico等をインストールした)VSCode や Thonny などのIDE等で、コピーしたプログラムをプログラム編集領域にペーストし、このプログラムを以下の様に編集します

5.1 プログラムの先頭近くにある、以下の部分を書き換えて、使えるWi-Fi アクセスポイントのSSIDとパスワードを設定します

```Python
ssid = 'Wi-Fi_Access_Point_SSID'
password = 'Wi_Fi_Access_Point_Password' 
```

5.2 プログラムの先頭近くにある、以下の部分を書き換えて、初期のPukiWikiのURLとページを設定します。この例場合、初期URLとページは書き換える前と同じになります。
 
```Python
init_url="http://192.168.1.16/pukiwiki/pico_wiki/"
init_page="20240131-02"
```

6\. 編集したプログラムをRaspberry Pi Pico に書き込み、実行します。

しばらくすると、1. で作成したPico BotのPico WのLEDが15秒くらいに1回、1.5秒、点灯するようになります。また、LEDが点いた後、消える直前のタクトスイッチのOn/Off の状態(On のときにv=0, Offのときにv=１)を表す行が2のPukiWikiのページに追加されます。このデータは最大１０行表示するようになっていて、古い行は消えていきます。

## Pico Bot を制御するPukiWikiページの書き方
Pico Botは、初期URLと初期ページで指定されたPukiWikiページの、\<pre>と\</pre>で囲まれた部分に書かれたスクリプトを繰り返し実行し、その実行結果を、この部分の`result:`の行の次の行以降に追加していきます。PukiWikiの整形記法では、行の最初に半角の空白がある行の列をHTMLに変換すると、その行の列が\<pre>と\</pre>に囲まれるので、行の左端に半角の空白を入れて以下のスクリプトを記述します。
まず、最初の行に、
```
 object_page <URI> or <URI>
```
を書きます。この行は、PukiWiki のサーバに障害が発生した場合等の, 代替のPuki WikiのURIを設定します。
次の行に、
```
 device <device> or <device> start after no write for <t> min.
```
を書きます。この行は、Pico Bot に障害が発生したとき, 代替のPico Botを利用することを表します。 この機能はまだ未実装です。この後、以下のコマンドやMicroPythonのプログラムを記述します。
 - Pico Bot がWikiPageを繰り返し読み込む間隔の設定
 ```
 command: set read_interval=<t>
 ```
この行は、Puki Wiki ページを読む間隔(t msec)を設定します。
- Pico Bot が読み込まれたスクリプトを繰り返し実行する間隔の設定
```
 command: set exec_interval=<t>
```
この行は、読み込んだPukiWiki ページのスクリプトを実行する間隔(t msec)を設定します。この値が0の場合は、PukiWiki ページのスクリプトが読まれた直後に１回だけそのスクリプトを実行することを表します。
- 実行結果の最大行数
```
 command: set report_length=<n>
```
Pico Botは、PukiWiki のスクリプトの, result: の行以降に, Pico Bot が実行した結果を追加します。実行結果の最大行数 n を指定します。あふれた行は、前の方から削除されます。
- MicroPythonのプログラムの開始
```
 command: py <プログラム名>
```
この行の後, 行の先頭に, 
```
 command: end <プログラム名> 
```
が現れるまで, 行の先頭に py: を付けた MicroPython のプログラムの行が並ぶことを表します。<プログラム名>は, そのプログラムの名前を表します。
- MicroPython のプログラム
```
 py: <MicroPythonのプログラムの行>
```
py: より後にMicroPython のプログラムの行（文）があることを表します。
- プログラムの実行
```
 command: run <プログラム名>
```
この行は、プログラム名が付いたMicropython のプログラムを実行することを表します。
- 実行結果の始まり
```
 result:
```
この行以降に, スクリプトの実行結果が並ぶことを表します。
- 実行しているPico BotのIDと最後に実行した日時
```
 current_device=”<device>”, Date=<日時>
```
Pico Bot に障害が発生したとき, 障害の発生可能性を知るために, 最後の行にPico Bot がPico BotのIDと、Wiki ページに最後にアクセスした日時を記載します。(この機能はまだ未実装)

## Pico BotのMicroPythonが利用できる関数
PukiWikiのページに書くMicroPythonのプログラムは以下の関数を利用することができます。
- `self.write_line(<line>)`
この関数は、result: 以降の結果を表す部分の最後の行に、実行結果である文字列<line>を追加することを表します。ただし、次の`self.send_result()`が実行されるまで、Pico Bot内のバッファに蓄えられます。
- `self.send_result()`
この関数は、Pico Bot内のバッファに蓄えられた実行結果をPukiWikiのページに書き出すことを表します。
- `self.clear_report()`
この関数は、Pico Bot内の実行結果を蓄えるバッファを空にすることを表します。
- `self.Pico_Time.get_now()`
この関数は、現在時刻を、`yyyy/MM/dd hh:mm:ss`形式で表された文字列として取得する関数です。

MicroPython はexec 関数を利用することができます。exec 関数を利用することによって、文字列として与えられたMicroPython のプログラムを実行することができます。 
Pico Botは、Wiki ページに書かれたMicroPython のプログラムを、exec 関数を使って実行しています。
exec 関数に、実行するプログラムのみを引数うとして渡して実行した場合、Pico Bot が持っている、実行結果を Wiki Page に書き戻す機能(関数)や、現在時刻を取り出す機能(関数)を使うことができませんでした。
exec 関数の[利用例](https://qiita.com/kammultica/items/3201f43eec53e3e56f54)を調べてみたところ、この関数の第 2 引数として、global 変数、 第 2 引数として local 変数を、dictionary として与える必要があることがわかりました。
Pico Bot では, local 変数として、```{‘self’:self}```を与えることで, exec 関数を実行している関数の self 引数から辿れる関数を実行できるようにしています。

# PukiWikiページに書くスクリプトの例
図5のスクリプトを以下に、再掲します。
```
object_page http://192.168.1.16/pukiwiki/pico_wiki/?20240131-02 or   http://192.168.1.16/pukiwiki/pico_wiki/?20240131-02
device yamaRasPiDp9_1 or yamaRasPiDp9_2 start after no write for 10 min.
command: set read_interval=15000
command: set exec_interval=0
command: set report_length=10
command: py test
py: #
py: from machine import Pin
py: import time
py: led = Pin('LED', Pin.OUT)
py: sw01 = Pin(14, Pin.IN, Pin.PULL_UP)
py: led.on()
py: time.sleep(1.5)
py: v=sw01.value()
py: led.off()
py: line="device=sw, Date="+self.Pico_Time.get_now()+", v="+str(v)
py: self.write_line(line)
py: self.send_result()
command: end test
command: run test
result:
device=sw, Date=2024/02/08 21:26:37, v=1
device=sw, Date=2024/02/08 21:26:52, v=1
device=sw, Date=2024/02/08 21:27:07, v=1
device=sw, Date=2024/02/08 21:27:22, v=1
device=sw, Date=2024/02/08 21:27:52, v=1
device=sw, Date=2024/02/08 21:28:07, v=0
device=sw, Date=2024/02/08 21:28:22, v=1
device=sw, Date=2024/02/08 21:28:37, v=0
device=sw, Date=2024/02/08 21:28:52, v=1
device=sw, Date=2024/02/08 21:29:07, v=1
current_device="yama_pico_0000_0000_0000_0001". Date=2024/02/09 21:02:36
```
- ```object_page http...```の行によって、現在実行しているPukiWikiページのサーバに障害などが発生した時、ここに書かれた代替URIを実行します
- ```device yamaRasPiDp9_1 or...```の行はこのページを実行しているWiki Bot (Pico Bot)に障害などが発生して、ページの更新が行われなくなった時、代替のWiki Bot (Pico Bot)を実行するようにします(現在機能未実装)
- ```command: set read_interval=15000```は、このページがPico Botによって15秒(15000msec)に一回読まれることを表しています
- ```command: set exec_interval=0```は、このページのスクリプトがPico Botに読まれた直後に１回だけそのスクリプトを実行することを表します
- ```command: set report_length=10```は、最大, 最近の10行がresult: 以降に表示されることを表します
- ```command: py test```と```command: end test```は、この間にMicroPython のプログラムが記述されていて、そのプログラムに test という名前が付けられていることを表します
- ```command: run test```は、testと名付けられたプログラムを実行することを表します

- ```command: py test```と```command: end test```の間の```py:```ではじまる部分がMicroPythonのプログラムの行で、以下のプログラムを表しています
```Python
 #
 from machine import Pin
 import time
 led = Pin('LED', Pin.OUT)
 sw01 = Pin(14, Pin.IN, Pin.PULL_UP)
 led.on()
 time.sleep(1.5)
 v=sw01.value()
 led.off()
 line="device=sw, Date="+self.Pico_Time.get_now()+", v="+str(v)
 self.write_line(line)
 self.send_result()
```
このプログラムは以下を行います。
1. GPIO'LED'のピンを出力に設定し、この Pin に、ledという名前をつけます
2. GPIO 14のピンを入力に設定すると同時に、Pull Up に設定(GPIO 14番Pinに何も接続されていないとき(インピーダンスが無限大のとき)、High になる)し、このPinにsw01という名前をつけます
3. led.on()で, Pico WのボードのLEDを点灯します
4. time.sleep(1.5)により、1.5秒、sleepします
5. v=sw01.value()により、sw01ピン(GPIO 14)から値を入力し、vに代入します
6. led.off()で、Pico WのボードのLEDを消灯します
7. ```line="...."```で、出力結果を表す行の文字列を作り、lineに代入します
8. self.write_line(line)で、line をPico Wのバッファに加えます
9. self.send_result()で、Pico Wのバッファの内容をPukiWikiページのresult: の後に加えます