ESP32のデバイスに対してAWS IoTからOTAをしてみる

# はじめに
[以前の記事](https://qiita.com/tsugunao/items/e6058df682728a94ef40)にてESP32のデバイスをAWSに接続しました。今回はその続きとして、ESP32のデバイスをAWS上からOTA（Over The Air update)を使って更新してみたいと思います。

# 使用する環境
OTAにあたり以下のような環境を使用しました
* [ESP-IDFのツールチェーン](https://docs.espressif.com/projects/esp-idf/en/release-v4.4/esp32/get-started/index.html)
* [esp-aws-iot](https://github.com/espressif/esp-aws-iot/tree/release/beta)上のOTA用サンプルコード
* [以前の手順](https://qiita.com/tsugunao/items/e6058df682728a94ef40)で作成したデバイス証明書、Thing, ポリシー

# FreeRTOS OTA update
AWSのFreeRTOSの OTA updateを使用すると、複数のデバイスにファームウエアをデプロイすることができます。AWS コンソール上で OTA Updateを作成すると、AWS IoT ジョブが作成され、デバイス上ではAWS IoT ジョブの通知トピック上で更新メッセージを待ち受けます。デバイス側ではダウンロードしたファイルのデジタル署名をチェックしてファームウエアの更新を実施します。[esp-aws-iot](https://github.com/espressif/esp-aws-iot/tree/release/beta)にはOTA用のサンプルコードが含まれているので、今回はそのサンプルコードを用いてOTAを実施してみます。FreeRTOS上でのOTAの実施にあたり、[こちら](https://docs.aws.amazon.com/freertos/latest/userguide/ota-prereqs.html)にある手順を順番に実施していきます。FreeRTOSではHTTPでOTAを行う方法とMQTTでOTAを行う方法がありますが、今回はMQTTを使用する方法で試していきます。

# ファームウエアアップデート手順
それでは早速、OTAを試していきます。
## ファームウエアを格納するS3バケットの作成
まずは、S3上に更新するファームウエアを格納するためのバケットを作成します。AWS S3のコンソールから`バケットを作成`をクリックし、`バケット名`に適切な名前をつけ、`バケットのバージョニング`を`有効にする`を選択し、`バケットを作成`をクリックします。
![スクリーンショット 2023-05-23 15.59.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/75a11ba2-d6fd-7cc8-da8a-598a5cb14ff1.png)

## OTA Update用のサービスロールの作成
1. OTA Update用のサービスロールを作成します。AWS IAMのコンソールの左側のメニューから、`アクセス管理`>`ロール`をクリックし、`ロールの作成` をクリックします
1. 信頼されたエンティティタイプから`AWSのサービス`を選択し、他のAWSのサービスのユースケースから`IoT`を選択し、`次へ`をクリックします ![スクリーンショット 2023-05-23 16.09.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/ba0f9ae0-2a29-ebca-a396-e8cc62a2aec4.png)
1. `許可を追加`の画面で`次へ`をクリック
1. ロール名を入力し、`ロールを作成`をクリックします
![スクリーンショット 2023-05-23 16.16.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/a070a302-8eb5-7cde-d992-511dc146183d.png)

## サービスロールに OTA updateのPermissionを追加
1. IAMのコンソール上から作成したロールを検索し、リストから選択します
1. `許可を追加`ボタンから`ポリシーをアタッチ`を選択します
1. 検索ボックスから`AmazonFreeRTOSOTAUpdate`を検索し追加します。![スクリーンショット 2023-05-23 16.23.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/84105016-9889-434a-7df3-b8e80e9d63cc.png)
1. `許可を追加`ボタンから`インラインポリシーを作成`を選択します
1. JSONのタブを選択し以下をテキストボックスに入力します.アカウントIDとロール名は自分のものに置き換えます
    ```
    {
        "Version": "2012-10-17",
        "Statement": [
          {
                "Effect": "Allow",
                "Action": [
                    "iam:GetRole",
                    "iam:PassRole"
                ],
                "Resource": "arn:aws:iam::your_account_id:role/your_role_name"
          }
        ]
      }
    ```
1. `ポリシーの確認`、`ポリシーの作成`をクリックします
1. 同様の手順でS3に対するアクセスのポリシーを追加します
1. 1. `許可を追加`ボタンから`インラインポリシーを作成`を選択します
1. JSONのタブを選択し以下をテキストボックスに入力します
1. JSONのタブを選択し以下をテキストボックスに入力します.バケット名は自分が作成したバケット名に置き換えます
    ```
        {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Action": [
                        "s3:GetObjectVersion",
                        "s3:GetObject",
                        "s3:PutObject"
                    ],
                    "Resource": [
                        "arn:aws:s3:::example-bucket/*"
                    ]
                }
            ]
        }
    ```
1. `ポリシーの確認`、`ポリシーの作成`をクリックします

## ファームウエアのCodeサイン用の証明書の作成
ファームウエアイメージに署名を行うために、Codeサイン用の証明書と秘密鍵を作成します。この手順はプラットフォームごとに異なります。ここでは[ESP32用の手順](https://docs.aws.amazon.com/freertos/latest/userguide/ota-code-sign-cert-esp.html)で証明書を作成します。他のハードウエアを使用している方は[こちら](https://docs.aws.amazon.com/freertos/latest/userguide/ota-code-sign-cert.html)からご自身のハードウエアにあった署名方法を確認してみてください。
1. `cert_config.txt`という名前で以下のようなテキストファイルを作成します。`test_signer@amazon.com`の部分は自分のメールアドレスに置き換えます。
    ```
    [ req ]
    prompt             = no
    distinguished_name = my_dn
                        
    [ my_dn ]
    commonName = test_signer@amazon.com
                        
    [ my_exts ]
    keyUsage         = digitalSignature
    extendedKeyUsage = codeSigning
    ```
1. 秘密鍵を作成します
    ```
    openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve -outform PEM -out ecdsasigner.key
    ```
1. Codeサイン用の証明書を作成します
    ```
    openssl req -new -x509 -config cert_config.txt -extensions my_exts -nodes -days 365 -key ecdsasigner.key -out ecdsasigner.crt
    ```
1. 秘密鍵と証明書を AWS Certificate Managerにインポートします。コマンド発行後に証明書のARNが表示されるので、そのARNをメモしておきます。OTAのジョブを作成する際に必要となります。
    ```
    aws acm import-certificate --certificate fileb://ecdsasigner.crt --private-key fileb://ecdsasigner.key
    ```

## サンプルコードのビルドと焼き込み
ターミナルを開きます。[前回](https://qiita.com/tsugunao/items/e6058df682728a94ef40)Cloneしてきた [esp-aws-iot](https://github.com/espressif/esp-aws-iot/tree/release/beta/examples/ota/ota_mqtt)のOTAのDemoプログラムを使用します。[前回](https://qiita.com/tsugunao/items/e6058df682728a94ef40)作成したデバイス証明書と秘密鍵をOTAのプログラムで使えるようにコピーします
```
cd ~/Work/esp32
cp xxx-certificate.pem.crt esp-aws-iot/examples/ota/ota_mqtt/main/certs/client.crt
cp xxx-private.pem.key esp-aws-iot/examples/ota/ota_mqttmain/certs/client.key
wget https://www.amazontrust.com/repository/AmazonRootCA1.pem -O esp-aws-iot/examples/ota/ota_mqttmain/certs/root_cert_auth.crt
```
idf.py menuconfigを行い、`Example Configuration`, `Example Connection Configration`にて MQTT endpointの設定、MQTT Client IDの設定、WiFiのSSIDとPasswordの設定を行います。
![スクリーンショット 2023-05-23 17.09.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/2e1a76b1-2dc4-d268-a02b-bb7103c74bcd.png)

次に以下のようにビルドしてデバイスにOTA Demoプログラムを書き込みます。
```
idf.py -p /dev/cu.usbserial-14240 build flash monitor
```
以下のようにターミナル上に表示され、焼き込みが行われたあと再起動し、OTAのJobを受け待ちしている状態となります。

```
Serial port /dev/cu.usbserial-14240
Connecting....
Chip is ESP32-D0WD-V3 (revision v3.0)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: 8c:4b:14:39:e3:f8
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 460800
Changed.
Configuring flash size...
Flash will be erased from 0x00001000 to 0x00007fff...
Flash will be erased from 0x00020000 to 0x000f3fff...
Flash will be erased from 0x00008000 to 0x00008fff...
Flash will be erased from 0x00017000 to 0x00018fff...
Compressed 25664 bytes to 16189...
Writing at 0x00001000... (100 %)
Wrote 25664 bytes (16189 compressed) at 0x00001000 in 0.8 seconds (effective 273.7 kbit/s)...
Hash of data verified.
Compressed 866272 bytes to 543660...
Writing at 0x00020000... (2 %)
Writing at 0x0002af42... (5 %)
Writing at 0x00038af0... (8 %)
Writing at 0x000426f6... (11 %)
Writing at 0x0004b6b3... (14 %)
Writing at 0x0005114e... (17 %)
Writing at 0x00056ec1... (20 %)
Writing at 0x0005c1ff... (23 %)
Writing at 0x00061a66... (26 %)
Writing at 0x000676f6... (29 %)
Writing at 0x0006d261... (32 %)
Writing at 0x00072abe... (35 %)
Writing at 0x000781c0... (38 %)
Writing at 0x0007d8a8... (41 %)
Writing at 0x00083267... (44 %)
Writing at 0x0008897b... (47 %)
Writing at 0x0008e317... (50 %)
Writing at 0x00094889... (52 %)
Writing at 0x0009a15a... (55 %)
Writing at 0x0009fbe2... (58 %)
Writing at 0x000a4f2a... (61 %)
Writing at 0x000aa2b8... (64 %)
Writing at 0x000af5e4... (67 %)
Writing at 0x000b4d40... (70 %)
Writing at 0x000ba3e5... (73 %)
Writing at 0x000bfd3f... (76 %)
Writing at 0x000c57ac... (79 %)
Writing at 0x000cb327... (82 %)
Writing at 0x000d0cd7... (85 %)
Writing at 0x000d80e0... (88 %)
Writing at 0x000e17d4... (91 %)
Writing at 0x000e7673... (94 %)
Writing at 0x000ed1e1... (97 %)
Writing at 0x000f24b8... (100 %)
Wrote 866272 bytes (543660 compressed) at 0x00020000 in 14.1 seconds (effective 490.2 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 162...
Writing at 0x00008000... (100 %)
Wrote 3072 bytes (162 compressed) at 0x00008000 in 0.1 seconds (effective 446.4 kbit/s)...
Hash of data verified.
Compressed 8192 bytes to 31...
Writing at 0x00017000... (100 %)
Wrote 8192 bytes (31 compressed) at 0x00017000 in 0.1 seconds (effective 643.4 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...
Executing action: monitor
Running idf_monitor in directory /Users/tsugunao/Work/esp32/esp-aws-iot/examples/ota/ota_mqtt
Executing "/Users/tsugunao/.espressif/python_env/idf4.4_py3.10_env/bin/python /Users/tsugunao/Work/esp32/v44/esp-idf/tools/idf_monitor.py -p /dev/cu.usbserial-14240 -b 115200 --toolchain-prefix xtensa-esp32-elf- --target esp32 --revision 0 /Users/tsugunao/Work/esp32/esp-aws-iot/examples/ota/ota_mqtt/build/ota_mqtt.elf -m '/Users/tsugunao/.espressif/python_env/idf4.4_py3.10_env/bin/python' '/Users/tsugunao/Work/esp32/v44/esp-idf/tools/idf.py' '-p' '/dev/cu.usbserial-14240'"...
--- idf_monitor on /dev/cu.usbserial-14240 115200 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
���)+
B���J����  5 �ets Jul 29 2019 12:21:46

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:6624
load:0x40078000,len:15140
ho 0 tail 12 room 4
load:0x40080400,len:3816
0x40080400: _init at ??:?

entry 0x40080698
I (29) boot: ESP-IDF v4.4.4-435-g7641c8ef4f 2nd stage bootloader
I (29) boot: compile time 10:29:54
I (29) boot: chip revision: v3.0
I (34) boot.esp32: SPI Speed      : 40MHz
I (38) boot.esp32: SPI Mode       : DIO
I (43) boot.esp32: SPI Flash Size : 4MB
I (47) boot: Enabling RNG early entropy source...
I (53) boot: Partition Table:
I (56) boot: ## Label            Usage          Type ST Offset   Length
I (64) boot:  0 pre_prov         unknown          3f 06 0000d000 00006000
I (71) boot:  1 nvs              WiFi data        01 02 00013000 00004000
I (79) boot:  2 otadata          OTA data         01 00 00017000 00002000
I (86) boot:  3 phy_init         RF data          01 01 00019000 00001000
I (93) boot:  4 ota_0            OTA app          00 10 00020000 00100000
I (101) boot:  5 ota_1            OTA app          00 11 00120000 00100000
I (108) boot:  6 storage          WiFi data        01 02 00220000 00004000
I (116) boot: End of partition table
I (120) boot: No factory image, trying OTA 0
I (125) esp_image: segment 0: paddr=00020020 vaddr=3f400020 size=24d64h (150884) map
I (188) esp_image: segment 1: paddr=00044d8c vaddr=3ffb0000 size=03204h ( 12804) load
I (194) esp_image: segment 2: paddr=00047f98 vaddr=40080000 size=08080h ( 32896) load
I (208) esp_image: segment 3: paddr=00050020 vaddr=400d0020 size=96448h (615496) map
I (431) esp_image: segment 4: paddr=000e6470 vaddr=40088080 size=0d348h ( 54088) load
I (463) boot: Loaded app from partition at offset 0x20000
I (501) boot: Set actual ota_seq=1 in otadata[0]
I (501) boot: Disabling RNG early entropy source...
I (512) cpu_start: Pro cpu up.
I (513) cpu_start: Starting app cpu, entry point is 0x400812fc
0x400812fc: call_start_cpu1 at /Users/tsugunao/Work/esp32/v44/esp-idf/components/esp_system/port/cpu_start.c:147

I (0) cpu_start: App cpu up.
I (528) cpu_start: Pro cpu start user code
I (528) cpu_start: cpu freq: 160000000
I (529) cpu_start: Application information:
I (533) cpu_start: Project name:     ota_mqtt
I (538) cpu_start: App version:      202210.01-LTS-release-dirty
I (545) cpu_start: Compile time:     May 20 2023 16:04:31
I (551) cpu_start: ELF file SHA256:  d7c5afbf4ffdc723...
I (557) cpu_start: ESP-IDF:          v4.4.4-435-g7641c8ef4f
I (563) cpu_start: Min chip rev:     v0.0
I (568) cpu_start: Max chip rev:     v3.99
I (573) cpu_start: Chip rev:         v3.0
I (578) heap_init: Initializing. RAM available for dynamic allocation:
I (585) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (591) heap_init: At 3FFC8058 len 00017FA8 (95 KiB): DRAM
I (597) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (603) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (610) heap_init: At 400953C8 len 0000AC38 (43 KiB): IRAM
I (617) spi_flash: detected chip: generic
I (620) spi_flash: flash io: dio
I (626) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (635) OTA_MQTT: [APP] Startup..
I (635) OTA_MQTT: [APP] Free memory: 209432 bytes
I (645) OTA_MQTT: [APP] IDF version: v4.4.4-435-g7641c8ef4f
I (715) wifi:wifi driver task: 3ffd09b8, prio:23, stack:6656, core=0
I (715) system_api: Base MAC address is not set
I (715) system_api: read default base MAC address from EFUSE
I (725) wifi:wifi firmware version: 0f80fa0
I (725) wifi:wifi certification version: v7.0
I (725) wifi:config NVS flash: enabled
I (725) wifi:config nano formating: disabled
I (735) wifi:Init data frame dynamic rx buffer num: 32
I (735) wifi:Init management frame dynamic rx buffer num: 32
I (745) wifi:Init management short buffer num: 32
I (745) wifi:Init dynamic tx buffer num: 32
I (755) wifi:Init static rx buffer size: 1600
I (755) wifi:Init static rx buffer num: 10
I (755) wifi:Init dynamic rx buffer num: 32
I (765) wifi_init: rx ba win: 6
I (765) wifi_init: tcpip mbox: 32
I (775) wifi_init: udp mbox: 6
I (775) wifi_init: tcp mbox: 6
I (775) wifi_init: tcp tx win: 5744
I (785) wifi_init: tcp rx win: 5744
I (785) wifi_init: tcp mss: 1440
I (795) wifi_init: WiFi IRAM OP enabled
I (795) wifi_init: WiFi RX IRAM OP enabled
I (805) example_connect: Connecting to HG8045-9653-bg...
I (805) phy_init: phy_version 4670,719f9f6,Feb 18 2021,17:07:07
W (815) phy_init: failed to load RF calibration data (0x1102), falling back to full calibration
I (975) wifi:mode : sta (8c:4b:14:39:e3:f8)
I (975) wifi:enable tsf
I (975) example_connect: Waiting for IP(s)
I (3385) wifi:new:<6,0>, old:<1,0>, ap:<255,255>, sta:<6,0>, prof:1
I (4135) wifi:state: init -> auth (b0)
I (4135) wifi:state: auth -> assoc (0)
I (4145) wifi:state: assoc -> run (10)
I (4155) wifi:connected with HG8045-9653-bg, aid = 3, channel 6, BW20, bssid = a4:c7:de:81:18:a4
I (4155) wifi:security: WPA2-PSK, phy: bgn, rssi: -54
I (4165) wifi:pm start, type: 1

I (4245) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (4445) wifi:<ba-add>idx:0 (ifx:0, a4:c7:de:81:18:a4), tid:0, ssn:0, winSize:64
I (5165) esp_netif_handlers: example_connect: sta ip: 192.168.1.219, mask: 255.255.255.0, gw: 192.168.1.1
I (5165) example_connect: Got IPv4 event: Interface "example_connect: sta" address: 192.168.1.219
I (5695) example_connect: Got IPv6 event: Interface "example_connect: sta" address: fe80:0000:0000:0000:8e4b:14ff:fe39:e3f8, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (5695) example_connect: Connected to example_connect: sta
I (5705) example_connect: - IPv4 address: 192.168.1.219
I (5705) example_connect: - IPv6 address: fe80:0000:0000:0000:8e4b:14ff:fe39:e3f8, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (5725) AWS_OTA: OTA over MQTT demo, Application version 0.10.2
I (5725) AWS_OTA: Establishing a TLS session to a3iwv27yeq472m-ats.iot.ap-northeast-1.amazonaws.com:8883.
I (7225) AWS_OTA: Creating an MQTT connection to a3iwv27yeq472m-ats.iot.ap-northeast-1.amazonaws.com.
I (7345) coreMQTT: MQTT connection established with the broker.
I (7345) AWS_OTA: MQTT connection successfully established with broker.


I (7355) AWS_OTA: otaPal_GetPlatformImageState
I (7355) esp_ota_ops: aws_esp_ota_get_boot_flags: 1
I (7365) esp_ota_ops: [0] aflags/seq:0x2/0x1, pflags/seq:0xffffffff/0x0
I (7365) AWS_OTA: Current State=[RequestingJob], Event=[Start], New state=[RequestingJob]
I (7385) AWS_OTA: SUBSCRIBE topic $aws/things/esp32-thing01/jobs/notify-next to broker.


I (7385) AWS_OTA: Subscribed to MQTT topic: $aws/things/esp32-thing01/jobs/notify-next
I (7445) AWS_OTA: Received SUBACK.


I (7445) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (7465) coreMQTT: Ack packet deserialized with result: MQTTSuccess.
I (7465) coreMQTT: State record updated. New state=MQTTPublishDone.
I (7475) AWS_OTA: PUBACK received for packet id 2.


I (7475) AWS_OTA: Sent PUBLISH packet to broker $aws/things/esp32-thing01/jobs/$next/get to broker.


I (7475) coreMQTT: De-serialized incoming PUBLISH packet: DeserializerResult=MQTTSuccess.
W (7485) AWS_OTA: OTA Timer handle NULL for Timerid=0, can't stop.
I (7505) AWS_OTA: Current State=[WaitingForJob], Event=[RequestJobDocument], New state=[WaitingForJob]
I (7505) coreMQTT: State record updated. New state=MQTTPublishDone.
I (7515) coreMQTT: Invoking subscription callback of matching topic filter: TopicFilter=$aws/things/+/jobs/#, TopicName=$aws/things/esp32-thing01/jobs/$next/get/accepted
I (7535) AWS_OTA: No active job available in received job document: OtaJobParseErr_t=OtaJobParseErrNoActiveJobs
I (7545) AWS_OTA: otaPal_GetPlatformImageState
I (7555) esp_ota_ops: aws_esp_ota_get_boot_flags: 1
I (7555) esp_ota_ops: [0] aflags/seq:0x2/0x1, pflags/seq:0xffffffff/0x0
I (7565) AWS_OTA: Current State=[WaitingForJob], Event=[ReceivedJobDocument], New state=[CreatingFile]
I (7575) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (8585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (9585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (10585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (11585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (12585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (13585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (14585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (15585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (16585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (17585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (18585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (19585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (20585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (21585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (22585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (23585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (24585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (25585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (26585) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
```
## OTA Updateの作成

次に、AWS IoTのコンソールにてOTA Updateのジョブを作成していきます。サイドパネルの`リモートアクション`>`ジョブ`からジョブの画面を開き、`ジョブを作成`をクリックします。
![スクリーンショット 2023-05-24 16.06.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/2e528304-acdb-940b-6cba-03b647f5ac39.png)
ジョブの作成画面で`FreeRTOS OTA 更新ジョブを作成`を選択し、`次へ`をクリックします。
![スクリーンショット 2023-05-24 16.09.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/37a72cba-105e-b742-438c-e982f4da3ff5.png)
ジョブ名と説明を入力し、`次へ`をクリックします。
![スクリーンショット 2023-05-24 16.11.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/4c70d5ee-1b09-5f2c-700c-e570a5805cc4.png)
OTA updateはThingに対して、もしくはグループに対して実施することができます。ここでは、更新するデバイスのドロップダウンメニューから作成したThingを選択します。今回はMQTTのプロトコルでOTAを行うので、`MQTT`を選択します。
![スクリーンショット 2023-05-24 16.14.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/b157f60a-3cc9-b100-8227-4b3cfd4ef4db.png)
署名の箇所では、`新しいファイルに署名します。`を選択し、`新しいプロファイルの作成`をクリックします。
![スクリーンショット 2023-05-24 16.18.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/86ad0514-06ce-5013-4955-ecc38557d610.png)

ポップアップが表示されるので、プロファイル名を入力し、デバイスのハードウエアプラットフォームを選択します。ここでは`ESP-WROVER_KIT`を選択しました。コード署名証明書のところで、`既存の証明書の選択`を選択し、先程作成して AWS Certificate Managerにインポートした証明書を選択します。`デバイスのコード署名証明書のパス名`には`Code Verify Key`と入力し、`作成`をクリックします。
![スクリーンショット 2023-05-24 16.21.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/9d0eb82d-67d0-1f71-cb2a-2f80ea7c1a3b.png)

ここで、実際にOTAに使用するファームウエアイメージを作成する必要があります。esp-aws-iot を実行していたターミナルに戻ります。
```
cd ~/Work/esp32/esp-aws-iot/examples/ota/ota_mqtt
```
esp-aws-iot/examples/ota/ota_mqtt/main/demo_config.h にファームウエアのバージョンを管理している部分があり、ファームウエアのバージョンは、現在デバイスに焼き込まれているバージョンより高く設定する必要があるので、以下の部分を修正します。

```
/**
 * @brief Configure application version.
 */

#define APP_VERSION_MAJOR         0
#define APP_VERSION_MINOR         9 --> 10 に修正
#define APP_VERSION_BUILD         2

```
修正後ビルドします。
```
idf.py build
```
esp-aws-iot/examples/ota/ota_mqtt/build/ota_mqtt.bin にファームウエアイメージが作成されます。再び AWS IoTコンソールに戻ります。
ファイルを選択する箇所で、`新しいファイルをアップロードします。`を選択し、`ファイルを選択`から先ほど作成した`ota_mqtt.bin`のファイルを選択します。`S3 でのファイルのアップロード先`から先ほど作成したS3バケットを選択します。`デバイス上のファイルのパス名`には `/`を指定します。
![スクリーンショット 2023-05-24 16.34.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/d56bfec7-1c07-0098-9e85-a4631d1da4ce.png)
IAMロールの設定で、先ほど作成したサービスロールを選択し、`次へ`をクリックします。
![スクリーンショット 2023-05-24 16.40.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/e303cae9-914a-b20a-d745-9251ff041922.png)
ジョブ実行タイプとしてスナップショットのジョブを選択し、`ジョブの作成`をクリックします。
![スクリーンショット 2023-05-24 16.42.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/6f121708-3c11-adcb-8bc6-7a589ba4f2f3.png)

ジョブが作成され、実行されます。デバイスのモニタには以下のようなログが表示されます。
```
I (50183) AWS_OTA: Received valid file block: Block index=209, Size=4096
I (50203) AWS_OTA: Number of blocks remaining: 2
I (50203) AWS_OTA: Current State=[WaitingForFileBlock], Event=[ReceivedFileBlock], New state=[WaitingForFileBlock]
I (50203) AWS_OTA:  Received: 210   Queued: 210   Processed: 210   Dropped: 0
I (50223) coreMQTT: De-serialized incoming PUBLISH packet: DeserializerResult=MQTTSuccess.
I (50223) coreMQTT: State record updated. New state=MQTTPublishDone.
I (50233) coreMQTT: Invoking subscription callback of matching topic filter: TopicFilter=$aws/things/+/streams/#, TopicName=$aws/things/esp32-thing01/streams/AFR_OTA-99e81721-118a-413c-9d3c-a1be83d173b9/data/cbor
I (50253) AWS_OTA: Received data message callback, size 2041.


I (50253) AWS_OTA: Received valid file block: Block index=211, Size=2016
I (50273) AWS_OTA: Number of blocks remaining: 1
I (50273) AWS_OTA: Current State=[WaitingForFileBlock], Event=[ReceivedFileBlock], New state=[WaitingForFileBlock]
I (50283) AWS_OTA:  Received: 211   Queued: 211   Processed: 211   Dropped: 0
I (50303) coreMQTT: De-serialized incoming PUBLISH packet: DeserializerResult=MQTTSuccess.
I (50303) coreMQTT: State record updated. New state=MQTTPublishDone.
I (50303) coreMQTT: Invoking subscription callback of matching topic filter: TopicFilter=$aws/things/+/streams/#, TopicName=$aws/things/esp32-thing01/streams/AFR_OTA-99e81721-118a-413c-9d3c-a1be83d173b9/data/cbor
I (50323) AWS_OTA: Received data message callback, size 4121.


I (50333) AWS_OTA: Received valid file block: Block index=210, Size=4096
I (50353) AWS_OTA: Received final block of the update.
I (50353) AWS_OTA:  Received: 212   Queued: 212   Processed: 211   Dropped: 0
I (51173) AWS_OTA: Signature verification succeeded.
I (51173) AWS_OTA: Received entire update and validated the signature.
I (51353) AWS_OTA:  Received: 212   Queued: 212   Processed: 211   Dropped: 0
W (51363) AWS_OTA: PINGRESP should not be handled by the application callback when using MQTT_ProcessLoop.


I (51363) AWS_OTA: Sent PUBLISH packet to broker $aws/things/esp32-thing01/jobs/AFR_OTA-MyOTAUpdateJob/update to broker.


I (51373) AWS_OTA: Received OtaJobEventActivate callback from OTA Agent.
I (51383) esp_image: segment 0: paddr=00120020 vaddr=3f400020 size=24d64h (150884) map
I (51393) coreMQTT: Ack packet deserialized with result: MQTTSuccess.
I (51403) coreMQTT: State record updated. New state=MQTTPublishDone.
I (51403) AWS_OTA: PUBACK received for packet id 40.


I (51413) AWS_OTA:  Received: 212   Queued: 212   Processed: 212   Dropped: 0
I (51423) coreMQTT: De-serialized incoming PUBLISH packet: DeserializerResult=MQTTSuccess.
I (51433) coreMQTT: State record updated. New state=MQTTPublishDone.
I (51433) coreMQTT: Invoking subscription callback of matching topic filter: TopicFilter=$aws/things/+/jobs/#, TopicName=$aws/things/esp32-thing01/jobs/AFR_OTA-MyOTAUpdateJob/update/accepted
I (51443) esp_image: segment 1: paddr=00144d8c vaddr=3ffb0000 size=03204h ( 12804)
I (51463) AWS_OTA: Received job message $aws/things/esp32-thing01/jobs/AFR_OTA-MyOTAUpdateJob/update/accepted{"timestamp":1684914310}rdyaf size 24.


I (51473) esp_image: segment 2: paddr=00147f98 vaddr=40080000 size=08080h ( 32896)
I (51493) esp_image: segment 3: paddr=00150020 vaddr=400d0020 size=96448h (615496) map
I (51703) esp_image: segment 4: paddr=001e6470 vaddr=40088080 size=0d348h ( 54088)
I (51723) esp_image: segment 0: paddr=00120020 vaddr=3f400020 size=24d64h (150884) map
I (51773) esp_image: segment 1: paddr=00144d8c vaddr=3ffb0000 size=03204h ( 12804)
I (51773) AWS_OTA:  Received: 212   Queued: 212   Processed: 212   Dropped: 0
I (51793) esp_image: segment 2: paddr=00147f98 vaddr=40080000 size=08080h ( 32896)
I (51803) esp_image: segment 3: paddr=00150020 vaddr=400d0020 size=96448h (615496) map
I (52003) esp_image: segment 4: paddr=001e6470 vaddr=40088080 size=0d348h ( 54088)
I (52573) wifi:state: run -> init (0)
I (52573) wifi:pm stop, total sleep time: 23447108 us / 48558519 us

I (52573) wifi:<ba-del>idx
I (52573) wifi:new:<6,0>, old:<6,0>, ap:<255,255>, sta:<6,0>, prof:1
E (52583) esp-tls-mbedtls: read error :-0x004C:
E (52583) coreMQTT: Call to receiveSingleIteration failed. Status=MQTTRecvFailed
E (52593) AWS_OTA: MQTT_ProcessLoop returned with status = MQTTRecvFailed.
I (52593) AWS_OTA: Disconnecting the MQTT connection with a3iwv27yeq472m-ats.iot.ap-northeast-1.amazonaws.com.
E (52613) esp-tls-mbedtls: write error :-0x004E:
E (52613) coreMQTT: sendBuffer: Unable to send packet: Network Error.
E (52623) coreMQTT: Transport send failed for DISCONNECT packet.
W (52633) AWS_OTA: OTA Timer handle NULL for Timerid=1, can't stop.
I (52643) wifi:flush txq
I (52643) wifi:stop sw txq
I (52643) wifi:lmac stop hw txq
I (52643) wifi:Deinit lldesc rx mblock:10
ets Jul 29 2019 12:21:46

rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:6624
load:0x40078000,len:15140
ho 0 tail 12 room 4
load:0x40080400,len:3816
0x40080400: _init at ??:?

entry 0x40080698
I (29) boot: ESP-IDF v4.4.4-435-g7641c8ef4f 2nd stage bootloader
I (29) boot: compile time 10:29:54
I (29) boot: chip revision: v3.0
I (33) boot.esp32: SPI Speed      : 40MHz
I (38) boot.esp32: SPI Mode       : DIO
I (43) boot.esp32: SPI Flash Size : 4MB
I (47) boot: Enabling RNG early entropy source...
I (53) boot: Partition Table:
I (56) boot: ## Label            Usage          Type ST Offset   Length
I (63) boot:  0 pre_prov         unknown          3f 06 0000d000 00006000
I (71) boot:  1 nvs              WiFi data        01 02 00013000 00004000
I (78) boot:  2 otadata          OTA data         01 00 00017000 00002000
I (86) boot:  3 phy_init         RF data          01 01 00019000 00001000
I (93) boot:  4 ota_0            OTA app          00 10 00020000 00100000
I (101) boot:  5 ota_1            OTA app          00 11 00120000 00100000
I (108) boot:  6 storage          WiFi data        01 02 00220000 00004000
I (116) boot: End of partition table
I (157) esp_image: segment 0: paddr=00120020 vaddr=3f400020 size=24d64h (150884) map
I (212) esp_image: segment 1: paddr=00144d8c vaddr=3ffb0000 size=03204h ( 12804) load
I (217) esp_image: segment 2: paddr=00147f98 vaddr=40080000 size=08080h ( 32896) load
I (231) esp_image: segment 3: paddr=00150020 vaddr=400d0020 size=96448h (615496) map
I (454) esp_image: segment 4: paddr=001e6470 vaddr=40088080 size=0d348h ( 54088) load
I (487) boot: Loaded app from partition at offset 0x120000
I (487) boot: Disabling RNG early entropy source...
I (499) cpu_start: Pro cpu up.
I (499) cpu_start: Starting app cpu, entry point is 0x400812fc
0x400812fc: call_start_cpu1 at /Users/tsugunao/Work/esp32/v44/esp-idf/components/esp_system/port/cpu_start.c:147

I (485) cpu_start: App cpu up.
I (515) cpu_start: Pro cpu start user code
I (516) cpu_start: cpu freq: 160000000
I (516) cpu_start: Application information:
I (520) cpu_start: Project name:     ota_mqtt
I (525) cpu_start: App version:      202210.01-LTS-release-dirty
I (532) cpu_start: Compile time:     May 20 2023 16:04:31
I (538) cpu_start: ELF file SHA256:  e75f3164f61169ce...
I (544) cpu_start: ESP-IDF:          v4.4.4-435-g7641c8ef4f
I (550) cpu_start: Min chip rev:     v0.0
I (555) cpu_start: Max chip rev:     v3.99
I (560) cpu_start: Chip rev:         v3.0
I (565) heap_init: Initializing. RAM available for dynamic allocation:
I (572) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (578) heap_init: At 3FFC8058 len 00017FA8 (95 KiB): DRAM
I (584) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (590) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (597) heap_init: At 400953C8 len 0000AC38 (43 KiB): IRAM
I (604) spi_flash: detected chip: generic
I (608) spi_flash: flash io: dio
I (613) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (622) OTA_MQTT: [APP] Startup..
I (622) OTA_MQTT: [APP] Free memory: 209432 bytes
I (632) OTA_MQTT: [APP] IDF version: v4.4.4-435-g7641c8ef4f
I (662) wifi:wifi driver task: 3ffd09f8, prio:23, stack:6656, core=0
I (662) system_api: Base MAC address is not set
I (662) system_api: read default base MAC address from EFUSE
I (672) wifi:wifi firmware version: 0f80fa0
I (672) wifi:wifi certification version: v7.0
I (672) wifi:config NVS flash: enabled
I (672) wifi:config nano formating: disabled
I (682) wifi:Init data frame dynamic rx buffer num: 32
I (682) wifi:Init management frame dynamic rx buffer num: 32
I (692) wifi:Init management short buffer num: 32
I (692) wifi:Init dynamic tx buffer num: 32
I (702) wifi:Init static rx buffer size: 1600
I (702) wifi:Init static rx buffer num: 10
I (702) wifi:Init dynamic rx buffer num: 32
I (712) wifi_init: rx ba win: 6
I (712) wifi_init: tcpip mbox: 32
I (722) wifi_init: udp mbox: 6
I (722) wifi_init: tcp mbox: 6
I (722) wifi_init: tcp tx win: 5744
I (732) wifi_init: tcp rx win: 5744
I (732) wifi_init: tcp mss: 1440
I (742) wifi_init: WiFi IRAM OP enabled
I (742) wifi_init: WiFi RX IRAM OP enabled
I (752) example_connect: Connecting to HG8045-9653-bg...
I (752) phy_init: phy_version 4670,719f9f6,Feb 18 2021,17:07:07
I (862) wifi:mode : sta (8c:4b:14:39:e3:f8)
I (862) wifi:enable tsf
I (862) example_connect: Waiting for IP(s)
I (3272) wifi:new:<6,0>, old:<1,0>, ap:<255,255>, sta:<6,0>, prof:1
I (4012) wifi:state: init -> auth (b0)
I (4022) wifi:state: auth -> assoc (0)
I (4022) wifi:state: assoc -> run (10)
I (4032) wifi:connected with HG8045-9653-bg, aid = 5, channel 6, BW20, bssid = a4:c7:de:81:18:a4
I (4042) wifi:security: WPA2-PSK, phy: bgn, rssi: -64
I (4042) wifi:pm start, type: 1

I (4092) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (4402) wifi:<ba-add>idx:0 (ifx:0, a4:c7:de:81:18:a4), tid:0, ssn:0, winSize:64
I (5042) esp_netif_handlers: example_connect: sta ip: 192.168.1.219, mask: 255.255.255.0, gw: 192.168.1.1
I (5042) example_connect: Got IPv4 event: Interface "example_connect: sta" address: 192.168.1.219
I (5652) example_connect: Got IPv6 event: Interface "example_connect: sta" address: fe80:0000:0000:0000:8e4b:14ff:fe39:e3f8, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (5652) example_connect: Connected to example_connect: sta
I (5662) example_connect: - IPv4 address: 192.168.1.219
I (5662) example_connect: - IPv6 address: fe80:0000:0000:0000:8e4b:14ff:fe39:e3f8, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (5682) AWS_OTA: OTA over MQTT demo, Application version 0.9.2
I (5682) AWS_OTA: Establishing a TLS session to a3iwv27yeq472m-ats.iot.ap-northeast-1.amazonaws.com:8883.
I (7202) AWS_OTA: Creating an MQTT connection to a3iwv27yeq472m-ats.iot.ap-northeast-1.amazonaws.com.
I (7312) coreMQTT: MQTT connection established with the broker.
I (7312) AWS_OTA: MQTT connection successfully established with broker.


I (7312) AWS_OTA: otaPal_GetPlatformImageState
I (7312) esp_ota_ops: aws_esp_ota_get_boot_flags: 1
I (7322) esp_ota_ops: [1] aflags/seq:0x1/0x2, pflags/seq:0x2/0x1
I (7332) AWS_OTA: Current State=[RequestingJob], Event=[Start], New state=[RequestingJob]
I (7342) AWS_OTA: SUBSCRIBE topic $aws/things/esp32-thing01/jobs/notify-next to broker.


I (7352) AWS_OTA: Subscribed to MQTT topic: $aws/things/esp32-thing01/jobs/notify-next
I (7392) AWS_OTA: Received SUBACK.


I (7402) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (7462) coreMQTT: Ack packet deserialized with result: MQTTSuccess.
I (7472) coreMQTT: State record updated. New state=MQTTPublishDone.
I (7472) AWS_OTA: PUBACK received for packet id 2.


I (7472) AWS_OTA: Sent PUBLISH packet to broker $aws/things/esp32-thing01/jobs/$next/get to broker.


W (7482) AWS_OTA: OTA Timer handle NULL for Timerid=0, can't stop.
I (7492) AWS_OTA: Current State=[WaitingForJob], Event=[RequestJobDocument], New state=[WaitingForJob]
I (7502) coreMQTT: De-serialized incoming PUBLISH packet: DeserializerResult=MQTTSuccess.
I (7512) coreMQTT: State record updated. New state=MQTTPublishDone.
I (7522) coreMQTT: Invoking subscription callback of matching topic filter: TopicFilter=$aws/things/+/jobs/#, TopicName=$aws/things/esp32-thing01/jobs/$next/get/accepted
I (7532) AWS_OTA: Extracted parameter: [key: value]=[execution.jobId: AFR_OTA-MyOTAUpdateJob]
I (7542) AWS_OTA: Extracted parameter: [key: value]=[execution.statusDetails.updatedBy: 655362]
I (7552) AWS_OTA: Extracted parameter: [key: value]=[execution.jobDocument.afr_ota.streamname: AFR_OTA-99e81721-118a-413c-9d3c-a1be83d173b9]
I (7572) AWS_OTA: Extracted parameter: [key: value]=[execution.jobDocument.afr_ota.protocols: ["MQTT"]]
I (7582) AWS_OTA: Extracted parameter: [key: value]=[filepath: /]
I (7582) AWS_OTA: Extracted parameter: [key: value]=[filesize: 866272]
I (7592) AWS_OTA: Extracted parameter: [key: value]=[fileid: 0]
I (7592) AWS_OTA: Extracted parameter: [key: value]=[certfile: Code Verify Key]
I (7602) AWS_OTA: Extracted parameter [ sig-sha256-ecdsa: MEUCIDQLKHRaIsvXn7vm4+vKz9Bzu6Ge... ]
I (7612) AWS_OTA: In self test mode.
I (7622) AWS_OTA: New image has a higher version number than the current image: New image version=0.10.2, Previous image version=0.9.2
I (7632) AWS_OTA: Image version is valid: Begin testing file: File ID=0
I (7642) AWS_OTA: otaPal_SetPlatformImageState, 1
W (7642) AWS_OTA: Set image as testing!
I (7652) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (7722) coreMQTT: Ack packet deserialized with result: MQTTSuccess.
I (7722) coreMQTT: State record updated. New state=MQTTPublishDone.
I (7732) AWS_OTA: PUBACK received for packet id 3.


I (7732) AWS_OTA: Sent PUBLISH packet to broker $aws/things/esp32-thing01/jobs/AFR_OTA-MyOTAUpdateJob/update to broker.


I (7742) AWS_OTA: Job parsing success: OtaJobParseErr_t=OtaJobParseErrNone, Job name=AFR_OTA-MyOTAUpdateJob
I (7752) AWS_OTA: otaPal_GetPlatformImageState
I (7762) esp_ota_ops: aws_esp_ota_get_boot_flags: 1
I (7772) esp_ota_ops: [1] aflags/seq:0x1/0x2, pflags/seq:0x2/0x1
I (7772) AWS_OTA: Current State=[CreatingFile], Event=[ReceivedJobDocument], New state=[CreatingFile]
I (7782) AWS_OTA: Beginning self-test.
I (7792) AWS_OTA: otaPal_GetPlatformImageState
I (7792) esp_ota_ops: aws_esp_ota_get_boot_flags: 1
I (7802) esp_ota_ops: [1] aflags/seq:0x1/0x2, pflags/seq:0x2/0x1
I (7802) AWS_OTA: Received OtaJobEventStartTest callback from OTA Agent.
I (7812) AWS_OTA: otaPal_SetPlatformImageState, 2
I (7822) AWS_OTA: Set image as valid one!
I (7822) esp_ota_ops: aws_esp_ota_get_boot_flags: 1
I (7832) esp_ota_ops: [1] aflags/seq:0x1/0x2, pflags/seq:0x2/0x1
I (7832) esp_ota_ops: aws_esp_ota_set_boot_flags: 2 1
I (7842) esp_ota_ops: [1] aflags/seq:0x1/0x2, pflags/seq:0x2/0x1
I (7892) AWS_OTA: Disabling RTC hardware watchdog timer
I (7892) esp_ota_ops: aws_esp_ota_get_boot_flags: 1
I (7892) esp_ota_ops: [1] aflags/seq:0x2/0x2, pflags/seq:0x2/0x1
I (7892) coreMQTT: De-serialized incoming PUBLISH packet: DeserializerResult=MQTTSuccess.
I (7902) coreMQTT: State record updated. New state=MQTTPublishDone.
I (7912) coreMQTT: Invoking subscription callback of matching topic filter: TopicFilter=$aws/things/+/jobs/#, TopicName=$aws/things/esp32-thing01/jobs/AFR_OTA-MyOTAUpdateJob/update/accepted
I (7932) AWS_OTA: Received job message $aws/things/esp32-thing01/jobs/AFR_OTA-MyOTAUpdateJob/update/accepted{"timestamp":1684914319}:1684914319,"execution":{"jobId":"AFR_OTA-MyOTAUpdateJob","status":"IN_PROGRESS","statusDetails":{"self_test":"ready","updatedBy":"0x000a0002"},"queuedAt":1684914281,"startedAt":1684914287,"lastUpdatedAt":1684914310,"versionNumber":10,"executionNumber":1,"jobDocument":{"afr_ota":{"protocols":["MQTT"],"streamname":"AFR_OTA-99e81721-118a-413c-9d3c-a1be83d173b9","files":[{"filepath":"/","filesize":866272,"fileid":0,"certfile":"Code Verify Key","sig-sha256-ecdsa":"MEUCIDQLKHRaIsvXn7vm4+vKz9Bzu6GeHKbYFPeFum9MrSR1AiEA+dFj6+tS3hsplEe2i2W4IQbGeouP91DbbK37YNQbE/k="}]}}}} size 24.


I (8002) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (8132) coreMQTT: Ack packet deserialized with result: MQTTSuccess.
I (8132) coreMQTT: State record updated. New state=MQTTPublishDone.
I (8132) AWS_OTA: PUBACK received for packet id 4.


I (8142) AWS_OTA: Sent PUBLISH packet to broker $aws/things/esp32-thing01/jobs/AFR_OTA-MyOTAUpdateJob/update to broker.


I (8142) coreMQTT: De-serialized incoming PUBLISH packet: DeserializerResult=MQTTSuccess.
I (8152) ota_pal: Running partition type 0 subtype 17 (offset 0x00120000)
I (8172) ota_pal: Configured partition type 0 subtype 17 (offset 0x00120000)
I (8172) ota_pal: Current running firmware is: ota_1
I (8172) coreMQTT: State record updated. New state=MQTTPublishDone.
I (8462) coreMQTT: Invoking subscription callback of matching topic filter: TopicFilter=$aws/things/+/jobs/#, TopicName=$aws/things/esp32-thing01/jobs/AFR_OTA-MyOTAUpdateJob/update/accepted
I (8752) AWS_OTA: Received job message $aws/things/esp32-thing01/jobs/AFR_OTA-MyOTAUpdateJob/update/accepted{"timestamp":1684914319}:1684914319,"execution":{"jobId":"AFR_OTA-MyOTAUpdateJob","status":"IN_PROGRESS","statusDetails":{"self_test":"ready","updatedBy":"0x000a0002"},"queuedAt":1684914281,"startedAt":1684914287,"lastUpdatedAt":1684914310,"versionNumber":10,"executionNumber":1,"jobDocument":{"afr_ota":{"protocols":["MQTT"],"streamname":"AFR_OTA-99e81721-118a-413c-9d3c-a1be83d173b9","files":[{"filepath":"/","filesize":866272,"fileid":0,"certfile":"Code Verify Key","sig-sha256-ecdsa":"MEUCIDQLKHRaIsvXn7vm4+vKz9Bzu6GeHKbYFPeFum9MrSR1AiEA+dFj6+tS3hsplEe2i2W4IQbGeouP91DbbK37YNQbE/k="}]}}}} size 24.


I (10172) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (10172) coreMQTT: De-serialized incoming PUBLISH packet: DeserializerResult=MQTTSuccess.
I (10452) coreMQTT: State record updated. New state=MQTTPublishDone.
I (10452) coreMQTT: Invoking subscription callback of matching topic filter: TopicFilter=$aws/things/+/jobs/#, TopicName=$aws/things/esp32-thing01/jobs/notify-next
I (10732) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (11852) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (12772) AWS_OTA: Current State=[WaitingForJob], Event=[StartSelfTest], New state=[WaitingForJob]
I (12772) AWS_OTA: No active job available in received job document: OtaJobParseErr_t=OtaJobParseErrNoActiveJobs
I (12782) AWS_OTA: otaPal_GetPlatformImageState
I (12792) esp_ota_ops: aws_esp_ota_get_boot_flags: 1
I (12792) esp_ota_ops: [1] aflags/seq:0x2/0x2, pflags/seq:0xffffffff/0x0
I (12802) AWS_OTA: Current State=[WaitingForJob], Event=[ReceivedJobDocument], New state=[CreatingFile]
I (12852) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (13852) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (14852) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (15852) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
I (16852) AWS_OTA:  Received: 0   Queued: 0   Processed: 0   Dropped: 0
```
流れてしまうので、よく見ないとわからないですが、よくみると
```
I (7622) AWS_OTA: New image has a higher version number than the current image: New image version=0.10.2, Previous image version=0.9.2
I (7632) AWS_OTA: Image version is valid: Begin testing file: File ID=0
I (7642) AWS_OTA: otaPal_SetPlatformImageState, 1

```
のように表示されており、バージョンを旧バージョンと比較して、設定していることが分かります。同じバージョンで行った場合、UpdateされずにエラーとしてJobが終了します。

AWS IoTのコンソールにてJobの状態を確認すると、成功していることが分かります。
![スクリーンショット 2023-05-24 16.59.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/9a23cfb5-089c-5865-a89b-dcea65ed0b7e.png)

# OTA サンプルコードの解析
今回使用した、~/Work/esp32/esp-aws-iot/examples/ota/ota_mqtt のサンプルコードの処理内容について少し解析してみます。こちらは、main/ota_demo_core_mqtt.c にある `aws_iot_demo_main` 言う関数がEntry Pointの関数となっているのですが、その中で、MQTTの初期化を行ったあと、`startOTADemo()`という関数を呼んでいます。

```
    if( returnStatus == EXIT_SUCCESS )
    {
        /* Initialize MQTT library. Initialization of the MQTT library needs to be
         * done only once in this demo. */
        returnStatus = initializeMqtt( &mqttContext, &networkContext );
    }

    if( returnStatus == EXIT_SUCCESS )
    {
        /* Start OTA demo. */
        returnStatus = startOTADemo();
    }
```
`startOTADemo()`関数の中で、`setOtaInterfaces`という関数を呼び、FreeRTOSで定義されているOTAの関数や、Platform依存の関数を PAL (Platform Abstruction Layer)の関数として登録しています。

```
tatic void setOtaInterfaces( OtaInterfaces_t * pOtaInterfaces )
{
    /* Initialize OTA library OS Interface. */
    pOtaInterfaces->os.event.init = OtaInitEvent_FreeRTOS;
    pOtaInterfaces->os.event.send = OtaSendEvent_FreeRTOS;
    pOtaInterfaces->os.event.recv = OtaReceiveEvent_FreeRTOS;
    pOtaInterfaces->os.event.deinit = OtaDeinitEvent_FreeRTOS;
    pOtaInterfaces->os.timer.start = OtaStartTimer_FreeRTOS;
    pOtaInterfaces->os.timer.stop = OtaStopTimer_FreeRTOS;
    pOtaInterfaces->os.timer.delete = OtaDeleteTimer_FreeRTOS;
    pOtaInterfaces->os.mem.malloc = Malloc_FreeRTOS;
    pOtaInterfaces->os.mem.free = Free_FreeRTOS;

    /* Initialize the OTA library MQTT Interface.*/
    pOtaInterfaces->mqtt.subscribe = mqttSubscribe;
    pOtaInterfaces->mqtt.publish = mqttPublish;
    pOtaInterfaces->mqtt.unsubscribe = mqttUnsubscribe;

    /* Initialize the OTA library PAL Interface.*/
    pOtaInterfaces->pal.getPlatformImageState = otaPal_GetPlatformImageState;
    pOtaInterfaces->pal.setPlatformImageState = otaPal_SetPlatformImageState;
    pOtaInterfaces->pal.writeBlock = otaPal_WriteBlock;
    pOtaInterfaces->pal.activate = otaPal_ActivateNewImage;
    pOtaInterfaces->pal.closeFile = otaPal_CloseFile;
    pOtaInterfaces->pal.reset = otaPal_ResetDevice;
    pOtaInterfaces->pal.abort = otaPal_Abort;
    pOtaInterfaces->pal.createFile = otaPal_CreateFileForRx;
}
```
ここで例えば、`otaPal_GetPlatformImageState`という関数はOTAのライブラリ内(esp-aws-iot/libraries/ota-for-aws-iot-embedded-sdk/port/ota_pal.c)で定義されていて、辿っていくと最終的には、ESP-IDFのツールチェーンで用意されているESP32用の関数（例えば、esp_ota_get_boot_partition()など)を呼び出しています。Platform依存部分が PALとして抽象化されている形となっています。

`startOTADemo()`の関数では、その後、`OTA_Init`にてOTAのライブラリを初期化し、各種イベントに対するCallbackを登録し、OTA用のスレッドを立ち上げて、OTAのJobを待ち受けします。その後、受信したMQTTのメッセージに従って登録しておいたCallbackを実行してOTAの処理を実行していく形となっています。`otaAppCallback`は更新が完了された際に呼ばれ新しいイメージをアクティベートする、`mqttEventCallback` はMQTTのメッセージを受信した際に呼ばれる、`mqttJobCallback`はジョブドキュメントを含むPublishを受け取った場合に呼ばれる、`mqttDataCallback` はデータブロックを受信した場合に呼ばれるというように用途別にCallbackが実装されています。以下は`otaAppCallback`の例です。
```
static void otaAppCallback( OtaJobEvent_t event,
                            void * pData )
{
    OtaErr_t err = OtaErrUninitialized;
    int ret;

    switch( event )
    {
        case OtaJobEventActivate:
            LogInfo( ( "Received OtaJobEventActivate callback from OTA Agent." ) );

            /* Activate the new firmware image. */
            OTA_ActivateNewImage();

            /* Shutdown OTA Agent, if it is required that the unsubscribe operations are not
             * performed while shutting down please set the second parameter to 0 instead of 1. */
            OTA_Shutdown( 0, 1 );

            /* Requires manual activation of new image.*/
            LogError( ( "New image activation failed." ) );

            break;

        case OtaJobEventFail:
            LogInfo( ( "Received OtaJobEventFail callback from OTA Agent." ) );

            /* Nothing special to do. The OTA agent handles it. */
            break;
```

# まとめ
ESP32のデバイスに対してFreeRTOS用のジョブを作成してOTAを実行してみました。実際にファームウェアイメージを作成してバージョンアップできることが確認できました。AWS上からグループを指定してESP32のデバイスのファームウエアをアップデートできるのは便利ですね。