IoT Amazon MQTT Client

What Is AWS IoT?

AWS IoT provides secure, bi-directional communication between Internet-connected devices such as sensors, actuators, embedded micro-controllers, or smart appliances and the AWS Cloud. This enables you to collect telemetry data from multiple devices, and store and analyze the data. You can also create applications that enable your users to control these devices from their phones or tablets.

 

Message broker

Provides a secure mechanism for devices and AWS IoT applications to publish and receive messages from each other. You can use either the MQTT protocol directly or MQTT over WebSocket to publish and subscribe.

 

The AWS IoT message broker is a publish/subscribe broker service that enables the sending and receiving of messages to and from AWS IoT. When communicating with AWS IoT, a client sends a message addressed to a topic like Sensor/temp/room1.

 

The message broker, in turn, sends the message to all clients that have registered to receive messages for that topic. The act of sending the message is referred to as publishing. The act of registering to receive messages for a topic filter is referred to as subscribing.

 

The topic namespace is isolated for each AWS account and region pair. For example, the Sensor/temp/room1 topic for an AWS account is independent from the Sensor/temp/room1 topic for another AWS account. This is true of regions, too. The Sensor/temp/room1 topic in the same AWS account in us-east-1 is independent from the same topic in us-east-2. AWS IoT does not support sending and receiving messages across AWS accounts and regions.

 

The message broker maintains a list of all client sessions and the subscriptions for each session. When a message is published on a topic, the broker checks for sessions with subscriptions that map to the topic. The broker then forwards the publish message to all sessions that have a currently connected client.

 

MQTT Client

TsgcIoTAmazon_MQTT_Client is the component used for connect to AWS IoT, one client can connect to only one device. Client connects using plain MQTT protocol and authenticates using a X.509 Client Certificate.

 

In order to connect to AWS IoT, client needs the following properties:

 

Amazon.ClientId: identification of client, optional.

Amazon.Endpoint: server name where MQTT client will connect.

Amazon.Port: by default uses port 8883. If port is 443, uses ALPN automatically to connect (Requires custom Indy version).

 

AWS IoT Core supports devices and clients that use the MQTT and the MQTT over WebSocket Secure (WSS) protocols to publish and subscribe to messages. The following table lists the protocols that the AWS IoT device endpoints support and the authentication methods and ports they use.

 

Protocol Authentication Port ALPN Protocol Name
MQTT over WebSocket Signature Version 4 443  
MQTT over WebSocket Custom Authentication 443  
MQTT X.509 client certificate 443 x-amzn-mqtt-ca
MQTT X.509 client certificate 8883  
MQTT Custom Authentication 443 mqtt

 

 

Certificates Authentication

Requires to create certificates in your Amazon AWS console and set the path were are stored.

 

Using OpenSSL as IOHandler you must set the certificate in the following paths

 

Certificate.Enabled: set to True if you want use certificates.

Certificate.CertFile: path to X.509 client certificate.

Certificate.KeyFile: path to X.509 client key file.

 

Using SChannel as IOHandler, first convert the PEM Certificate + Key to a PFX certificate, requires openssl binaries:

 


openssl pkcs12 -inkey 884ccf73ff-private.pem.key -in 884ccf73ff-certificate.pem.crt -export -out 884ccf73ff-certificate.pfx

Then set the following paths (there is no need to set the keyfile because is already included in the certificate).

 

Certificate.Enabled: set to True if you want use certificates.

Certificate.CertFile: path to PFX certificate

 

SignatureV4 Authentication

Requires create an user in your Amazon AWS console and save the Access and Secret key which will be used to Sign the WebSocket request.

 

SignatureV4.Enabled: set to True if you want use this type of Authentication.

SignatureV4.Region: the region were is located your device (example: us-east-1).

SignatureV4.AccessKey: the access key created in your amazon console or get as temporary credential

SignatureV4.SecretKey: the secret key created in your amazon console  or get as temporary credential

SignatureV4.SessionToken: (conditional) if you are using Temporary Security Credentials, set here the security token.

OpenSSL_Options: configuration of the openSSL libraries.

APIVersion: allows defining which OpenSSL API will be used.

oslAPI_1_0: uses API 1.0 OpenSSL, it's latest supported by Indy

oslAPI_1_1: uses API 1.1 OpenSSL, requires our custom Indy library and allows using OpenSSL 1.1.1 libraries (with TLS 1.3 support).

oslAPI_3_0: uses API 3.0 OpenSSL, requires our custom Indy library and allows using OpenSSL 3.0.0 libraries (with TLS 1.3 support).

LibPath: here you can configure where are located the openSSL libraries

oslpNone: this is the default, the openSSL libraries should be in the same folder where is the binary or in a known path.

oslpDefaultFolder: sets automatically the openSSL path where the libraries should be located for all IDE personalities.

oslpCustomFolder: if this is the option selected, define the full path in the property LibPathCustom.

LibPathCustom: when LibPath = oslpCustomFolder define here the full path where are located the openSSL libraries.

UnixSymLinks: enable or disable the loading of SymLinks under Unix systems (by default is enabled, except under OSX64):

oslsSymLinksDefault: by default are enabled except under OSX64 (after MacOS Monterey fails trying to load the library without version.).

oslsSymLinksLoadFirst: Load SymLinks and do before trying to load the version libraries.

oslsSymLinksLoad: Load SymLinks after trying to load the version libraries.

oslsSymLinksDontLoad: don't load the SymLinks.

 

*SignatureV4 requires Indy 10.5.7+

Custom Authentication

Custom authentication enables you to define how to authenticate and authorize clients by using authorizer resources. The device passes credentials in either the request’s header fields or query parameters (for MQTT over WebSockets protocols) or in the user name and password field of the MQTT CONNECT message (for the MQTT and MQTT over WebSockets protocols).

 

CustomAuthentication.Enabled: set to True if you want to use this type of Authentication.

CustomAuthentication.Parameters: set here the query parameters which will be passed to the server (by default is /mqtt)

CustomAuthentication.Headers: here you can put the custom header fields.

CustomAuthentication.WebSockets: if set to true, the connection will work over WebSocket protocol, otherwise will work over plain TCP.

 

MQTTAuthentication.Enabled: if you need to pass the username/password in the mqtt connection, enable this property

MQTTAuthentication.Username: username of the mqtt connection

MQTTAuthentication.Password: secret of the mqtt connection.

 

 

Client can send optionally a ClientId to identifiy client connection, then others clients can subscribe to receive a notification every time this client has connected, subscribed, disconnected...

 

Authorization

If you can't connect using port 8883 and use TCP as transport (which is the default), amazon takes "AWS IoT Core policy" to provide or not authorization to clients and subscriptions. Most probably you must authorize your client id.

Enter in your Amazon AWS console, go to IoT Core and access the menu "Secure/Policies", there select the policy attached to your IoT Thing and check at the end how connection is configured. Example:

 

    {

      "Effect": "Allow",

      "Action": [

        "iot:Connect"

      ],

      "Resource": [

        "arn:aws:iot:us-east-1:222178873557:client/sdk-java",

        "arn:aws:iot:us-east-1:222178873557:client/basicPubSub",

        "arn:aws:iot:us-east-1:222178873557:client/sdk-nodejs-*"

      ]

    }

 

This configuration means that only clients with ID: sdk-java, basicPubSub and sdk-nodejs-* will be allowed to connect. Change accordingly and try again.

If it still doesn't work, enable log and check in cloudwatch the reason why you can't connect.

 

Other properties

 

     MQTTHeartBeat: if enabled attempts to keep alive MQTT connection sending a ping every x seconds.

 

Interval: number of seconds between each ping.

 

     MQTTAuthentication: if enabled includes in MQTT connection the username and password

 

UserName: name of the user

Password: secret string

 

     WatchDog: if enabled, when an unexpected disconnection is detected, tries to reconnect to the server automatically.

 

Interval: seconds before reconnection attempts.

 

Attempts: maximum number of reconnection attempts; zero means unlimited.

 

     LogFile: if enabled, saves socket messages to a log file (useful for debugging). The access to log file is not thread safe if it's accessed from several threads.

 

Enabled: if enabled every time a message is received and sent by socket it will be saved on a file.

 

FileName: full path to the filename.

 

Implementation

 

Amazon MQTT implementation is based on MQTT version 3.1.1 but it deviates from the specification as follows:

 

 

Connect to AWS IoT

First, you must sign in your AWS console, register a new device and create a X.509 certificate for this device. Once is done, you can create create a new TsgcIoTAmazon_MQTT_Client and connect to AWS IoT Server. For example:

 


oClient := TsgcIoTAmazon_MQTT_Client.Create(nil);
oClient.Amazon.Endpoint := 'a2ohgdjqitsmij-ats.iot.us-west-2.amazonaws.com';
oClient.Amazon.ClientId := 'sgcWebSockets';
oClient.Certificate.CertFile := 'amazon-certificate.pem.crt';
oClient.Certificate.KeyFile := 'amazon-private.pem.key';
oClient.OnMQTTConnect := OnMQTTConnectEvent;
oClient.Active := True;
 
procedure OnMQTTConnect(Connection: TsgcWSConnection; const Session: Boolean; const ReturnCode: TmqttConnReturnCode);
begin
  ShowMessage('Connected to AWS');
end;

Topics

The message broker uses topics to route messages from publishing clients to subscribing clients. The forward slash (/) is used to separate topic hierarchy. The following table lists the wildcards that can be used in the topic filter when you subscribe. # Must be the last character in the topic to which you are subscribing. Works as a wildcard by matching the current tree and all subtrees.

For example, a subscription to Sensor/# receives messages published to Sensor/, Sensor/temp, Sensor/temp/room1, but not the messages published to Sensor.

+ Matches exactly one item in the topic hierarchy. For example, a subscription to Sensor/+/room1 receives messages published to Sensor/temp/room1, Sensor/moisture/room1, and so on.

 


oClient := TsgcIoTAmazon_MQTT_Client.Create(nil);
...
oClient.OnSubscribe := OnSubscribeEvent;
 
vPacketIdentifier := oClient.Subscribe('Sensor/moisture/room1');
  
procedure OnMQTTSubscribe(Connection: TsgcWSConnection; aPacketIdentifier: Word; aCodes: TsgcWSSUBACKS);
begin
  if vPacketIdentifier = aPacketIdentifier then
    ShowMessage('Subscribed to topic Sensor/moisture/room1'); 
end;
 
// Client, can send a message using Publish method.
oClient.Publish('Sensor/moisture/room1', '{"temp"=10}');
  
// Messages received from server, are dispatched OnMQTTPublishEvent.
procedure OnMQTTPublish(Connection: TsgcWSConnection; aTopic, aText: string);
begin
  DoLog('Received Message: ' + aTopic + ' ' + aText);
end;

Reserved Topics

Following methods are used to subscribe / publish to reserved topics.

 

    Subscribe_ClientConnected(const aClientId: String): AWS IoT publishes to this topic when an MQTT client with the specified client ID connects to AWS IoT

    Subscribe_ClientDisconnected(const aClientId: String): AWS IoT publishes to this topic when an MQTT client with the specified client ID disconnects to AWS IoT

    Subscribe_ClientSubscribed(const aClientId: String): AWS IoT publishes to this topic when an MQTT client with the specified client ID subscribes to an MQTT topic

    Subscribe_ClientUnSubscribed(const aClientId: String): AWS IoT publishes to this topic when an MQTT client with the specified client ID unsubscribes to an MQTT topic

 

    Publish_Rule(const aRuleName, aText: String): A device or an application publishes to this topic to trigger rules directly

 

    Publish_DeleteShadow(const aThingName, aText: String): A device or an application publishes to this topic to delete a shadow

    Subscribe_DeleteShadow(const aThingName: String): A device or an application subscribe to this topic to delete a shadow

    Subscribe_ShadowDeleted(const aThingName: String): The Device Shadow service sends messages to this topic when a shadow is deleted

    Subscribe_ShadowRejected(const aThingName: String): The Device Shadow service sends messages to this topic when a request to delete a shadow is rejected

    Publish_ShadowGet(const aThingName, aText: String): An application or a thing publishes an empty message to this topic to get a shadow

    Subscribe_ShadowGet(const aThingName: String): An application or a thing subscribe to this topic to get a shadow

    Subscribe_ShadowGetAccepted(const aThingName: String): The Device Shadow service sends messages to this topic when a request for a shadow is made successfully

    Subscribe_ShadowGetRejected(const aThingName: String): The Device Shadow service sends messages to this topic when a request for a shadow is rejected

    Publish_ShadowUpdate(const aThingName, aText: String): A thing or application publishes to this topic to update a shadow

    Subscribe_ShadowUpdateAccepted(const aThingName: String): The Device Shadow service sends messages to this topic when an update is successfully made to a shadow

    Subscribe_ShadowUpdateRejected(const aThingName: String): The Device Shadow service sends messages to this topic when an update to a shadow is rejected

    Subscribe_ShadowUpdateDelta(const aThingName: String): The Device Shadow service sends messages to this topic when a difference is detected between the reported and desired sections of a shadow

    Subscribe_ShadowUpdateDocuments(const aThingName: String): AWS IoT publishes a state document to this topic whenever an update to the shadow is successfully performed

 

Persistent Sessions

A persistent session represents an ongoing connection to an MQTT message broker. When a client connects to the AWS IoT message broker using a persistent session, the message broker saves all subscriptions the client makes during the connection. When the client disconnects, the message broker stores unacknowledged QoS 1 messages and new QoS 1 messages published to topics to which the client is subscribed. When the client reconnects to the persistent session, all subscriptions are reinstated and all stored messages are sent to the client at a maximum rate of 10 messages per second.

 

You create an MQTT persistent session setting the cleanSession parameter to False OnMQTTBeforeConnect event. If no session exists for the client, a new persistent session is created. If a session already exists for the client, it is resumed.

 

Devices need to look at the Session attribute in the OnMQTTConnect event to determine if a persistent session is present. If Session is True, a persistent session is present and stored messages are delivered to the client. If Session is False, no persistent session is present and the client must re-subscribe to its topic filters.

 

Persistent sessions have a default expiry period of 1 hour. The expiry period begins when the message broker detects that a client disconnects (MQTT disconnect or timeout). The persistent session expiry period can be increased through the standard limit increase process. If a client has not resumed its session within the expiry period, the session is terminated and any associated stored messages are discarded. The expiry period is approximate, sessions might be persisted for up to 30 minutes longer (but not less) than the configured duration.

 

Temporary Credentials

AWS IoT Core can work with Temporary Credentials obtained through Identity Pools, there are 2 types of Identities:

 

 

Unauthenticated

If you are using Unauthenticated credentials, just attach the policy in the UnAuthenticated Role automatically created in the IAM menu. Then configure the client setting the Access, Secret Key and Token returned by Cognito service.

Find below a code in .NET to get unauthenticated credentials

 


CognitoAWSCredentials credentials = new CognitoAWSCredentials(
    "us-east-1:cc3c9c48-646d-44ef-bfd5-0c5fb2f0882f", // Identity pool ID
    Amazon.RegionEndpoint.USEast1 // Region
);
 
var identityPoolId = credentials.GetCredentialsAsync();
 
AmazonCognitoIdentityClient cognitoClient = new AmazonCognitoIdentityClient(
    credentials, // the anonymous credentials
    Amazon.RegionEndpoint.USEast1 // the Amazon Cognito region
);
 
GetIdRequest idRequest = new GetIdRequest();
idRequest.AccountId = "222178873557";
idRequest.IdentityPoolId = "us-east-1:cc3c9c48-646d-44ef-bfd5-0c5fb2f0882f";
 
GetIdResponse idResp = cognitoClient.GetId(idRequest);
 
string AccessKey = identityPoolId.Result.AccessKey;
string SecretKey = identityPoolId.Result.SecretKey;
string SessionToken = identityPoolId.Result.Token;
 
string IdentityId = idResp.IdentityId;

Authenticated

Authenticated credentials, requires to attache de policy in the Authenticated Role automatically created in the IAM menu and attach the policy of the user in AWS IoT Core policies.

So create a new policy in the IoT Core policies menu and every time a new user authenticates, attach this policy to this user.

You can use the following command of AWS to attach a policy or create a lambda function.

 

aws iot attach-policy --policy-name PolicyName --target us-east-1:XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

 

 

Device Provisioning

The Fleet Provisioning service supports the following MQTT API operations:

 

 

CreateCertificateFromCsr

 

Use the method CreateCertificateFromCsr passing the CertificateSigningRequest as a parameter to create the certificate. In order to receive the response to this request, first subscribe to the following methods: SubscribeCreateCertificateFromCsrResponse and SubscribeCreateCertificateFromCsrError

 

CreateKeysAndCertificate

 

Use the method CreateKeysAndCertificate to create a new certificate and keys. In order to receive the response to this request, first subscribe to the following methods SubscribeCreateKeysAndCertificateResponse and SubscribeCreateKeysAndCertificateError

 

RegisterThing

 

Use the method RegisterThing to register a new thing passing as a parameter the Template Name and the Payload in JSON format. In order to receive the response to this request, first subscribe to the following methods SubscribeRegisterThingResponse and SubscribeRegisterThingError.