Raspberry pi 3+ に気圧／湿度／温度センサを付けてデータ取得する

Raspberry pi 3+ を買ったので、折角なのでセンサーも買ってみました。
BME280（気圧／湿度／温度センサ）です。
コマンドでデータ取得して、更にWebSocket経由でブラウザに表示してみます。

# 接続

|BME280モジュール|RasPIピン番号|RasPIピン機能|
|----|--:|----|
|VCC|1|3V3 Power|
|GND|9|Ground|
|SCL|5|GPIO4(SCL)|
|SDA|3|GPIO3(SDA)|
|CSB|17|3V3 Power|
|SDD|13|Ground|

はんだは気合で付ける。
メスメスのジャンパピンは予め用意しときましょう。

## I2Cを有効にする

raspi-config をインストールします。
Ubuntu Mate でしたが、apt ですんなり入りました。

```bash
$ sudo apt install -y raspi-config
```

設定を開きます。

```bash
$ sudo raspi-config
```

以下の画面が開くので、I2Cを有効にします。
![スクリーンショット 2025-05-27 011253.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428227/65e40704-1f00-4b27-9596-ba74c51c3759.png)

![スクリーンショット 2025-05-27 011320.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428227/6deeaac0-2675-429c-bba3-df25731cac86.png)

## デバイス確認

再起動後に /dev/i2c-nn でデバイスが認識されています。

```bash
$ ls  /dev/i2c-*
/dev/i2c-1  /dev/i2c-2
```

i2cdetect コマンドでスキャンしてみます。

```bash
$ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- 76 --   
```

0x76 でデバイスが見つかりました。適当なはんだでも認識するもんですね。

# データ取得

データを取得してみます。巷では python が大半ですが、コマンドにしたいので Rust で書きます。
BME280のデータ取得できる Crates があるのでお世話になってみます。

https://crates.io/crates/bme280

ほぼ example のまま使えます。

```rust:main.rs
use linux_embedded_hal as hal;

use linux_embedded_hal::{Delay, I2cdev};
use bme280::i2c::BME280;

fn main() {
    let i2c_bus = I2cdev::new("/dev/i2c-1").unwrap();
    let mut delay = Delay;
    let mut bme280 = BME280::new_primary(i2c_bus);

    bme280.init(&mut delay).unwrap();

    let measurements = bme280.measure(&mut delay).unwrap();

    println!("Relative Humidity = {} %", measurements.humidity);
    println!("Temperature = {} C", measurements.temperature);
    println!("Pressure = {} hPa", measurements.pressure/100.0);
}
```

ビルドしてみます。

```bash
$ cargo run
Relative Humidity = 52.11411 %
Temperature = 25.01252 C
Pressure = 1015.226 hPa
```

データが取れました。温度が5℃くらい高い気がしますが、こんなもんなんでしょうきっと。
リリースビルドします。

```bash
$ cargo build --release
```

target/release/ 以下にバイナリができます。実行するとデータが表示されます。
i2c-view というコマンドにしました。

```bash
$ ./i2c-view
Relative Humidity = 52.08504 %
Temperature = 25.02518 C
Pressure = 1015.21954 hPa
```

良いですね。/usr/sbin/ 以下に移動して実行権限をつけておきます。

# ローカルのWebサービスからデータ参照する

ラズパイ上のWebブラウザから、ローカルのデータを拾います。
今回は WebSocket サーバを起動して、Webブラウザから投げたメッセージでコマンド結果を返すサービスを作りました。
localhost でphpなりpythonなりを動かせばいいんですが、諸事情でそうはなってないので少し遠回りします。

## WebSocket サーバを作る

メッセージを受け取ってコマンドの結果を返すだけなので、セキュリティなんかは無視します。
どうせLAN内からでしか見ません。

```rust:main.rs
use tokio::net::TcpListener;
use tokio_tungstenite::accept_async;
use tokio_tungstenite::tungstenite::protocol::Message;
use anyhow::Result;
use futures_util::{SinkExt, StreamExt};
use tokio::process::Command;

#[tokio::main]
async fn main() -> Result<()> {
    let addr = "127.0.0.1:8888".to_string();
    let listener = TcpListener::bind(&addr).await?;
    println!("WebSocket server started on ws://{}", addr);

    while let Ok((stream, _)) = listener.accept().await {
        tokio::spawn(handle_connection(stream));
    }

    Ok(())
}

async fn handle_connection(stream: tokio::net::TcpStream) -> Result<()> {
    let mut ws_stream = accept_async(stream).await?;
    println!("WebSocket connection established");

    while let Some(msg) = ws_stream.next().await {
        let msg = msg?;
        if msg.is_text() {
            let received_text = msg.to_text()?;
            println!("Received message: {}", &received_text);
            let mut res = "".to_string();

            if received_text == "i2c-view" {
                let output = Command::new("i2c-view").output().await.unwrap();
                res = String::from_utf8_lossy(&output.stdout).to_string();
                println!("{}", &res);
            }

            ws_stream.send(Message::Text(res)).await?;
        }
    }

    Ok(())
}
```

println!は無くてもいいです。分りやすいように書いただけなので。

HTML側は以下にしました。
サービス自体は別サーバに置いていても良いので、WebSocket は楽で良いですね。
※当然ラズパイ以外からはデータ取得できませんが。

```javascript
var ws = new WebSocket('ws://127.0.0.1:8888');
ws.addEventListener("message", function(event) {
	var i2cs = String(event.data).split(/\r?\n/g);
	var hum = Number(i2cs[0].match(/([\d\.]+)/)[0]);
	var temp = Number(i2cs[1].match(/([\d\.]+)/)[0]);
	var pres = Number(i2cs[2].match(/([\d\.]+)/)[0]);
	document.querySelector('.humi').textContent = '湿度: ' + Math.round(hum) + '%';
	document.querySelector('.temp').textContent = '温度: ' + Math.round(temp)+ '℃';
	document.querySelector('.pres').textContent = '気圧: ' + Math.round(pres)+ 'hPa';
});
setInterval(function() {
	ws.send('i2c-view');
}, 10000);
```
エラーハンドリングがあまあまですが、必要に応じて。

BME280をラズパイに付けてデータ取得、更にWebSocketを通してWebブラウザ上に表示することができました。

## 所感

ラズパイ面白いですが、3+は流石に重いですね。
サイネージとかのGUI使うサービスを考えるならラズパイ4以上の方が良さそう…と買ってから思いました。
いかんせん、tauri のビルドに一晩どころか丸一日かかるとかしんどすぎます。
今回の Rust のコードのビルドは10分くらいで終わるので、スペックに合ったものを作るならいいデバイスですね。