公開日:2023/09/02
title.png

27th Season


#27-1 カレンダ付腕時計

 M5StickC Plusを使って、時計の中でも特に、腕時計⌚をイメージした物を作ろうと思います。M5StickC Plusなので、WiFiには接続できるのですが、スマートウォッチと言えるほどの物ではありません。普通、カレンダ機能付きの腕時計は、今日が何月何日か?を表示することができるようになっています。

 今回作るカレンダ時計は、単に「○月○日、○時○分○秒」等と表示するだけではなくて、今月のカレンダを全体表示した上で、

を、直観的に感じられる様な腕時計にしたいと思います。

 M5StickC PlusのTFTディスプレイは小さくて狭いので、ひと月分のカレンダを表示すると、もう、いっぱい、いっぱいです。あくまで、カレンダ表示を主として、現在時刻および、バッテリ残量表示は二の次(おまけ)とします。おまけなので、表示サイズは小さいけれど、ちゃんと表示してあるので、腕時計としては、機能するはずです。
 あと、実用的に腕時計として使うには、M5StickC Plus用の「腕時計アクセサリ」等が必要となります。ちなみに、筆者は持っていません……m(1^_^;)m。

カレンダの描画

 一般的に、ひと月分のカレンダは、横方向に曜日、縦方向に週が並び、そこへ日付を1から当該月の最終日まで、左から右/上から下の順で埋めることで表示されます。従って、カレンダを描画するのに必要な情報は以下の二点です。

 これまで本HPにて解説済みの技術を使って描画が可能です。上に張っておいたリンク先を参照して見てください。それほど難しいことではないので、UIFlowを使って実現して見ようと思います。

 ただし、残る課題として、祝日の表示を挙げることができます。

 公式や一定のルールに当てはまらないので、各年の祝日データを用意して、描画しなければなりません。

祝日データをインターネットから取得する

 拙作iConClock(Windows版ソフトウェア)も、同様に各年の祝日をCSVデータとして配布しています。
 CSVデータは、毎年2月に筆者が更新しています。このCSVデータは、iConClockのHPからダウンロードできるようになっているので、CSVデータをインターネットから取得して使うと、カレンダに祝日データを反映することができます。

 CSVデータは、筆者が毎年更新しているので、ご使用になる皆様から見ると、「自動的に毎年の祝日データが更新される」様に見えるはずです。

HTTPブロック

 インターネットのWebサイトからデータを取得するには、HTTPブロックを使います(下図)。

a271_httpblk.png

 HTTPプロトコルについては、こちらをご確認ください。データの取得には、GETメソッドを使います。上図のHttp Requestブロックを配置して:

 HTTPリクエストが成功したら、Get Dataブロックを使うと、取得したデータを参照することができます。聞くだけだと、簡単ですよね?でも、このブロック、少し癖があって、そう簡単には行きませんでした。

 iConClockのHPから、祝日のCSVデータダウンロードコーナを確認して、CSVデータのURLをHTTPブロックに指定しました。
 HTTPブロック自体は成功するので、「Success」内のコードが実行されるのですが、この中でGet Dataブロックを実行すると、エラーとなってデータが取得できません。
 何でかな?と、色々試してみたところ、どうやら、S-JIS符号化されたテキストデータが嫌われている模様です。試しに2023年のCSVデータだけをUTF-8で符号化してみたところ、正常に取得できました。

 ふぅ〜ん。

 でも、筆者は反unicode派なので、データをUTF-8に変更するつもりは一切ありませぬ。
 S-JISコードは、「元日」など、祝日の名前を日本語で記載するために使っているのですが、いま必要なのは、祝日の日付データだけです。日付データは、普通にASCIIコードで記載してあるので、S-JIS部分は無視して取り込んでくれたらそれで良いのだけど、エラーではじくというのは頂けない。
 まあ、もう少しあがいてみましょう。

HTTPブロックのSuccessとFailには注意

 時に、前節で示したHTTPブロックのSuccessとFailには注意、と言うお話です。

 HTTPリクエストを投げると、正常な反応としては、ステータスコード=200番台が返ってきます。一方、エラーが起きると、400番台、500番台のエラーステータスが返ってきます。

 このイメージでSuccessとFailを考えては、だめです。

 よく見るエラーステータス=404 not found、なんて、使う側から見れば「読み出し失敗(Fail)」なのですが、HTTPブロックにとってはSuccessだからです。

 「HTTPブロックは正しく実行できて(Success)、ステータス404が返って来た」って解釈みたいです。

 なので、ステータスが200番台であれ、400番台であれ、Successが実行されます。HTTPブロックを使う場合には、Success処理内で、Get Status Codeブロックを使ってHTTPリクエストのステータスコードを確認し、エラー処理を別途実行する必要があります。

 更に更に、完成したUIFlowプログラムをPythonのコードで確認してみると、HTTPブロックのSuccess/Fail分岐には、try〜exceptが使われています。これは、tryからexceptまでの間で致命的なエラーが発生して例外(exception)が投げられた場合に、except以下のコードが実行されるステートメントです。
 要は、HTTPブロックの実行に際して、致命的なエラーが発生して例外が投げられた時、初めてFailが実行される訳です。が、try〜exceptの間には、Success内の処理も記載されています。つまり、Success処理内で、HTTPリクエスト以外の致命的エラーが発生した場合にも、Fail処理に飛んでしまうのです。

 実は、S-JISコードが符号化できなかったGet Dataブロックが例外を投げるのですが、この時Success処理とFail処理が両方とも実行されている様に見えて、大混乱してしまいました。
 そういう物(仕様)だと判っていれば、対処方法はあるので、単なる注意喚起です。

S-JISデータのハンドリング

 話を元に戻して、S-JIS符号化されたテキストデータの中から、ASCIIコードで記載された部分のデータだけを取り出して使う事はできないのか?あがいてみます。

 iConClockのHPから取得したCSVファイル(S-JIS符号化)がエラーとなるのは以下の場所です。

a271_getdata.png

 そもそもUIFlowはブロックで作ったプログラムをPythonというスクリプト言語に変換して実行しています。上のコード部分をPythonのコードで確認してみると、以下の様な記述に変換されています。

  lst = str (req.text);

 HTTPブロックで取得したHTTPデータは、reqというクラスで受けられています。そこからtextメソッドを使うと、取得したデータを参照できる、と言うのが通常の流れです。
 実際、取得したデータがUTF-8符号化されている場合、この部分はエラー無く正常に処理されます。ところが、取得したデータがS-JIS符号化の場合、エラーとなって例外が発生します。

 UIFlowではこれ以上何もできないのですが、Pythonの言語仕様を調べてみると、HTTPブロックの文字コード処理前の生データが、contentメソッドで取得できることが判りました。文字コード(S-JIS等)を処理する前のデータを取得できるので、「req.content」はエラー無く実行できました。
 そうと判れば、下図のExecute codeブロックを使って、Pythonのコードを直接記述することで回避できるかもしれません。

a271_execblk.png

※Python言語使用を理解している必要はあるけど、UIFlowが対応していない機能でも、Pythonには実装されているケースが有るので、Execute codeブロックは中々有用。

 以下図の様に使います。

a271_content.png

 上のブロック実行後、変数lstには、バイト文字列を表す「b'〜'」という文字列が追加され、「〜」の部分に受信したデータが格納されています。後は普通に文字列処理で、CSVデータの中から所望の祝日データを抜き出すだけです。

CSVデータの読み込み

 S-JIS符号化された文字列は諦めるとして、CSVファイル中の日付データを吸い上げたいと思います。

 CSV(Comma Separated Values)は、カンマ(,)で区切られたデータ形式です。

 同様に、HTTP GETしてきた祝日のCSVデータには、改行コードが含まれていて、これが厄介です。

 前節のExecute codeブロックで、「lst=req.content」したことで、改行コードなどの非印刷可能文字は「\r\n」等の文字列に変換されています。

※CR+LFの様なコードではなく、「\r\n」という文字列です。

 そこで、改行コード(「\r\n」)を、カンマ(,)に置換して、処理を簡略化したいと思いました。文字列中の特定の文字を置換する処理は、UIFlowではreplaceブロックを使って以下の様に実行することができます。

a271_replng.png

 これはこれでOKでした。contentメソッドを使って、S-JISデータを取得した場合に適応できます。

 ただ、取得したデータがUTF-8で符号化されていた場合、「req.text」で取得したデータに対して同じ処理を使用とすると、それはダメでした。なぜダメなのか?Pythonのコードで確認してみると、以下の様に変換されていました。

  lst = str(lst).replace('\\r\\n', ',')

 特殊記号「\」が打ち消されて「\\」しまっています。やりたかったのは、以下処理です。

  lst = str(lst).replace('\r\n', ',')

 UIFlowが勝手に「\」を打ち消して「\\」に変えてしまっている様です。そういう仕様なのだろうから、バグでは無いけど、やりたいことは「これぢゃない」です。
 これもExecute codeブロックを使って解決することができます(下図)。

a271_replok.png


 「ブロックを使ってGUIでプログラムが組めますよ」が売りのUIFlowで、一々Pythonのコードを確認しながら、Pythonコードを直接指定してプログラムを組むなんて本末転倒です。しかし、たま〜にC言語からインラインアセンブラでプログラムを記述して、C言語だけでは実装不可能な機能を記述するのと同じだと思えば、機能としては、こういう機能が有って良かったな、と。

 ともあれ以上の処理で、改行コードを含むCSVデータに於いて、改行をカンマ(,)に置き換えた文字列がlstに入りました。ここで、make with delimiterブロックを使って、CSVデータをリスト形式に変換します(下図)。

a271_mkwdlm.png

 これで漸く、CSVデータをUIFlowのリスト型に変換できたので、リスト型データを操作(下図、LISTSブロック)しながら、日付データを吸い上げればOKです。

a271_listblk.png

プログラム

 完成したプログラムを以下にリンクしておきます。

  完成プログラム:caln_watch.m5f

 オンライン版UIFlowから読み込み、M5StickC Plusへ書き込んでお使いください。本プログラムではHTTPで祝日のCSVデータを取得していますが、インターネットに接続する処理は記述していませんので、オフライン版UIFlowでは実行できません。

 なお、setupブロックの先頭付近で定義している、変数mon_startによって、カレンダの週表示を日曜日スタートにするか、月曜日スタートにするか、選択できるようになっています。

 月曜日スタートの方が好みなのですが、日曜日スタート派の方は、設定を変更してお使いください。

 拙作iConClockサポートの一環として、S-JISの祝日CSVデータは今後も更新して行く予定です。ので、「皆様、使う側」から見ると、毎年自動的に祝日データが更新されて、カレンダに表示されるように見えるはずです。

実験、○月○日

 では、実験です。以下図に実験中の様子を示します。

a271_expr.jpg

※上図では「山の日」と日本語表示されていますが、2023年の祝日CSVデータのみ、UTF-8で用意したためです。他の年ではS-JISデータしか用意しないので、何の祝日なのか?については表示されません。あしからず。

 カレンダの表示が主役のプログラムなので、日付や時刻を変更しながら動作を確認しました。このためのデバッグ用コードがrtc_ntp関数の中に入っているので、ご確認ください。

等、ざっと確認済み、問題なしです。

 ただ、「秒」が時々飛んで表示される事が有ります。Pythonの処理速度が遅いので、loop内の処理が1秒以内に終わらないケースが有り、その場合に「秒」表示が飛んでしまう様です。致命的な問題では無いと判断したのと、Python処理速度の問題なので、放置してあります。

※「飛ぶ」とは?……10,11,13,14,15,……の様に、この例だと、「12秒」が表示されずに飛んでしまう事を指しています。

 気になるようなら、秒表示を削除して、分表示止まりの時計に改造してみてください。あるいは、割り切って5秒毎に表示を更新するとか。普段の生活で、正確に秒まで知りたいケースは少ないはずです。女性用のアクセサリを兼ねた、おしゃれな腕時計には、そもそも、秒針が付いていない物もあるので、それと同じ使い勝手となります。


 M5StickC Plusを本当に腕時計として使用するには、もう一つ、バッテリ容量の問題をクリアしないとならないので、実用に使うのはちょっと……、な、感じではあります。まあ、UIFlowでHTTPプロトコルを使うサンプルとして、ご覧ください。


btn_arduino.png

btn_prev.png btn_next.png

home