Raspberry Pi 3 Model BとMH-Z19でCO2測定
きっかけ
ほぼ自宅勤務になったので部屋の換気が気になり始める
そんな中で CO2 計測してみたいが、市販の計測器高いと感じていたがふとラズパイ (Raspberry Pi 3 Model B) が転がってることを思い出す。
センサーとかないのかなと調べてみたら、MH-Z19 というセンサーで計測できることを知る。
色々購入してみる
アマゾンで注文してみると 2 週間かかった。中国から送ってくるみたいなのでそんなもんなんだろう。
電子工作っぽいものは初めてなのでハンダゴテすら持ってなかったので USB はんだごてを買う。
(はんだの種類すら知らなかったので調べる)
さらにこのセンサーを取り付けるためには、どうやらピンヘッダ、ジャンプワイヤーってのが必要らしい。
→ 買う
届いたMH-Z19にもケーブルが付属していたが、これは使わない。
はんだ付けの練習
Youtube で勉強して何度か練習してはんだ付けをしてみる。
ちっちゃくてうまく見えないがうまくいったっぽい
ラズパイのインターフェースを調べてみる
どうやらこの 2 つのピンがキモらしい。
- VIN = 電力を外部に出力(供給)するピンらしい。(乾電池で言うプラス極)
- GND = 乾電池で言うマイナス極的なピンらしい
あとこの用語もよく出てきたのメモしておく
UART = Universal Asynchronous Receiver/Transmitter (ユーアートと読むらしい) シリアル通信上の規格、信号線は TX(送信)を相手の RX(受信)に接続する。 - 各デバイスは対等 - 1 対 1 の通信 - 信号線は送受信の 2 本 - 9600bps と 115200bps が標準(最高 1Mbps 程度)
MH-Z19 をラズパイにはんだ付けする
MH-Z19 でググると出てくる通りにはんだ付けする。
ピンヘッダというのを、MH−Z19 にぶっ刺してはんだ付けした後に、ジャンプワイヤーでラズパイと接続する。
この時点ではうまくいっているのかはわからない。
本体に雑に輪ゴムでくくりつけておくことにする。
MH-Z19 にコマンドを送ることができるユーティリティツールをラズパイにインストール
Python 環境整えて、mh_z19 ライブラリをインストール
MH-Z19 の Python 用ライブラリ
github.com
MH-Z19 の仕様書
https://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf
動かす
# sudo python3 -m mh_z19 {"co2": 726}
すんなり計測できた(ありがたい
しばらくバックグラウンドプロセスでログを出し続けてみる
楽しい
外の空気が 400ppm らしいので、それがこのセンサーの最低値になるらしい。最低値としてリセットするためにしばらく外に放置して下記コマンドでリセットする。(キャリブレーションというらしい)
# sudo python3 -m mh_z19 --zero_point_calibration
さらになんかいい感じにキャリブレーションを調整してくれるモードがあるらしいので、有効にしておく。
# sudo python3 -m mh_z19 --abc_on
CO2 値がめっちゃ高い部屋で暮らしていたことを知る。
CO2センサー、寝てる間のログを見ると2400ppmがMax、今日の仕事中も2時間ぐらいで2000ppmに到達。なるほどー
— waysaku (@waysaku) May 1, 2020
狭い部屋で締め切っているので当たり前っちゃーあたり前だけど、部屋についてる通気口をなんとかしてもうちょっとまともな数値(せめて 1000ppm 以下)をキープできないか?
部屋に換気ファンを付けてみる
PC 用のファンを取り付けてテストしてみる。
ラズパイは 5V 出力までしかできないので、トランジスタというのを使って昇圧すればいけるっぽい。
が、回路図の読み方がわからず挫折。
USB(5V) から電源をとって、12V に昇圧して PC 用ファンの 4 ピンにできるパーツを発見したので購入。
ラズパイの USB ポートからその変換パーツを通じて、PC ファンに電源を供給した状態で通気孔にファンを貼り付けてみる。
排気(部屋から外に空気を逃がす)で設置してみると改善なし。吸気(外から部屋に空気を吸い込む)で設置してみたら改善して常に 500 ~ 800ppm を維持できた
計測をモニタリング、アラート通知をしてみる
ssh でラズパイにログインして CO2 計測ログを見るのが面倒なので、せっかくなので Fluent-Bit でどこかにログ転送してみる。
datadog は無料枠だとアラート通知できないみたいなので、mackerel を使ってみる。
CO2センサーの値をFluent-Bitで転送するようにした。せっかくの Fluent-Bit なので本当はセンサー値読み出しのpython依存なくしたかったけどシリアル通信の仕組み知らなすぎて挫折したhttps://t.co/wJaPjLkpnk
— waysaku (@waysaku) May 6, 2020
実際は curl を cron なんかでスケジュール実行して、mackerel に送ればいいだけなんだけど Fluent-Bit を使ってみたかった。
無事計測できて、とりあえず 1200ppm を超えたら slack に通知するように設定した。
寝てる間も通気孔のファン回しっぱなしだと締め切っていても800ppm維持できてるっぽい
よくわらなかったところ
MH-Z19 にコマンドを送るライブラリめっちゃ便利で楽だったが、Fluent-Bit から python 呼び出すときに外部ライブラリ依存させたくなかったので、MH-Z19へのコマンド送信部分を抜き出してFluent-Bitからの呼び出し用にScriptにしたらなんかよくわからんエラーが出てかなりの時間ハマった。
どうやら、ラズパイからMH-Z19への接続(通信)が多重になると出るエラーっぽい?よくわからんが tty を開け閉めしてたら直った。
この辺シリアル通信の知識がまったくないので調べることが多すぎて挫折した。
調べたことだけメモしておく
tty = 端末(ターミナル)のこと。ssh ログインした状態で `tty` すると現在の端末デバイス ID がわかる。 echo 'Hello' > /dev/pts/2 とかやると/dev/pts/2 の端末にメッセージ送信できる stty = tty の各種設定を表示・変更できるコマンド
エラー内容
Traceback (most recent call last): File "/usr/local/lib/python3.7/dist-packages/serial/serialposix.py", line 501, in read 'device reports readiness to read but returned no data ' serial.serialutil.SerialException: device reports readiness to read but returned no data (device disconnected or multiple access on port?) During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 6, in f File "/usr/local/lib/python3.7/dist-packages/serial/serialposix.py", line 509, in read raise SerialException('read failed: {}'.format(e)) serial.serialutil.SerialException: read failed: device reports readiness to read but returned no data (device disconnected or multiple access on port?)
どうやら getty
というサービスが起動していて、指定の tty (今回は/dev/ttyS0) の接続を監視し続けているらしい。
# systemctl status serial-getty@ttyS0.service ● serial-getty@ttyS0.service - Serial Getty on ttyS0 Loaded: loaded (/lib/systemd/system/serial-getty@.service; disabled; vendor preset: enabled) Active: active (running) since Wed 2020-05-06 18:46:11 JST; 2min 41s ago Docs: man:agetty(8) man:systemd-getty-generator(8) http://0pointer.de/blog/projects/serial-console.html Main PID: 15798 (agetty) Tasks: 1 (limit: 4915) CGroup: /system.slice/system-serial\x2dgetty.slice/serial-getty@ttyS0.service └─15798 /sbin/agetty --keep-baud 115200,38400,9600 ttyS0 vt220 May 06 18:46:11 raspberrypi systemd[1]: Started Serial Getty on ttyS0.
で確認すると active
になっているので
sudo systemctl stop serial-getty@ttyS0.service
で停止させて、再度
sudo systemctl start serial-getty@ttyS0.service
で開始させたらエラー解消した。 よくわからん
[参考]
窓を開けて新鮮な空気をいれよう!Raspberry Pi でCO2 濃度を測ろう - Qiita
mh_z19b 仕様書 https://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf
プライバシーポリシー
広告の配信について
当サイトは第三者配信の広告サービス「Googleアドセンス」を利用しています。
広告配信事業者は、ユーザーの興味に応じた広告を表示するために「Cookie(クッキー)」を使用することがあります。Cookieを無効にする設定およびGoogleアドセンスに関して、詳しくはこちらをクリックしてください。
第三者がコンテンツおよび宣伝を提供し、訪問者から直接情報を収集し、訪問者のブラウザにCookie(クッキー)を設定したりこれを認識したりする場合があります。
アクセス解析ツールについて
当サイトでは、Googleによるアクセス解析ツール「Googleアナリティクス」を利用しています。このGoogleアナリティクスはトラフィックデータの収集のためにCookieを使用しています。このトラフィックデータは匿名で収集されており、個人を特定するものではありません。
この機能はCookieを無効にすることで収集を拒否することが出来ますので、お使いのブラウザの設定をご確認ください。この規約に関して、詳しくはこちらをクリックしてください。
当サイトへのコメントについて
当サイトでは、スパム・荒らしへの対応として、コメントの際に使用されたIPアドレスを記録しています。これはブログの標準機能としてサポートされている機能で、スパム・荒らしへの対応以外にこのIPアドレスを使用することはありません。
当サイトでは、次の各号に掲げる内容を含むコメントは管理人の裁量によって承認せず、削除する事があります。
- 特定の自然人または法人を誹謗し、中傷するもの。
- 極度にわいせつな内容を含むもの。
- 禁制品の取引に関するものや、他者を害する行為の依頼など、法律によって禁止されている物品、行為の依頼や斡旋などに関するもの。
- その他、公序良俗に反し、または管理人によって承認すべきでないと認められるもの。
免責事項
当サイトで掲載している画像の著作権・肖像権等は各権利所有者に帰属致します。権利を侵害する目的ではございません。記事の内容や掲載画像等に問題がございましたら、各権利所有者様本人が直接メールでご連絡下さい。確認後、対応させて頂きます。
当サイトからリンクやバナーなどによって他のサイトに移動された場合、移動先サイトで提供される情報、サービス等について一切の責任を負いません。
当サイトのコンテンツ・情報につきまして、可能な限り正確な情報を掲載するよう努めておりますが、誤情報が入り込んだり、情報が古くなっていることもございます。
当サイトに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。
運営者:waysaku
初出掲載:2020年8月30日
RSSの <content:encoded> の文字列が数値文字参照になっているので読める文字列に変換する
こんなやつ
大昔にRSSでシステム間連携を実装したときにハマった気がしたけど一切覚えてない。
content:encoded
にある メーガン妃
のような文字列はどうやら 符号化文字集合の一つである Unicode におけるその文字のCode Point を16進数で表示する 数値文字参照
という形式で出力されてるらしい。
- 数値文字参照
この数値文字参照を実際の文字に戻すには
文字符号化方式 vvvvvvvvvvvvvvvvvvvvv 文字 <-> Code Point <-> byte列 ^^^^^^^^^^^^^^^^^^^ 符号化文字集合
のうち Code Point > 文字
をすればよい。
Pythonでやる場合は unicodedata
モジュールを使って
def unicode_test(codepoint): import unicodedata #コードポイントを対応する文字の名前に変換 name = unicodedata.name(codepoint) #対応する名前に当てはまる文字を取得 letter = unicodedata.lookup(name) result = """ Unicode文字: {} 対応する文字の名前: {} """.format(letter, name) print(result)
とすると変換できる。
試してみる
>>> import re >>> pattern = '&#x([0-9A-F]{4});' >>> res = re.findall(pattern, 'メーガン妃') # 数値文字参照からCode Pointの16進数文字列部分だけ抜き出す >>> print(res) ['30E1', '30FC', '30AC', '30F3', '5983'] >>> m = map(lambda x: unicode_test(chr(int(x, 16))), res) #16進数文字列から16進数数値型に変換した上で、(Unicode)文字列に変換して上記の関数に渡してみる >>> print(list(m)) Unicode文字: メ 対応する文字の名前: KATAKANA LETTER ME Unicode文字: ー 対応する文字の名前: KATAKANA-HIRAGANA PROLONGED SOUND MARK Unicode文字: ガ 対応する文字の名前: KATAKANA LETTER GA Unicode文字: ン 対応する文字の名前: KATAKANA LETTER N Unicode文字: 妃 対応する文字の名前: CJK UNIFIED IDEOGRAPH-5983
できた。
Pythonの html.unescape()
を使うと 数値文字参照からコードポイント16進数文字列への変換
と コードポイント16進数文字列から文字に変換
を一気にやってくれるから便利。
Presto/HiveにはUTF-16を操作する関数が用意されてないっぽい
あるにはあったが、数値文字参照からコードポイント16進数文字列への変換
と コードポイント16進数文字列から文字に変換
をSQLでやるのは相当気合がいりそう(できるかどうかわからない)
Raspberry Piでmdadmを使ったraid1ディスクでTimeMachineサーバ構築のメモ
Raspberry Pi OSをインストールした後の作業メモ
GUIでの設定
ネットワーク設定
とりあえずwifiつなげる
その他
GUIから - sshの有効化 - CUIでのデフォルト起動に変更 - current user(piユーザー)でのデフォルトログインを無効化 - キーボード設定を日本語キーボードに変更
apt upgrade と vimインストール
pi@raspberrypi:~ $ sudo apt update pi@raspberrypi:~ $ sudo apt upgrade pi@raspberrypi:~ $ sudo apt install -y vim gnome-screenshot
ユーザーの追加とpiユーザのパスワード変更
FYI: https://qiita.com/R-STYLE/items/b481ba2d695ddf8bcee4
pi@raspberrypi:~ $ sudo adduser watanabe_yusaku pi@raspberrypi:~ $ sudo gpasswd -a watanabe_yusaku sudo pi@raspberrypi:~ $ sudo gpasswd -d pi sudo
piユーザーはデフォルトユーザとして何かしらOSのデフォルト設定に絡んでるかもしれないので削除せずにパスワード変更だけしておく
pi@raspberrypi:~ $ sudo passwd pi
固定IPの設定
pi@raspberrypi:~ $ sudo vim /etc/dhcpcd.conf interface eth0 static ip_address=192.168.11.203/24 static routers=192.168.11.1 static domain_name_servers=8.8.8.8 interface wlan0 static ip_address=192.168.11.204/24 static routers=192.168.11.1 static domain_name_servers=8.8.8.8
再起動後にCUIでログイン
再起動してIP確認
watanabe_yusaku@raspberrypi:~ $ ifconfig eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.11.203 netmask 255.255.255.0 broadcast 192.168.11.255 inet6 fe80::58d9:9e7:854:df74 prefixlen 64 scopeid 0x20<link> inet6 2400:4051:23c0:600:6c36:2388:c5:e8ab prefixlen 64 scopeid 0x0<global> ether dc:a6:32:91:39:36 txqueuelen 1000 (Ethernet) RX packets 543 bytes 131236 (128.1 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 149 bytes 22984 (22.4 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 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 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.11.204 netmask 255.255.255.0 broadcast 192.168.11.255 inet6 fe80::fd63:d724:db0d:fbd5 prefixlen 64 scopeid 0x20<link> inet6 2400:4051:23c0:600:225b:bdb7:cad3:d443 prefixlen 64 scopeid 0x0<global> ether dc:a6:32:91:39:37 txqueuelen 1000 (Ethernet) RX packets 386 bytes 106463 (103.9 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 35 bytes 4952 (4.8 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
時刻合わせ
watanabe_yusaku@raspberrypi:~ $ sudo vim /etc/systemd/timesyncd.conf [Time] NTP=ntp.jst.mfeed.ad.jp FallbackNTP=ntp.nict.jp time.google.com
を追記してrestart
sudo systemctl restart systemd-timesyncd
sudo systemctl status systemd-timesyncd
で確認するとSyncronizedになっていたが、TimezoneがUSになっていたのでraspi-configから 4 Localisation Options -> I2 Change Timezone
でTokyoに設定したら時間が正しくなった
mdadmインストールとraid1デバイスの復旧
/dev/sda1と/dev/sdb1は既にmdadmでraid1デバイスとして構築されているものを再度assembleする。 新たにmd0デバイスを構築する場合はこっちを参考に
watanabe_yusaku@raspberrypi:~ $ sudo apt install -y mdadm watanabe_yusaku@raspberrypi:~ $ sudo mdadm --assemble /dev/md0 /dev/sda1 /dev/sdb1 mdadm: /dev/md0 has been started with 2 drives. watanabe_yusaku@raspberrypi:~ $ cat /proc/mdstat Personalities : [raid1] md0 : active (auto-read-only) raid1 sda1[0] sdb1[1] 976629440 blocks super 1.2 [2/2] [UU] bitmap: 0/8 pages [0KB], 65536KB chunk unused devices: <none> watanabe_yusaku@raspberrypi:~ $ sudo mdadm --detail /dev/md0 /dev/md0: Version : 1.2 Creation Time : Tue May 19 05:30:38 2020 Raid Level : raid1 Array Size : 976629440 (931.39 GiB 1000.07 GB) Used Dev Size : 976629440 (931.39 GiB 1000.07 GB) Raid Devices : 2 Total Devices : 2 Persistence : Superblock is persistent Intent Bitmap : Internal Update Time : Fri May 22 18:45:33 2020 State : clean Active Devices : 2 Working Devices : 2 Failed Devices : 0 Spare Devices : 0 Consistency Policy : bitmap Name : raspberrypi:0 (local to host raspberrypi) UUID : 30e8a7d7:7fe4d381:3a337844:d762b48f Events : 10672 Number Major Minor RaidDevice State 0 8 1 0 active sync /dev/sda1 1 8 17 1 active sync /dev/sdb1
/dev/md0のマウント
watanabe_yusaku@raspberrypi:~ $ sudo mkdir /data watanabe_yusaku@raspberrypi:~ $ sudo chmod 777 /data watanabe_yusaku@raspberrypi:~ $ sudo mount -t ext4 /dev/md0 /data
TimeMachineサーバ設定
netatalkのインストール
watanabe_yusaku@raspberrypi:~ $ apt install -y netatalk watanabe_yusaku@raspberrypi:~ $ sudo vim /etc/netatalk/afp.conf ; ; Netatalk 3.x configuration file ; [Global] ; Global server settings mimic model = TimeCapsule6,106 mac charset = MAC_JAPANESE log file = /var/log/netatalk.log ; [Homes] ; basedir regex = /xxxx ; [My AFP Volume] ; path = /path/to/volume [TimeMachine] valid users = watanabe_yusaku path = /data/timemachine time machine = yes
time machine = yes
を書くの忘れててTimemachine設定の バックアップ用デスク選択の一覧に表示されなくてだいぶ時間とられた
再起動
watanabe_yusaku@raspberrypi:~ $ sudo service netatalk restart
確認
MacからTimeMachineの設定からディスク選択を確認すると表示されていることを確認
Google Cloud SpannerでFORMAT_TIMESTAMP関数使うと時間がずれる
SpannerにUTCでTIMESTAMP型で保存されてるデータをJSTに戻そうとしたときにはまった。
SELECT TIMESTAMP "2017-08-20 20:00:00 UTC" AS ORIGIN_UTC, TIMESTAMP_ADD(TIMESTAMP "2017-08-20 20:00:00 UTC", INTERVAL 9 HOUR) AS DateTime_9_Plus, FORMAT_TIMESTAMP('%Y-%m-%d %H:%M:%S', TIMESTAMP_ADD(TIMESTAMP "2017-08-20 20:00:00 UTC", INTERVAL 9 HOUR)) AS DateTime_WITH_TIME_9_Plus_FORMATED, FORMAT_TIMESTAMP('%Y-%m-%d %H:%M:%S', TIMESTAMP_ADD(TIMESTAMP "2017-08-20 20:00:00 UTC", INTERVAL (8 + 9) HOUR)) AS DateTime_8_Plus_9_FORMATED
ORIGIN_UTC | DateTime_9_Plus | DateTime_WITH_TIME_9_Plus_FORMATED | DateTime_8_Plus_9_FORMATED |
---|---|---|---|
2017-08-20T20:00:00Z | 2017-08-21T05:00:00Z | 2017-08-20 22:00:00 (なぜかずれる) | 2017-08-21 05:00:00 (8時間を補正分としていれてズレを戻す |
よくわからんが、内部で太平洋標準時(たいへいようひょうじゅんじ、Pacific Standard Time: 略称PST)+8:00が影響してるとかしてないとか? www.en.advertisercommunity.com
FORMAT_TIMESTAMP関数の第三引数で"+9:00"のように指定しないとだめだった。
FORMAT_TIMESTAMP(format_string, timestamp[, time_zone])
でも、Data Studio使ったときに期間指定すると
(FORMAT_TIMESTAMP('%Y%m%d', t0.DateTime) >= '20181201' AND FORMAT_TIMESTAMP('%Y%m%d', t0.DateTime) <= '20181231')
みたいなクエリになっちゃうので、上記のように手動で8を足しこんだカラムにしないとだめだった
さくらVPSにSoftEtherサーバ構築
基本的にはラズパイに構築したときと同じ
waysaku.hatenablog.com
ただ、すっかり忘れていたのでメモ
基本的にはここの通りにセットアップすればOKだが、vpnserverとvpnclientの両方セットアップが必要
linuxconfig.org
あと、さくらのVPSはiptableが /etc/iptables/iptables.rules で明示的にセットされてしまっているのでデフォだとudp/500とudp/4500がつながらないっぽい。
/etc/iptables/iptables.rules を削除してrebootして解決
golangでbyte配列から整数型への変換メモ
メモ
package main import ( "fmt" "golang.org/x/crypto/scrypt" "encoding/hex" "encoding/binary" "bytes" ) func main() { b := []byte{0x00, 0x00, 0x00, 0xFF} fmt.Println(b) fmt.Println(hex.EncodeToString(b)) var i int32 buf := bytes.NewReader(b) err := binary.Read(buf, binary.LittleEndian, &i) if err != nil { fmt.Println("binary.Read failded:", err) } fmt.Println(i) }