Front-End Web & Mobile

Monitoring IoT devices in real time with AWS AppSync

This article was written by David Moser, Senior Solutions Architect, AWS

IoT devices can generate a tremendous amount of data. Analytics in the cloud and at the edge turn this data into information. Ultimately, this information must be delivered to users. Often, the use case demands real-time access to the information as it changes.

Consider a technician monitoring a power plant. It is not optimal if they are required to continually refresh an application’s screen to see if the status of their equipment has changed. It is a much more powerful user experience for the app’s screen to receive changing data in real time and present it to the user.

In this post, I walk you through how to accomplish real-time IoT data monitoring in a mobile app. For this use case, I use a temperature sensor. As the sensor emits changing temperature values, the latest value is reflected in real time in the app.

The architecture

The solution uses the following architecture:

  1. The sensor uses the AWS IoT Device SDK to connect to AWS IoT Core in the cloud and publish temperature messages.
  2. AWS IoT Core receives the messages and forwards them to an AWS Lambda function.
  3. The Lambda function executes an AWS AppSync GraphQL mutation that updates Amazon DynamoDB and broadcasts the changed data to mobile application subscribers.
  4. The mobile application receives the data and updates the screen values in real time.

The full source code and instructions for deploying the solution can be found at the end of the post. In this post, I highlight key components of the code that make it work. All of the code examples are in JavaScript.

The sensor

The sensor connects to the cloud using the AWS IoT Device SDK. When authenticated through client certificates, the device publishes messages to the MQTT broker managed by AWS IoT Core. Messages are published in JSON format to a specific topic.

In this example, the message contains the following elements:

  • Type of the sensor as Temperature
  • Current temperature reading of 79.00
  • Current timestamp (in Epoch format)

The data is posted to the topic sensor-view/sensor-01/sensor-update. The topic specifies the name of the app, sensor-view, the ID of the sensor, sensor-01, and the context of the data, sensor-update.

const topic = "sensor-view/sensor-01/sensor-update";
 
var message = {
    sensorType: 'Temperature'
    value: 79.00,
    timestamp: 1571782204837
}
 
sensor.publish(topic, message);

AWS IoT Core

After the data is transmitted to AWS IoT Core, it can be processed by an AWS IoT rule. AWS IoT rules allow you to subscribe to a specific topic and act on the published data. There are numerous actions available, ranging from storing the data in an Amazon S3 bucket to posting the message to an Amazon SNS topic. In this case, the rule forwards the sensor data to a Lambda function.

AWS IoT rules consist of a query statement and an action. The query statement allows you to select data from a topic using a SQL syntax:

select * as data, topic(2) as id from 'sensor-view/+/sensor-update'

In this case, you perform the following steps:

  • Select the entire sensor message with select *.
  • Name the message content as data.
  • Select the second element of the topic /+/ to identify the ID of the sensor and naming the value id.

The + is a wildcard symbol and contains the ID of the sensor that published the message. The rule is designed to process messages from any sensor that publishes to this topic structure.

The result is a JSON message to be transmitted to the Lambda function:

{
    id: "sensor-01",
    data: {
	    sensorType: "Temperature",
        value: 79.00,
        timestamp: 1571782204837
    }
}

The second part of the AWS IoT rule is the action. In this case, you send the data to a Lambda function named sensor-view-update-sensor. The following screenshot shows what this rule looks like in the AWS IoT Core console:

Lambda function

Upon receiving the event message from the AWS IoT rule, the Lambda function initiates a connection to the app’s AWS AppSync GraphQL API endpoint. Lambda functions authenticate to the API by means of an IAM role applied to the function.

const client = new appsync.AWSAppSyncClient({
     url: "https://your-endpoint-id.appsync-api.us-east-1.amazonaws.com/graphql",
     region: "us-east-1",
     auth: {
          type: 'AWS_IAM',
          credentials: credentials
     },
     
     disableOffline: true
 });

Next, the function defines the AWS AppSync GraphQL update mutation. A mutation is an action that creates, updates, or deletes data. In a RESTful API, this would be analogous to POST, PUT, and DELETE operations.

const mutation = gql`mutation UpdateSensor($input: UpdateSensorInput!) {
     updateSensor(input: $input) {
          id
          sensorType
          value
          timestamp
     }
}`;

Finally, the Lambda function uses the AWS AppSync client to execute the mutation, passing in the Lambda event properties as parameters.

await client.mutate({
  mutation,
  variables: {input: {
      id: event.id,
      sensorType: event.data.sensorType,
      value: event.data.value,
      timestamp: event.data.timestamp
  }}
}); 

When the AWS AppSync API receives the request, it performs two actions:

  1. Updates the sensor’s record in a DynamoDB table with the timestamp and temperature value transmitted by the sensor.
  2. Broadcasts the updated data to all clients subscribed to that sensor’s updates.

Mobile app

The mobile app is written in React Native. A screen in the app connects to the AWS AppSync GraphQL endpoint and initiates a subscription. Just as the Lambda function publishes data to the API, the mobile app subscribes to the API for changes and receives them in real time.

First, the mobile app defines the onUpdateSensor subscription requesting the ID, sensorType, value, and timestamp of every update. The (id: $id) parameter of the subscription allows you to subscribe to the changes for the specific sensor ID.

const onUpdateSensor = `subscription OnUpdateSensor($id: ID!) {
  onUpdateSensor(id: $id) {
    id
    sensorType
    value
    timestamp
  }
}`;

Next, the app initiates the subscription. Whenever new values are received, they are stored in the screen’s React state object.

//subscribe to changes in sensor value and update the state object

API.graphql(
  graphqlOperation(subscriptions.onUpdateSensor, {id: 'sensor-01'})
).subscribe({
    next: eventData => {          
        //update the screen's state variables
        this.setState({
            id: eventData.value.data.onUpdateSensor.id,
            sensorType: eventData.value.data.onUpdateSensor.sensorType,
            value: eventData.value.data.onUpdateSensor.value,
            timestamp: eventData.value.data.onUpdateSensor.timestamp,
        })
    }
});

}

Finally, the app’s screen is designed to display the changing values as the screen’s state object is updated. The values of the screen widget automatically update as subscription data is received.

<View>
  <View>
    <Text h1>{this.state.sensorType}</Text>
  </View>
  <View>
    <GaugeWidget value={this.state.value} />
  </View>
  <View>
    <Text h2>{ this.state.value }</Text>
  </View>
  <View>
      <Text h3>{ new Date(this.state.timestamp).toLocaleTimeString() }</Text>
  </View>
</View>

This results in a screen that looks like the following illustration:

Conclusion

IoT solutions often demand real-time access to changing information. Using building blocks from AWS, you can develop end-to-end solutions to acquire, process, and deliver this data to your front-end applications.

This post focused on key areas of the solution’s code. You can download and deploy the full solution from this GitHub repository. It also walks you through using AWS Amplify to deploy the entire solution to your AWS account and mobile device.