Tech Blog

FreeRTOS Modular Over-The-Air

AWS IoTFreeRTOS でモジュラー Over-The-Air アップデートを試してみる

今回の環境

TL;DR

AWS IoT デバイス登録

AWS IoT Home

Create things

Specify thing properties

Create policy

// Policy document
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:Connect",
      "Resource": "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:*"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Publish",
      "Resource": "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:*"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Subscribe",
      "Resource": "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:*"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Receive",
      "Resource": "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:*"
    }
  ]
}

Attach policies to certificates

Download certificate and keys

コード署名証明書の登録
  # cert_config.txt
+ [ req ]
+ prompt             = no
+ distinguished_name = my_dn
+
+ [ my_dn ]
+ commonName = test_signer@amazon.com
+
+ [ my_exts ]
+ keyUsage         = digitalSignature
+ extendedKeyUsage = codeSigning
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve -outform PEM -out ecdsasigner.key
openssl req -new -x509 -config cert_config.txt -extensions my_exts -nodes -days 365 -key ecdsasigner.key -out ecdsasigner.crt
aws acm import-certificate --certificate fileb://ecdsasigner.crt --private-key fileb://ecdsasigner.key
{
    "CertificateArn": "arn:aws:acm:${AWS::Region}:${AWS::AccountId}:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
  // signing-profile.json
+ {
+   "platformId": "AmazonFreeRTOS-Default",
+   "signingMaterial": {
+     "certificateArn": "arn:aws:acm:${AWS::Region}:${AWS::AccountId}:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+   },
+   "signingParameters": {
+     "certname": "/"
+   }
+ }
aws signer put-signing-profile \
    --profile-name "ExampleThing_ESP32C3" \
    --cli-input-json file://signing-profile.json
{
    "arn": "arn:aws:signer:${AWS::Region}:${AWS::AccountId}:/signing-profiles/ExampleThing_ESP32C3",
    "profileVersion": "xxxxxxxxxx",
    "profileVersionArn": "arn:aws:signer:${AWS::Region}:${AWS::AccountId}:/signing-profiles/ExampleThing_ESP32C3/xxxxxxxxxx"
}
ファームウェアビルド環境の準備
brew tap espressif/eim
==> Tapping espressif/eim
Cloning into '/opt/homebrew/Library/Taps/espressif/homebrew-eim'...
Tapped 1 cask and 1 formula (16 files, 68.6KB).
brew trust espressif/eim
Trusted tap: espressif/eim
brew install eim
==> Would install 1 formula:
eim
==> Fetching downloads for: eim
...
Run 'eim' to install ESP-IDF.
==> Summary
🍺  /opt/homebrew/Cellar/eim/0.17.1: 7 files, 21.5MB, built in 1 second
==> Running `brew cleanup eim`...
zsh completions have been installed to:
  /opt/homebrew/share/zsh/site-functions
eim install -i release-v5.5
2026-07-24 06:16:15 -  6 - 07 - INFO - Found git: /usr/bin/git
2026-07-24 06:16:15 -  6 - 07 - INFO - All prerequisites are satisfied!
2026-07-24 06:16:15 -  6 - 07 - INFO - Running python sanity check
2026-07-24 06:16:15 -  6 - 07 - INFO - Found python3
...
============================================
to activate the environment, run the following command in your terminal:
       source "~/.espressif/tools/activate_idf_release-v5.5.sh"
============================================
2026-07-24 06:23:33 -  6 - 07 - INFO - Wizard result: Ok
2026-07-24 06:23:33 -  6 - 07 - INFO - Successfully installed IDF
2026-07-24 06:23:33 -  6 - 07 - INFO - Now you can start using IDF tools
レファレンス実装の取得
gh repo clone FreeRTOS/iot-reference-esp32 -- --recursive
Cloning into 'iot-reference-esp32'...
remote: Enumerating objects: 3494, done.
remote: Counting objects: 100% (786/786), done.
...
Receiving objects: 100% (2238/2238), 1.66 MiB | 8.02 MiB/s, done.
Resolving deltas: 100% (1142/1142), done.
Submodule path 'components/esp-aws-iot/libraries/corePKCS11/corePKCS11/source/dependency/3rdparty/pkcs11': checked out '478bfc5077901865993e3a193777c1a9f6516cdf'
idf.py set-target esp32c3
Adding "set-target"'s dependency "fullclean" to list of commands with default set of options.
Executing action: fullclean
Build directory '~/workspaces/iot-reference-esp32/build' not found. Nothing to clean.
Executing action: set-target
...
-- Configuring done (8.8s)
-- Generating done (0.5s)
-- Build files have been written to: ~/workspaces/iot-reference-esp32/build
証明書の書き込み
cp ~/Downloads/*-certificate.pem.crt main/certs/client.crt
cp ~/Downloads/*-private.pem.key main/certs/client.key
cp ~/ecdsasigner.crt main/certs/aws_codesign.crt
curl -fsSLo main/certs/root_cert_auth.crt https://www.amazontrust.com/repository/AmazonRootCA1.pem
python managed_components/espressif__esp_secure_cert_mgr/tools/configure_esp_secure_cert.py \
    -p /dev/cu.usbmodem --keep_ds_data_on_host \
    --ca-cert main/certs/root_cert_auth.crt \
    --device-cert main/certs/client.crt \
    --private-key main/certs/client.key \
    --target_chip esp32c3 \
    --secure_cert_type cust_flash \
    --priv_key_algo RSA 2048
=== Flashing ESP Secure Cert Partition ===
Partition file: esp_secure_cert_data/esp_secure_cert.bin
Flash offset: 0xD000
Note: You can skip this step by providing --skip_flash argument
...
Writing at 0x0000d000... (100 %)
Wrote 24576 bytes (3127 compressed) at 0x0000d000 in 0.3 seconds (effective 647.3 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...
ファームウェアの書き込み
aws iot describe-endpoint --endpoint-type iot:Data-ATS
{
    "endpointAddress": "xxxxxxxxxxxxxx-ats.iot.${AWS::Region}.amazonaws.com"
}
idf.py menuconfig

Endpoint for MQTT Broker to use

Thing name

Executing action: menuconfig
Running ninja in directory ~/workspaces/iot-reference-esp32/build
Executing "ninja menuconfig"...
[0/1] cd ~/workspaces/iot-reference-esp32/b... ~/workspaces/iot-reference-esp32/sdkconfig
...
warning: unknown kconfig symbol 'OTA_DATA_OVER_MQTT_PRIMARY' assigned to 'y' in ~/workspaces/iot-reference-esp32/sdkconfig.defaults
warning: unknown kconfig symbol 'OTA_PRIMARY_DATA_PROTOCOL' assigned to '1' in ~/workspaces/iot-reference-esp32/sdkconfig.defaults
idf.py -p /dev/cu.usbmodem flash
Executing action: flash
Running ninja in directory ~/workspaces/iot-reference-esp32/build
Executing "ninja flash"...
[0/1] Re-running CMake...
...
Writing at 0x00019000... (100 %)
Wrote 8192 bytes (31 compressed) at 0x00019000 in 0.1 seconds (effective 586.7 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...
EPS32-C3 プロビジョニング
idf.py -p /dev/cu.usbmodem monitor

monitor

Google Play Store

ESP BLE Prov

Select Wi-Fi Network

Provisioning

S3 バケットの作成
aws s3 mb s3://afr-ota-example-thing-bucket
make_bucket: afr-ota-example-thing-bucket
aws s3api put-bucket-versioning \
    --bucket afr-ota-example-thing-bucket \
    --versioning-configuration Status=Enabled
aws s3api get-bucket-versioning \
    --bucket afr-ota-example-thing-bucket
{
    "Status": "Enabled"
}
OTA アップデートサービスロールの作成
  // trust-policy.json
+ {
+   "Version": "2012-10-17",
+   "Statement": [
+     {
+       "Effect": "Allow",
+       "Principal": {
+         "Service": "iot.amazonaws.com"
+       },
+       "Action": "sts:AssumeRole"
+     }
+   ]
+ }
aws iam create-role \
    --role-name AmazonFreeRTOSOTAUpdateServiceRole \
    --assume-role-policy-document file://trust-policy.json
{
    "Role": {
        "Path": "/",
        "RoleName": "AmazonFreeRTOSOTAUpdateServiceRole",
        "RoleId": "AROAX46R4F6ZDBXAHI3YB",
        "Arn": "arn:aws:iam::543216644018:role/AmazonFreeRTOSOTAUpdateServiceRole",
        "CreateDate": "2026-07-23T20:51:25+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "iot.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}
aws iam attach-role-policy \
    --role-name AmazonFreeRTOSOTAUpdateServiceRole \
    --policy-arn arn:aws:iam::aws:policy/service-role/AmazonFreeRTOSOTAUpdate
  // inline-policy.json
+   {
+     "Version":"2012-10-17",
+     "Statement": [
+         {
+             "Effect": "Allow",
+             "Action": [
+                 "iam:GetRole",
+                 "iam:PassRole"
+             ],
+             "Resource": "arn:aws:iam::543216644018:role/AmazonFreeRTOSOTAUpdateServiceRole"
+         }
+     ]
+ }
aws iam put-role-policy \
    --role-name AmazonFreeRTOSOTAUpdateServiceRole \
    --policy-name PassRole \
    --policy-document file://inline-policy.json
OTA イメージの作成
idf.py -p build
Executing action: build
Running ninja in directory ~/workspaces/iot-reference-esp32/build
Executing "ninja build"...
[0/1] Re-running CMake...
...
aws s3 cp build/FeaturedFreeRTOSIoTIntegration.bin s3://afr-ota-example-thing-bucket/unsigned/update.bin
upload: build/FeaturedFreeRTOSIoTIntegration.bin to s3://afr-ota-example-thing-bucket/unsigned/update.bin
OTA アップデートジョブ作成
aws s3api list-object-versions \
    --bucket afr-ota-example-thing-bucket \
    --prefix unsigned/update.bin \
    --query "Versions[?IsLatest].[VersionId]"
[
    [
        "${LatestVersionId}"
    ]
]
  // ota-update.json
+ {
+     "otaUpdateId": "v0-0-1",
+     "targets": [
+         "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:thing/ExampleThing"
+     ],
+     "targetSelection": "SNAPSHOT",
+     "protocols": [
+         "MQTT"
+     ],
+     "files": [
+         {
+             "fileName": "NA",
+             "fileLocation": {
+                 "s3Location": {
+                     "bucket": "afr-ota-example-thing-bucket",
+                     "key": "unsigned/update.bin",
+                     "version": "${LatestVersionId}"
+                 }
+             },
+             "codeSigning": {
+                 "startSigningJobParameter": {
+                     "signingProfileName": "ExampleThing_ESP32C3",
+                     "destination": {
+                         "s3Destination": {
+                             "bucket": "afr-ota-example-thing-bucket",
+                             "prefix": "signed/"
+                         }
+                     }
+                 }
+             }
+         }
+     ],
+     "roleArn": "arn:aws:iam::${AWS::AccountId}:role/AmazonFreeRTOSOTAUpdateServiceRole"
+ }
aws iot create-ota-update --cli-input-json file://ota-update.json 
{
    "otaUpdateId": "v0-0-1",
    "otaUpdateArn": "arn:aws:iot:us-east-1:543216644018:otaupdate/v0-0-1",
    "otaUpdateStatus": "CREATE_PENDING"
}

```sh[class='command-line'][data-user='takeharak'][data-host='macbook-pro'][data-filter-continuation='(con)'][data-filter-output='(out)']
idf.py -p /dev/cu.usbmodem monitor
OTA アップデート

OTA CompletedSuccessfully

参考にしたページ