Sending commands from nRF Cloud to LTE Device

Hi!

In our project, we need to send cloud-to-device commands. By "command" I mean a request to do some short action, not a request to change device's configuration/state. We see two ways of sending such requests:

1. C2D messages

2. Device shadow

What are the pros and cons of each way?

Here I see a recommendation to use Device shadow for commands sending. But if we talk about commands in the sense I described above, would this recommendation be still actual?

If yes, is there a recommended way (or some sample) of Device shadow usage for sending such commands? I mean, which fields Device shadow should contain and some algorithm of updating desired and reported section during sending command and its execution.

In case of C2D messages, the main problem seems to be command delivery when the device is offline. Right? Or is there some mechanism for this case in nRF Cloud?

Thank you!

Parents
  • Hello,

    So, there are two separate topics to subscribe. Please see https://docs.nordicsemi.com/bundle/nrf-cloud/page/APIs/MQTT/Topics.html#shadow-topics 

    `${deviceId}/shadow/get/accepted` - nRF Cloud will send delta only when you send empty message on  $aws/things/${deviceId}/shadow/get.
    `${deviceId}/shadow/update/delta` - nRf Cloud will send event automatically when change happens
    SO, you need to subscribe both. And when device is connecting again after offline period you send a empty message to the $aws/things/${deviceId}/shadow/get. This will trigger nRF Cloud to send the delta to the "get/accepted".
    Justin's coment that cloud would be sending even on "update/delta" is incorrect in the use case you described. Yes could will be sending the message if there is connection (device is online and keeping the connection alive). But if device is sleeping, the connection will close after 30 seconds. 
  • Hi  ,

    what you describe is what I would expect to see, but it's not what I am seeing.

    When the device comes online and sends an empty message to  $aws/things/${deviceId}/shadow/get it gets a shadow back, but that shadow does not contain the pending delta, even though the delta is pending in the cloud:

    i.e. a cloud API FetchDevice shows 

     "state": {

    "desired": {

    "nrfcloud_mqtt_topic_prefix": "prod/...",

    "pairing": {

    "state": "paired",

    "topics": {

    "d2c": "prod/.../d2c",

    "c2d": "prod/.../m/d/devsim4/+/r"

    }

    },

    "config": {

    "cmd": "command26"

    }

    },

     

    but the shadow received via ${deviceId}/shadow/get/accepted shows the following (note that the pending delta "config" is missing:

      "desired": {

        "nrfcloud_mqtt_topic_prefix": "prod/.../"

        "pairing": {

          "state": "paired",

          "topics": {

            "d2c": "prod/.../d2c",

            "c2d": "prod/.../+/r"

          }

        },

      },

    Here is the relevant code in the device simulator, this is executed in the onconnect callback:

     // subscribe to shadow gets `${deviceId}/shadow/get/accepted`
     await device.subscribe(device.topics.shadow.accepted);
    device.registerListener(device.topics.shadow.accepted, (param: { topic: string; payload: object}) =>
    {
    log.info(`shadow accepted: ${JSON.stringify(param.payload)}`);
    processCmd(param);
    });


    // subscribe to shadow rejected `${deviceId}/shadow/get/rejected`
     await device.subscribe(device.topics.shadow.rejected);
    device.registerListener(device.topics.shadow.rejected, (param: { topic: string; payload: object}) =>
    {
    log.info(`shadow rejected: ${JSON.stringify(param.payload)}`);
    });

    // subscribe to shadow updates `${deviceId}/shadow/update/delta`
     await device.subscribe(device.topics.shadow.delta);
    device.registerListener(device.topics.shadow.delta, (param: { topic: string; payload: object}) =>
    {
    log.info(`shadow delta: ${JSON.stringify(param.payload)}`);
    processCmd(param);
    });


    // since we just connected, get the current shadow from the cloud `$aws/things/${deviceId}/shadow/get`
     await device.publish(device.topics.shadow.get, {}); // payload is irrelevant

    Thanks,
    -- Terrence

  •   

    I think I finally have some insight. I think the nRF Cloud incorrectly trims away parts of the desired section, whereas AWS doesn't.

    Here is what I did:

    1. The previous reported state of the device is {"cmd": "sendReport": "7"}

    2. The device is offline

    3. I send the desired update to the server {"cmd": "sendReport": "8"}

    4. When the device comes online it gets the shadow via ${deviceId}/shadow/get/accepted.

    The shadow shows config {"cmd": "sendReport": "7"} but is missing desired {"cmd": "sendReport": "8"}:

    ************** MESSAGE RECEIVED ***********

    TOPIC: devsim4/shadow/get/accepted

    MESSAGE: {

      "desired": {

        "pairing": {

          "state": "paired",

          "topics": {

            "d2c": "...",

            "c2d": "..."

          }

        },

        "nrfcloud_mqtt_topic_prefix": "..."

      },

      "reported": {

        "control": {

          "alertsEn": true,

          "logLvl": 3

        }

      },

      "config": {

        "cmd": {

          "sendReport": "7"

        }

      }

    }

    *******************************************

    4. I restart the device and it now it gets the shadow via $aws/things/${deviceId}/shadow/get/accepted.

    The shadow now contains the desired {"cmd": "sendReport": "8"}:

    ************** MESSAGE RECEIVED ***********

    TOPIC: $aws/things/devsim4/shadow/get/accepted

    MESSAGE: {

      "state": {

        "desired": {

          "nrfcloud_mqtt_topic_prefix": "...",

          "pairing": {

            "state": "paired",

            "topics": {

              "d2c": "...",

              "c2d": "..."

            }

          },

          "config": {

            "cmd": {

              "sendReport": "8"

            }

          }

        },

        "reported": {

          "connection": {

            "status": "connected",

            "keepalive": 30

          },

          "control": {

            "alertsEn": true,

            "logLvl": 3

          },

          "config": {

            "cmd": {

              "sendReport": "7"

            }

          },

    I did nothing else, the device did not report any changes and I did not resend any commands. 

    It looks like the nRF cloud ${deviceId}/shadow/get/accepted trims the desired delta, regardless of whether the device received it (i.e. was offline) or reported (cleared) it. 

    This does not look right. Why is the config section of desired trimmed away by nRF Cloud? What is the correct way to do this?

    Thanks,
    -- Terrence

     

  • Hello,

    I'll report this to the RnD.

    I tried to simulate the device being offline just by stopping the simulator after the device had been associated. At least the device did not get update/delta but received correct info with get/accepted.

    I'll check from our testing engineers that do we have this kind of test case in our setup. I suspecting that no or there is similar wrong approach as what I used. 

  • Hello  

    You are right, the nRF Cloud trims the config from the shadow. 

    There is now a BETA feature in production for you to try out. Unfortunately I have not been able to test this yet, so only "copy and pasting" instructions for you.

    There are now new topics:

    subscribe "{deviceId}/shadow/get/accepted/tf" to receive the transformed subset of the devices full shadow (response to `.../get/tf`). See (3)

    Publish to "{deviceId}/shadow/get/tf" to request a subset of the devices full shadow, defined by a transform.

    Transformation example

    The required 't' property defines the transform string or JSON object.

    The optional 'l' property defines the payload size limit (500 bytes in this example):

    ```
    {'t':'state.desired.config', 'l': 500}
    ```

    The transformation is used in REST side and you can check https://docs.nordicsemi.com/bundle/nrf-cloud/page/APIs/REST/Tutorials/Transforms.html 

    This should enable you to get only the parts you need with the get/tf. 

  • Hi  ,

    I am finally getting back to this.

    I can confirm that {'t':'state.desired.config'} returns the pending desired changes. This allows us to receive and process these desired changes which the device previously missed because it was offline.

    However, to me this seems like a "workaround" because the behavior of shadow/get/accepted/trim appears to be inconsistent with the general semantics of the  shadow desired (and delta) functionality.  

    I would expect that pending changes in the desired:config section are present in the shadow until the device has processed them -- either by reporting the config changes back or by overwriting the desired:config section (if the device is ignoring the changes). 

    However, the fact that the desired:config section is always trimmed away -- regardless of whether the device has processed the section or not -- is counterintuitive.

    This makes device code unnecessarily complex. If the pending changes in the desired:config section would be present until the device processed them, then the handling of online and offline shadow updates could be the same. Maybe I am misunderstanding something. 

    Thanks again for all the help.

    Best,
    -- Terrence

Reply
  • Hi  ,

    I am finally getting back to this.

    I can confirm that {'t':'state.desired.config'} returns the pending desired changes. This allows us to receive and process these desired changes which the device previously missed because it was offline.

    However, to me this seems like a "workaround" because the behavior of shadow/get/accepted/trim appears to be inconsistent with the general semantics of the  shadow desired (and delta) functionality.  

    I would expect that pending changes in the desired:config section are present in the shadow until the device has processed them -- either by reporting the config changes back or by overwriting the desired:config section (if the device is ignoring the changes). 

    However, the fact that the desired:config section is always trimmed away -- regardless of whether the device has processed the section or not -- is counterintuitive.

    This makes device code unnecessarily complex. If the pending changes in the desired:config section would be present until the device processed them, then the handling of online and offline shadow updates could be the same. Maybe I am misunderstanding something. 

    Thanks again for all the help.

    Best,
    -- Terrence

Children
No Data
Related