3密見える化センサのCO2値をプッシュ通知する

900ppmを超えるCO2濃度がDiscordに通知された

更新履歴 見出しにジャンプ

日時 変更内容
2025-03-27
  • bluetoothctl scan onではアクティブスキャンになってしまっていたので、パッシブスキャンに変更

Senseair Sunriseとは 見出しにジャンプ

今の住宅は、隙間風を減らし計画的な換気を行うよう設計される。ただし、人はCO2を知覚できない(臭気、眠気や頭痛で間接的に感じることもあるが、個人差がある)ため、換気が十分かは実際に測らなければ分からない。
一般消費者におけるCO2濃度計は、精度の高いNDIR方式のセンサを買ってきて、マイコンに繋いで自作するのが一般的で、やや敷居が高かった。

ところが2020年以降、SARS-Cov-2への対策の一つとして換気が推奨され、その指標として人の呼気に含まれるCO2を計測する需要が高まった。
そんな中で登場したのが旭化成の「3密見える化センサ」で、そのあまりカッコよくないネーミングや筐体からは想像できない世界最高性能のCO2センサ「Senseair Sunrise」を搭載している。
Sunrise | Air quality & Gas sensing technology from Senseair

Senseair Sunriseは、海外の科学者・公衆衛生エンスージアストの中でデファクトの「Aranet4」も採用する、高性能なセンサだ。
NDIRの精度に加え、これらの2機種は他の追随を許さない年単位のバッテリーライフを持つ。これならば自作するよりもよい結果が得られると思い、購入に至った。
旭化成 3密見える化センサ – コロナ対策換気視覚化CO2モニター情報

換気View Proの無償化 見出しにジャンプ

ポストコロナ社会では、日常化した感染イベントから身を守る手段の一つとして、CO2計測の重要性が再認識されている。
そんな中、企業向けの「換気View Pro」アプリが無償化され、閾値を超えた際に通知できるようになった。

換気View Proの通知

「換気View Pro」をApp Storeで

「換気View」では断続的にしか確認できなかったため重宝したが、iPhoneがBLEビーコンを購読する際のバッテリーへの影響がそれなりに大きいことが悩みの種だった。

nRF ConnectでCO2濃度を確認 見出しにジャンプ

iOSのnRF Connect AppでBLEビーコンを覗くと...

nRF Connect Appで3密見える化センサのBLEビーコンを取得 換気View Proの表示と比較

平文で、CO2濃度0x0236(566ppm)、温度0x09FD(25.57°C)、湿度0x17B4(60.68%)を示している。
このため、これらのBLEビーコンを継続的にdumpすれば、世界最高性能のCO2センサの値を容易に記録できると分かった。

こちらもBLE?平文かはdumpしてみないと分からない。

bluetoothctl 見出しにジャンプ

最新のQSVが動くミニPCにたまたまBluetoothが付いていただけだが、据え置きのLinuxでBLEビーコンを購読するにあたり白羽の矢が立った。
筆者はN5105モデルを使っているが、廃盤らしい。今買うならN100一択だろう。

Bluezをインストール。後述の通り、hcitoolhcidumpを含むbluez-deprecatedは使用せず、bluetoothctlbtmonを導入。

dnf install usbutils bluez

systemctl enable bluetooth
systemctl start bluetooth

Failed to access management interface 見出しにジャンプ

systemctl status bluetooth
# bluetoothd[1031]: src/adapter.c:adapter_init() Failed to access management interface

bluetoothctl scan on
# Unable to open mgmt_socket

PVEのコンテナからのBluetoothデバイスの動作に失敗した。下記手順でもコンテナからは使えなかった。
Bluetooth device on container | Proxmox Support Forum
Bluetooth dongle not working with LXC | Proxmox Support Forum
PVE 7.4 + Home Assistant: Intel AX200 Bluetooth issues | Proxmox Support Forum
VMオプション設定コマンドqm setが使用できるVMでセットアップしたところ、すんなり動作した。
Error Configuration file 'nodes/proxmox01/qemu-server/100.conf' does not exist | Proxmox Support Forum

  1. PVE側でBluetoothドライバを無効化
+ blacklist btusb

再起動が必要。

update-initramfs -u
shutdown -r now
  1. Bluetoothデバイスの紐づけ
    ネットワーク設定/ワイヤレス - ArchWiki
lsusb
# Bus 001 Device 003: ID 8087:0a2a Intel Corp. Bluetooth wireless interface

VM -> Hardware -> Add -> USB Deviceで、上記Vender/Device IDを追加。

qm set <VMID> --usb0 host=8087:0a2a

# 削除
qm set <VMID> --delete usb0

Intel AX211 WiFi 6E PCIe Passthrough no Bluetooth | Proxmox Support Forum
HomeAssistantでSwitchBotスマートロックの鍵状態を表示する(Bluetooth接続編)|nano

  1. VMでドライバを読み込み
sudo dmesg | grep -i bluetooth

sudo modprobe -r btusb
sudo modprobe btusb

BLEビーコンの取得 見出しにジャンプ

bluez-deprecatedの非推奨コマンドhcitool lescan --acceptlisthcidump --manufacturerが動作しなかったため、素直にbluetoothctlを使用した。
新しいBluezの機能をみていくと、どうやらbluetoothctl: hcitool, btmon: hcidumpの関係らしい。

  1. スキャンの実行
bluetoothctl
menu scan
    clear
    transport le
    duplicate-data on
    pattern 00:53:FE:DC:BA:98
    back
scan on
[bluetooth]# SetDiscoveryFilter success
[bluetooth]# Discovery started
[bluetooth]# [CHG] Controller 00:53:AB:CD:EF:01 Discovering: yes
[bluetooth]# [NEW] Device 00:53:12:34:56:78 00-53-12-34-56-78
[bluetooth]# [NEW] Device 00:53:FE:DC:BA:98 AS1s
[bluetooth]# [CHG] Device 00:53:FE:DC:BA:98 RSSI: 0xffffffca (-54)
[bluetooth]# [CHG] Device 00:53:FE:DC:BA:98 ManufacturerData.Key: 0x0965 (2405)
[bluetooth]# [CHG] Device 00:53:FE:DC:BA:98 ManufacturerData.Value:
[bluetooth]#   a1 ** ** 00 02 9d 09 d0 16 8b 00 3c 01 55 00 64  ..e........<.U.d
[bluetooth]#   a1                                               .
sudo btmon
> HCI Event: LE Meta Event (0x3e) plen 43                                                                                   #6 [hci0] 1.637790
      LE Advertising Report (0x02)
        Num reports: 1
        Event type: Non connectable undirected - ADV_NONCONN_IND (0x03)
        Address type: Random (0x01)
        Address: 00:53:FE:DC:BA:98 (Static)
        Data length: 31
        Company: not assigned (2405)
          Data[17]: a1****00030b0a0c1718003c01550064a1
        Name (short): AS1s
        16-bit Service UUIDs (complete): 1 entry
          Unknown (0xfd29)
        RSSI: -58 dBm (0xc6)
  • スキャン中btmonでは、LE Advertising ReportやLE Meta Eventのデータ部まで確認できる。
  1. スキャンの停止
scan off

パッシブスキャン 見出しにジャンプ

bluetoothctl scan onはアクティブスキャンを高頻度で実行するようだ。
BLEビーコンを受信するにあたって必要ないし、周囲のデバイスに対するバッテリーへの影響が心配だ。

sudo btmon
# ~~
# < HCI Command: LE Set Scan Parameters (0x08|0x000b) plen 7                              #165 [hci0] 17.038490 
#         Type: Active (0x01)
#         Interval: 22.500 msec (0x0024)
#         Window: 11.250 msec (0x0012)
#         Own address type: Random (0x01)
#         Filter policy: Accept all advertisement (0x00)

bluetoothctlでパッシブスキャンを行うには、BluezのExperimental機能を有効にする。
Bluetooth - Home Assistant

[General]
- #Experimental = false
+ Experimental = true
sudo systemctl restart bluetooth

このように、パターンを記述してビーコンを引っ掛ける。

bluetoothctl
[bluetoothctl]> menu monitor
[bluetoothctl]> add-or-pattern 0 255 6509
# Advertisement Monitor 0 added
# Advertisement monitor 0 activated
# [NEW] Device 00:53:FE:DC:BA:98 AS1s
# Advertisement monitor 0 found device /org/bluez/hci0/dev_00_53_FE_DC_BA_98
# [CHG] Device 00:53:FE:DC:BA:98 ManufacturerData.Key: 0x0965 (2405)
# [CHG] Device 00:53:FE:DC:BA:98 ManufacturerData.Value:
#   a1 ** ** 00 02 4f 08 cf 14 ef 00 3c 01 74 00 64  ..e..O.....<.t.d
#   a1
[bluetoothctl]> remove-pattern all

下記Issueにパターンの例があった。
Unable to monitor for patterns with data types other than 1 (Raspberry Pi 3b+) · Issue #901 · bluez/bluez

You should be able to reproduce this in bluetoothctl. If you have any Apple devices nearby, you should be able to detect them with:

menu monitor
add-or-pattern 0 1 1a

The above should work even with BlueZ 5.69 and newer. On the other hand, if you then run:

remove-pattern all
add-or-pattern 0 255 4c

Advertisement monitor matching no longer works for most data types · Issue #652 · bluez/bluez

上記を実行し、sudo btmonでヒットしたデバイスを確認すると、
add-or-pattern 0 1 1aFlags: 0x1aに、
add-or-pattern 0 255 4cはManufacturerData255Company: Apple, Inc. (76)(76 = 0x4c)に一致しているようだった。

> HCI Event: LE Meta Event (0x3e) plen 29                                  #45 [hci0] 8.470057
      LE Advertising Report (0x02)
        Num reports: 1
        Event type: Connectable undirected - ADV_IND (0x00)
        Address type: Random (0x01)
        Address: 5A:46:7E:D0:73:56 (Resolvable)
        Data length: 17
        Flags: 0x1a
          LE General Discoverable Mode
          Simultaneous LE and BR/EDR (Controller)
          Simultaneous LE and BR/EDR (Host)
        TX power: 12 dBm
        Company: Apple, Inc. (76)
          Type: Unknown (16)
          Data[5]: 211ca987bb
        RSSI: -87 dBm (0xa9)

注意点として、ManufacturerData Key: 0x02e1のデバイスを引っ掛けるパターンとしてadd-or-pattern 0 255 e102が使われている事例があるように、おそらくエンディアンの都合でbyteを入れ替える必要がありそうだ。
Passive scan or_pattern is ignored if connected to another device · Issue #1445 · hbldh/bleak
以上から、Company: not assigned (2405)に一致するのはadd-or-pattern 0 255 6509であった。

Discordへ通知 見出しにジャンプ

ユーザ操作が前提のbluetoothctlを自動化するために、expectで入力をエミュレートする。下記をパ...参考にさせて頂いた。
bluetooth - How to use bluetoothctl like hcitool lescan to report repeated proximity beacons - Stack Overflow

#!/bin/bash

max_co2=1000 # ppm (decimal)
sensor_id="0F FF" # Sensor 4095 (hex)
webhook_url="https://discord.com/api/webhooks/XXXXXXXX"

(cat <<'END' | /usr/bin/expect

    set prompt ">"
    set timeout -1

    spawn bluetoothctl

    expect -re $prompt
    send "menu monitor\r"

    expect -re $prompt
    send "add-or-pattern 0 255 6509\r"

    trap {
        expect -re $prompt
        send "remove-pattern all\r"

        expect -re $prompt
        send "quit\r"
    } SIGINT

    expect eof

END
) | grep -oP --line-buffered "a1 $sensor_id 0\d \K(\w+\s){2}" | while read -r bytes; do
    # rate limit
    if [ "$value" != "$before_value" ]; then
        before_value=$value

        # hex to decimal
        decimal_value=$((16#$value))

        if [ "$decimal_value" -gt $max_co2 ]; then
            echo "$decimal_value > $max_co2"
            curl -H "Content-Type: application/json" -X POST -d "{\"content\":\"Current CO2 concentration is $decimal_value ppm.\"}" $webhook_url
        else
            echo "$decimal_value <= $max_co2"
        fi
    fi
done
  • データ部もbluetoothctlで取得できたため、btmonは使っていない
  • max_co2, sensor_id, webhook_urlを設定する
  • crontab -eにでも書いておくとよい
@reboot /bin/bash /home/user/beacon-scan.sh

Grafanaで表示 見出しにジャンプ