この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
テントの中から失礼します、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 を有効にしてください。ここでは詳細の手順についての説明は省きますが、下記の記事が参考になると思います。
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で学ぶ電子工作 作る、動かす、しくみがわかる! がオススメです。
今回は以上になります。ありがとうございました。