Pico Wiki Driver, Raspberry Pi Pico Wで PukiWikiのページを読み書きする

# はじめに

Wikiは人間同士の共同作業を推進するのによく使われるツールです。[PukiWiki](https://en.wikipedia.org/wiki/PukiWiki)は、日本で開発され、日本で多く使われているWikiの一つです。PukiWikiのページをRaspberry Pi Pico W (以後 Pico W)の Micro Python のプログラムで読み書きするため、Pico Wiki Driver を開発しています（多分、いつまでたっても開発途上です）。
Pico Wiki Driver は、PukiWikiのページをPico W の Micro Python プログラムによって読み書きするためのAPIです。Web APIはCRUD (Create, Read, Update, Delete)できる必要があることになっていますが、Pico Wiki Driverは現時点では Create の機能は持っていません。ごめんなさい。したがって、Pico Wiki Driver でPukiWikiを操作するためには、事前にその操作対象となるページをPukiWikiのサーバ上で作っておく必要があります。
Deleteも、それを直接行うことはできないのですが、Updateを使って、実質的には実現可能です。
PukiWikiのページの記述で Pico Wを制御する、[Pico Bot](https://qiita.com/takashiyamanoue/items/bb4b08c0f220ba4fd91a)は、Pico Wiki Driver を使って実現しています。

# Pico Wiki Driver の利用方法
Pico Wiki Driver を使ってPico WでPukiWikiのページを読み書きさせる場合、以下の手順を実行します。
1.  [このサイト](https://www2.yama-lab.org/IoT/pico_wiki/index.php?pico_wiki_driver_ex02.py)から、pico_wiki_driver_ex02.py のプログラムをコピーします
2.  ([MicroPico](https://qiita.com/IoriGunji/items/ebe6cefd3562639f7ca2)等のextenitonsをインストールした)Visual Studio Code (VSCode)や [Thonny](https://thonny.org) などのIDE等で、コピーしたプログラムをプログラム編集領域にペーストし、このプログラムを以下の様に編集します
(VSCodeとMicroPico を使ったプログラミング環境構築については[ここ](https://qiita.com/takashiyamanoue/items/4c109c0d214e223dd211)が参考になると幸いです)

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

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

　2.2 プログラムの先頭近くにある、以下の部分を書き換えて、初期のPukiWikiのURLとページを設定します
 
```Python
init_url="http://192.168.13.30/pukiwiki/pico_wiki/"
init_page="20240207-01"
```

　2.3 Puki Wiki Driverを使った、このPukiWikiのページの読み書きするプログラムを書き加えます。pico_wiki_driver_ex02.py の最後の部分に、PukiWikiページを読んだり、書き込んだりする例題プログラムが書き込まれています
3. 編集したプログラムをRaspberry Pi Pico に書き込んで実行します


# Pico Wiki Driver の利用例

図1 の PukiWiki のページが、http://192.168.13.30/pukiwiki/pico_wiki/ の PukiWiki サーバの、20240207-01 という名前のページにあったとします。このとき、このページのURIは、http://192.168.13.30/pukiwiki/pico_wiki/?20240207-01 になります（PUkiWiki 1.5.4の場合）。また、このPukiWikiを誰でも読み書き可能な状態に設定しておきます。
![20240207-wiki-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/630c824e-3ba6-98bd-a61f-1058f988e3d5.png)
図1. Pico Wiki Driver が読み書きするPuki Wikiのページの例

[Pico Wiki Driver の利用](#Pico-Wiki-Driver-の利用例)に従って、以下を行います。

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

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

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

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

 2.3 Puki Wiki Driverを使った、このPukiWikiのページの読み書きするプログラムを書き加えます。pico_wiki_driver_ex02.py の最後の部分に、PukiWikiページを読んだり、書き込んだりする例題プログラムが書き込まれています。今回は、この例題のプログラムのままにしておきます。
以下、その部分です

```Python
def main(): 
    bot=pico_wiki_driver(init_url,init_page)
    print("-----------get_wiki_page-----------------")
    print(bot.get_wiki_page())
    print("-----------get_wiki_source-----------------")
    print(bot.get_wiki_source())
    print("-----------replace_wiki_page-----------------")
    print(bot.replace_wiki_page(" abcdefgh, \n Hello, world!"))
    print("-----------get_wiki_source-----------------")
    print(bot.get_wiki_source())
 
main()
```

3 .  編集したプログラムをPico Wに書き込み、実行します

このプログラムの実行がはじまると、

```Python
    bot=pico_wiki_driver(init_url,init_page)
```

によって、これから読み書きするPukiWikiのページのURLとページが設定された puki_wiki_driverのオブジェクト、bot、が生成されます。

次に、CRUDの R (Read)を実行するメソッド、bot.get_wiki_page()

```Python
    print("-----------get_wiki_page-----------------")
    print(bot.get_wiki_page())
```

によって、該当のWikiページが読み込まれ、IDEのコンソールに、

```
 -----------get_wiki_page-----------------
 <pre>
 abcdefgh,
 Hello, world!
 </pre>
```

 が表示されます。
 次に、CRUDの、もう一つの R (Read)を実行するメソッド、bot.get_wiki_source()

```Python
    print("-----------get_wiki_source-----------------")
    print(bot.get_wiki_source())
```

によって、IDEのコンソールに、該当のWikiページのPukiWikiのソーステキスト

```
 -----------get_wiki_source-----------------
 abcdefgh,
 Hello, world!
```

 が表示されます。
 次に、CRUDの U (Update)を実行するメソッド、bot.replace_wiki_page("<文字列>")

```Python
    print("-----------replace_wiki_page-----------------")
    print(bot.replace_wiki_page(" abcdefgh, \n Hello, world!"))
```
によって、URI http://192.168.13.30/pukiwiki/pico_wiki/?20240207-01 のページが

```
 abcdefgh,
 Hello, world!
```

に書き換えられます。ここで、" abcefgth, \n Hello, world!"が新たにWikiページに記述されるページ内容を表しています。PukiWikiのテキスト整形記法では、行の左端に半角の空白がある場合、その右側の文字列は、整形記法を無視してそのまま表示されることになっています。\\n は改行記号ですので、上の様にページが書き換えられることになります。
その次の、
```Python
    print("-----------get_wiki_source-----------------")
    print(bot.get_wiki_source())
```
によって、IDEのコンソールに、書き換えたページの内容(PukiWikiの整形記法での表示)が以下の様に表示されます。
```
 -----------get_wiki_source-----------------
 abcdefgh,
 Hello, world!
```

## Pico Wiki Driver の主なメソッド（関数）
class pico_wiki_driver は、以下のメソッドを持っています。
- class pico_wiki_driver のコンストラクタ(def __init__(self,url,page):)
自分自身のオブジェクトへのポインタ self の他に, 読み書きする Puki Wiki のサーバの url (文字列)と, そのサーバ内の読み書きする page (文字列)の２つの引数を持ちます。
```
bot=pico_wiki_driver(init_url,init_page)
```
を実行すると, Puki Wikiのinit_urlのサーバの、init_pageのページを読み書きするための、pico_wiki_driverのオブジェクトが生成されます。

- def get_wiki_page(self): 
コンストラクタで与えられた Puki Wiki サーバの、page (Puki Wikiのページ)の html format のテキストを取り出します。Web API のCRUDの R (Read)を行うメソッドです。

- def get_wiki_source(self):
コンストラクタで与えられた Puki Wiki サーバの、page (Puki Wikiのページ)の ソーステキスト(PukiWikiの整形記法で記述された、整形前のテキスト)を取り出します。Web APIのCRUDの R (Read)を行う、もう一つのメソッドです。

- def replace_wiki_source(self, wiki):
コンストラクタで与えられた Puki Wiki サーバの、page (Puki Wikiのページ)を、引数 wiki で与えたPukiWikiのソーステキスト(PukiWikiの整形記法で記述された、整形前のテキスト)に入れ替えます。Web APIのCRUDの U (Update)を行うメソッドです。

# Pico Wiki Driver の開発
Raspberry Pi PicoのMicroPythonはnetworkに関する以下のモジュールを持っています。
- Wi-Fi に接続するためのモジュール、network
- TCP/IPを使うためのモジュール、socket
- HTTP通信を行うためのモジュール、urequests
- 時間に関するモジュール、time

まず、これらのモジュールがあることや、どのように使うことができるか？などの勉強をしました。また、これらのモジュールを自分好みにして使うための、wrapper クラス、pico_net、pico_http、pico_time([このページ](http://wisteriahill.sakura.ne.jp/CMS/WordPress/2023/05/10/raspberry-pi-pico-w-ntp-server/)を利用させていただきました) を作りました。

Pico Wiki Driver は、Pico W でPukiWikiのWikiページのWeb Scraping を行うことで実現しています。Pico Wで Seleniumのような、Web Scraping を容易にするライブラリが使えれば楽なのですが、すぐには見当たらなかったので、[Wireshark](https://www.wireshark.org)を使ってPukiWikiを読み書きしているときの、HTTPクライアント(Webブラウザ)とサーバ(PukiWikiサーバ)間の通信内容を入手しました。これにより、どのボタンが押されたら、HTTPクライアントの、どのメソッド(GETまたはPOST)が使われて、どのようなデータのやりとりが行われるか？を解析しました。その解析結果と、表示状態の時と編集の時のPukiWikiのページのソース(html)を照らし合わせた結果を元に、自作のHTMLのパーサ、class html_parser を使って、PukiWikiを読み書きするのに必要な部分を取り出すプログラムを作成しました。
class html_parserは、パーサというよりは、HTMLの特定の部分を見つけたり、取り出したりするライブラリ、と言った方がよいかもしれません。

# 開発の途中ではまったところ
開発途中でいろいろ苦労ました。皆様のお役に立つかもしれないので、苦労した点を以下に書きます。
## VSCooce で作成するソースプログラムのPATH
今回、Pico Wの開発環境として、VSCodeに、拡張機能のMicroPico をインストールして利用しました(内緒ですが、Github Copilotも使いました)。
ところが、最初の例題のLチカプログラムを動かそうとしても、動きません。どうも人によってはちゃんと動くようです。以前、開発環境のPATHに日本語や空白が入っていたら動かなくなる、という現象があるよ、聞いたことを思い出したので、ソースの場所を日本語が入らない場所に移動しました。そうしたらこの問題が解決しました。

## MicroPico がうごかなくなる
どうも[この](https://github.com/paulober/MicroPico/issues/65)問題に突き当たったみたいで、うんともすんとも動かなくなりました。以下、その経緯です。

- VSCodeにMicroPicoを入れて、Pico Wの programmingをしていたのですが、なんらかのタイミングで突然、terminalのpromptが >>> ではなく、 > になり、Picoでプログラムが実行できなくなりました
- Thonnyだと、同じプログラムでちゃんと Pico Wでプログラムが実行できました
- MicroPicoの削除と再インストールをしてもダメでした
- VSCodeのプログラムの extentions が格納されているところで、MicroPicoの部分を削除してからMiciroPicoを再インストールすれば良い、というページがあったので、それをやってみたのですが、ダメでした
- なんか、いろいろいじくっていると、Terminalのところに、conda が実行できない、というメッセージがでてきているのに気が付きました
- Anacondaを再インストールしましたがだめでした
- 同様の現象の原因と解決策として、condaへのPathが消えるので、それを直せばよい、という[記事](https://qiita.com/nikujagakun/items/73ae11dc4caec4af8d02)を見つけました
- Windowsの環境変数の設定のところで、condaへのpathを追加しました（condaの場所を探すのに苦労しました。C:\ProgramDdata の下にありました）
- それでも状況は変わりませんでした
- その後、なんかいろいろいじっていたら、なぜか、治りました

## USB ケーブルの接触不良
Pico W に USB ケーブルを挿したまま, その Pico W を職場と自宅の間で持ち運びながら開発をしていたところ, 突然, 図2のようなメッセージが現れて, 開発に使っていた PC と Pico W が接続できなくなりました。
以前, [着る電光掲示板](https://www2.yama-lab.org/WearableDigitalSignage-IoT/index.phpを長い間使っていたところ, 着る電光掲示板につかっているスマホと Arduino Mega ADKの間の USB ケーブルの, スマホ側のコネクタの接触不良が発生しました。このとき, 使っていないときにこのコネクタを抜いて保存しておくことにより, この障害が発生しにくくなることがわかっていたので, USB ケーブルを交換してみました。その結果, 再び, PC と Pico W の間の接続が復活しました。
USB ケーブルの micro-B コネクタは, 振動が発生する環境で, 長い間接続したままにしていると, 端子のばねが弱り, 接触不良が発生する可能性があると思われます。

![20240127-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/80734fc6-dcf3-915c-4fb7-1caeb7fc891a.png)
図 2. PC と Raspberry Pi Pico W (Pico W)の間の接続不良

## メモリ不足
Pico W は SRAM 256KB, フラッシュメモリ 2MB を利用することができます。これは, 1980 年代のパソコンと同等以上の容量ですが、Raspberry Pi の, メモリ 512KB～ , 外付けフラッシュメモリ(標準イメージ 8GB 以上推奨)と比べると少ないです。 
Pico Wiki Driver および Pico Bot は HTML ファイルをWiki ページから読み込み, Web Scraping[8]を行い, 結果を書き戻すため, Web ページや結果の大きさに比例したメモリを消費することが予想できます。
開発の途中で図 3のようなエラーが頻繁に発生しました。このエラーを解消するため以下のような対策を行いました。
- 文字列の+演算をできるだけ使わない
- 長い文字列配列へのポインタを利用することにより, 
短い文字列をできるだけ使わないようにする 
- HTTP の GET メソッド, PUT メソッド, POST メソッ
ドの発生頻度を抑える
- 大量の文字列処理が発生しそうな場所の前および最
中に, gc.collect()を入れる

![20240202-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/88e7e16b-65bc-58c1-2747-4daf7effa248.png)
図 3. 開発中に発生した MemoryError 

以上の対策を行うことで, MemoryError の出現頻度を下げることができました。しかしながら、一度止めて再起動させるとき, 電源を切るなどしてリセットしないと MemoryError が発生する場合があります。

## ValueError 
古いバージョンのPukiWiki であれば, 例で示したプログラムが動くようにななりましたが、新しいバージョン(1.5.4)の PukiWiki で例のプログラムを実行させると、bot.replace_wiki_page("...")の実行時の、Wiki ページの更新のための POST method 実行後に, 図 4 の ValueError が発生して停止しました。このとき, Wiki ページの内容は書き換わっていました。
このときの, Stack Trace が表示されていますが、エラーが発生した場所のソースは組込み部分のためか, 表示することはできませんでした。Web サーバ側の HTTP レスポンスステータスコードを見ると 302 が表示されており, ページのre-direct が発生していることが伺えます。しかしながら, Wikiのページの更新はページの re-direct が発生するはずなので、その code である可能性もああります。とりあえず、このエラーが発生する部分を、try: exception: で囲って、対策しています。
![20240202-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/cc0c1da5-d382-5b73-9a99-13a531d9a56e.png)
図4. ValueError