注意
この記事の内容は、開発途中のものです。 今後、変更などを行う可能性が高いのでご注意ください。
はじめに
「着ぐるみ天気予報」は、気象データから、着ぐるみで活動してよいかを予報するWebサービスです。 Web版の公開については、着ぐるみ天気予報「FursuitWeather」を公開しましたにまとめています。 Web版のほかに、自分用のMac版も作っています(配布はしていません)。
9月9日にWindows版のリポジトリを作り、v0.3.0を9月16日に公開しました。
- リポジトリ:223n/FursuitWeather_Windows
- 配布:Releasesのインストーラー(x64)
この記事では、Windows版の作りと、v0.3.0で加えた会場向けの「掲示モード」、Windowsでつまずいた点を記録します。
着ぐるみ天気予報の判定
判定の土台は、暑さ指数(WBGT)です。 気温、湿度、日射量、風速から、環境省と同じ推定式でWBGTを求めます。
そのうえで、着ぐるみを着ていることを考えて、WBGTに11℃を足します。 厚生労働省の熱中症予防の要綱にある「フード付き蒸気不透過つなぎ服」の着衣補正値です。 判定のくわしい考え方は、Web版の公開の記事を見てください。
| 補正後のWBGT | 判定 | 1回あたりの連続活動時間 |
|---|---|---|
| 21℃未満 | ほぼ安全 | 45分 |
| 21℃から | 注意 | 30分 |
| 25℃から | 警戒 | 20分 |
| 28℃から | 厳重警戒 | 10分 |
| 31℃以上 | 危険 | 着用中止 |
気温が15℃未満のときは、体感温度による低温側の判定も使い、暑熱側と低温側のうち深刻なほうを採ります。 気象データにはOpen-Meteoを使っています。
Windows版は、この判定を自分では計算しません。 本体のAPIが返す判定を表示するだけにして、判定の規則をアプリに複製することを禁じています。 規則を2か所に持つと、片方だけ直したときに表示が食い違うためです。
技術の選び方
| 項目 | 内容 |
|---|---|
| 言語 | C# |
| ランタイム | .NET 10(LTS) |
| UI | WPF |
| トースト通知 | Windows App SDK |
| タスクトレイ | H.NotifyIcon.Wpf |
| テスト | xUnit v3 |
| インストーラー | Inno Setup 6 |
| 対象 | Windows 11 22H2以降 |
UIの候補には、WPFのほかにTauri v2とWinUI 3がありました。
WPFを選んだ決め手は2つです。 1つ目は、窓の余白の部分だけクリックを背後へ通す動きが、WPFの透過ウィンドウなら追加の実装なしで手に入ることです。 2つ目は、アプリをパッケージ化しなくても、ボタン付きのトースト通知を出せることです。 Tauri v2はこの2点で落ち、WinUI 3は透過ウィンドウが公式のサポートの外にあるため外しました。 なお、v0.3.0のトーストには、まだボタンを置いていません。
プロジェクトは、何を表示し、いつ知らせるかを決める部分と、画面を持つ部分の2つに分けています。 前者はUIに依存しないので、テストをLinuxのCIでも回せます。
小窓、通知、掲示モード
Windows版には、表示の面が3つあります。
- 小窓:画面の隅に出る小さなカードです(既定は最前面)。 地点、いまの判定、連続活動時間、気温や補正後のWBGTを出します。 フォーカスは奪いません。
- 通知:タスクトレイに常駐し、トーストで知らせます。 知らせる主な場面は、着用中止の水準になったときと連続活動時間が短くなったとき、環境省の熱中症警戒アラートが出たとき、着用中止から戻ったときの4つです。
- 掲示モード:会場の画面に判定を映し続ける全画面の窓です(v0.3.0で追加)。
予報は11分ごと、アラートは31分ごとに取得します。 位置は小数第2位に丸めてから送ります。
判定が変わらない期間の通知
通知の設計を進める中で、本体のAPIから4つの都市の88時間分の予報を取り、判定がどれだけ変わるかを数えました。
すると、那覇は88時間すべてが最も深刻な判定のままで、一度も変化しませんでした。 判定が変わったときだけ知らせる作りでは、こうした時期に何日も通知が出ません。 いちばん危ない場所ほど何も知らされない、という状態になります。
そこで、変化の通知とは別に、毎朝8時にその日の見通しを知らせる「朝のブリーフィング」を置きました。
会場向けの掲示モード(v0.3.0)
何のためのものか
掲示モードは、イベント会場に置いた端末で、来場者に向けて判定を掲げ続けるための画面です。 Web版やMac版の会場表示モードに倣いました。
小窓とは別の、全画面の窓として作りました。 画面は1280×720で組み、実際の画面の大きさに合わせて拡大します。
スライドの中身
スライドは次の4枚を、決まった順に回します。
- いまの判定(15秒)
- この後の予報(20秒)
- 3日間の天気(15秒)
- 全国の天気(20秒)
判定が厳重警戒以上のとき(低温側を除く)は、具合が悪くなったときの対処を示す「もしものとき」の1枚が加わります。
画面の上の帯には、地点といまの判定を常に出します。 この先3時間で連続活動時間がいまより短くなるときは、その時刻と判定も出します。 環境省の発表と、通信の失敗などの注意も、あれば帯に出します。 下の帯には、何時の時点の情報かと、データの出典を出します。
無人で動かし続けるための工夫
会場では、誰も見ていない時間に画面が止まったり、別の表示が割り込んだりすると困ります。 そのため、掲示のあいだは次のようにしています。
- トースト通知は、着用中止の通知も含めて出さない(判定と状態の記録は続ける)
- アプリの更新は、自動では入れずに掲示を終えるまで待つ(取得は続ける)
- パソコンのスリープを抑える
- 画面の焼き付きを避けるため、3分ごとに表示を1pxずつずらす
左右の矢印キーで前後のスライドへ移り、画面のクリックで次のスライドへ進みます。
スペースキーで一時停止します。
一時停止は、止めたまま忘れられるのを防ぐため、5分で自動的に解けます。
掲示を終える経路は、Escキー、トレイのメニュー、Ctrl+Alt+Dの3つを用意しました。
「起動したら掲示で始める」という設定も用意しています(既定は切)。 Windowsへのサインイン時にアプリを自動で起動する設定と一緒に使えば、停電や再起動のあとも、サインインした時点で掲示へ戻ります。
地点と表示先のモニターを選ぶ
v0.3.0では、設定画面に地点の検索を足しました。 地名か郵便番号で探し、候補から選びます。 地点を選ばずに既定の地点のまま掲示すると、上の帯に「地点が設定されていません」という注意を出したままにします。 会場とは違う場所の予報を掲げ続ける事故を防ぐためです。
表示先のモニターも選べます。 選んだモニターが外れたら残りのモニターへ移り、戻ったら元のモニターへ戻ります。
あえて写さなかったもの
Web版の会場表示モードには、推定の警戒帯(暑さ指数33以上)を示す表示があります。 Windows版の掲示モードでは、これを写していません。 写すには、アプリ側で閾値を使って判定することになり、「判定をアプリに複製しない」という決まりに反するためです。
その結果、同じ会場のWeb版の表示より、安全側の情報が1つ少なくなっています。 このことは設計の文書に明記しています。
Windowsでつまずいたところ
最背面に置いた窓が消える
小窓は最初、デスクトップに貼り付くように最背面へ置くことを考えていました。
ところが、HWND_BOTTOMを指定しても、デスクトップを描く窓(Progman)より下へは行けませんでした。
さらに「デスクトップの表示」を実行するとProgmanが前に出てきて、最背面の小窓は裏に回って見えなくなります。
そのため、既定を最前面に改めました。
マルチモニターとDPI
このアプリは、モニターごとの拡大率に追従するモード(Per-Monitor V2)で動かしています。
このモードでモニターごとに拡大率が違うと、WPFのWindow.LeftとWindow.Topは正しい値にならないことがあります(dotnet/wpf#4127)。
別々のモニター上にある異なる位置が、同じ値に換算されてしまう場合があるためです。
そのため、窓の位置はWin32のSetWindowPosを使い、物理ピクセルで扱っています。
WPFは窓を出すときに大きさを当て直すことがあるので、出す前と出したあとの両方で位置を置き直します。
また、作業に使った端末では、モニターを見分けるためのEnumDisplayDevicesがモニターを1台も返しませんでした。
その場合は、見分ける値を\\.\DISPLAY1のようなアダプターの名前に、設定画面に出す名前を「モニター1」のような番号だけの表記に切り替えます。
アダプターの名前は、抜き差しで入れ替わりうる値です。
クリックを背後へ通したら操作できなくなった
小窓には、クリックを背後の窓へ通す設定があります(既定は切)。 最初の実装では、これを解除する手段がホットキーだけで、ホットキーが効かないと小窓を操作できなくなりました。 解除の経路を、トレイのメニュー、ホットキー、30秒での自動復帰の3つに増やしました。
トレイのアイコンを作るとアプリが省電力状態になる
H.NotifyIconでトレイのアイコンを作るForceCreate()を引数なしで呼ぶと、プロセス全体が効率モード(EcoQoS)に入り、優先度も下がります。
通知の遅れを避けるため、効率モードを使わない指定を付けて呼んでいます。
そのほか
SourceInitializedの中でHide()を呼んでも、窓は隠れないEnvironment.TickCount64は、.NET 11からスリープ中の時間を含まなくなる予定なので、経過時間は壁時計と単調な時計の両方で見るSetThreadExecutionStateでスリープは抑えられるが、スクリーンセーバーは止められない
配布と自動更新
インストーラーはInno Setup 6で作りました。 管理者権限がなくても、利用者単位で入れられます。 .NETのランタイムは同梱しているので、別に入れる必要はありません。
コード署名はまだしていないため、初回はSmartScreenの警告が出ます。 署名の第一候補は、OSSに無償で証明書を提供するSignPath Foundationです。 申請にはリリース済みであることが条件のため、v0.3.0の公開を受けて、これから申請を検討します。
自動更新は自前で作りました。
Releaseに更新情報のファイル(update.json)とその署名(ECDSA P-256)を添え、アプリは署名を確かめてからインストーラーを取りに行きます。
更新のしかたは「自動」「取得だけ自動(既定)」「お知らせのみ」の3つから選べます。
v0.3.0は、タグを打ってから公開まで約41時間かかりました。 更新情報に署名するジョブが承認待ちで止まっていたためです。
版の流れ
| 版 | 日付 | 主な内容 |
|---|---|---|
| v0.3.0-rc.1 | 2026-09-09 | 小窓、常駐、トースト通知、朝のブリーフィング、設定画面 |
| v0.3.0-rc.2 | 2026-09-10 | インストーラーを作り、Releaseへ自動で添付 |
| v0.3.0-rc.4 | 2026-09-11 | 更新情報の署名と検証 |
| v0.3.0 | 2026-09-16 | 自動更新、掲示モード、地点の検索、表示先のモニターの選択 |
まだ確かめていないこと
掲示モードは、会場を想定した実機での確認がまだ残っています。
- 全画面の窓がタスクバーを覆うか
- スリープの抑止が実際に効くか
- モニターを抜き差ししたあとも、同じモニターを見分けられるか
- 離れた場所から文字が読めるか
拡大率の違う2枚目のモニターでの表示も、まだ確かめていません。
同じ週のほかの版
Web版では、9月13日に豊橋で開かれた「あまケモ祭2026」をイベントの一覧に加えています。
おわりに
Windows版では、判定の規則を持たずに本体の表示に徹することで、Web版やMac版と判定が食い違わないようにしました。 一方で、窓の置き方や通知の出し方、無人で動かし続けるための抑止など、Windowsならではの作業に時間を使いました。
掲示モードは、タスクバーの扱いやスリープの抑止など、実機での確認が残っています。 会場に近い環境で確かめていきます。
予報は目安であり、安全を保証するものではありません。 体調や装備、活動内容によって、安全な活動時間は変わります。