

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 步骤 2：开发可延迟更新的组件
<a name="develop-component-defer-updates"></a>

在本节中，您将在 Python 中开发一个 Hello World 组件，当核心设备的电池电量低于您在部署组件时配置的阈值时，该组件会延迟组件更新。在此组件中，您将使用 Amazon IoT Device SDK 适用于 Python 的 v2 中的[进程间通信 (IPC) 接口](interprocess-communication.md)。当核心设备收到部署时，您可以使用 [SubscribeToComponentUpdates](ipc-component-lifecycle.md#ipc-operation-subscribetocomponentupdates)IPC 操作接收通知。然后，您可以根据设备的电池电量使用 [DeferComponentUpdate](ipc-component-lifecycle.md#ipc-operation-defercomponentupdate)IPC 操作来推迟或确认更新。

**要开发可延迟更新的 Hello World 组件，请执行以下步骤：**

1. 在开发计算机上，为组件源代码创建一个文件夹。

   ```
   mkdir com.example.BatteryAwareHelloWorld
   cd com.example.BatteryAwareHelloWorld
   ```

1. 使用文本编辑器创建名为 `gdk-config.json` 的文件。GDK CLI 从名为 `gdk-config.json` 的 [GDK CLI 配置文件](gdk-cli-configuration-file.md)中读取数据，以生成和发布组件。此配置文件存在于组件文件夹的根目录中。

   <a name="nano-command-intro"></a>例如，在基于 Linux 的系统上，您可以运行以下命令来使用 GNU nano 创建该文件。

   ```
   nano gdk-config.json
   ```

   将以下 JSON 复制到该文件中。
   + {{Amazon}}用你的名字替换。
   + {{us-west-2}}替换为核心设备的运行 Amazon Web Services 区域 位置。GDK CLI 将在此 Amazon Web Services 区域发布该组件。
   + {{greengrass-component-artifacts}}替换为要使用的 S3 存储桶前缀。当您使用 GDK CLI 发布组件时，GDK CLI 会使用以下格式将组件的项目上传到 S3 存储桶，其名称由此值 Amazon Web Services 区域、和您的 Amazon Web Services 账户 ID 组成：。`{{bucketPrefix}}-{{region}}-{{accountId}}`

     例如，如果您指定**greengrass-component-artifacts**和，且您的 Amazon Web Services 账户 ID 为 **us-west-2****123456789012**，则 GDK CLI 将使用名为`greengrass-component-artifacts-us-west-2-123456789012`的 S3 存储桶。

   ```
   {
     "component": {
       "com.example.BatteryAwareHelloWorld": {
         "author": "{{Amazon}}",
         "version": "NEXT_PATCH",
         "build": {
           "build_system" : "zip"
         },
         "publish": {
           "region": "{{us-west-2}}",
           "bucket": "{{greengrass-component-artifacts}}"
         }
       }
     },
     "gdk_version": "1.0.0"
   }
   ```

   配置文件可指定以下内容：
   + GDK CLI 将 Greengrass 组件发布到云服务时要使用的版本。 Amazon IoT Greengrass `NEXT_PATCH`指定在 Amazon IoT Greengrass 云服务中可用的最新版本之后选择下一个补丁版本。如果组件在 Amazon IoT Greengrass 云服务中还没有版本，GDK CLI 会使用`1.0.0`。
   + 组件生成系统。当您使用 `zip` 生成系统时，GDK CLI 会将组件的源代码打包成一个 ZIP 文件，该文件将成为该组件的单个构件。
   + GDK CLI Amazon Web Services 区域 在那里发布 Greengrass 组件。
   + GDK CLI 上传组件构件的 S3 存储桶前缀。

1. 使用文本编辑器在名为 `main.py` 的文件中创建组件源代码。

   <a name="nano-command-intro"></a>例如，在基于 Linux 的系统上，您可以运行以下命令来使用 GNU nano 创建该文件。

   ```
   nano main.py
   ```

   将以下 Python 代码复制到该文件中。

   ```
   import json
   import os
   import sys
   import time
   import traceback
   
   from pathlib import Path
   
   from awsiot.greengrasscoreipc.clientv2 import GreengrassCoreIPCClientV2
   
   HELLO_WORLD_PRINT_INTERVAL = 15  # Seconds
   DEFER_COMPONENT_UPDATE_INTERVAL = 30 * 1000  # Milliseconds
   
   
   class BatteryAwareHelloWorldPrinter():
       def __init__(self, ipc_client: GreengrassCoreIPCClientV2, battery_file_path: Path, battery_threshold: float):
           self.battery_file_path = battery_file_path
           self.battery_threshold = battery_threshold
           self.ipc_client = ipc_client
           self.subscription_operation = None
   
       def on_component_update_event(self, event):
           try:
               if event.pre_update_event is not None:
                   if self.is_battery_below_threshold():
                       self.defer_update(event.pre_update_event.deployment_id)
                       print('Deferred update for deployment %s' %
                             event.pre_update_event.deployment_id)
                   else:
                       self.acknowledge_update(
                           event.pre_update_event.deployment_id)
                       print('Acknowledged update for deployment %s' %
                             event.pre_update_event.deployment_id)
               elif event.post_update_event is not None:
                   print('Applied update for deployment')
           except:
               traceback.print_exc()
   
       def subscribe_to_component_updates(self):
           if self.subscription_operation == None:
               # SubscribeToComponentUpdates returns a tuple with the response and the operation.
               _, self.subscription_operation = self.ipc_client.subscribe_to_component_updates(
                   on_stream_event=self.on_component_update_event)
   
       def close_subscription(self):
           if self.subscription_operation is not None:
               self.subscription_operation.close()
               self.subscription_operation = None
   
       def defer_update(self, deployment_id):
           self.ipc_client.defer_component_update(
               deployment_id=deployment_id, recheck_after_ms=DEFER_COMPONENT_UPDATE_INTERVAL)
   
       def acknowledge_update(self, deployment_id):
           # Specify recheck_after_ms=0 to acknowledge a component update.
           self.ipc_client.defer_component_update(
               deployment_id=deployment_id, recheck_after_ms=0)
   
       def is_battery_below_threshold(self):
           return self.get_battery_level() < self.battery_threshold
   
       def get_battery_level(self):
           # Read the battery level from the virtual battery level file.
           with self.battery_file_path.open('r') as f:
               data = json.load(f)
               return float(data['battery_level'])
   
       def print_message(self):
           message = 'Hello, World!'
           if self.is_battery_below_threshold():
               message += ' Battery level (%d) is below threshold (%d), so the component will defer updates' % (
                   self.get_battery_level(), self.battery_threshold)
           else:
               message += ' Battery level (%d) is above threshold (%d), so the component will acknowledge updates' % (
                   self.get_battery_level(), self.battery_threshold)
           print(message)
   
   
   def main():
       # Read the battery threshold and virtual battery file path from command-line args.
       args = sys.argv[1:]
       battery_threshold = float(args[0])
       battery_file_path = Path(args[1])
       print('Reading battery level from %s and deferring updates when below %d' % (
           str(battery_file_path), battery_threshold))
   
       try:
           # Create an IPC client and a Hello World printer that defers component updates.
           ipc_client = GreengrassCoreIPCClientV2()
           hello_world_printer = BatteryAwareHelloWorldPrinter(
               ipc_client, battery_file_path, battery_threshold)
           hello_world_printer.subscribe_to_component_updates()
           try:
               # Keep the main thread alive, or the process will exit.
               while True:
                   hello_world_printer.print_message()
                   time.sleep(HELLO_WORLD_PRINT_INTERVAL)
           except InterruptedError:
               print('Subscription interrupted')
           hello_world_printer.close_subscription()
       except Exception:
           print('Exception occurred', file=sys.stderr)
           traceback.print_exc()
           exit(1)
   
   
   if __name__ == '__main__':
       main()
   ```

   该 Python 应用程序执行以下操作：
   + 从您稍后将在核心设备上创建的虚拟电池电量文件中读取核心设备的电池电量。该虚拟电池电量文件模拟真实电池，因此您可以在没有电池的核心设备上完成本教程。
   + 读取电池电量阈值和虚拟电池电量文件路径的命令行参数。组件配方根据配置参数设置这些命令行参数，因此您可以在部署组件时自定义这些值。
   + 使用 [Python 版本 2 中的 IPC 客户端Amazon IoT Device SDK V](https://github.com/aws/aws-iot-device-sdk-python-v2) 2 与核心软件通信。 Amazon IoT Greengrass 与初始 IPC 客户端相比，IPC 客户端 V2 减少了在自定义组件中使用 IPC 所需编写的代码量。
   + 使用 [SubscribeToComponentUpdates](ipc-component-lifecycle.md#ipc-operation-subscribetocomponentupdates)IPC 操作订阅更新通知。C Amazon IoT Greengrass ore 软件在每次部署之前和之后都会发送通知。每次收到通知时，该组件都会调用以下函数。如果通知涉及即将进行的部署，则组件会检查电池电量是否低于阈值。如果电池电量低于阈值，则组件会使用 [DeferComponentUpdate](ipc-component-lifecycle.md#ipc-operation-defercomponentupdate)IPC 操作将更新推迟 30 秒。否则，如果电池电量不低于阈值，则组件会确认更新，因此更新可以继续进行。

     ```
     def on_component_update_event(self, event):
         try:
             if event.pre_update_event is not None:
                 if self.is_battery_below_threshold():
                     self.defer_update(event.pre_update_event.deployment_id)
                     print('Deferred update for deployment %s' %
                           event.pre_update_event.deployment_id)
                 else:
                     self.acknowledge_update(
                         event.pre_update_event.deployment_id)
                     print('Acknowledged update for deployment %s' %
                           event.pre_update_event.deployment_id)
             elif event.post_update_event is not None:
                 print('Applied update for deployment')
         except:
             traceback.print_exc()
     ```
**注意**  
 Amazon IoT Greengrass Core 软件不发送本地部署的更新通知，因此您可以使用 Amazon IoT Greengrass 云服务部署此组件来对其进行测试。

1. 使用文本编辑器在名为 `recipe.json` 或 `recipe.yaml` 的文件中创建组件配方。组件*配方*定义了组件的元数据、默认配置参数和平台特定的生命周期脚本。

------
#### [ JSON ]

   <a name="nano-command-intro"></a>例如，在基于 Linux 的系统上，您可以运行以下命令来使用 GNU nano 创建该文件。

   ```
   nano recipe.json
   ```

   将以下 JSON 复制到该文件中。

   ```
   {
     "RecipeFormatVersion": "2020-01-25",
     "ComponentName": "COMPONENT_NAME",
     "ComponentVersion": "COMPONENT_VERSION",
     "ComponentDescription": "This Hello World component defers updates when the battery level is below a threshold.",
     "ComponentPublisher": "COMPONENT_AUTHOR",
     "ComponentConfiguration": {
       "DefaultConfiguration": {
         "BatteryThreshold": 50,
         "LinuxBatteryFilePath": "/home/ggc_user/virtual_battery.json",
         "WindowsBatteryFilePath": "C:\\Users\\ggc_user\\virtual_battery.json"
       }
     },
     "Manifests": [
       {
         "Platform": {
           "os": "linux"
         },
         "Lifecycle": {
           "install": "python3 -m pip install --user awsiotsdk --upgrade",
           "Run": "python3 -u {artifacts:decompressedPath}/com.example.BatteryAwareHelloWorld/main.py \"{configuration:/BatteryThreshold}\" \"{configuration:/LinuxBatteryFilePath}\""
         },
         "Artifacts": [
           {
             "Uri": "s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/com.example.BatteryAwareHelloWorld.zip",
             "Unarchive": "ZIP"
           }
         ]
       },
       {
         "Platform": {
           "os": "windows"
         },
         "Lifecycle": {
           "install": "py -3 -m pip install --user awsiotsdk --upgrade",
           "Run": "py -3 -u {artifacts:decompressedPath}/com.example.BatteryAwareHelloWorld/main.py \"{configuration:/BatteryThreshold}\" \"{configuration:/WindowsBatteryFilePath}\""
         },
         "Artifacts": [
           {
             "Uri": "s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/com.example.BatteryAwareHelloWorld.zip",
             "Unarchive": "ZIP"
           }
         ]
       }
     ]
   }
   ```

------
#### [ YAML ]

   <a name="nano-command-intro"></a>例如，在基于 Linux 的系统上，您可以运行以下命令来使用 GNU nano 创建该文件。

   ```
   nano recipe.yaml
   ```

   将以下 YAML 复制到该文件中。

   ```
   ---
   RecipeFormatVersion: "2020-01-25"
   ComponentName: "COMPONENT_NAME"
   ComponentVersion: "COMPONENT_VERSION"
   ComponentDescription: "This Hello World component defers updates when the battery level is below a threshold."
   ComponentPublisher: "COMPONENT_AUTHOR"
   ComponentConfiguration:
     DefaultConfiguration:
       BatteryThreshold: 50
       LinuxBatteryFilePath: "/home/ggc_user/virtual_battery.json"
       WindowsBatteryFilePath: "C:\\Users\\ggc_user\\virtual_battery.json"
   Manifests:
     - Platform:
         os: linux
       Lifecycle:
         install: python3 -m pip install --user awsiotsdk --upgrade
         Run: python3 -u {artifacts:decompressedPath}/com.example.BatteryAwareHelloWorld/main.py "{configuration:/BatteryThreshold}" "{configuration:/LinuxBatteryFilePath}"
       Artifacts:
         - Uri: "s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/com.example.BatteryAwareHelloWorld.zip"
           Unarchive: ZIP
     - Platform:
         os: windows
       Lifecycle:
         install: py -3 -m pip install --user awsiotsdk --upgrade
         Run: py -3 -u {artifacts:decompressedPath}/com.example.BatteryAwareHelloWorld/main.py "{configuration:/BatteryThreshold}" "{configuration:/WindowsBatteryFilePath}"
       Artifacts:
         - Uri: "s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/com.example.BatteryAwareHelloWorld.zip"
           Unarchive: ZIP
   ```

------

   此配方可指定以下内容：
   + 电池阈值、Linux 核心设备上的虚拟电池文件路径以及 Windows 核心设备上的虚拟电池文件路径的默认配置参数。
   + 安装适用于 Python 的最新版本 Amazon IoT Device SDK v2 的 `install` 生命周期。
   + 在 `run` 中运行 Python 应用程序的 `main.py` 生命周期。
   + 占位符，例如 `COMPONENT_NAME` 和 `COMPONENT_VERSION`，其中 GDK CLI 在生成组件配方时会替换信息。

   有关组件配方的更多信息，请参阅 [Amazon IoT Greengrass 组件配方参考](component-recipe-reference.md)。