Amazon Location Service is a mapping service that allows you to add geospatial data and location functionality to applications, including dynamic and static maps, places search and geocodes, route planning, and device tracking and geofencing capabilities.
This project demonstrates how to build the foundation for an asset tracking application that receives device positions from IoT Core and updates a map in real-time using Amazon Location Service, AWS AppSync Events, and Amazon EventBridge.
- A device sends its position to an IoT Core topic named
assets/tracker
. - An IoT Rule forwards the message to an Amazon Location Service Tracker.
- The Tracker replicates the device position to an Amazon EventBridge event bus.
- An EventBridge rule triggers a Lambda function that updates the device position in an AppSync Events API.
- Frontend applications subscribed to the AppSync Events API receive the device position in real-time and display it on a map, also provided by Amazon Location Service.
For this project, you should have the following prerequisites:
- An AWS account with access to create and edit resources in the architecture
- Node.js (
>=v20.x
) andnpm
(>=v10.x
) installed on your machine
To get started, clone this repository and install the dependencies by running npm ci
in the root folder:
git clone git@github.com:aws-samples/amazon-location-service-iot-asset-tracking.git
cd amazon-location-service-iot-asset-tracking
npm ci
Next, create the backend resources by deploying the CloudFormation template named InfraStack.template.yaml
found in the assets
folder.
If you have the AWS CLI installed, you can run the following command to deploy the CloudFormation stack:
aws cloudformation create-stack --template-body file://assets/InfraStack.template.yaml --stack-name IoTTracker --capabilities CAPABILITY_IAM
Once the CloudFormation stack is created, you can get the values for the frontend application by running the following command:
aws cloudformation describe-stacks \
--stack-name IoTTracker \
--query 'Stacks[0].Outputs[*].[OutputKey,OutputValue]' \
--output table
This command will return an output similar to the following:
----------------------------------------------------------------------------------------------------------
| DescribeStacks |
+----------------------------+---------------------------------------------------------------------------+
| TrackerName | trackerAsset01 |
| WebsocketHttpEndpoint | gasctn76kraldllcpmqms44pyu.appsync-api.eu-west-1.amazonaws.com |
| Region | eu-west-1 |
| IdentityPoolId | eu-west-1:c83e6705-42d8-4de4-a371-6e3974c9c2b7 |
| WebsocketRealtimeEndpoint | gasctn76kraldllcpmqms44pyu.appsync-realtime-api.eu-west-1.amazonaws.com |
+----------------------------+---------------------------------------------------------------------------+
If instead you prefer to work using the AWS Console, you can follow the Deploying the CloudFormation template page for instructions.
Once the CloudFormation stack is created, head to the "Outputs" tab to get the values for the frontend application.
Next, you need to configure the frontend application so it can connect to the backend resources.
To do so, create a config.ts
file in the src
folder with the following content:
Note
You can also use the file at src/config.example.ts
file as a template and rename it to config.ts
. Make sure to replace the values with the ones from the CloudFormation stack outputs.
const config = {
/**
* The AWS region where the resources are located
*
* @example 'eu-west-1'
*/
region: '',
/**
* The Cognito Identity Pool ID
*
* @example 'eu-west-1:12345678-abcd-2345-efgh-6e3974c9c2b7'
*/
identityPoolId: '',
/**
* The Amazon Location Service v2 map style name
*
* @example 'Standard', 'Monochrome', 'Hybrid', or 'Satellite'
* @default 'Standard'
*/
mapStyleName: 'Standard',
/**
* The AppSync Events API HTTP endpoint
*
* @example 'abcdef12ghilmnopqrstu34vwyz.appsync-api.eu-west-1.amazonaws.com'
*/
httpDomain: '',
/**
* The AppSync Events API Realtime endpoint
*
* @example 'abcdef12ghilmnopqrstu34vwyz.appsync-realtime-api.eu-west-1.amazonaws.com'
*/
realtimeDomain: '',
/**
* The AppSync Events API channel
*
* @example 'asset-tracker/thing123'
* @default 'asset-tracker/thing123'
*/
channel: 'asset-tracker/thing123',
};
export default config;
Next, you can start the frontend application by running the following command:
npm start
To see the device position in the map, you need to send a position to the IoT Core topic assets/tracker
. You can do this using the AWS CLI or the IoT Core AWS Console.
If you have the AWS CLI installed, you can send a position to the map by running the following command:
aws iot-data publish --topic assets/tracker \
--payload '{"deviceId": "thing123", "latitude": "49.282301", "longitude": "-123.118408" }' \
--cli-binary-format raw-in-base64-out
Note
The commands don't return any output, but you should see the device position in the map after sending the message.
and then send another position:
aws iot-data publish --topic assets/tracker \
--payload '{"deviceId": "thing123", "latitude": "49.292301", "longitude": "-123.119408" }' \
--cli-binary-format raw-in-base64-out
To send a position to the map, log in into the IoT Core AWS Console and publish a message to the assets/tracker
topic:
{
"deviceId": "thing123",
"latitude": "49.282301",
"longitude": "-123.118408"
}
and then send another position:
{
"deviceId": "thing123",
"latitude": "49.292301",
"longitude": "-123.119408"
}
Whenever you send a position to IoT Core, the position update should be sent to the Amazon Location Service Tracker and then to Amazon EventBridge.
The EventBridge rule will trigger a Lambda function that will update the device position in AppSync Events API, which the frontend application is subscribed to.
To clean up the resources created in this project, you can delete the CloudFormation stack by running the following command:
aws cloudformation delete-stack --stack-name IoTTracker
AWS AppSync Events lets you create secure and performant serverless WebSocket APIs that can broadcast real-time event data to millions of subscribers, without you having to manage connections or resource scaling.
In this sample application we have a single namespace called asset-tracker
and shared by all devices, with each one having its own channel. This allows you to broadcast messages to all devices or to a specific device. The channel name is composed of the namespace and the device ID, separated by a slash, like asset-tracker/thing123
.
When a device sends a position to the IoT Core topic, it includes the device ID in the message. This device ID is used both for the Amazon Location Service Tracker and the AppSync Events API channel.
The frontend application subscribes to the AppSync Events API using the device ID as the channel name. This way, the application receives only the messages for the devices it is interested in.
The WebSocket API is secured using IAM. The Lambda function that pushes updates to the AppSync Events API has an IAM role that allows it to publish messages to the WebSocket API. Requests to the WebSocket API are signed using SigV4 (see code).
Likewise, the frontend application obtains temporary credentials using the Cognito Identity Pool ID and uses these credentials to sign requests to connect and subscribe to the WebSocket API (see code).
For a more turnkey solution, you can use the AWS Amplify library to connect to the AppSync Events API in a few lines of code.
This library is licensed under the MIT-0 License. See the LICENSE file.