Note
This guide makes use of "gated" features of AWS IoT FleetWise for which you will need to request access. See here for more information, or contact the AWS Support Center.
This guide demonstrates how to use AWS IoT FleetWise to implement a CAN command dispatcher for use
with the remote commands feature, along with the
Network agnostic actuator commands (NADC) feature. The NADC
feature allows the actuators to be identified by the full-qualified-name (FQN) at the edge. The demo
uses application-specific request and response CAN payload formats to forward the command request to
CAN, including the 'command ID', 'issued timestamp' and 'execution timeout' parameters as well as
the requested actuator value. A Python script called can_command_server.py
is used to simulate
another vehicle in the network that receives the request and responds on CAN with a response
message. The Reference Implementation for AWS IoT FleetWise (FWE) receives this response, and sends
it back to the cloud.
The format of the CAN request and response messages implemented in
CanCommandDispatcher
is as follows:
- The command CAN request payload is formed from the null-terminated command ID string, a
uint64_t
issued timestamp in ms since epoch, auint64_t
relative execution timeout in ms since the issued timestamp, and one actuator argument serialized in network byte order. A relative timeout value of zero means no timeout. Example with command ID"01J3N9DAVV10AA83PZJX561HPS"
, issued timestamp of1723134069000
, relative timeout of1000
, and actuator datatypeint32_t
with value1234567890
:
|----------------------------------------|----------------------------------------|---------------------------------|---------------------------------|---------------------------|
| Payload byte: | 0 | 1 | ... | 24 | 25 | 26 | 27 | 28 | ... | 33 | 34 | 35 | 36 | ... | 41 | 42 | 43 | 44 | 45 | 46 |
|----------------------------------------|----------------------------------------|---------------------------------|---------------------------------|---------------------------|
| Value: | 0x30 | 0x31 | ... | 0x50 | 0x53 | 0x00 | 0x00 | 0x00 | ... | 0x49 | 0x08 | 0x00 | 0x00 | ... | 0x03 | 0xE8 | 0x49 | 0x96 | 0x02 | 0xD2 |
|----------------------------------------|----------------------------------------|---------------------------------|---------------------------------|---------------------------|
Command ID (null terminated string)-------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Issued timestamp (uint64_t network byte order)-------------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Execution timeout (uint64_t network byte order)----------------------------------------------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Request argument (int32_t network byte order)----------------------------------------------------------------------------------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^
- The command CAN response payload is formed from the null-terminated command ID string, a 1-byte
command status code, a 4-byte
uint32_t
reason code, and a null-terminated reason description string. The values of the status code correspond with the enumCommandStatus
Example with command ID"01J3N9DAVV10AA83PZJX561HPS"
, response statusCommandStatus::EXECUTION_FAILED
, reason code0x0001ABCD
, and reason description"hello"
:
|----------------------------------------|----------------------------------------|------|---------------------------|-----------------------------------------|
| Payload byte: | 0 | 1 | ... | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
|----------------------------------------|----------------------------------------|------|---------------------------|-----------------------------------------|
| Value: | 0x30 | 0x31 | ... | 0x50 | 0x53 | 0x00 | 0x03 | 0x00 | 0x01 | 0xAB | 0xCD | 0x68 | 0x65 | 0x6C | 0x6C | 0x6F | 0x00 |
|----------------------------------------|----------------------------------------|------|---------------------------|-----------------------------------------|
Command ID (null terminated string)-------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Command status (enum CommandStatus)------------------------------------------------^^^^^^
Reason code (uint32_t network byte order)-------------------------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^
Reason description (null terminated string)---------------------------------------------------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The table EXAMPLE_CAN_INTERFACE_SUPPORTED_ACTUATOR_MAP
in
IoTFleetWiseEngine
defines the mapping between the FQN of the
actuator signal and the CAN request and response message IDs, along with the expected datatype of
the signal. (Note: when the most-significant bit of the CAN message ID is set, it denotes a 29-bit
extended CAN ID.):
static const std::unordered_map<std::string, CanCommandDispatcher::CommandConfig>
EXAMPLE_CAN_INTERFACE_SUPPORTED_ACTUATOR_MAP = {
{ "Vehicle.actuator6", { 0x00000123, 0x00000456, SignalType::INT32 } },
{ "Vehicle.actuator7", { 0x80000789, 0x80000ABC, SignalType::DOUBLE } },
};
These IDs and types can also be found in the configuration table at the top of
can_command_server.py
- Access to an AWS Account with administrator privileges.
- Your AWS account has access to AWS IoT FleetWise "gated" features. See here for more information, or contact the AWS Support Center.
- Logged in to the AWS Console in the
us-east-1
region using the account with administrator privileges.- Note: if you would like to use a different region you will need to change
us-east-1
to your desired region in each place that it is mentioned below. - Note: AWS IoT FleetWise is currently available in these regions.
- Note: if you would like to use a different region you will need to change
- A local Windows, Linux or MacOS machine.
An Ubuntu 20.04 development machine with 200GB free disk space will be required. A local Intel x86_64 (amd64) machine can be used, however it is recommended to use the following instructions to launch an AWS EC2 Graviton (arm64) instance. Pricing for EC2 can be found, here.
-
Launch an EC2 Graviton instance with administrator permissions: Launch CloudFormation Template.
-
Enter the Name of an existing SSH key pair in your account from here.
- Do not include the file suffix
.pem
. - If you do not have an SSH key pair, you will need to create one and download the corresponding
.pem
file. Be sure to update the file permissions:chmod 400 <PATH_TO_PEM>
- Do not include the file suffix
-
Select the checkbox next to 'I acknowledge that AWS CloudFormation might create IAM resources with custom names.'
-
Choose Create stack.
-
Wait until the status of the Stack is CREATE_COMPLETE; this can take up to five minutes.
-
Select the Outputs tab, copy the EC2 IP address, and connect via SSH from your local machine to the development machine.
ssh -i <PATH_TO_PEM> ubuntu@<EC2_IP_ADDRESS>
-
Run the following on the development machine to clone the latest FWE source code from GitHub.
git clone https://github.com/aws/aws-iot-fleetwise-edge.git ~/aws-iot-fleetwise-edge
To quickly run the demo, download the pre-built FWE binary, and install CAN support:
-
If your development machine is ARM64 (the default if you launched an EC2 instance using the CloudFormation template above):
cd ~/aws-iot-fleetwise-edge \ && mkdir -p build \ && curl -L -o build/aws-iot-fleetwise-edge.tar.gz \ https://github.com/aws/aws-iot-fleetwise-edge/releases/latest/download/aws-iot-fleetwise-edge-arm64.tar.gz \ && tar -zxf build/aws-iot-fleetwise-edge.tar.gz -C build aws-iot-fleetwise-edge \ && sudo -H ./tools/install-socketcan.sh
-
If your development machine is x86_64:
cd ~/aws-iot-fleetwise-edge \ && mkdir -p build \ && curl -L -o build/aws-iot-fleetwise-edge.tar.gz \ https://github.com/aws/aws-iot-fleetwise-edge/releases/latest/download/aws-iot-fleetwise-edge-amd64.tar.gz \ && tar -zxf build/aws-iot-fleetwise-edge.tar.gz -C build aws-iot-fleetwise-edge \ && sudo -H ./tools/install-socketcan.sh
Alternatively if you would like to build the FWE binary from source, follow these instructions. If you already downloaded the binary above, skip to the next section.
-
Install the dependencies for FWE and the CAN simulator:
cd ~/aws-iot-fleetwise-edge \ && sudo -H ./tools/install-deps-native.sh \ && sudo -H ./tools/install-socketcan.sh \ && sudo ldconfig
-
Compile FWE with remote commands support:
./tools/build-fwe-native.sh --with-remote-commands-support
A simulator is used to model another node in the vehicle network that responds to CAN command requests.
-
Start the CAN command server:
cd tools/cansim \ && python3 can_command_server.py --interface vcan0
-
Open a new terminal on the development machine, and run the following to provision credentials for the vehicle and configure the network interface for CAN commands:
cd ~/aws-iot-fleetwise-edge \ && mkdir -p build_config \ && ./tools/provision.sh \ --region us-east-1 \ --vehicle-name fwdemo-can-actuators \ --certificate-pem-outfile build_config/certificate.pem \ --private-key-outfile build_config/private-key.key \ --endpoint-url-outfile build_config/endpoint.txt \ --vehicle-name-outfile build_config/vehicle-name.txt \ && ./tools/configure-fwe.sh \ --input-config-file configuration/static-config.json \ --output-config-file build_config/config-0.json \ --log-color Yes \ --log-level Trace \ --vehicle-name `cat build_config/vehicle-name.txt` \ --endpoint-url `cat build_config/endpoint.txt` \ --certificate-file `realpath build_config/certificate.pem` \ --private-key-file `realpath build_config/private-key.key` \ --persistency-path `realpath build_config` \ --session-expiry-interval-seconds 3600 \ --can-command-interface vcan0
-
Run FWE:
./build/aws-iot-fleetwise-edge build_config/config-0.json
The instructions below will register your AWS account for AWS IoT FleetWise, create a demonstration vehicle model, register the virtual vehicle created in the previous section.
-
Open a new terminal on the development machine and run the following to install the dependencies of the demo script:
cd ~/aws-iot-fleetwise-edge/tools/cloud \ && sudo -H ./install-deps.sh
-
Run the demo script:
./demo.sh \ --region us-east-1 \ --vehicle-name fwdemo-can-actuators \ --node-file custom-nodes-can-actuators.json \ --decoder-file custom-decoders-can-actuators.json \ --network-interface-file network-interface-custom-can-actuators.json
The demo script:
- Registers your AWS account with AWS IoT FleetWise, if not already registered.
- Creates a signal catalog, containing
custom-nodes-can-actuators.json
which includes the CAN actuator signals. - Creates a model manifest that references the signal catalog with all of the signals.
- Activates the model manifest.
- Creates a decoder manifest linked to the model manifest using
custom-decoders-can-actuators.json
for decoding the CAN signals from the network interfacenetwork-interface-custom-can-actuators.json
. - Updates the decoder manifest to set the status as
ACTIVE
. - Creates a vehicle with a name equal to
fwdemo-can-actuators
, the same as the name passed toprovision.sh
. - Creates a fleet.
- Associates the vehicle with the fleet.
The following steps will send a CAN command via the AWS IoT FleetWise 'remote commands' feature.
-
Run the following command on the development machine to create an IAM role to generate the command payload:
SERVICE_ROLE_ARN=`./manage-service-role.sh \ --service-role IoTCreateCommandPayloadServiceRole \ --service-principal iot.amazonaws.com \ --actions iotfleetwise:GenerateCommandPayload \ --resources '*'`
-
Next create a remote command to send the CAN command with message ID
0x123
and expect a response on CAN message ID0x456
. This CAN command is mapped via the decoder manifest to the 'actuator' nodeVehicle.actuator6
in the signal catalog.aws iot create-command --command-id actuator6-command --namespace "AWS-IoTFleetWise" \ --region us-east-1 \ --role-arn ${SERVICE_ROLE_ARN} \ --mandatory-parameters '[{ "name": "$actuatorPath.Vehicle.actuator6", "defaultValue": { "S": "0" } }]'
-
Run the following command to start the execution of the command defined above with the value to set for the actuator.
JOBS_ENDPOINT_URL=`aws iot describe-endpoint --region us-east-1 --endpoint-type iot:Jobs | jq -j .endpointAddress` \ && ACCOUNT_ID=`aws sts get-caller-identity | jq -r .Account` \ && COMMAND_EXECUTION_ID=`aws iot-jobs-data start-command-execution \ --region us-east-1 \ --command-arn arn:aws:iot:us-east-1:${ACCOUNT_ID}:command/actuator6-command \ --target-arn arn:aws:iot:us-east-1:${ACCOUNT_ID}:thing/fwdemo-can-actuators \ --parameters '{ "$actuatorPath.Vehicle.actuator6": { "S": "10" } }' \ --endpoint-url https://${JOBS_ENDPOINT_URL} | jq -r .executionId` \ && echo "Command execution id: ${COMMAND_EXECUTION_ID}"
-
Run the following command to get the command execution status.
aws iot get-command-execution \ --region us-east-1 \ --target-arn arn:aws:iot:us-east-1:${ACCOUNT_ID}:thing/fwdemo-can-actuators \ --execution-id ${COMMAND_EXECUTION_ID}
-
You should see the following output indicating the command was successfully executed. Note that the
reasonCode
(uint32) andreasonDescription
(string) are extensible result information fields. Refer to ICommandDispatcher.h for the reason codes defined by FWE. The OEM range of reason codes begins at 65536. In this example implementation thereasonCode
is set to0x1234
(4660), andreasonDescription
is set to"hello"
by the CAN command servercan_command_server.py
.{ "executionId": "<COMMAND_EXECUTION_ID>", "commandArn": "arn:aws:iot:us-east-1:<ACCOUNT_ID>:command/actuator6-command", "targetArn": "arn:aws:iot:us-east-1:<ACCOUNT_ID>:thing/fwdemo-can-actuators", "status": "SUCCEEDED", "statusReason": { "reasonCode": "4660", "reasonDescription": "hello" }, "parameters": { "$actuatorPath.Vehicle.actuator6": { "S": "10" } }, "executionTimeoutSeconds": 10, "createdAt": "<CREATION_TIME>", "lastUpdatedAt": "<LAST_UPDATE_TIME>", "completedAt": "<COMPLETED_TIME>" }
In the FWE log you should see the following indicating that the command was successfully executed:
[TRACE] [ActuatorCommandManager.cpp:125] [processCommandRequest()]: [Processing Command Request with ID: <COMMAND_EXECUTION_ID>] [INFO ] [CanCommandDispatcher.cpp:365] [setActuatorValue()]: [Sending request for actuator Vehicle.actuator6 and command id <COMMAND_EXECUTION_ID>] [INFO ] [CanCommandDispatcher.cpp:390] [operator()()]: [Request sent for actuator Vehicle.actuator6 and command id <COMMAND_EXECUTION_ID>] [INFO ] [CanCommandDispatcher.cpp:209] [handleCanFrameReception()]: [Received response for actuator Vehicle.actuator6 with command id <COMMAND_EXECUTION_ID>, status SUCCEEDED, reason code 4660, reason description hello]
It is possible for commands to take an extended time to complete. In this case the vehicle can
report the command status as IN_PROGRESS
to indicate that the command has been received and is
being run, before the final status of SUCCEEDED
etc. is reported.
In the example CAN commands provided, Vehicle.actuator7
is configured as such a "long-running
command". After running of this command is started the CAN command server will periodically notify
FWE that the status is CommandStatus::IN_PROGRESS
. The intermediate status is sent to the cloud
and can also be obtained by calling the aws iot get-command-execution
API.
-
Run the following to create the long-running command:
aws iot create-command --command-id actuator7-command --namespace "AWS-IoTFleetWise" \ --region us-east-1 \ --role-arn ${SERVICE_ROLE_ARN} \ --mandatory-parameters '[{ "name": "$actuatorPath.Vehicle.actuator7", "defaultValue": { "S": "0" } }]'
-
Then start the command:
COMMAND_EXECUTION_ID=`aws iot-jobs-data start-command-execution \ --region us-east-1 \ --command-arn arn:aws:iot:us-east-1:${ACCOUNT_ID}:command/actuator7-command \ --target-arn arn:aws:iot:us-east-1:${ACCOUNT_ID}:thing/fwdemo-can-actuators \ --parameters '{ "$actuatorPath.Vehicle.actuator7": { "S": "10" } }' \ --endpoint-url https://${JOBS_ENDPOINT_URL} \ --execution-timeout 20 | jq -r .executionId` \ && echo "Command execution id: ${COMMAND_EXECUTION_ID}"
-
Now repeatedly run this command to get the command status:
aws iot get-command-execution \ --region us-east-1 \ --target-arn arn:aws:iot:us-east-1:${ACCOUNT_ID}:thing/fwdemo-can-actuators \ --execution-id ${COMMAND_EXECUTION_ID}
The command takes 10 seconds to complete. In this time you will see that the status is
IN_PROGRESS
. After the command completes the status changes toSUCCEEDED
.
It is possible for commands to be executed concurrently, even for the same actuator. Each execution
is uniquely identified by the execution ID. In the following example, 3 executions of the
Vehicle.actuator7
command are started spaced by 1 second. Since each execution takes 10 seconds to
complete, all 3 will run in parallel.
-
Run the following to begin 3 executions of the
Vehicle.actuator7
command:COMMAND_EXECUTION_IDS=() \ && for ((i=0; i<3; i++)); do if ((i>0)); then sleep 1; fi COMMAND_EXECUTION_ID=`aws iot-jobs-data start-command-execution \ --region us-east-1 \ --command-arn arn:aws:iot:us-east-1:${ACCOUNT_ID}:command/actuator7-command \ --target-arn arn:aws:iot:us-east-1:${ACCOUNT_ID}:thing/fwdemo-can-actuators \ --parameters '{ "$actuatorPath.Vehicle.actuator7": { "S": "10" } }' \ --endpoint-url https://${JOBS_ENDPOINT_URL} \ --execution-timeout 20 | jq -r .executionId` echo "Command execution ${i} id: ${COMMAND_EXECUTION_ID}" COMMAND_EXECUTION_IDS+=("${COMMAND_EXECUTION_ID}") done
-
Now repeatedly run the following to get the status of the 3 commands as they run in parallel:
for ((i=0; i<3; i++)); do echo "---------------------------" echo "Command execution ${i} status:" aws iot get-command-execution \ --region us-east-1 \ --target-arn arn:aws:iot:us-east-1:${ACCOUNT_ID}:thing/fwdemo-can-actuators \ --execution-id ${COMMAND_EXECUTION_IDS[i]} done
It is possible for a command execution to be started while FWE is offline, then FWE will begin execution of the command when it comes online so long as the following prerequisites are met:
- FWE has successfully connected via MQTT at least once, with persistent session enabled and the
MQTT session timeout has not elapsed. To enable persistent session, set
.staticConfig.mqttConnection.sessionExpiryIntervalSeconds
in the config file or--session-expiry-interval-seconds
when runningconfigure-fwe.sh
to a non-zero value sufficiently large. - Persistency is enabled for FWE (so that the decoder manifest is available immediately when FWE starts).
- The command timeout has not been exceeded.
- The responding CAN actuator is available when FWE is started (in this case the
can_command_server.py
tool).
The following steps demonstrate offline commands:
-
Switch to the terminal running FWE, and stop it using
CTRL-C
. -
Switch to the terminal used to run the AWS CLI commands, and start execution of a command with a 30 second timeout:
COMMAND_EXECUTION_ID=`aws iot-jobs-data start-command-execution \ --region us-east-1 \ --command-arn arn:aws:iot:us-east-1:${ACCOUNT_ID}:command/actuator6-command \ --target-arn arn:aws:iot:us-east-1:${ACCOUNT_ID}:thing/fwdemo-can-actuators \ --parameters '{ "$actuatorPath.Vehicle.actuator6": { "S": "456" } }' \ --endpoint-url https://${JOBS_ENDPOINT_URL} \ --execution-timeout 30 | jq -r .executionId` \ && echo "Command execution id: ${COMMAND_EXECUTION_ID}"
-
Get the current status of the command, which will remain as
CREATED
since FWE is not running:aws iot get-command-execution \ --region us-east-1 \ --target-arn arn:aws:iot:us-east-1:${ACCOUNT_ID}:thing/fwdemo-can-actuators \ --execution-id ${COMMAND_EXECUTION_ID}
-
Switch to the FWE terminal, and restart it by running:
./build/aws-iot-fleetwise-edge build_config/config-0.json
-
Switch to the AWS CLI terminal, and run the following to get the new status of the command, which should be
SUCCEEDED
. Since FWE rejoined an existing MQTT session and the command was published with QoS 1 (at least once), the MQTT broker sends the command to FWE as soon as it connects to the cloud. FWE is able to execute the command, since it has not timed out, the decoder manifest is available (as persistency for FWE is enabled), and the CAN command server is available.aws iot get-command-execution \ --region us-east-1 \ --target-arn arn:aws:iot:us-east-1:${ACCOUNT_ID}:thing/fwdemo-can-actuators \ --execution-id ${COMMAND_EXECUTION_ID}
-
Repeat the above, but this time wait longer than 30s before restarting FWE. In this case FWE will still receive the command request from cloud, but since the timeout has expired it will not be executed and the returned status will be
TIMED_OUT
.
-
Run the following on the development machine to clean up resources created by the
provision.sh
anddemo.sh
scripts.cd ~/aws-iot-fleetwise-edge/tools/cloud \ && ./clean-up.sh \ && ../provision.sh \ --vehicle-name fwdemo-can-actuators \ --region us-east-1 \ --only-clean-up \ && ./manage-service-role.sh \ --service-role IoTCreateCommandPayloadServiceRole \ --clean-up
-
Delete the CloudFormation stack for your development machine, which by default is called
fwdev
: https://us-east-1.console.aws.amazon.com/cloudformation/home