AWS Shadows Up and Downlink

Hello everyone

I’m quite new to TTN and AWS and have been struggling with the following issues for weeks now.
I try to be as specific as I can so excuse me if the post gets a bit long.

I’m using the Things Stack Community Edition and am recieving data from my edge device.

So far so good. Data comes in to The Things Stack and I can format the payload:

function decodeUplink(input) {
  var reserved = input.bytes[0];
  var parkState = input.bytes[1];
  return {
    data: {
      parkState: parkState,
      state: {
        reserved: reserved

Decoded payload looks good to me:

"decoded_payload": {
    "parkState": 0,
    "state": {
      "reserved": 0

Switching to AWS. I subscribe (with MQTT Test Client) to the lorawan/+/uplink topic to test if the data is recieved.
No problem JSON data is coming in as already seen in TTS.

I also want to send data down to the edge device, for this I have a payload encoder in TTS.

function encodeDownlink(input) {
  return {
    bytes: []

Works like a charm if I publish it in AWS (with MQTT Test Client) with the following message payload to the lorawan/downlink topic (LED on edge device changes state as planned):

  "thingName": "myproject_mything",
  "payload": {
    "reserved": 0

I want to combine these capabilities using the device Shadow in AWS.
I have no idea how I can edit the shadow so that my uplink message gets written in to the shadow.

On the contriary I want to be able to change the desired state to trigger a downlink message to change the “reserved” value 0 or 1.
The shadow doesn’t contain my payload, and a lot of uneccesary data I don’t need.

lorawan shadow on the thing in AWS IoT Core:

  "reported": {
    "activated": "2021-04-27T15:28:44.976761797Z",
    "lastSeen": "2021-04-27T19:58:58.851973028Z",
    "gateways": 1,
    "sessionID": "asdfasdfasdf==123123",
    "fCntUp": 1534,
    "fCntDown": 0,
    "devAddr": "XXXXXX",
    "rssi": -109,
    "snr": 2.5

But I want something like this:

  "desired": {
    "reserved": 0
  "reported": {
    "reserved": 0

Maybe this is simple for someone of you guys but I’ve done a lot of research and experiments and haven’t found a solution. Maybe I’m looking to far. If you need more information I’m happy to provide you with that.

@lukasmerz I’ve noticed this as well. Migrating to TTN V3 has apparently removed the functionality from V2 that allowed us to parse packet data and add it to the shadow, providing only some basic metrics instead. I’m actively working on a workaround and will post whatever I find. It may be necessary to write a custom AWS Lambda function to parse the data and update the shadow state, but I’m hoping that it’s just a matter of finding the right (undocumented?) key name to which we can assign a new JSON object that will be persisted in the AWS Shadow State, like it was in V2.

Here’s a basic framework of an AWS Lambda function to update the Shadow State with data (without obliterating the metadata placed there by the default integration). This is tested and working as of July 22, 2021, but it’s possible future updates to the API or the integration might require it to be modified. If so, please contribute what you learn.

Create an AWS IoT Rule with this select statement (customize as desired):

SELECT * FROM 'lorawan/+/uplink'

Use this rule to trigger the following Python Lambda function. Important: Assign a role to the Lambda function and allow CloudWatchLogsFullAccess, AwsIoTEventsFullAccess, and AWSIoTDataAccess in addition to the standard Lambda permissions role.
NOTE: I named my AWS integration “ttn-v3-integration” when I created it in the AWS console. Update the IntegrationName variable to match yours. I believe there is a way to get this value programmatically, but for now I’ve hard-coded it. Also verify that the shadowName is actually ‘lorawan’ in your AWS dashboard, or update it below to match. Near the end of the code, the version number value is removed to avoid a conflict when attempting to update the shadow state.

import json
import boto3
client = boto3.client('iot-data')

def lambda_handler(event, context):
    IntegrationName = 'ttn-v3-integration'
    DeviceId = event['end_device_ids']['device_id']
    ShadowDocumentPath = IntegrationName + '_' + DeviceId
    ShadowState = event['uplink_message']['decoded_payload']['shadowstate']
    response = client.get_thing_shadow(thingName=ShadowDocumentPath, shadowName='lorawan')
    shadowvalue = response['payload'].read()
    shadowjson = json.loads(shadowvalue)
    combinedstate = shadowjson['state']['reported']
    shadowjson.pop('version', None)
    client.update_thing_shadow(thingName=ShadowDocumentPath, shadowName='lorawan', payload=json.dumps(shadowjson))

Use AWS LogWatch to monitor the results while testing with actual uplinks. All the print statements will output data into LogWatch. Once it’s running for you, you can comment them out to reduce log activity. This code should be pretty easy to modify to handle downlinks as well.