58
39

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

iOSウィジェットにCO2濃度を表示する 【Raspberry Pi × CO2-mini × co2meter】

Last updated at Posted at 2020-04-30

WFH、捗ってますでしょうか。

我が家の場合、机やモニターなど家で仕事をするのに十分な設備は元々整っていたのですが、たったひとつだけオフィスにはあって自宅にないものがありました。

CO2モニターです。

成果物

パッと目につくところに表示させたかったので、iOSのウィジェットに置くことにしました。
最終的にこんな感じでCO2濃度、ついでに室温が見えるようになりました。

iPhone

iPad

スクリーンショット 2020-04-26 22.34.59.png

構成と概要解説

構成はこんな感じです
image.png

ポイントは以下です。

  • 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

以下の内容を書き込みます。

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
スクリーンショット 2020-04-29 0.38.34.png

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 で以下の内容を追記します。

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

以下を書き込みます。

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濃度、室温が見れるようになりました🎉🎉🎉

スクリーンショット 2020-04-26 18.31.51.png スクリーンショット 2020-04-26 18.32.09.png

サーバーとして動かすことで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 という名前で作成しました。

RoomCondition.swift
public struct RoomCondition: Codable {
    public let co2: String
    public let temp: String
    public let timestamp: String
}
APIRequest.swift
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 して使います。

呼び出し側.swift
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によって色分けしました。

TodayViewController.swift
    func co2Color(co2Value: String) -> UIColor {
        switch Int(co2Value) {
        case (..<1000)?:
            return .systemGreen
        case (..<1200)?:
            return .systemYellow
        default:
            return .systemRed
        }
    }

快適

そろそろ換気しよ

あかん

1000とか1200の基準は環境にもよると思うので適宜変更してください。
また室温の色分けは季節によって変えるべきかもしれません。

おまけ、本体アプリ

本体アプリを起動しても真っ白なのは寂しかったのでとりあえず SFSafariViewController でダッシュボートを表示させておきました。

ViewController.swift
    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を〜🏡

参考記事

参考にさせていただきました。先人の皆様ありがとうございます。

58
39
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
58
39

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?