- 暑い寝室と体温への敏感さから Sleep.me Chilipad 2.0 を1,700ドルで購入し、メーカーのクラウドAPIを制御する
bedctlを開発した - Chilipadは水が循環する薄いトッパーとポンプ・貯水タンク・温度調節器を組み合わせたもので、曜日別の温度スケジュールと、ベッドの片側から導入できる モジュール式構成 に対応する
- APIでは
/devicesと/devices/{:id}でデバイスを照会し、PATCHで温度・稼働状態・画面の明るさ・タイムゾーンなどを制御でき、水位と現在の水温も確認できる - Zig で書かれた
bedctlは依存関係のないライブラリと薄いCLIで構成され、今後はCインターフェースを備えたlibbedctl動的ライブラリへ拡張する計画 - 初期の睡眠トラッキングでは回復睡眠の時間が増え、夜間の覚醒が減ったが、使用期間は数日だけの ハネムーン段階 であり、長期的な効果を判断するには早い
ベッドの温度調節が必要だった理由
- 睡眠は免疫・認知機能や身体活動後の回復に役立つが、健康関連の見出しでは科学的なニュアンスが失われることがあるため、医療従事者や専門家の合意に裏付けられているかを重視している
- ADHDのため思考が止まらず寝つきにくく、ベッドで読書する習慣によって刺激を減らし、神経系に就寝の合図を送ってきた
- 自閉スペクトラムの特性により特定の質感や感覚に敏感で、体温調節も難しいため、眠るには 温度と布団の重さ が適切でなければならない
- ポートランドの新しいアパートはエアコンがリビングにしかなく、天井ファンもないため、高層階の寝室が夜に最も暑い空間になる
- 温度センサーを設置し、寝室がアパートで最も暑いことも確認した
- 不規則な睡眠、刺激薬の服用、蒸し暑い寝室、体温への敏感さが重なり、ベッド温度調節装置に費用をかけることにした
Chilipad 2.0を選んだ理由
- Chilipad 2.0 は、水の入った薄いマットレストッパーをポンプ・貯水タンク・温度調節器を含む装置に接続してベッドの温度を制御する
- ファンで空気を送り込むBedJetは、夏に冷たい風が足へ直接当たるため候補から外した
- Eight Sleepは最も安いモデルでも 3,000ドル で、サブスクリプションが必要なうえAI機能まで含まれているため購入対象から外した
- Chilipadはベッド片側用の装置から購入できるモジュール式製品なので、初期価格がより低い
- 2台設置すれば左右を別々の温度に設定でき、体感温度が異なるカップルに便利
- インターネット接続製品だが 機内モード に対応し、Bluetoothリモコンを標準で提供する
- 機内モードがWi-Fiだけを切るのか、すべての無線接続を遮断するのかは確認していない
- 最初はローカルAPIだと誤解していたが、実際にはクラウドサービスと通信する
- メーカーは開発者API、Postmanコレクション、オンラインのSwagger UIとOpenAPIスキーマを提供している
Chilipadのデバイス構成
- Chilipadは制御装置である dock と、水が循環するcoverで構成される
- dockには温度調節器、ポンプ、貯水タンクが入っている
- ベッドの足元かつドアに近い場所に配置し、熱交換器が排出した暖かい空気が寝室内へ再び広がるのを減らした
- coverは下層、TPU膜で作られたhydrolayer、上部coverの3層で構成される
- 上部coverのジッパーを開け、使用者が寝る側にhydrolayerを設置する
- 上部には触ると涼しい生地と薄いパディングを使っており、hydrolayerを直接感じないようになっている
Sleep.me API
-
認証とPostmanの例
- APIを使うにはまずユーザー トークン を生成する必要があり、ドキュメントへアクセスするにはアカウントが必要
- Postmanの例はドキュメントの詳細情報が不足しているときに役立つが、別途会社アカウントが必要で、構成も複雑
- オープンソースの
hurlスクリプトのような代替を好む - ほとんどの例には変数名のタイプミスがある
{{SleepMeAPI_URL}}ではなく、大文字小文字を区別した{{SleepMeAPI_Url}}を使う必要がある- “Set Device Status” の例は
set_temperature_fを69、thermal_control_statusをstandbyに設定する
-
エンドポイントとレスポンス構造
- APIはデバイス一覧と個別デバイスを扱う2種類のエンドポイントを提供する
GET /devices: トークンに紐づくデバイス一覧を返すGET /devices/{:id}: 特定デバイスの全情報を返すPATCH /devices/{:id}: デバイスの属性を変更する/devicesの各項目にはデバイスID、ユーザーが付けた名前、attachmentsが含まれるattachmentsが接続された別のデバイスを意味するのかは確かではない- 詳細レスポンスは
about、control、statusオブジェクトに分かれるabout: ファームウェアバージョン、外部IP、LAN IP、MACアドレス、モデル、シリアル番号を含む読み取り専用情報control: 画面の明るさ、表示温度単位、設定温度、温度制御状態、タイムゾーンなど変更可能な属性status: API接続の有無、水不足の有無、0〜100の水位、現在の水温など読み取り専用の状態
control属性はデバイスによって異なる場合があり、ドキュメントにないbrightness_levelのようなキーが返ることもあるthermal_control_statusはactiveまたはstandbyで、装置が温度を能動的に調節しているかを示す
-
温度値とデバイス制御
- 華氏温度は整数、摂氏温度は浮動小数点数として扱われる
- 正常範囲は華氏 55〜115°F、摂氏 13.0〜48.0°C
MAX_COLDは-1または-1.0、MAX_HEATは999または999.0で表される- 待機状態で
status.water_temperature_*が-1になるのはセンチネル値のように見えるが、文書化されていない - デバイス属性を変更するときは、変更したいキーだけをJSON本文に入れて
PATCHリクエストを送る - 華氏温度だけを73に設定するには
{ "set_temperature_f": 73 }を送信する - すべての制御キーを含めたり、
{ "control": ... }で包んだりする必要はない - このリクエスト方式は公式ドキュメントではなくPostmanコレクションで確認した
bedctl の構成と使い方
bedctlはZigモジュールを中心に作られており、CLIはそのライブラリを呼び出す薄いラッパー- 名前は制御プログラムに
-ctl接尾辞を付ける慣例に従い、“bed cuttle” が “bed cuddle” のように聞こえる点も反映している - 主なコマンドは次の通り
bedctl devices: トークンに登録されたデバイス名とIDを照会bedctl status: ファームウェア、ネットワークアドレス、モデル、設定温度、動作状態、水位、観測温度を照会bedctl set temp 74: 目標温度を設定
- トークンとデバイスIDは環境変数で指定するか、
--token、--deviceフラグで渡せる
Zigを選んだ理由と依存関係
- Rustを仕事で約5年、趣味で2018年から使ってきたが、即時のフィードバックと低い認知負荷を求め、最近好んでいる Zig で実装した
- M1 Max MacBook Proで
zig build check --watch -fincrementalは約 75ms で更新される - CLIの唯一の外部依存は、TigerBeetle のコードから抽出・修正し、Zig 0.16.0に合わせた argument parser
- ライブラリはZig標準ライブラリ以外に依存せず、HTTPリクエストには
std.http.Clientを使う - CLIは
libcとリンクせず、今後の動的ライブラリもlibcなしで構成する予定 - Zigの標準的なクロスコンパイル機能により、対象システムへ直接アクセスしなくても対応プラットフォーム向けのビルドを作成できる
データモデルと型システム
- 中核となる構造体はデバイス一覧用の
Deviceと詳細レスポンス用のDeviceStatusDeviceはid、name、attachmentsを含むDeviceStatusはDevice.About、Device.Control、Device.Statusで構成される
Control属性はモデルによって存在しない場合があるため、明るさ、表示単位、設定温度、動作状態、タイムゾーンをオプション値として表現している- JSON文字列をそのまま使う代わりに、Zigの 匿名enum を活用している
- 温度単位は
.cと.f - 温度制御状態は
.activeと.standby
- 温度単位は
- 華氏の設定温度には、特定の整数値に意味のあるenum名を付けている
-1を覚える代わりに.max_coldを使える- 通常の温度は
78のような整数リテラルで渡せる
- 型だけで正常範囲である55〜115°Fを制限する方法は見つからず、リクエスト前の別途検証もしていない
- 摂氏値は浮動小数点数なので、許容範囲を型で表現できない
Zig HTTPクライアントの実装経験
- Zig標準HTTPクライアントは、単純なURI・本文・
send抽象化より低レベルの手順を要求する- URIオブジェクトを作成
- パスを指定するようURIを変更
- ベースURI、ヘッダー、接続設定でリクエストオブジェクトを作成
- リクエストヘッダーを送信
- レスポンスヘッダーを受信
- 本文readerを作成
- readerからレスポンス本文を消費
- 精密な制御は可能だが、必要なときだけ低レベルへ降りられるRustとは違い、Zigでは常に 低レベル作業 をしている感覚がある
/devicesの実装では、認証ヘッダーを入れたGETリクエストをsendBodiless()で送信した後、レスポンスを最大64,000バイトまで読み、JSONを[]const DeviceとしてパースするparseFromSliceLeakyを使っているが、外部に公開していないarena allocator構成のおかげでメモリ解放の問題はない
動的ライブラリへの拡張計画
- 最終目標は、高水準言語から使える 依存関係のない動的ライブラリ を作ること
- 予定している構成は次の通り
- Zigモジュール
bedctl - Zigモジュールを直接使うCLI
bedctl - ZigモジュールにCインターフェースを提供する
libbedctl - 動的ライブラリを使い、Cインターフェースを自己検証する
bedctl-dl
- Zigモジュール
- Pythonラッパーが作られたらHome Assistantで使いたいが、自分で書く計画はない
- 動的ライブラリのビルドとインターフェースはまだ用意できておらず、近いうちに追加する予定
数日使ってみた睡眠体験
- 使用した数日間、主観的にはぐっすり眠れ、フィットネストラッカーでも 回復睡眠段階 が長くなり、夜間の覚醒回数が減った
- ウイスキーを飲んだ日でも普段よりよく眠れたが、これを酒が睡眠に良いという意味に解釈してはならない
- アルコールは依然として健康と睡眠に有害であり、装置が睡眠悪化を一部軽減した可能性があるだけ
- 装置を使い始めて数日しか経っておらず、
bedctlも約3晩かけて書いたものなので、結果はまだハネムーン段階 - 最初に温度を低く設定しすぎたときは、濡れた場所に横たわっているような冷感があったが、温度を調整した後は消えた
- 変化にかかる時間を直接測定したわけではないが、10分というより 1分に近いほど 速く感じる
- 満たすのに必要な水は1ガロン未満なので、加熱または冷却する熱容量は大きくない
リモコン・扇風機・暖房利用
- 標準リモコンはタッチスクリーン付きの大きなダイヤル型で、予約温度を調整している間にすぐ温度を変えやすい
- ダイヤルの触感は満足できるものではないが、温度単位ごとに振動するため、画面を見なくても調整できる
- 睡眠マスクを着けた状態でも ハプティックフィードバック で変更量が分かる
- 弱い気流も必要なため、以前は部屋を冷やす風量と、体に直接当たったり気道を乾燥させたりしない強さを同時に調整する必要があった
- Chilipad使用後は、扇風機を最も低い段階で回すだけで済む
- 朝に温度を少し上げると心地よく、冬にはベッドを事前に温める機能に期待している
- トッパーを敷くと、下側の通気性はやや下がる
- プラスチックの上に横たわっている感じではないが、高い温度ではベッドに触れた肌がごくわずかに汗ばむ感覚があった
- 個人的な体温調節特性による経験なので、他のユーザーに同じとは限らない
- 初期体験は全体として前向きで、動的ライブラリを構成した後に続報を投稿する可能性がある
1件のコメント
Lobste.rsのコメント
テーマに合わせて、寝る直前にこの記事を投稿
bedctlを設定すれば、目覚まし用途にも使えるのか気になるタイトルも記事も素晴らしい。加重ブランケットも試したことがあるのか、実際に効果があったのか気になる
作り方によっては通気性が悪く、価格も高くなり得る。Chilipadを買った後では高いと言うのも気が引けるが、後で見てみようと覚えておいた製品は、通気性が良さそうなオープンウィーブ構造の https://bearaby.com/products/tree-napper だ
こういう製品を調べていて、いつかは自分でも使ってみるつもり。この動画によると、コスパ最高の解決策はバケツだが、その場合APIはなさそう
やや関連する記事: https://trufflesecurity.com/blog/removing-jeff-bezos-from-my-bed (議論)
華氏と摂氏を比較した箇所で、Celsius is the better temperature と書くつもりではなかったようだ
記事は面白く、ファンに関する部分など考え方にもかなり共感するが、この作業で何を制御できるようになったのかが明確ではない
物理学博士としての経験から、科学・健康関連の報道を証明されるまで疑うという段落は、ややもするとワクチン不信に似た危険な方向に読める。ただ、博士課程によってメディアの「事実」を白黒で受け取らなくなったという意味だと理解しており、具体的に何が傷を残したのか気になる
Andrew Wakefieldのワクチン論文が出たとき、要旨を読んだ記者の短い記事だけを見た人々が、ワクチンは絶対に危険だと断言し、動物学修士の高校生物教師や物理学博士3人まで反ワクチンに回った。しかし、医療界のコンセンサスは一貫して、ワクチンは安全で効果的であり、命を救う最も強力な手段の1つだというものだったし、出会った医学研究者も全員一致していた
博士課程で傷ついたのは、自分の分野における実際の発見とニュース報道の間にどれほど大きな隔たりがあるかを知ったからだ。すべての分野を完全に理解することはできないので、医学は医師を、環境は生態学者を、気候は気候学者を信頼すべきだ。中学レベルの数学と小学校レベルの科学で専門家を「論破」する物理学博士たちを見たが、本当の専門家は学校レベルの知識と現実についての知識の間にある巨大な差を知っていた。自分が学んだ分野でその隔たりを目撃したからこそ、学んでいない分野ではむしろ専門家をより信頼するようになった
望む構造でフィールドを適当に投げてもよいなら、ハンドラ側ではほぼ確実に
re.search('set_temperature_f\": (\\d+)',...)のような正規表現検索をしていそう