diff --git a/docs/resources/iotda_device_async_command.md b/docs/resources/iotda_device_async_command.md
new file mode 100644
index 0000000000..329541542b
--- /dev/null
+++ b/docs/resources/iotda_device_async_command.md
@@ -0,0 +1,114 @@
+---
+subcategory: "IoT Device Access (IoTDA)"
+layout: "huaweicloud"
+page_title: "HuaweiCloud: huaweicloud_iotda_device_async_command"
+description: |-
+ Manages a device asynchronous command delivery resource within HuaweiCloud.
+---
+
+# huaweicloud_iotda_device_async_command
+
+Manages a device asynchronous command delivery resource within HuaweiCloud.
+
+-> 1.This resource is only a one-time action resource for doing API action. Deleting this resource will not clear
+ the corresponding request record, but will only remove the resource information from the tfstate file.
+
2.Currently, this resource is only supported deliver commands asynchronously to NB-IoT devices.
+
3.After the resource is created, please pay attention to the command executed result through `status`,
+ you can execute the **terraform plan** command at regular intervals to monitor `status` changes.
+
+-> When accessing an IoTDA **standard** or **enterprise** edition instance, you need to specify the IoTDA service
+ endpoint in `provider` block.
+ You can login to the IoTDA console, choose the instance **Overview** and click **Access Details**
+ to view the HTTPS application access address. An example of the access address might be
+ **9bc34xxxxx.st1.iotda-app.ap-southeast-1.myhuaweicloud.com**, then you need to configure the
+ `provider` block as follows:
+
+ ```hcl
+ provider "huaweicloud" {
+ endpoints = {
+ iotda = "https://9bc34xxxxx.st1.iotda-app.ap-southeast-1.myhuaweicloud.com"
+ }
+ }
+ ```
+
+## Example Usage
+
+```hcl
+variable "device_id"{}
+variable "send_strategy"{}
+
+resource "huaweicloud_iotda_device_async_command" "test" {
+ device_id = var.device_id
+ send_strategy = var.send_strategy
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `region` - (Optional, String, ForceNew) Specifies the region in which to create the resource.
+ If omitted, the provider-level region will be used.
+ Changing this parameter will create a new resource.
+
+* `device_id` - (Required, String, ForceNew) Specifies the ID of the device to which the command is delivered.
+ Changing this parameter will create a new resource.
+
+* `send_strategy` - (Required, String, ForceNew) Specifies the delivery policy.
+ The valid values are as follows:
+ + **immediately**: The command is delivered immediately.
+ + **delay**: The command is cached and delivered after the device reports data or goes online.
+
+ Changing this parameter will create a new resource.
+
+* `service_id` - (Optional, String, ForceNew) Specifies the ID of the device service to which the device command belongs,
+ which is defined in the product model associated with the device.
+ This parameter is mandatory if the device requires codecs to parse commands.
+ Changing this parameter will create a new resource.
+
+* `name` - (Optional, String, ForceNew) Specifies the command name, which is defined in the product model
+ associated with the device.
+ This parameter is mandatory if the device requires codecs to parse commands.
+ Changing this parameter will create a new resource.
+
+* `paras` - (Optional, Map, ForceNew) Specifies the command executed by the device.
+ If `service_id` is specified, each key is the parameter in commands in the product model.
+ If `service_id` is left empty, the key can be customized.
+ The maximum size of the request object is `256` KB.
+ Changing this parameter will create a new resource.
+
+* `expire_time` - (Optional, Int, ForceNew) Specifies the duration of caching commands on the IoT platform.
+ This parameter is valid only when `send_strategy` is set to **delay**. The unit is second.
+ If `expire_time` is set to **0** or not specified, the command is cached for `24` hours (`86,400` seconds) by default,
+ and the maximum cache duration is `2` days.
+ Changing this parameter will create a new resource.
+
+## Attribute Reference
+
+In addition to all arguments above, the following attributes are exported:
+
+* `id` - The resource ID.
+
+* `status` - The status of the command.
+ The valid values are as follows:
+ + **PENDING**: The command is not delivered and is cached on the platform.
+ + **EXPIRED**: The command has expired, the cache time exceeds the value of `expire_time`.
+ + **SENT**: The command is being delivered.
+ + **DELIVERED**: The command has been delivered.
+ + **SUCCESSFUL**: The command has been executed.
+ + **FAILED**: The command fails to be executed.
+ + **TIMEOUT**: After the command is delivered, no response is received from the device or the response times out.
+
+* `result` - The command execution result.
+
+* `sent_time` - The time of the platform sent the command.
+ The format is **yyyyMMdd'T'HHmmss'Z'**, e.g. **20151212T121212Z**.
+
+* `delivered_time` - The time of the device received the command.
+ The format is **yyyyMMdd'T'HHmmss'Z'**, e.g. **20151212T121212Z**.
+
+* `response_time` - The time of the device responded to the command.
+ The format is **yyyyMMdd'T'HHmmss'Z'**, e.g. **20151212T121212Z**.
+
+* `created_at` - The creation time of the device command.
+ The format is **yyyyMMdd'T'HHmmss'Z'**, e.g. **20151212T121212Z**.
diff --git a/huaweicloud/provider.go b/huaweicloud/provider.go
index 3f8da57e65..b75c488d57 100644
--- a/huaweicloud/provider.go
+++ b/huaweicloud/provider.go
@@ -1685,18 +1685,19 @@ func Provider() *schema.Provider {
"huaweicloud_images_image_share": ims.ResourceImsImageShare(),
"huaweicloud_images_image_share_accepter": ims.ResourceImsImageShareAccepter(),
- "huaweicloud_iotda_access_credential": iotda.ResourceAccessCredential(),
- "huaweicloud_iotda_amqp": iotda.ResourceAmqp(),
- "huaweicloud_iotda_batchtask": iotda.ResourceBatchTask(),
- "huaweicloud_iotda_dataforwarding_rule": iotda.ResourceDataForwardingRule(),
- "huaweicloud_iotda_device": iotda.ResourceDevice(),
- "huaweicloud_iotda_device_certificate": iotda.ResourceDeviceCertificate(),
- "huaweicloud_iotda_device_group": iotda.ResourceDeviceGroup(),
- "huaweicloud_iotda_device_linkage_rule": iotda.ResourceDeviceLinkageRule(),
- "huaweicloud_iotda_device_proxy": iotda.ResourceDeviceProxy(),
- "huaweicloud_iotda_product": iotda.ResourceProduct(),
- "huaweicloud_iotda_space": iotda.ResourceSpace(),
- "huaweicloud_iotda_upgrade_package": iotda.ResourceUpgradePackage(),
+ "huaweicloud_iotda_access_credential": iotda.ResourceAccessCredential(),
+ "huaweicloud_iotda_amqp": iotda.ResourceAmqp(),
+ "huaweicloud_iotda_batchtask": iotda.ResourceBatchTask(),
+ "huaweicloud_iotda_dataforwarding_rule": iotda.ResourceDataForwardingRule(),
+ "huaweicloud_iotda_device": iotda.ResourceDevice(),
+ "huaweicloud_iotda_device_async_command": iotda.ResourceDeviceAsyncCommand(),
+ "huaweicloud_iotda_device_certificate": iotda.ResourceDeviceCertificate(),
+ "huaweicloud_iotda_device_group": iotda.ResourceDeviceGroup(),
+ "huaweicloud_iotda_device_linkage_rule": iotda.ResourceDeviceLinkageRule(),
+ "huaweicloud_iotda_device_proxy": iotda.ResourceDeviceProxy(),
+ "huaweicloud_iotda_product": iotda.ResourceProduct(),
+ "huaweicloud_iotda_space": iotda.ResourceSpace(),
+ "huaweicloud_iotda_upgrade_package": iotda.ResourceUpgradePackage(),
"huaweicloud_kms_data_encrypt_decrypt": dew.ResourceKmsDataEncryptDecrypt(),
"huaweicloud_kms_key": dew.ResourceKmsKey(),
diff --git a/huaweicloud/services/acceptance/iotda/resource_huaweicloud_iotda_device_async_command_test.go b/huaweicloud/services/acceptance/iotda/resource_huaweicloud_iotda_device_async_command_test.go
new file mode 100644
index 0000000000..8e220281d1
--- /dev/null
+++ b/huaweicloud/services/acceptance/iotda/resource_huaweicloud_iotda_device_async_command_test.go
@@ -0,0 +1,157 @@
+package iotda
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+
+ "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iotda/v5/model"
+
+ "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config"
+ "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance"
+)
+
+func getDeviceAsyncCommandFunc(conf *config.Config, state *terraform.ResourceState) (interface{}, error) {
+ client, err := conf.HcIoTdaV5Client(acceptance.HW_REGION_NAME, WithDerivedAuth())
+ if err != nil {
+ return nil, fmt.Errorf("error creating IoTDA v5 client: %s", err)
+ }
+
+ return client.ShowAsyncDeviceCommand(&model.ShowAsyncDeviceCommandRequest{DeviceId: state.Primary.Attributes["device_id"],
+ CommandId: state.Primary.ID})
+}
+
+func TestAccDeviceAsyncCommand_basic(t *testing.T) {
+ var obj model.ShowDeviceResponse
+
+ name := acceptance.RandomAccResourceName()
+ rName := "huaweicloud_iotda_device_async_command.test"
+ rc := acceptance.InitResourceCheck(
+ rName,
+ &obj,
+ getDeviceAsyncCommandFunc,
+ )
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acceptance.TestAccPreCheck(t) },
+ ProviderFactories: acceptance.TestAccProviderFactories,
+ CheckDestroy: rc.CheckResourceDestroy(),
+ Steps: []resource.TestStep{
+ {
+ Config: testAccDeviceAsyncCommand_basic(name),
+ Check: resource.ComposeTestCheckFunc(
+ rc.CheckResourceExists(),
+ resource.TestCheckResourceAttrPair(rName, "device_id", "huaweicloud_iotda_device.test", "id"),
+ resource.TestCheckResourceAttrPair(rName, "service_id", "huaweicloud_iotda_product.test", "services.0.id"),
+ resource.TestCheckResourceAttrPair(rName, "name", "huaweicloud_iotda_product.test", "services.0.commands.0.name"),
+ resource.TestCheckResourceAttr(rName, "send_strategy", "delay"),
+ resource.TestCheckResourceAttr(rName, "expire_time", "80000"),
+ resource.TestCheckResourceAttrSet(rName, "status"),
+ resource.TestCheckResourceAttrSet(rName, "created_at"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccDeviceAsyncCommand_derived(t *testing.T) {
+ var obj model.ShowDeviceResponse
+
+ name := acceptance.RandomAccResourceName()
+ rName := "huaweicloud_iotda_device_async_command.test"
+ rc := acceptance.InitResourceCheck(
+ rName,
+ &obj,
+ getDeviceAsyncCommandFunc,
+ )
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() {
+ acceptance.TestAccPreCheck(t)
+ acceptance.TestAccPreCheckHWIOTDAAccessAddress(t)
+ },
+ ProviderFactories: acceptance.TestAccProviderFactories,
+ CheckDestroy: rc.CheckResourceDestroy(),
+ Steps: []resource.TestStep{
+ {
+ Config: testAccDeviceAsyncCommand_basic(name),
+ Check: resource.ComposeTestCheckFunc(
+ rc.CheckResourceExists(),
+ resource.TestCheckResourceAttrPair(rName, "device_id", "huaweicloud_iotda_device.test", "id"),
+ resource.TestCheckResourceAttrPair(rName, "service_id", "huaweicloud_iotda_product.test", "services.0.id"),
+ resource.TestCheckResourceAttrPair(rName, "name", "huaweicloud_iotda_product.test", "services.0.commands.0.name"),
+ resource.TestCheckResourceAttr(rName, "send_strategy", "delay"),
+ resource.TestCheckResourceAttr(rName, "expire_time", "80000"),
+ resource.TestCheckResourceAttrSet(rName, "status"),
+ resource.TestCheckResourceAttrSet(rName, "created_at"),
+ ),
+ },
+ },
+ })
+}
+
+func testAccDeviceAsyncCommand_base(name string) string {
+ return fmt.Sprintf(`
+resource "huaweicloud_iotda_space" "test" {
+ name = "%[1]s"
+}
+
+resource "huaweicloud_iotda_product" "test" {
+ name = "%[1]s"
+ device_type = "AI"
+ protocol = "CoAP"
+ space_id = huaweicloud_iotda_space.test.id
+ data_type = "json"
+ industry = "smart-home"
+
+ services {
+ id = "001211985996"
+ type = "001002"
+
+ commands {
+ name = "cmd-test"
+
+ paras {
+ name = "cmd-req"
+ type = "string"
+ max_length = 20
+ }
+
+ responses {
+ name = "cmd-resp"
+ type = "string"
+ max_length = 20
+ }
+ }
+ }
+}
+
+resource "huaweicloud_iotda_device" "test" {
+ node_id = "101112026"
+ name = "%[1]s"
+ space_id = huaweicloud_iotda_space.test.id
+ product_id = huaweicloud_iotda_product.test.id
+ description = "demo"
+}
+`, name)
+}
+
+func testAccDeviceAsyncCommand_basic(name string) string {
+ return fmt.Sprintf(`
+%[1]s
+
+resource "huaweicloud_iotda_device_async_command" "test" {
+ device_id = huaweicloud_iotda_device.test.id
+ service_id = huaweicloud_iotda_product.test.services.0.id
+ name = huaweicloud_iotda_product.test.services.0.commands.0.name
+ send_strategy = "delay"
+ expire_time = "80000"
+
+ paras = {
+ (huaweicloud_iotda_product.test.services.0.commands.0.paras.0.name) = "tf-acc"
+ }
+}
+`, testAccDeviceAsyncCommand_base(name))
+}
diff --git a/huaweicloud/services/iotda/resource_huaweicloud_iotda_device_async_command.go b/huaweicloud/services/iotda/resource_huaweicloud_iotda_device_async_command.go
new file mode 100644
index 0000000000..e52a2739c6
--- /dev/null
+++ b/huaweicloud/services/iotda/resource_huaweicloud_iotda_device_async_command.go
@@ -0,0 +1,175 @@
+package iotda
+
+import (
+ "context"
+
+ "github.com/hashicorp/go-multierror"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+ "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iotda/v5/model"
+
+ "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/common"
+ "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config"
+ "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/utils"
+)
+
+// @API IoTDA POST /v5/iot/{project_id}/devices/{device_id}/async-commands
+// @API IoTDA GET /v5/iot/{project_id}/devices/{device_id}/async-commands/{command_id}
+func ResourceDeviceAsyncCommand() *schema.Resource {
+ return &schema.Resource{
+ CreateContext: resourceDeviceAsyncCommandCreate,
+ ReadContext: resourceDeviceAsyncCommandRead,
+ DeleteContext: resourceDeviceAsyncCommandDelete,
+
+ Schema: map[string]*schema.Schema{
+ "region": {
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ ForceNew: true,
+ },
+ "device_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ },
+ "send_strategy": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ },
+ "service_id": {
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ ForceNew: true,
+ },
+ "name": {
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ ForceNew: true,
+ },
+ "paras": {
+ Type: schema.TypeMap,
+ Optional: true,
+ ForceNew: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ },
+ "expire_time": {
+ Type: schema.TypeInt,
+ Optional: true,
+ Computed: true,
+ ForceNew: true,
+ },
+ "status": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "result": {
+ Type: schema.TypeMap,
+ Computed: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ },
+ "created_at": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "sent_time": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "delivered_time": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "response_time": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ }
+}
+
+func buildDeviceAsyncCommandBodyParams(d *schema.ResourceData) *model.CreateAsyncCommandRequest {
+ commandParas := utils.ValueIgnoreEmpty(d.Get("paras"))
+ bodyParams := model.CreateAsyncCommandRequest{
+ DeviceId: d.Get("device_id").(string),
+ Body: &model.AsyncDeviceCommandRequest{
+ ServiceId: utils.StringIgnoreEmpty(d.Get("service_id").(string)),
+ CommandName: utils.StringIgnoreEmpty(d.Get("name").(string)),
+ SendStrategy: d.Get("send_strategy").(string),
+ //nolint:gosec
+ ExpireTime: utils.Int32IgnoreEmpty(int32(d.Get("expire_time").(int))),
+ },
+ }
+ if commandParas != nil {
+ bodyParams.Body.Paras = &commandParas
+ }
+
+ return &bodyParams
+}
+
+func resourceDeviceAsyncCommandCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ cfg := meta.(*config.Config)
+ region := cfg.GetRegion(d)
+ isDerived := WithDerivedAuth(cfg, region)
+ client, err := cfg.HcIoTdaV5Client(region, isDerived)
+ if err != nil {
+ return diag.Errorf("error creating IoTDA v5 client: %s", err)
+ }
+
+ createOpts := buildDeviceAsyncCommandBodyParams(d)
+ resp, err := client.CreateAsyncCommand(createOpts)
+ if err != nil {
+ return diag.Errorf("error creating device asynchronous command : %s", err)
+ }
+
+ if resp == nil || resp.CommandId == nil {
+ return diag.Errorf("error creating device asynchronous command: ID is not found in API response")
+ }
+
+ d.SetId(*resp.CommandId)
+
+ return resourceDeviceAsyncCommandRead(ctx, d, meta)
+}
+
+func resourceDeviceAsyncCommandRead(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ c := meta.(*config.Config)
+ region := c.GetRegion(d)
+ isDerived := WithDerivedAuth(c, region)
+ client, err := c.HcIoTdaV5Client(region, isDerived)
+ if err != nil {
+ return diag.Errorf("error creating IoTDA v5 client: %s", err)
+ }
+
+ deviceId := d.Get("device_id").(string)
+
+ response, err := client.ShowAsyncDeviceCommand(&model.ShowAsyncDeviceCommandRequest{DeviceId: deviceId, CommandId: d.Id()})
+ if err != nil {
+ return common.CheckDeletedDiag(d, err, "error retrieving device asynchronous command")
+ }
+
+ mErr := multierror.Append(
+ d.Set("region", region),
+ d.Set("device_id", response.DeviceId),
+ d.Set("service_id", response.ServiceId),
+ d.Set("name", response.CommandName),
+ d.Set("status", response.Status),
+ d.Set("result", response.Result),
+ d.Set("send_strategy", response.SendStrategy),
+ d.Set("expire_time", response.ExpireTime),
+ d.Set("sent_time", response.SentTime),
+ d.Set("delivered_time", response.DeliveredTime),
+ d.Set("response_time", response.ResponseTime),
+ d.Set("created_at", response.CreatedTime),
+ )
+
+ return diag.FromErr(mErr.ErrorOrNil())
+}
+
+func resourceDeviceAsyncCommandDelete(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
+ // No processing is performed in the 'Delete()' method because the resource is a action resource.
+ return nil
+}