先週(というか今週の頭)にソースを掲載していながら、またもやUpdateという有様ですが、ソースコード(TempSensor.zip)と実行プログラム(TempSensor.exe)を更新しました。お手数ですが既にダウンロードした方はもう一度ダウンロードし直して頂けますよう、お願いいたします。

さて、肝心のプログラムであるが、今回のポイントはタイマーを2種類同時につかっていること。これはなぜか? といえば、仮想COMポートからの読み出しと、実際の画面表示が必ずしも連動していないからだ。前回もちょっと触れたが、読み込み時にデータの欠落があったら読み飛ばす処理なども必要になるし、そもそもReadFile()を使ってCOMポートから読み込みを行う場合、「データが無いとすぐ帰ってきてしまう」から、定期的にポーリングの必要がある。これを1種類のタイマーでやることは不可能ではないが、2種類のタイマーで別々に記述したほうがすっきりするし、動作上も別に問題は無い。

細かい話をすると、実はReadFile()も「データが来るまで待っている」という動作は可能だ。これはそもそもRS232CポートをCreateFile()を使って開くときに、dwDesiredAccessパラメータにSYNCHRONIZEフラグを立ててやればよい。ところが、これをすると今度は「何かの理由で通信が阻害されると、プログラムがいつまでたっても帰ってこない」事になってしまう。これを避ける方法はあって、やはりCreateFile()でdwFlagsAndAttributesパラメータにFILE_FLAG_OVERLAPPEDフラグを立てると、I/Oを非同期で行えるようになる。ただ、ここまでやるくらいならタイマーを2種類用意した方が楽、というのが筆者の判断である。美しくない、という方はぜひ改造にチャレンジしていただければと思う。

さてプログラムに話を戻すと、そんなわけでWndProc()のWM_CREATEメッセージではしょっぱなからタイマーを2種類定義し、ついでフォントを3種類用意している。フォントは今回の場合、「現在時刻」「温度」「設定」の3つを異なるサイズで表示することを考えたために、それぞれ別のフォントを指定した形だ。後は80LEDの時と概ね同じである。

さてWM_TIMERメッセージだが、そんなわけで今回は2種類のTimerメッセージがやってくる。そこで、冒頭でwParamの値を判断し、これがRS232Cからの取り込みか、それとも画面表示/ログ出力かを判断する訳である。RS232C取り込みの場合、まずReadFile()を呼び出し、ここで取得サイズを判断。0Bytesならデータが空ということで無視する。また0Bytesで無い場合は、先頭が'z'かどうかを判断、'z'でなければ読み飛ばしし、'z'であればsscanf_s()をつかってtempBuf[]に値を格納する。

実はちょっと悩んだのがここである。今回の場合、常に最新の温度のみを保持する形にして、それ以前のデータは破棄しているが、これを破棄せずに常に蓄積しておいて、出力は平均値で出すという実装もそれほど難しくはないからだ。例えば10秒毎に結果を表示/記録するとすれば、1回表示/記録を行う間に実際は50回(200ms毎にデータを送ってるから、毎秒5回という計算になる)分のデータを取得し、このうち49回分を捨てている計算になる。特に温度変動がむやみに多いという場合は、平均値を取ることで不要なピーク値に惑わされにくくなるわけだが、逆に温度変動がそう多くない場合は波形が不必要になまることになる。最終的には「平均値がほしい場合は、表示/記録頻度を上げて、後処理で平均値を取ればいいや」という結論に達したので今回は平均値を取るロジックは含めなかったが、もし入れるのであればsscanf_s()の後で取得したデータを加算バッファに入れると共にカウンタを1増やすロジックを追加し、一方画面表示の方はtempBuf[]からデータを取得するのではなく、加算バッファからの値をカウンタの数で割り、その結果を使う(このタイミングで加算バッファ・カウンタ共に0クリア)という形にすれば、平均値での表示/記録が可能になる。

さて、もう一つのTimerメッセージが画面表示/ログ出力である。最初に現在時間の取得(これをやらないと、ログで書き出すときに色々面倒になる)。今回は「日付はいらないだろう」(時間は表示しているから1~2日の範囲ならこれで日付の判断がつく)と割り切ったので、時/分/秒のみの取得としているが、日付がほしいというのであればwsprintf()の引数を増やすだけである(データそのものはtime()で取得し、localtime64s()で変換して構造体に入っている)。例えば"年/月/日 時:分:秒")というフォーマットにしたければ、

wsprintf(timeStr, TEXT("%02d:%02d:%02d"), nowTime.tm_hour, nowTime.tm_min, nowTime.tm_sec );

という部分を

wsprintf(timeStr, TEXT("%04d/%02d/%02d %02d:%02d:%02d"), nowTime.tm_year+1900, nowTime.tm_mon+1, nowTime.tm_mday, nowTime.tm_hour, nowTime.tm_min, nowTime.tm_sec );

と書き換えればすむ(詳しくはこちら)を参照。ただ日時の文字列が長くなる分、ログファイルはともかくとして画面サイズが足りなくなるので、画面サイズ(ソースの先頭で#defineで定義しているWINDOWWIDTH)の値を適時大きくする必要がある。

(続く)