AWS IoT Core を使って Jetson Nano をクラウドに接続してみた

# 1. はじめに
普段シンクライアントやクラウドにしか触れることのない今、物理デバイスへの熱きノスタルジーを捨てきれないエンジニアたちはどこに向かえばよいのでしょうか。

本稿では、CUDA コア 128 基の GPU を搭載した可愛いやつ、NVIDIA Jetson Nano を AWS IoT Core に接続してみたいと思います。

# 2. 概要
手順はシンプルで、以下の 2 ステップで進めて行きます：

1. まずは Jetson Nano のセットアップをおこないます。
1. 次に AWS IoT Core の管理コンソールの手順に従い、Jetson Nano を AWS IoT Core に接続します。

# 3. 用意した機材
本稿を執筆するにあたり、用意した機材は以下となります：

* NVIDIA Jetson Nano 開発者キット B01
* Nextorage 64GB micro SDXC (UHS-I, U3, V30)
  * カメラ関連でお世話になっている Nextorage さんの micro SDXC カードを使用させていただきました。
* サードパティ製 AC アダプタ（5V, 4A, PSE マーク付き）
* HHKB Professional Classic 無刻印／墨（英語配列）
* Logicool MX MASTER 2S
  * 無線でも普通に接続できました。
* EIZO ColorEdge CS2731
  * HDMI でも DP でも接続できました。
* MacBook Air (M1 チップ搭載 13 インチモデル)
  * JetPack を micro SDXC カードに書き込むために使用しました。
* ~~Camera Module for NVIDIA Jetson NANO, 8MP, D160°~~
  * 今回は使用しませんでした。

なお、無線 LAN には対応していないので有線 LAN ケーブルを繋ぐ必要があります。

# 4. Jetson Nano を準備する
## 4.1. micro SDXC カードをフォーマット
[SD Memory Card Formatter for Mac](https://www.sdcard.org/downloads/formatter/sd-memory-card-formatter-for-mac-download/) を使用して micro SDXC カードのフォーマットをおこないます。クイックフォーマットにしたので一瞬で終わります。

![スクリーンショット 2023-12-14 11.18.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333253/32beeb97-80e4-f93b-f2b0-a111c3240e15.png)

## 4.2. JetPack の SD カードイメージをダウンロード
[NVIDIA のサイト](https://developer.nvidia.com/jetpack-sdk-464) から JetPack 4.6.1 の SD カードイメージをダウンロードします。SD カードイメージは ZIP で約 6 GB です。

:::note info
JetPack は Jetson シリーズ向けの Ubuntu ベースの OS で、Jetson シリーズ独自の機能が付加されています。
:::

## 4.3. micro SDXC カードに JetPack を書き込む
[Etcher](https://etcher.balena.io/) を使用して、先ほどダウンロードした JetPack 4.6.1 の SD カードイメージを micro SDXC カードに書き込みます。

![スクリーンショット 2023-12-14 12.11.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333253/40f807f5-cab1-f77f-6ccc-f67c4c5e02dc.png)

## 4.4. JetPack 初期設定
JetPack 4.6.1 を書き込んだ micro SDXC カードを Jetson Nano に挿し込み、AC アダプタを接続します。Jetson Nano には電源ボタンが存在しないため、AC アダプタを接続したら即電源 ON になります。
画面の指示に従い、言語選択、キーボード選択…と進めて行くと JetPack の恰好良いデスクトップが表示されるかと思います。

![Screenshot from 2023-12-17 23-14-08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333253/0767f753-164d-f577-61a3-164dee549b56.png)

これで JetPack の準備が整いました。次は AWS IoT Core に繋げて行きます。

# 5. AWS IoT Core を準備する
ここからは簡単のため Jetson Nano から直接操作をしています。

## 5.1. デバイスを準備する
Jetson Nano のブラウザから AWS IoT Core の管理コンソールにアクセスしモノの接続を始めます。

![Screenshot from 2023-12-17 22-38-30(2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333253/789cc2c9-f45d-d7f5-ddd2-850cee9d4e18.png)

まずは Jetson Nano から AWS IoT Core への疎通確認をおこないます。
ターミナルから `ping` コマンドを実行します。

```bash
$ ping aaq***.iot.ap-northeast-1.amazonaws.com
PING aaq***.iot.ap-northeast-1.amazonaws.com (35.75.217.165) 56(84) bytes of data.
64 bytes from ec2-35-75-217-165.ap-northeast-1.compute.amazonaws.com (35.75.217.165): icmp_seq=1 ttl=245 time=15.1 ms
64 bytes from ec2-35-75-217-165.ap-northeast-1.compute.amazonaws.com (35.75.217.165): icmp_seq=2 ttl=245 time=15.3 ms
64 bytes from ec2-35-75-217-165.ap-northeast-1.compute.amazonaws.com (35.75.217.165): icmp_seq=3 ttl=245 time=14.9 ms
64 bytes from ec2-35-75-217-165.ap-northeast-1.compute.amazonaws.com (35.75.217.165): icmp_seq=4 ttl=245 time=16.8 ms
64 bytes from ec2-35-75-217-165.ap-northeast-1.compute.amazonaws.com (35.75.217.165): icmp_seq=5 ttl=245 time=14.9 ms
64 bytes from ec2-35-75-217-165.ap-northeast-1.compute.amazonaws.com (35.75.217.165): icmp_seq=6 ttl=245 time=15.2 ms
64 bytes from ec2-35-75-217-165.ap-northeast-1.compute.amazonaws.com (35.75.217.165): icmp_seq=7 ttl=245 time=18.0 ms
^C
--- aaq***.iot.ap-northeast-1.amazonaws.com ping statistics ---
7 packets transmitted, 7 received, 0% packet loss, time 6008ms
rtt min/avg/max/mdev = 14.904/15.775/18.011/1.114 ms
```

無事に `ping` は通ったようです。

## 5.2. デバイスを登録して保護する
モノの名前に `jetson-nano` と入力し、他はデフォルトで `次へ` を選択します。

![Screenshot from 2023-12-17 22-39-35 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333253/21e7013e-cc05-fc53-9663-b72d190ae65d.png)

## 5.3. プラットフォームと SDK を選択します
JetPack は Ubuntu ベースであるため、デバイスプラットフォームのオペレーティングシステムは `Linux / macOS` を選択します。
AWS IoT デバイス SDK は好みで良いですが、ここでは `Python` を選択しました。

![Screenshot from 2023-12-17 22-40-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333253/a597de54-72a4-4567-5237-83cdc377a827.png)

## 5.4. 接続キットをダウンロード
`接続キットをダウンロード` を選択し接続キットをダウンロードします。

![Screenshot from 2023-12-17 22-40-44 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333253/649eb097-effc-ba15-df16-43933f324208.png)

ダウンロードした接続キットは `unzip` で解凍します。

```bash
$ mkdir awsiotcore
$ cp connect_device_package.zip awsiotcore/
$ cd awsiotcore/
$ unzip connect_device_package.zip
```

## 5.5. 接続キットを実行
`start.sh` に実行権限を与えて、`start.sh` を実行します。

![Screenshot from 2023-12-17 22-43-08 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333253/9990add2-ae10-7a16-adbe-10ff4807a1ab.png)

```bash
$ chmod +x start.sh
$ ./start.sh

Downloading AWS IoT Root CA certificate from AWS...
./start.sh: line 14: curl: command not found
```

`curl` 入ってないのですね、、ということで `apt` でインストールします。

```bash
$ sudo apt install curl
```

改めて `start.sh` を実行します。

```bash
$ ./start.sh 

Cloning the AWS SDK...
Cloning into 'aws-iot-device-sdk-python-v2'...
remote: Enumerating objects: 2472, done.
remote: Counting objects: 100% (940/940), done.
remote: Compressing objects: 100% (248/248), done.
remote: Total 2472 (delta 771), reused 763 (delta 678), pack-reused 1532
Receiving objects: 100% (2472/2472), 2.11 MiB | 4.08 MiB/s, done.
Resolving deltas: 100% (1614/1614), done.

Installing AWS SDK...
/usr/bin/python3: No module named pip
```

`pip` 入れてなかったですね、、ということで `apt` でインストールします。

```bash
$ sudo apt install python3-pip
```

改めて `start.sh` を実行します。

```bash
$ ./start.sh 

Installing AWS SDK...
Processing ./aws-iot-device-sdk-python-v2
awsiotsdk requires Python '>=3.7' but the running Python is 3.6.9
```

Python が古いんですね、、ということで `apt` でインストールします。
ついでに、デフォルトを `3.8` となるように変更します。

```bash
$ sudo apt install python3.8
$ sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1
update-alternatives: /usr/bin/python3 (python3) を提供するために自動モードで /usr/bin/python3.8 を使います
$ sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 2
update-alternatives: /usr/bin/python3 (python3) を提供するために自動モードで /usr/bin/python3.6 を使います
$ sudo update-alternatives --config python3
alternative python3 (/usr/bin/python3 を提供) には 2 個の選択肢があります。

  選択肢    パス              優先度  状態
------------------------------------------------------------
* 0            /usr/bin/python3.6   2         自動モード
  1            /usr/bin/python3.6   2         手動モード
  2            /usr/bin/python3.8   1         手動モード

現在の選択 [*] を保持するには <Enter>、さもなければ選択肢の番号のキーを押してください: 2
update-alternatives: /usr/bin/python3 (python3) を提供するためにマニュアルモードで /usr/bin/python3.8 を使います
$ python3 -V
Python 3.8.0
```

気を取り直して `start.sh` を実行します。

```bash
$ ./start.sh 

Downloading AWS IoT Root CA certificate from AWS...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1188  100  1188    0     0   3346      0 --:--:-- --:--:-- --:--:--  3346

Cloning the AWS SDK...
Cloning into 'aws-iot-device-sdk-python-v2'...
remote: Enumerating objects: 2472, done.
remote: Counting objects: 100% (953/953), done.
remote: Compressing objects: 100% (250/250), done.
remote: Total 2472 (delta 783), reused 774 (delta 689), pack-reused 1519
Receiving objects: 100% (2472/2472), 2.11 MiB | 6.01 MiB/s, done.
Resolving deltas: 100% (1614/1614), done.

Running pub/sub sample application...
Connecting to aaq***.iot.ap-northeast-1.amazonaws.com with client ID 'basicPubSub'...
Connection Successful with return code: 0 session present: True
Connected!
Subscribing to topic 'sdk/test/python'...
Subscribed with QoS.AT_LEAST_ONCE
Sending messages until program killed
Publishing message to topic 'sdk/test/python': Hello World!  [1]
Received message from topic 'sdk/test/python': b'"Hello World!  [1]"'
Publishing message to topic 'sdk/test/python': Hello World!  [2]
Received message from topic 'sdk/test/python': b'"Hello World!  [2]"'
Publishing message to topic 'sdk/test/python': Hello World!  [3]
Received message from topic 'sdk/test/python': b'"Hello World!  [3]"'
^CTraceback (most recent call last):
  File "aws-iot-device-sdk-python-v2/samples/pubsub.py", line 142, in <module>
    time.sleep(1)
KeyboardInterrupt
```

ようやく繋がりました！
AWS のコンソール上でもメッセージを確認することができます。

![Screenshot from 2023-12-17 22-44-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333253/8d276d84-6c87-4172-855b-7cf25a87316e.png)

これで、Jetson Nano から AWS IoT Core へのメッセージ送信は完了です。

![Screenshot from 2023-12-17 22-57-52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333253/4337da48-1741-8f4f-da01-d7ed6067500b.png)

# 6. 自分の好きなメッセージを送ってみる
ここまでは管理コンソールの手順通りに実行しただけですので、少しだけメッセージを変更してみたいと思います。
`start.sh` の最終行を以下のように書き換えます：

```bash
python3 aws-iot-device-sdk-python-v2/samples/pubsub.py --endpoint aaq***.iot.ap-northeast-1.amazonaws.com --ca_file root-CA.crt --cert jetson-nano.cert.pem --key jetson-nano.private.key --client_id basicPubSub --topic sdk/test/python --count 1 --message "I am Jetson Nano." 
```

`--count 1` とすることで、メッセージは 1 回だけ送信するようにしています。
では、再度 `start.sh` を実行します。

```bash
$ ./start.sh 

Running pub/sub sample application...
Connecting to aaq***.iot.ap-northeast-1.amazonaws.com with client ID 'basicPubSub'...
Connection Successful with return code: 0 session present: True
Connected!
Subscribing to topic 'sdk/test/python'...
Subscribed with QoS.AT_LEAST_ONCE
Sending 1 message(s)
Publishing message to topic 'sdk/test/python': I am Jetson Nano. [1]
Received message from topic 'sdk/test/python': b'"I am Jetson Nano. [1]"'
1 message(s) received.
Disconnecting...
Connection closed
Disconnected!
```

"I am Jetson Nano." というメッセージを 1 回だけ送ることができました。AWS のコンソール上でもメッセージを確認することができます。

![Screenshot from 2023-12-17 22-46-43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333253/237d64cb-4044-5cb1-52fc-4b770601cff7.png)

# 7. おわりに
Jetson Nano をモノとして AWS IoT Core にメッセージを送ってみました。AWS IoT Core の管理コンソールのステップに従って行くと、接続キットを用意してくれて実行すべきコマンドも丁寧に示してくれるので、セミオートで接続まで辿り着けるかと思います。

本稿ではただメッセージ送ったに過ぎず、Jetson Nano の GPU, Camera Module を活かせていません。次回は画像・映像を使ったより実践的な内容にしたいと思います。

それでは、アディオス！

# 参考記事
* [RaspberryPiをクラウドとちゃんと話せるようにする（第１回）](https://zenn.dev/singularity/articles/20230611_raspi-for-aws-1)