日記/2020-6-20

最終更新時間:2020年06月20日 20時33分20秒

室内のCO2濃度が見たい

 

注意

 以下の記事は技術的にはほとんど役に立たない。馬鹿な不注意が招く間抜けな徒労に充ちた失敗談である。
 

LaMetric Time

 数年前に買ったLaMetric Timeに、いろんな情報を表示して楽しんでいる。

 正直、実用性には乏しい気もするが、まあ、賑やかしということで。

 表示しているのは、順に、

  • 時刻
  • 日付 曜日
  • 近所のアメダスの情報
    • 気温
    • 天気(アイコンで晴/曇/雨を切り換え)と降水量
    • 風速
  • Nature Remoの内蔵センサーの値
    • 室温
    • 湿度

 最近部屋に籠もる機会が増え、以前にも増して室内環境を気にするようになったのだが、これまた何年も前に買ったWithingsの体重計WS-50、

こいつにCO2濃度センサーが内蔵されていることを思い出した。手持ちのWithingsの機器の情報が閲覧できるサイトからも、以下のようにCO2濃度のグラフが確認できる。

 
 このCO2濃度をLaMetric Timeに表示したい!
 
 室内のCO2濃度が1000ppmを越えると、人は眠気を覚え作業効率が大きく下がるという。
 グラフを見るとCO2濃度は30分に一度測定されているようなので、これをLaMetric Timeに常時表示し、さらに1000ppmを越えたら赤く表示するなどして換気をうながす。そういう仕組みを構築したいと考えた。
 

Withings API(OAUTH 2.0)

 ググってみると、Withingsが機器の情報を取れるAPIを提供していることがわかった。

 使い方は、以下のサイトで詳細に説明されている。

 上記のページの手順どおり進めて、WS-50の情報を取ることに成功した。
 しかし、確かに体重なんかの情報は取れるんだが、CO2濃度の取り方がわからない。
 事ここに至りようやく気づいたのだが、取れる情報の中にCO2濃度がないのである! Withings APIの解説サイトのこちらのページに掲げられている、取れる情報一覧の表をよくよく見てみると、CO2濃度の項目はない。
 なお、どのみちCO2濃度の情報が取れないので、私にとってはどうでもいいのだが、このAPIを使うにはアクセストークンが必要で、これが30分で期限が切れてしまい、そのたびに再発行する必要がある。これが何気に面倒くさくてかなり使いにくい。
 

Withings WS-50 Scale Syncer - Temperature & CO2

 あきらめきれずになおもググっていると、まさに私の用途にばっちりと思えるページを見つけた。

 上記のページでは、Withings WS-50 Scale Syncer - Temperature & CO2という、Withingsの非公式なAPIを使ってWS-50の気温とCO2濃度のセンサーの値を取り、DomoticzというOSSのホームオートメーションシステムと連携させるツールをそのまま流用しているのだが、こちとらWS-50のデータさえ取れればそれでいいので、このツールを書き換えることにした。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from datetime import datetime
import sys
import time
import hashlib
import requests

TMPID = 12
CO2ID = 35

NOW = int(time.time())
PDAY = NOW - (60 * 60 * 24)

HEADER = {'user-agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'}

URL_BASE = "https://scalews.withings.net/cgi-bin"
URL_AUTH = URL_BASE + "/auth?action=login&appliver=3000201&apppfm=android&appname=wiscaleNG&callctx=foreground"
URL_ASSO = URL_BASE + "/association?action=getbyaccountid&enrich=t&appliver=3000201&apppfm=android&appname=wiscaleNG&callctx=foreground&sessionid="
URL_USAGE = "https://goo.gl/z6NNlH"


def authenticate_withings(username, password):
    global pem
    try:
        import certifi
        pem = certifi.old_where()
    except Exception:
        pem = True
    requests.head(URL_USAGE, timeout=3, headers=HEADER, allow_redirects=True, verify=pem)
    payload = {'email': username, 'hash': hashlib.md5(password.encode('utf-8')).hexdigest(), 'duration': '900'}
    response = requests.post(URL_AUTH, data=payload)
    iddata = response.json()
    sessionkey = iddata['body']['sessionid']
    response = requests.get(URL_ASSO + sessionkey)
    iddata = response.json()
    deviceid = iddata['body']['associations'][0]['deviceid']
    return deviceid, sessionkey


def download_data(deviceid, sessionkey, mtype, lastdate):
    payload = '/v2/measure?action=getmeashf&deviceid=' + str(deviceid) + '&meastype=' + str(mtype) + '&startdate=' + str(lastdate) + '&enddate=' + str(NOW) + \
        '&appliver=3000201&apppfm=android&appname=wiscaleNG&callctx=foreground&sessionid=' + str(sessionkey)
    try:
        response = requests.get(URL_BASE + payload)
    except Exception:
        sys.exit("[-] Data download failed, exiting" + "\n")
    dataset = response.json()
    return dataset


def main():
    username = 'mail@address'
    password = 'password'

    deviceid, sessionkey = authenticate_withings(username, password)
    co2data = download_data(deviceid, sessionkey, CO2ID, PDAY)

    for item in sorted(co2data['body']['series'][0]['data'], key=lambda x:x['date'], reverse=True):
        dt = datetime.fromtimestamp(item['date'])
        print(f"date:{dt}, co2:{item['value']}")

    return

if __name__ == "__main__":
    main()

 上記のスクリプトではデバッグ用に、測定日時とその時のCO2濃度を一行に、一日分を新しい順に表示している。

date:2020-06-20 13:30:13, co2:509
date:2020-06-20 13:00:12, co2:519
date:2020-06-20 12:30:13, co2:506
date:2020-06-20 12:00:12, co2:497
date:2020-06-20 11:30:13, co2:509
……

 当初はこの測定日時を、取れたデータそのままのunixtimeで表示していたため、なかなか気づかなかったのだが、このスクリプトを実行しても、最新のデータとして取れるのは、実行時の何時間も前のものだったのである。
 これは一体なぜなのか?
 事ここに至りようやく気づいたのだが、前述の「Withings WS-50を使ってCO2濃度をCloudWatchメトリクスに保存する」のページに、それも冒頭部分にしっかりと、以下のように書いてある。

ただし、WS-50の場合センサ情報の送信が以下の2パターンしかないため、リアルタイムでCO2濃度を取得することはできない。
・1日一回どこかのタイミングで環境センサ情報(CO2濃度、温度)を送信
・体重を測ったタイミングで環境センサ情報も送信
なお、CO2濃度情報は30分おきに記録されている。

 つまり、CO2濃度の測定自体は30分毎に行われているのだが、それがウェブ上にアップロードされるのは体重測定時か一日一度のどこかのタイミングのみ。いくら情報を取りに行っても、取れる情報が更新されるのは一日一度だけ。上記スクリプトで表示されるのは、前回の更新時の情報が最新になるわけで、それがスクリプト実行時の何時間も前だったのである。

 結局、WS-50内蔵のCO2濃度センサーは、私が想定した用途では使えないということがわかった。
 

MH-Z19

 それでもあきらめきれない私がどうしたかというと、Banggood.comで以下のCO2濃度センサーMH-Z19を発注してしまった。

 なぜAmazonではなくBanggoodで買ったかというと、Banggoodの方が値段も安く納期も短かったから。それでも納期は10日から30日後。値段は送料込みで2529円。
 CO2濃度センサーが届く頃まで、この情熱が失われていなければ良いが。