WFH、捗ってますでしょうか。
我が家の場合、机やモニターなど家で仕事をするのに十分な設備は元々整っていたのですが、たったひとつだけオフィスにはあって自宅にないものがありました。
CO2モニターです。
成果物
パッと目につくところに表示させたかったので、iOSのウィジェットに置くことにしました。
最終的にこんな感じでCO2濃度、ついでに室温が見えるようになりました。
iPhone
iPad
構成と概要解説
ポイントは以下です。
- CO2-miniでCO2濃度、室温を計測する
- CO2-miniとRaspberry Piを接続して定期的な計測を行い、Webサーバーとしてアクセス可能にする
- iOSアプリからWebサーバーへアクセスし、ウィジェットとして表示する
- 換気したくなる
CO2-mini
CO2濃度の計測は既製品であるこちらを使います。
CO 2モニター CO2-mini | 自然環境測定器 - 製品情報 - 計測器のカスタム
MH-Z19などのモジュールも検討したのですが、WFHによる需要の高まりのためか高価になってる or 配送に時間がかかるため、比較的手に入りやすいこちらを選択しました。
電源供給がmicro USB Type-Bなのでこれを利用してPaspberry Piと接続します。
公式でAPIが公開されているわけではありませんが、有志の解析によってOSSなどを経由してアクセスできるようになっています。
なお、今回はCO2-miniに予め備わっている表示部を見て換気したくなる行為は反則負けとします。
サーバーサイド
主となるCO2濃度のロギング、Webサーバー化は co2meter
を使います。
https://github.com/vfilimonov/co2meter
このOSSを使うことで、Pythonを使ったCO2-miniへのアクセスが可能になります。さらに定期的な計測、JSON/CSVへの書き出し、FlaskによるWebサーバー化、折れ線グラフによる可視化まで担ってくれます。
クライアントサイド
iOSアプリ部分については自作して、簡単なものですが公開しました。
https://github.com/akeome/RoomCondition
ラズパイで構築したサーバーにアクセスして、レスポンスのJSONの最新値を表示するだけのものです。
CO2濃度を見える化する
この環境を構築するための手順を記します。
僕は今回初めてRaspberry Piをいじったので、備忘も兼ねてセットアップの記載から始めます。
準備
必要なハード類です。
- Mac
- Raspberry Pi
- 今回はRaspberry Pi Zero WHを使いました。Wi-Fiに接続できればどのモデルでも大丈夫なはず
- CO2-mini
- microSDカード
- アダプタ、ケーブル等
- MacからmicroSDに書き込むためのハブなどが必要です
- Raspberry PiとCO2-miniの接続は、micro USB Type-Bとmicro USB Type-Bです
- Raspberry Pi Zero WHの場合、電源供給はmicro USB Type-Bです
Raspberry Pi のセットアップ
今回使用したRaspberry Pi Zero WHには有線LANポートがないので、Wi-Fi経由でMacから操作を行うための手順を書きます。
MacからmicroSDに書き込む
Paspberry PiにmicroSDを挿す前に、Macで諸々書き込んでいきます。
OSを書き込む
公式サイトからOSをダウンロードします。
https://www.raspberrypi.org/downloads/raspbian/
- GUIは使わないので軽量な
Raspbian Buster Lite
にしました - めっちゃ時間かかります(1時間ぐらいかかりました)
diskutill list
コマンドでMacに接続されているmicroSDのパスを取得します。
$ diskutil list
# (中略)
/dev/disk4 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: FDisk_partition_scheme *32.0 GB disk4
1: Windows_FAT_32 NO NAME 32.0 GB disk4s1
自分の環境の場合、32.0 GBの表示から /dev/disk4
が該当のmicroSDを指すとわかりました。
diskutil unmountDisk
でアンマウントと、 dd
でOSの書き込みを行います。
$ diskutil unmountDisk /dev/disk4
Unmount of all volumes on disk4 was successful
$ sudo dd bs=1m if=/👉パス/2020-02-13-raspbian-buster-lite.img of=/dev/rdisk4 conv=sync
Password:
1764+0 records in
1764+0 records out
1849688064 bytes transferred in 55.800569 secs (33148194 bytes/sec)
-
/dev/disk4
としている箇所は環境に合わせて変えてください -
👉パス
としている箇所はOSをダウンロードしたパスです - .imgファイルのファイル名も時期によって変わるはずです
Macに boot
という名前でディスクが接続されていればOKです。
$ ls /Volumes/
Macintosh HD boot
ssh接続を有効にする
MacからsshでRaspberry Piに(一時的に)接続するために空ファイルが必要です。持続的に接続する方法については後述します。
$ touch /Volumes/boot/ssh
Wi-Fi接続を有効にする
Wi-Fi接続に必要なファイルを作成します。
$ nano /Volumes/boot/wpa_supplicant.conf
以下の内容を書き込みます。
country=JP
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
key_mgmt=WPA-PSK
ssid="👉SSID名"
psk="👉パスワード"
}
- SSID、パスワード(場合によってはkey_mgmtも)は環境に合わせて変えてください
- パスワードを直接入力するとセキュリティ上のリスクがあります。ここでは触れませんが
wpa_passphrase
コマンドで暗号化できます
もちろん nano
コマンドを使わなくても、.confを作成できればおっけーです。
ここまででmicroSDへの書き込みは完了です。
Paspberry Piを起動する
諸々書き込みが終わったmicroSDカードをRaspberry Piに差し込みます。
電源に接続します。Raspberry Pi Zero WHの場合、電源供給はPWRと書かれた方の差込口を使います。
電源が入ると緑のLEDが点灯します。
ssh
コマンドでMacからラズパイに接続します。
接続先は pi@raspberrypi.local
、パスワードは raspberry
です。
$ ssh pi@raspberrypi.local
The authenticity of host 'raspberrypi.local (xxxx)' can't be established.
ECDSA key fingerprint is SHA256:xxxx.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'raspberrypi.local,xxxx' (ECDSA) to the list of known hosts.
pi@raspberrypi.local's password:
pi@raspberrypi:~ $
こんな感じでプロンプトがラズパイになっていれば成功です🎉
続いて諸々の設定をやっていきます。
タイムゾーンを設定する
ラズパイの設定画面に入るコマンドを使います。
pi@raspberrypi:~ $ sudo raspi-config
4 Localisation Options > I2 Change Timezone > Asia > Tokyo
でタイムゾーンを日本にしておきます。
ssh接続を継続して利用可能にする
上記設定画面から、
5 Interfacing Options > P2 SSH > Yes
でssh接続が持続的に利用可能になります。
IPアドレスを固定する
ifconfig
で現在のIPアドレスを、 route -n
でデフォルトゲートウェイを確認します。
pi@raspberrypi:~ $ ifconfig
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 4032 bytes 203013 (198.2 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 4032 bytes 203013 (198.2 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.100.50 netmask 255.255.255.0 broadcast 192.168.100.255
pi@raspberrypi:~ $ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.100.254 0.0.0.0 UG 302 0 0 wlan0
無線LANはwlan0なので、上記の場合 192.168.100.50
が現在のIPです。
また、Gatewayに表示された 192.168.100.254
が現在のデフォルトゲートウェイです。
確認できたら sudo nano /etc/dhcpcd.conf
で以下の内容を追記します。
interface wlan0
static ip_address=192.168.100.50/24
static routers=192.168.100.254
static domain_name_servers=192.168.100.254
- ip_addressに固定するIPアドレスを入力します。今回は
ifconfig
で確認した192.168.100.50
を使っています。サブネットマスクは基本的にはそのまま/24
でいいはずです - routersとdomain_name_serversには
route -n
で確認したデフォルトゲートウェイを入力します
再起動後、設定したIPアドレスになっていればおっけーです。
# 再起動
pi@raspberrypi:~ $ sudo reboot
# 再接続
$ ssh pi@raspberrypi.local
# IP確認
pi@raspberrypi:~ $ ifconfig
wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.100.50 netmask 255.255.255.0 broadcast 192.168.100.255
各種ライブラリをアップデートする
ラズパイのセットアップでのお決まりのようです。
pi@raspberrypi:~ $ sudo apt update
pi@raspberrypi:~ $ sudo apt upgrade
デフォルトのPythonのバージョンを3系にする
これは必須ではありませんが、 python
コマンドのデフォルトで Python2系が起動したので3系にしておきます。
pi@raspberrypi:~ $ python --version
Python 2.7.16
pi@raspberrypi:~ $ sudo unlink /user/bin/python
pi@raspberrypi:~ $ sudo ln -s /user/bin/python3 python
pi@raspberrypi:~ $ python --version
Python 3.7.3
Gitをインストールする
Gitをインストールします。ライブラリの使用に必要です。
pi@raspberrypi:~ $ sudo apt install git
pi@raspberrypi:~ $ git --version
git version 2.20.1
Raspberry Pi と CO2-mini の接続
ラズパイとCO2-miniを接続します。
接続端子はお互いmicro USB Type-Bです。
接続できるとCO2-miniはしばらくの待機後、CO2濃度と室温を表示してくれます。
co2meter
の事前準備
Python製のライブラリ co2meter
を使って、ラズパイからCO2-miniへアクセスします。
vfilimonov/co2meter: A Python library for USB CO2 meter
このco2meterを動かすにあたって必要なライブラリをインストールしていきます。
依存ライブラリのインストール
USB接続機器にアクセスしたりするために必要になるみたいです。
pi@raspberrypi:~ $ sudo apt install libusb-1.0-0-dev libudev-dev
rulesファイルの作成
/etc/udev/rules.d/98-co2mon.rules
を作成します。
root@raspberrypi:/home/pi# nano /etc/udev/rules.d/98-co2mon.rules
以下を書き込みます。
KERNEL=="hidraw*", ATTRS{idVendor}=="04d9", ATTRS{idProduct}=="a052", GROUP="plugdev", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="04d9", ATTRS{idProduct}=="a052", GROUP="plugdev", MODE="0666"
udevadm
コマンドで設定を反映させます。
が、この操作はrootユーザーとしての実行が必要らしく、まずはrootユーザーになるためにパスワードを設定します。
pi@raspberrypi:~ $ sudo passwd root
New password:
Retype new password:
passwd: password updated successfully
- パスワードを2度聞かれるので2度入力します
su
コマンドでrootユーザーに変身します。
pi@raspberrypi:~ $ su
Password:
root@raspberrypi:/home/pi#
プロンプトが root@raspberrypi:
になっていればおっけーです。
この状態で udevadm
コマンドを実行します。
root@raspberrypi:/home/pi/# sudo udevadm control --reload-rules && udevadm trigger
rootユーザーから一般ユーザーに戻るには exit
です。
root@raspberrypi:/home/pi# exit
exit
pi@raspberrypi:~ $
ここまでで事前準備は完了です。
ライブラリ co2meter
のインストール
これによっていよいよCO2-miniへのアクセスが可能になります。
pi@raspberrypi:~ $ pip3 install hidapi co2meter
README記載の基本的な使い方を試してみます。
pi@raspberrypi:~ $ python
>>> import co2meter as co2
>>> mon = co2.CO2monitor()
>>> mon.read_data()
(datetime.datetime(2020, 4, 25, 11, 10, 21), 1005, 24.100000000000023)
取得できました🎉🎉
内容は (タイムスタンプ, CO2濃度, 室温)
のタプルです。
Raspberry Pi をWebサーバーとして動かす
CO2濃度、室温が取得できることがわかったところで、続いてWebサーバーとして動かす方法を記載します。Webサーバーとして動かすことで、外部からブラウザ経由で計測結果を確認することができます。
co2meter
には、予めサーバーとして動かす機能が実装されているので利用します。
- 定期的な計測
- JSON/CSVへのロギング
- 折れ線グラフによる可視化
- Webサーバー立ち上げ
といった機能が簡単に使えます。
関連パッケージのインストール
flask、pandasのインストール
co2meter
のWebサーバー化はflaskで実装されています。
pi@raspberrypi:~ $ pip3 install -U flask pandas
Webサーバー起動
co2meter
をサーバーとして実行するには co2meter_server
コマンドだけでOKです。
これと組み合わせて、バックグラウンドでプロセスを継続させるために nohup
コマンドと &
オプションをつけて実行します。
pi@raspberrypi:~ $ nohup co2meter_server -H 0.0.0.0 -P 1201 &
-
-H 0.0.0.0
オプションをつけることで外部のブラウザから(例えば同じLAN内のMacから)接続できます -
-P 1201
オプションはポート番号です。デフォルトで使われていた1201に指定していますが数字に意味はないです
これで、ブラウザから http://ラズパイのIPアドレス:ポート番号
(この手順通りに進めていれば http://192.168.100.50:1201/
)へアクセスしてCO2濃度、室温が見れるようになりました🎉🎉🎉
サーバーとして動かすことでlogsディレクトリが作成され、CSVでログが蓄積されていきます。
計測間隔はデフォルトで約35秒のようです。
pi@raspberrypi:~ $ cat logs/co2.csv
timestamp,co2,temp
2020-04-26 15:01:59,772,23.9
2020-04-26 15:02:34,768,23.9
2020-04-26 15:03:09,766,23.9
2020-04-26 15:03:44,766,23.9
2020-04-26 15:04:20,760,24.0
Webサーバーの停止
停止させるには、 ps
コマンドでPIDを確認して kill
コマンドです。 -9
オプションは強制終了です。
pi@raspberrypi:~ $ ps
PID TTY TIME CMD
599 pts/0 00:00:00 bash
626 pts/0 00:23:44 co2meter_server
6201 pts/0 00:00:00 ps
pi@raspberrypi:~ $ kill -9 626
プロセスが表示されない場合は x
オプションをつけて ps x
で試してみてください。
iOSウィジェットに表示する
ここまでの手順で念願のCO2濃度可視化は実現できました。
あとはどこにどんな感じで表示するかですが、今回はWebサーバーから返されるJSONを使ってiOSウィジェットに表示することにしました。
できたソースはこちら
https://github.com/akeome/RoomCondition
Xcodeからこのプロジェクトをビルドすれば動くはずです。
- Targetはまず本体アプリ
RoomCondition
、次にウィジェットRoomConditionTodayExtension
の順番でビルドします - 接続先のIPアドレス、ポート番号は
http://192.168.100.50:1201/
にしています。環境に合わせて適宜変更してください。この記事通りに進めていればそのままで問題ないです
ちょっとコード解説
ウィジェット(Today Extension)は今回初めて実装してみたので知見を書いておきます。
API通信部分のコード共通化
本体アプリ、ウィジェットで共通して使う処理はFrameworkとして実装することで共通化できます。
Frameworkの作成はメニューの File > New > Target > Framework
です。
今回は APIRequest
という名前で作成しました。
public struct RoomCondition: Codable {
public let co2: String
public let temp: String
public let timestamp: String
}
public struct API {
public static func request(completion: @escaping (RoomCondition?) -> Void) {
let url = "http://192.168.100.50:1201/log.json"
let session = URLSession.shared
let task = session.dataTask(with: URL(string: url)!) { data, urlResponse, error in
let currentCondition = try! JSONDecoder().decode([RoomCondition].self, from: data!)
completion(currentCondition.last)
}
task.resume()
}
}
呼び出し側では import APIRequest
して使います。
API.request(completion: { roomCondition in
guard let roomCondition = roomCondition else {
return
}
print(roomCondition) // RoomCondition(co2: "1015", temp: "21.4", timestamp: "2020-04-28 00:35:32")
})
今回は結局Today Extension側でしか使ってないのでFrameworkにしなくてもよかったかもしれません😇
Today Extensionの更新契機について
ウィジェットが表示される度に func widgetPerformUpdate(completionHandler: @escaping (NCUpdateResult) -> Void)
が呼び出されるようです。
この中に通信処理を書いているのですがちゃんと都度最新の値を取ってきてくれます。
completionHandler
の引数には NCUpdateResult
を渡します。
public enum NCUpdateResult : UInt {
case newData
case noData
case failed
}
上から順に、新規データあり、新規データなし(更新不要)、失敗を表します。
これによっていい感じにウィジェットの再描画が行われるようです。
色分けのロジック
CO2-miniの表示部に着想を得て、ppmによって色分けしました。
func co2Color(co2Value: String) -> UIColor {
switch Int(co2Value) {
case (..<1000)?:
return .systemGreen
case (..<1200)?:
return .systemYellow
default:
return .systemRed
}
}
1000とか1200の基準は環境にもよると思うので適宜変更してください。
また室温の色分けは季節によって変えるべきかもしれません。
おまけ、本体アプリ
本体アプリを起動しても真っ白なのは寂しかったのでとりあえず SFSafariViewController
でダッシュボートを表示させておきました。
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let url = URL(string: "http://192.168.100.50:1201/dashboard")
if let url = url {
let safari = SFSafariViewController(url: url)
present(safari, animated: false)
}
}
おわりに 〜二酸化炭素濃度が人体に与える影響について〜
一般的に空気中の二酸化炭素濃度が概ね1000ppmを超えると眠気や倦怠感を誘発すると言われています。
厚生労働省が規定する建築物環境衛生管理基準においても、建築物の管理者は1000ppmになるように空調設備をちゃんとしてね〜との記載があります。
建築物環境衛生管理基準について|厚生労働省
この基準値を検証する資料もありました。
1968年のWHO報告書を根拠としていると考えられる、との考察があったりなかなか興味深いです。
[建築物環境衛生管理基準の設定根拠の検証について] (https://www.mhlw.go.jp/seisakunitsuite/bunya/kenkou_iryou/kenkou/seikatsu-eisei/gijutukensyuukai/dl/h23_3.pdf)
(※pdfです)
上記資料から一部抜粋
- 18ページ
二酸化炭素自体は、少量であれば人体に有害ではな
いが、1000ppmを超えると倦怠感、頭痛、耳鳴り、息苦しさ等の症
状を訴えるものが多くなり、フリッカー値(フリッカー値が小さいほ
ど疲労度が高い)の低下も著しい
- 19ページ
1000ppmのCO2の吸入実験(Eliseeva 1964)で呼吸、循環器系、大
脳の電気活動に変化がみられたと報告している。
街の噂で聞く1000ppmで眠くなる説はこのあたりがソースになっているのかもしれませんね。
ということで我が家のCO2濃度可視化の記録でした。
適度に換気して、快適なWFHを〜🏡
参考記事
参考にさせていただきました。先人の皆様ありがとうございます。
- Raspberry Pi のセットアップ!モニター、キーボードなしでMacからSSH
- Raspberry Pi Zero W 環境構築ことはじめ MacOS編 - Qiita
- Raspberry Pi Zero(W, WH)のセットアップ - Qiita
- Raspberry Pi Zero WHの環境構築(Mac PC) - 日々の学びのアウトプットするブログ
- Raspberry Pi に固定IPアドレスを割り当てる方法(Raspbian Jessie) - Qiita
- RaspberryPiでPythonのデフォルトをPython2.7からPython3に変更する | そう備忘録
- 「apt-get」はもう古い?新しい「apt」コマンドを使ったUbuntuのパッケージ管理 | LFI
- How to Install Git on Raspberry Pi | Linuxize
- Python - Raspberry piでnumpyがimportできない。|teratail
- nohupを使ってsshログアウト後もシェルスクリプトを動かす - Qiita
- networking - Opening port not working - Unix & Linux Stack Exchange
- Today Extension Tutorial: Getting Started | raywenderlich.com