現在の天気(予報)⛅☔と、時刻を表示する、「天気時計」を作って見ようと思います。
使うのは、FPGAボード(Sipeed Lichee Tang Primer)と、オリジナル16ビットCPUコアMoscovium、ESP32、OLEDディスプレイです(下図)。
時計表示の方は、これまでにも何度かプログラムしてきているし、難しいことは無いでしょう。
「#11-5 WiFi電波時計」
「#18-1 WBGT時計の設計」
等。
問題なのは、インターネットからの気象情報の読み取りです。
インターネット上の適切なサイトからHTMLを取得、その中から気象情報を読み取って表示しなくてはなりません。サイト毎にHTMLの構成が異なるので、解析プログラム部分の作り替えが必要となります。→ どのページにも共通して使える様な、汎用のプログラムにはならない、と言う事です。
なのでまず、参照するサイトを固定しておきます。本ページのプログラムがアクセスするのは、以下のページです。
「Yahoo!天気・災害」
Yahoo!天気・災害サイトから、都市毎の天気予報を提供する「ピンポイント天気」ページを参照します。
メモリが潤沢で高速な、PCなりスマホなりと言った機器であれば、そう難しい処理ではないのですが、24MHz程でRAM容量も限られているMCU(組み込み用途)でこれを実行するのは結構しんどいのです。
今回使用する「Yahoo!天気・災害」サイトのピンポイント天気ページですが、執筆時点で確認したところ、HTMLサイズはざっと80Kバイトでした。あくまで「執筆時点」では。今後、サイトの仕様などが変更となると、増減する可能性があります。
すると、「HTMLデータを受信しきった後で解析する」様なわけには行かないので、ストリーム解析が必要となります。
ストリーム解析:
具体的手順は後ほど改めて議論します。
ESP32を使って、インターネットへアクセスします。無線LAN(WiFi)を使った無線アクセスでインターネット通信が可能な上、ATコマンドを使ってUARTから制御可能なので、便利なツールです。
ESP32が、以下の様なセッティングになっていることを前提として進めます。
ESP32の設定:
過去に何度か使ってきた設定と同じですので、過去記事を参照してセットアップしておいてください。
今回使おうと思っているYahoo!天気・災害サイトは、UTF-8でHTMLデータを送ってきます。日本語のサイトなので、気象情報は、「晴れ」とか「雨」とか、日本語で記載されています。つまり、何とかHTMLを解析して気象情報を抜き出したとしても、文字コードがUTF-8のままでは表示ができません。
UTF-8 → S-JISコード変換が必要となります。このコード変換は、機械的に変換することができず、「変換テーブル」を持つ必要があります。約7,000文字の日本語文字に対する変換テーブルはそれなりのサイズになるため、これも、メモリ容量の限られた組み込み用途では大きな障害となります。
組み込み用途でのコード変換の課題については、以下の記事で議論して、解決済みですので、改めてご確認ください。
「#21-10 文字コード変換」
「#22-1 文字コード変換回路」
unicode(UTF-8)は、世界中からアクセスされるインターネット上のWEBページでこそ、存在意義があり、真価を発揮するので、HTMLがUTF-8で符号化されるのは当然の流れと承知しています。
これに対応するため、unicode⇔S-JIS変換UNISJIユニットを作ったので、ここが正に使い所なのです。
何かしらのアプリケーションを考え、それを実現しようと設計を進めたところ、「この処理はハードウェアにやらせた方が効率が高い」ってところが必ず出てきます。そんな時、FPGAボード(下図)と言うツールは本当に便利なもので、自分で必要な回路を設計して、オリジナルマイコンに組み込むことができてしまいます。
Arduino UNO等、既成マイコンの外部に、ブレッドボードなどでちょっとした回路を追加して、色々な電子工作を実施もしていますが、FPGAボードに書き込むことのできる回路は、規模が全く異なる(圧倒的に大きい)ので、unicode⇔S-JIS変換UNISJIユニットなんてものも、自前で設計して使えてしまいます。Good。
地域名や天気予報などが日本語文字列なので、日本語フォントFONTJPユニットを使って、OLEDディスプレイに表示します。
「#18-4 Moscoviumと日本語フォント」
上の記事で議論したように、日本語文字が無駄なく詰め込まれている区点コードに従って、日本語フォントを保持しています。このため、FONTJPユニットを使って日本語フォントを引き出すためには、区点コードをベースにした文字コードが必要となります。
FONTJPユニットは、区点コードをベースとしたS-JISコードを元に、日本語フォントを引き出すようになっています。
区点コードとコード体系の異なるunicode(UTF-8)では、日本語フォントを引き出すことができません。
Moscoviumマイコンには、既にunicode⇒S-JIS変換UNISJIユニットがあるので、死角なしです。
気象情報を得るため、インターネット上のホームページから、HTMLデータを取得します。これを実行するためには、HTTP(Hyper Text Transfer Protocol)を喋る必要があります。
※ああ、「HTTPプロトコル」って書いてしまうと、「プロトコル」が重なってしまうのね……。SNTPも、SMTPも全部そうなのか〜。軽く流しておいてくださいm(1__)m。
HTTPの仕様書は、IETF(Internet Engineering Task Force)という組織が、RFC(Request For Comments)というドキュメントで規定しています。RFCは随時アップデートされていて、今最新のRFCが、新しいRFCで上書きされて古くなる、ことはよくある事です。
執筆時点でHTTPのRFCはRFC-7230〜7235辺りが最新です。詳しくはこちらを参照してください。
HTTPの話に戻ると、ホームページからHTMLを取得するだけなら、片言で喋ることができたらOKです。使うのは、「GETメソッド」だけです。既に昔一度使っていて解説済みなので、こちらもご確認ください。
HTTP-GETメソッド:
ここで、何気に重要なのは、「取得したHTMLのサイズはいくらなの?」と、言う事です。
ESP32を制御してHTTPサーバから受信したデータは、ESP32との間の非同期シリアル通信で受信して行きます。ESP32側に、まだ送信すべきデータが存在している時にシリアルからデータを読み出すのは問題ありません。ですが、ESP32側に、送信すべきデータが無くなってしまった後で、更に読み出そうとするとハングアップしてしまいます。
つまり、受信したHTMLデータが全部で何バイトになるのか判らないと、どこで受信を止めて良いのかわからないのです。
GETメソッドで受け取ったデータサイズは、レスポンスヘッダの中に記載される「Content-Length:」ヘッダまたは、「Transfer-Encoding:」ヘッダで知ることができます。
Yahoo!天気・災害サイトは、「Transfer-Encoding: chunked」らしいので、これを前提として受信することとします。
他には「Content-Length:」ヘッダでサイズが通知される場合もあるのですが、今回は未対応です。
どうせ、選択するサイト次第で、解析プログラムを変更する必要が出てくるので、Yahoo!天気・災害サイトを選んだ時点で、そのサイトに特化してプログラムしても問題ないはずです。
※将来、Yahoo!天気・災害サイトの仕様が変わって、「Transfer-Encoding: chunked」が他の方式に変更された場合、エラーとなって、取得に失敗します。これは止む無し。HTTPが既定するすべての方法で受信できるようにプログラムするのは、メモリと処理速度に制限のある組み込み用途では厳しいためです。
「Transfer-Encoding: chunked」は、以下の様な構造になっています(ざっくり簡単化、正確なところはRFC-7230(またはその最新版)を参照してください)。
<チャンクサイズ1><CRLF>
<データ×チャンクサイズ1><CRLF>
<チャンクサイズ2><CRLF>
<データ×チャンクサイズ2><CRLF>
:(繰り返し)
<チャンクサイズN=0><CRLF>
<CRLF>
それ以降受信しようとしても、サーバがデータを送ってこないので、ハングアップしてしまいます。データがどこで終わるのかを知るためのチャンクなので、何バイト受信したかカウントしながら受信して行く必要があります。
メモリと処理速度に制限のある組み込み用途故、ここまでの間に色々と制限を設けましたので、以下にまとめておきます。
今回選択したYahoo!天気・災害サイトでは、上の制限で問題ありませんでした(執筆当時)。将来、サイトの仕様が変わった場合は、エラーになる場合があります。
ここで、GETメソッドへのYahoo!天気・災害サイト(HTTPサーバ)からの応答について、まとめておきます。
HTTP/1.1 200 OK<CRLF>
<レスポンスヘッダ><CRLF> …… ヘッダの1つが「Transfer-Encoding: chunked」
:(繰り返し)
<CRLF>
<チャンクサイズ1><CRLF>
<データ×チャンクサイズ1><CRLF>
<チャンクサイズ2><CRLF>
<データ×チャンクサイズ2><CRLF>
:(繰り返し)
<チャンクサイズN=0><CRLF>
<CRLF>
これだけ解っていれば、片言のHTTPを喋って、Yahoo!天気・災害サイトからHTMLデータを取得することが可能となります。
※「How much is this?〜Too expensive, discount please.」てな具合です(片言)。嫌な観光客だな〜(1^^;)。
他のサイトから受信する場合は、サイト毎に方式が異なる可能性があるので、HTTPのRFCを読み込んで、もっと深く理解する必要があります。
ここまでの節にて、必要な技術の予備調査が終わりましたので、本節以降、具体的なプログラムに落とし込む詳細設計に移ります。
Yahoo!天気・災害サイトから取得したHTMLをストリームで解析して、その中から必要な気象情報を抜き出すプログラムを作ります。
HTML(Hyper Text Markup Language)も進化していて、より目を引くページを作るためにいろいろな機能が備わっています。HTML仕様については、以下のリンクから検索してみてください。
HTMLの仕様を検索する(別ウインドウが開きます)
さて、普通のテキストファイルなどの中身を解析する時、どういう手順を使うか?と言うと、普通は、「改行コードまでの1行」を読み込んで、行毎に解析して行きます。
ところが、HTMLには改行と言う概念がありません。
HTMLに於いて、表示用の改行は「<br>」と言うタグで制御していますが、「<br>」タグの直後に改行コード(CR+LF)が来ても来なくても良い仕様です。更に困ったことに、何処に改行コードを挟んでも良い様な仕様なのです。
例えば、「東京都の週間天気予報」という1行は、以下の様に途中に改行コードを挟んでも良いことになっています。
東京都
の週間
天気予報<br>20日:<br>
上例の様に、<br>の後ろに次の行のデータが改行コードなしで続く事だってあり得ます。「改行しても、しなくても良い」ってところが厄介です。
つまり普通のテキストファイルの場合によく使われる、「改行コードまでの1行を読み込んで解析」と言う手段は、基本的には使えません。
まずここから解決して行きましょう。
HTMLは、「<」と「>」で括られたタグと言う物で表示を制御する言語です。そこで、「タグ」を1つの単位として読み込み、解析することにしてみます。
HTMLデータの読み込み手順:
この動作をするhtml_get_lineサブルーチンを作りました。詳細は、次回完成させるプログラム(weather_clock.asm)で確認して見てください。html_get_lineサブルーチンから呼び出して、HTMLから1バイトを取り出すhtml_get_byteサブルーチン内で、チャンクの処理を実装しているので、併せて確認してください。
Yahoo!天気・災害サイトのピンポイント天気ページは、天気予報が3時間毎の表に記載されています。
本ページでサンプルとして使う兵庫県伊丹市のピンポイント天気ページ:
URL: https://weather.yahoo.co.jp/weather/28/6310/28207.html
上のリンクを開いて、これから読み込もうとしている、3時間毎の天気予報を示した表の形式を確認しておいてください。ブラウザによっては、HTMLのソースコードを閲覧する機能を持っているものもあるので、ソースコードを眺めながら以下の説明を読めば、より理解を深められるでしょう。
HTMLの表:
HTMLの表は、<table>〜<tbody>〜</tbody></table>と言う構造をしている。
<tbody>〜</tbody>の中に、行データ<tr>〜</tr>が行数分あり、
更に<tr>〜</tr>の中に列データ<td>〜</td>が列数分ある。
階層構造。
前節で設計した、HTMLからタグ毎に読み出すhtml_get_lineサブルーチンを使ってデータを取り出し、解析して行きます。
各「<tr>」タグ(行データ)の中で、「<td>」タグ(列データ)が×9列(個)登場します。例えば時刻行は、「時刻/0時/3時/6時/9時/12時/15時/18時/21時」の、9列が読み出せます。
簡単化のため、「<td>」タグ×9列づつ登場することを前提として、「<tr>」タグを探さずに「<td>」タグだけを検索してしまう事に決めます。
テーブル全体をマトリクスとして取得するのは無駄なので、最初に現在時刻から必要な列番号を計算しておいて、目的の列番号だけ抜き出して取り込みます。3時間毎の表なので、列番号は以下式で計算できます。
列番号=int (<24時間制時>÷3)
地域名と天気予報は「伊丹市」、「晴れ」等の様に日本語の文字列なので、取り込んだ後に、UNISJIユニットを使ってUTF-8 → S-JISコードに変換します。
気温、湿度、降水量は、ASCIIコードの整数値なので、そのままatoiサブルーチンが使えます。
詳細設計途中ですが、一旦ここで切って、完成は次回へ持ち越します。実は、山積みだった課題も、日本語フォントFONTJPユニットや、unicode⇒S-JIS変換UNISJIユニット等の設計によって1つ1つ乗り越えて来ていました。本ページだけ読んでしまうと、「課題はほとんど解決済みだったじゃないか」と、思われるかもしれませんが、本HP通して、課題を解決しながら新しいテーマに取り組んでおります。