Rust & Raspberry Pi で温度センサーの値を AWS IoT Core に Pub してみた

はじめに

テントの中から失礼します、CX事業本部のてんとタカハシです!

タイトルを見てロマンを感じた方、是非お友達になりましょう。Rust、ラズパイ、IoT、うん、かっけー!な気分で記事を書いています。よろしくお願いします。

前々から、Python & Raspberry Pi でセンサー等をいじってお遊びすることはあったのですが、近年 Rust が色々と話題 & 組み込み関連とも相性が良いのでは?といったことから、ずーっとやってみたかったこと、ついにやってみました。

今回は、Rust を使って Raspberry Pi に接続している温度センサーから値を読み取り、AWS IoT Core に Publish する方法についてご紹介したいと思います。

尚、この記事の中では、AWS IoT Core らへんのデプロイ等については語りませんが、下記のリポジトリの方で、Publish した値が AWS IoT Core を経由して DynamoDB に格納されるまでの流れを試して頂くことができるので、お時間あれば是非チェックしてみてください。

GitHub - iam326/send-temp-to-aws-iot-with-rpi

環境

Raspberry Pi OS や Rust のバージョンは下記の通りです。Rust の インストールについては手順を載せますが、Raspberry Pi のセットアップは完了していることを前提とさせてください。

$ lsb_release -a
No LSB modules are available.
Distributor ID:	Raspbian
Description:	Raspbian GNU/Linux 10 (buster)
Release:	10
Codename:	buster

$ cargo --version
cargo 1.44.1 (88ba85757 2020-06-11)

$ rustc --version
rustc 1.44.1 (c7087fe00 2020-06-17)

また、Raspberry Pi 自体のモデルは、「Raspberry Pi 2 Model B」を使用して記事を書いていますが、今回使用する Pin の配置は、Raspberry Pi 3 or 4 系 と同じになります。

使用する部品

温度センサ ADT7410 と I2C で通信可能な基板を使用します(はんだ付けが必要です)。みんな大好き秋月電子さんで購入可能です。

ADT7410使用 高精度・高分解能 I2C・16Bit 温度センサモジュール

配線

下記の通りに Raspberry Pi と温度センサーを接続してください。

温度センサーとの接続を確認する

Raspberry Pi で I2C を有効にする

raspi-config から I2C を有効にしてください。ここでは詳細の手順についての説明は省きますが、下記の記事が参考になると思います。

Raspberry Piでi2c通信を有効にする

I2C を有効にしたら、Raspberry Pi を再起動する必要があります。

接続を確認する

i2cdetect を使用して、温度センサーとの接続を確認します。

下記のように、48と数値が表示されていれば OK です。

$ sudo apt-get install -y i2c-tools

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

Raspberry Pi に Rust をインストールする

下記の手順で Raspberry Pi に Rust をインストールしてください。 インストールが完了すると PATH を通すための設定が ~/.profile に追加されるので、source を叩いた後、cargo が叩けるか確認してください。

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

$ tail -n 1 ~/.profile
export PATH="$HOME/.cargo/bin:$PATH"

$ source ~/.profile

$ cargo --version
cargo 1.44.1 (88ba85757 2020-06-11)

お待ちかねの実装です

Rust の Project を作成する

cargo を使用して新しい Project を作成します。Project の名前は何でも良いですが、ここでは「rust-rpi-sample」にします。作成したらそのディレクトリの中に移動してください。

$ cargo new rust-rpi-sample
     Created binary (application) `rust-rpi-sample` package

$ cd rust-rpi-sample/

$ tree
.
├── Cargo.toml
└── src
    └── main.rs

使用するクレートの指定

Cargo.toml 内の dependencies を下記の通りにします。

[dependencies]
rppal = "0.11.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
rumqtt = "0.31.0"
chrono = "0.4.13"

rppal で Raspberry Pi を操作して、rumqtt で AWS IoT Core に データを mqtt で Publish します。

プログラム全貌

main.rs の中身を下記の通りにします。

プログラムを実行する

下記の通りにプログラムを実行すると、10秒おきに温度センサーの値を AWS IoT Core に Publish します(初回実行時は、クレートのインストールにかなりの時間がかかります)。

$ cd src/

$ cargo run main.rs
    Finished dev [unoptimized + debuginfo] target(s) in 1.03s
     Running `<PATH>/rust-rpi-sample/target/debug/rust-rpi-sample`
Publish(Publish { dup: false, qos: AtLeastOnce, retain: false, topic_name: "iot/topic", pkid: Some(PacketIdentifier(1)), payload: [123, 34, 116, 105, 109, 101, 115, 116, 97, 109, 112, 34, 58, 49, 53, 57, 53, 51, 52, 51, 51, 53, 53, 44, 34, 116, 101, 109, 112, 101, 114, 97, 116, 117, 114, 101, 34, 58, 50, 55, 46, 48, 125] })
Publish(Publish { dup: false, qos: AtLeastOnce, retain: false, topic_name: "iot/topic", pkid: Some(PacketIdentifier(1)), payload: [123, 34, 116, 105, 109, 101, 115, 116, 97, 109, 112, 34, 58, 49, 53, 57, 53, 51, 52, 51, 51, 54, 53, 44, 34, 116, 101, 109, 112, 101, 114, 97, 116, 117, 114, 101, 34, 58, 50, 55, 46, 48, 125] })
Publish(Publish { dup: false, qos: AtLeastOnce, retain: false, topic_name: "iot/topic", pkid: Some(PacketIdentifier(1)), payload: [123, 34, 116, 105, 109, 101, 115, 116, 97, 109, 112, 34, 58, 49, 53, 57, 53, 51, 52, 51, 51, 55, 53, 44, 34, 116, 101, 109, 112, 101, 114, 97, 116, 117, 114, 101, 34, 58, 50, 55, 46, 48, 125] })

...

プログラムの細かい説明

温度センサーの値を読み取る部分

プログラムの中で、センサーの値を読み取っている部分は下記になります。

const BUS: u8 = 1;
const ADDRESS_ADT7410: u16 = 0x48;
const ADDRESS_REGISTER: u8 = 0x00;

...

let mut i2c = I2c::with_bus(BUS).expect("Couldn't start i2c. Is the interface enabled?");
i2c.set_slave_address(ADDRESS_ADT7410).unwrap();

...

fn read_temperature(i2c: &I2c) -> f32 {
    let word = i2c.smbus_read_word(ADDRESS_REGISTER).unwrap();
    let data = ((word & 0xff00) >> 8 | (word & 0xff) << 8) >> 3;

    if data & 0x1000 == 0 {
      data as f32 * 0.0625
    } else {
      ((!data & 0x1fff) + 1) as f32 * -0.0625
    }
}

更に分解して説明します。

I2C で 温度センサーの値を取得するための準備をする

I2C のバス & 温度センサーのアドレスを指定して、温度センサーと通信できるようにします。

const BUS: u8 = 1;
const ADDRESS_ADT7410: u16 = 0x48;

...

let mut i2c = I2c::with_bus(BUS).expect("Couldn't start i2c. Is the interface enabled?");
i2c.set_slave_address(ADDRESS_ADT7410).unwrap();

バスについては rppal - I2c - with_bus の説明から、バス1番を指定すれば良さそうなことが分かります(Raspberry Pi 1 の場合は0番になります)。

On the Raspberry Pi Model B Rev 1, those pins are tied to bus 0. On every other Raspberry Pi model, they're connected to bus 1.

温度センサーのアドレスについては マニュアル の説明から、0x48 であることが分かります。

I2C のバスアドレスは、ADT7410 の 3 番・4 番ピンで設定します。 基板のデフォルト状態で A1-A0:0-0(0x48) です。

温度センサーの値を読み取る

実際に I2C で温度センサーの値を読み取っていきます。

const ADDRESS_REGISTER: u8 = 0x00;

...

fn read_temperature(i2c: &I2c) -> f32 {
    let word = i2c.smbus_read_word(ADDRESS_REGISTER).unwrap();
    let data = ((word & 0xff00) >> 8 | (word & 0xff) << 8) >> 3;

    if data & 0x1000 == 0 {
      data as f32 * 0.0625
    } else {
      ((!data & 0x1fff) + 1) as f32 * -0.0625
    }
}

データシート から、レジスタ先頭 2byte が温度の値であることが分かるので、i2c.smbus_read_word(ADDRESS_REGISTER) で 2byte 読み取っています。

Table 6. ADT7410 Registers

...

0x00 | Temperature value most significant byte
0x01 | Temperature value least significant byte

読み取った値のフォーマットについても、データシート から、先頭 3bit を除いた 13bit が 0.0625°C 刻みで入っていることが分かるので、後段の処理で少しごにょごにょしています。条件分岐の部分は、温度が + なのか - なのかを判定して、その結果に合わせて変換方法を変えています。

Table 5. 13-Bit Temperature Data Format

...

−0.0625°C | 1 1111 1111 1111
0°C           | 0 0000 0000 0000
+0.0625°C | 0 0000 0000 0001

AWS IoT Core に Publish する部分

プログラムの中で、AWS IoT Core に Publish する部分は下記になります。

エンドポイントや証明書、秘密鍵等を読み込んで mqtt クライアントを作成した後、トピックと payload を指定して Publish しています。他の言語で AWS IoT Core を触ったことがある方には、すぐ理解できる内容だと思います。

let client_id = env::var("AWS_IOT_CLIENT_ID").expect("AWS_IOT_CLIENT_ID is undefined.");
let aws_iot_endpoint = env::var("AWS_IOT_ENDPOINT").expect("AWS_IOT_ENDPOINT is undefined.");
let ca_path = "AmazonRootCA1.pem";
let client_cert_path = "certificate.pem.crt";
let client_key_path = "private.pem.key";

let mqtt_options = MqttOptions::new(client_id, aws_iot_endpoint, 8883)
    .set_ca(read(ca_path).unwrap())
    .set_client_auth(read(client_cert_path).unwrap(), read(client_key_path).unwrap())
    .set_keep_alive(10)
    .set_reconnect_opts(ReconnectOptions::Always(5));

let (mut mqtt_client, notifications) = MqttClient::start(mqtt_options).unwrap();
mqtt_client.subscribe("iot/topic", QoS::AtLeastOnce).unwrap();

...

mqtt_client.publish("iot/topic", QoS::AtLeastOnce, false, payload).unwrap();

下記のリポジトリにて、Publish したデータが DynamoDB に格納される流れを確認できます。

GitHub - iam326/send-temp-to-aws-iot-with-rpi

おわりに

今回は、Rust & Raspberry Pi で 温度センサーの値を AWS IoT Core に Publish する方法についてご紹介しましたが、いかがだったでしょうか。

私自身は、ほぼ初めて Rust に触れたということもあり、この記事を書くまでに結構な時間がかかりました。その分、やりたかったことが実現できたときの感動は最大レベルで最&高でした。 まだ Rust について全然詳しくないので、基本文法レベルでちょいちょい勉強していきたいのと、引き続きセンサー周りも動かしていきたいな〜と考えています。

もしこの記事を見て、Raspberry Pi でセンサー等を動かしてみたいと思った方がいれば、ブルーバックス出版の Raspberry Piで学ぶ電子工作 作る、動かす、しくみがわかる! がオススメです。

今回は以上になります。ありがとうございました。