IoT Amazon MQTT Client

Che cos'è AWS IoT?

AWS IoT fornisce comunicazione sicura e bidirezionale tra dispositivi connessi a Internet come sensori, attuatori, microcontrollori incorporati o elettrodomestici intelligenti e il Cloud AWS. Ciò consente di raccogliere dati di telemetria da più dispositivi, archiviarli e analizzarli. È anche possibile creare applicazioni che consentano agli utenti di controllare questi dispositivi dai propri telefoni o tablet.

 

Message broker

Fornisce un meccanismo sicuro per dispositivi e applicazioni AWS IoT per pubblicare e ricevere messaggi l'uno dall'altro. È possibile utilizzare direttamente il protocollo MQTT oppure MQTT su WebSocket per pubblicare e sottoscrivere.

 

L'AWS IoT message broker è un servizio broker publish/subscribe che consente l'invio e la ricezione di messaggi da e verso AWS IoT. Quando comunica con AWS IoT, un client invia un messaggio indirizzato a un topic come Sensor/temp/room1.

 

Il message broker, a sua volta, invia il messaggio a tutti i client che si sono registrati per ricevere messaggi su quel topic. L'atto di inviare il messaggio è denominato pubblicazione. L'atto di registrarsi per ricevere messaggi su un filtro di topic è denominato sottoscrizione.

 

Lo spazio dei nomi dei topic è isolato per ogni coppia account e regione AWS. Per esempio, il topic Sensor/temp/room1 per un account AWS è indipendente dal topic Sensor/temp/room1 per un altro account AWS. Questo vale anche per le regioni. Il topic Sensor/temp/room1 nello stesso account AWS in us-east-1 è indipendente dallo stesso topic in us-east-2. AWS IoT non supporta l'invio e la ricezione di messaggi tra account e regioni AWS diversi.

 

Il broker di messaggi mantiene un elenco di tutte le sessioni client e delle sottoscrizioni per ciascuna sessione. Quando viene pubblicato un messaggio su un argomento, il broker verifica le sessioni con sottoscrizioni corrispondenti all'argomento. Il broker inoltra quindi il messaggio pubblicato a tutte le sessioni che hanno un client attualmente connesso.

 

Client MQTT

TsgcIoTAmazon_MQTT_Client è il componente utilizzato per connettersi ad AWS IoT. Un client può connettersi a un solo dispositivo. Il client si connette utilizzando il protocollo MQTT semplice e si autentica utilizzando un certificato client X.509.

 

Per connettersi ad AWS IoT, il client necessita delle seguenti proprietà:

 

Amazon.ClientId: identificazione del client, facoltativa.

Amazon.Endpoint: nome del server a cui si connetterà il client MQTT.

Amazon.Port: per impostazione predefinita utilizza la porta 8883. Se la porta è 443, utilizza ALPN automaticamente per la connessione (richiede la versione Indy personalizzata).

 

AWS IoT Core supporta dispositivi e client che utilizzano i protocolli MQTT e MQTT over WebSocket Secure (WSS) per pubblicare e sottoscrivere messaggi. La tabella seguente elenca i protocolli supportati dagli endpoint del dispositivo AWS IoT e i metodi di autenticazione e le porte utilizzati.

 

Protocol Authentication Port Nome del protocollo ALPN
MQTT su WebSocket Signature Version 4 443  
MQTT su WebSocket Autenticazione personalizzata 443  
MQTT Certificato client X.509 443 x-amzn-mqtt-ca
MQTT Certificato client X.509 8883  
MQTT Autenticazione personalizzata 443 mqtt

 

 

Autenticazione tramite certificati

È necessario creare certificati nella console Amazon AWS e impostare il percorso in cui sono archiviati.

 

Utilizzando OpenSSL come IOHandler è necessario impostare il certificato nei seguenti percorsi

 

Certificate.Enabled: imposti su True se desidera utilizzare i certificati.

Certificate.CertFile: percorso del certificato client X.509.

Certificate.KeyFile: percorso del file chiave client X.509.

 

Utilizzando SChannel come IOHandler, convertire prima il certificato PEM + chiave in un certificato PFX. Questa operazione richiede i binari OpenSSL:

 


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

Quindi impostare i seguenti percorsi (non è necessario impostare il file della chiave perché è già incluso nel certificato).

 

Certificate.Enabled: imposti su True se desidera utilizzare i certificati.

Certificate.CertFile: percorso del certificato PFX

 

Autenticazione SignatureV4

È necessario creare un utente nella console Amazon AWS e salvare le chiavi di accesso e segrete, che verranno utilizzate per firmare la richiesta WebSocket.

 

SignatureV4.Enabled: impostare su True se si desidera utilizzare questo tipo di autenticazione.

SignatureV4.Region: la regione in cui si trova il Suo dispositivo (esempio: us-east-1).

SignatureV4.AccessKey: la chiave di accesso creata nella console Amazon o ottenuta come credenziale temporanea.

SignatureV4.SecretKey: la chiave segreta creata nella console Amazon o ottenuta come credenziale temporanea

SignatureV4.SessionToken: (condizionale) se si utilizzano credenziali di sicurezza temporanee, impostare qui il token di sicurezza.

OpenSSL_Options: configurazione delle librerie openSSL.

APIVersion: consente di definire quale API OpenSSL verrà utilizzata.

oslAPI_1_0: utilizza l'API OpenSSL 1.0, l'ultima supportata da Indy

oslAPI_1_1: utilizza OpenSSL API 1.1, richiede la nostra libreria Indy personalizzata e consente l'utilizzo delle librerie OpenSSL 1.1.1 (con supporto TLS 1.3).

oslAPI_3_0: utilizza l'API 3.0 OpenSSL, richiede la nostra libreria Indy personalizzata e consente l'uso delle librerie OpenSSL 3.0.0 (con supporto TLS 1.3).

LibPath: qui è possibile configurare la posizione delle librerie openSSL

oslpNone: questa è l'impostazione predefinita; le librerie openSSL devono trovarsi nella stessa cartella del binario o in un percorso noto.

oslpDefaultFolder: imposta automaticamente il percorso openSSL dove le librerie devono essere situate per tutte le personalità IDE.

oslpCustomFolder: se questa è l'opzione selezionata, definire il percorso completo nella proprietà LibPathCustom.

LibPathCustom: quando LibPath = oslpCustomFolder, definire qui il percorso completo dove si trovano le librerie openSSL.

UnixSymLinks: abilita o disabilita il caricamento dei SymLink sui sistemi Unix (per impostazione predefinita è abilitato, tranne che su OSX64):

oslsSymLinksDefault: per impostazione predefinita sono abilitati, eccetto su OSX64 (dopo MacOS Monterey si verifica un errore nel tentativo di caricare la libreria senza versione.).

oslsSymLinksLoadFirst: Carica i SymLink e li elabora prima di tentare il caricamento delle librerie di versione.

oslsSymLinksLoad: Carica i SymLink dopo aver tentato di caricare le librerie di versione.

oslsSymLinksDontLoad: non caricare i SymLink.

 

*SignatureV4 richiede Indy 10.5.7+

Autenticazione personalizzata

L'autenticazione personalizzata consente di definire come autenticare e autorizzare i client utilizzando risorse di autorizzazione. Il dispositivo trasmette le credenziali nei campi header della richiesta o nei parametri di query (per i protocolli MQTT su WebSockets) oppure nei campi nome utente e password del messaggio MQTT CONNECT (per i protocolli MQTT e MQTT su WebSockets).

 

CustomAuthentication.Enabled: impostare a True se si desidera utilizzare questo tipo di autenticazione.

CustomAuthentication.Parameters: impostare qui i parametri di query che verranno passati al server (per impostazione predefinita è /mqtt)

CustomAuthentication.Headers: qui è possibile inserire i campi di intestazione personalizzati.

CustomAuthentication.WebSockets: se impostato a true, la connessione funzionerà tramite il protocollo WebSocket, altrimenti funzionerà tramite TCP semplice.

 

MQTTAuthentication.Enabled: se è necessario passare nome utente/password nella connessione mqtt, abilitare questa proprietà

MQTTAuthentication.Username: nome utente della connessione MQTT

MQTTAuthentication.Password: segreto della connessione mqtt.

 

 

Il client può inviare facoltativamente un ClientId per identificare la connessione client; altri client possono quindi iscriversi per ricevere una notifica ogni volta che questo client si connette, si iscrive, si disconnette...

 

Authorization

Se non è possibile connettersi utilizzando la porta 8883 e si utilizza TCP come trasporto (che è quello predefinito), Amazon utilizza la "AWS IoT Core policy" per fornire o meno l'autorizzazione ai client e alle sottoscrizioni. Molto probabilmente è necessario autorizzare il proprio client id.

Accedere alla console Amazon AWS, selezionare IoT Core e aprire il menu "Secure/Policies"; selezionare quindi il criterio collegato al proprio IoT Thing e verificare in fondo come è configurata la connessione. Esempio:

 

{

"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-*"

]

}

 

Questa configurazione significa che solo i client con ID: sdk-java, basicPubSub e sdk-nodejs-* saranno autorizzati a connettersi. Modificare di conseguenza e riprovare.

Se ancora non funziona, abiliti il log e verifichi in cloudwatch il motivo per cui non riesce a connettersi.

 

Altre proprietà

 

MQTTHeartBeat: se abilitato, tenta di mantenere attiva la connessione MQTT inviando un ping ogni x secondi.

 

Interval: numero di secondi tra ogni ping.

 

MQTTAuthentication: se abilitato, include nella connessione MQTT il nome utente e la password

 

UserName: nome dell'utente

Password: stringa segreta

 

WatchDog: se abilitato, quando viene rilevata una disconnessione inattesa, tenta di riconnettersi automaticamente al server.

 

Interval: secondi prima dei tentativi di riconnessione.

 

Attempts: numero massimo di tentativi di riconnessione; zero significa illimitato.

 

LogFile: se abilitato, salva i messaggi del socket in un file di log (utile per il debugging). L'accesso al file di log non è thread safe se vi si accede da più thread.

 

Enabled: se abilitato, ogni messaggio ricevuto e inviato tramite socket verrà salvato su un file.

 

FileName: percorso completo al nome del file.

 

Implementazione

 

L'implementazione Amazon MQTT è basata su MQTT versione 3.1.1 ma si discosta dalla specifica come segue:

 

 

Connetti ad AWS IoT

Per prima cosa, deve accedere alla Sua console AWS, registrare un nuovo dispositivo e creare un certificato X.509 per questo dispositivo. Una volta fatto, può creare un nuovo TsgcIoTAmazon_MQTT_Client e connettersi al server AWS IoT. Ad esempio:

 


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

Il message broker utilizza i topic per instradare i messaggi dai client publisher ai client subscriber. La barra (/) viene utilizzata per separare la gerarchia dei topic. La tabella seguente elenca i caratteri jolly che possono essere utilizzati nel filtro dei topic al momento della sottoscrizione. # Deve essere l'ultimo carattere nel topic a cui ci si iscrive. Funziona come jolly corrispondendo all'albero corrente e a tutti i sottoalberi.

Ad esempio, una sottoscrizione a Sensor/# riceve i messaggi pubblicati su Sensor/, Sensor/temp, Sensor/temp/room1, ma non i messaggi pubblicati su Sensor.

+ corrisponde esattamente a un elemento nella gerarchia degli argomenti. Ad esempio, una sottoscrizione a Sensor/+/room1 riceve messaggi pubblicati su Sensor/temp/room1, Sensor/moisture/room1 e così via.

 


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.
// For extended payload access (string, bytes or stream), use OnMQTTPublishEx.
procedure OnMQTTPublish(Connection: TsgcWSConnection; aTopic, aText: string);
begin
  DoLog('Received Message: ' + aTopic + ' ' + aText);
end;

Argomenti riservati

I seguenti metodi vengono utilizzati per sottoscrivere/pubblicare su topic riservati.

 

Subscribe_ClientConnected(const aClientId: String): AWS IoT pubblica su questo topic quando un client MQTT con l'ID client specificato si connette ad AWS IoT

Subscribe_ClientDisconnected(const aClientId: String): AWS IoT pubblica su questo topic quando un client MQTT con l'ID client specificato si disconnette da AWS IoT

Subscribe_ClientSubscribed(const aClientId: String): AWS IoT pubblica su questo argomento quando un client MQTT con l'ID client specificato si iscrive a un argomento MQTT

Subscribe_ClientUnSubscribed(const aClientId: String): AWS IoT pubblica su questo topic quando un client MQTT con il client ID specificato annulla l'iscrizione a un topic MQTT

 

Publish_Rule(const aRuleName, aText: String): Un dispositivo o un'applicazione pubblica su questo topic per attivare le regole direttamente

 

Publish_DeleteShadow(const aThingName, aText: String): Un dispositivo o un'applicazione pubblica su questo topic per eliminare un shadow

Subscribe_DeleteShadow(const aThingName: String): Un dispositivo o un'applicazione si iscrive a questo topic per eliminare uno shadow

Subscribe_ShadowDeleted(const aThingName: String): Il servizio Device Shadow invia messaggi a questo topic quando un'ombra viene eliminata

Subscribe_ShadowRejected(const aThingName: String): Il servizio Device Shadow invia messaggi a questo topic quando una richiesta di eliminazione di uno shadow viene rifiutata

Publish_ShadowGet(const aThingName, aText: String): Un'applicazione o un dispositivo pubblica un messaggio vuoto su questo topic per ottenere uno shadow

Subscribe_ShadowGet(const aThingName: String): Un'applicazione o un dispositivo si iscrive a questo topic per ottenere uno shadow

Subscribe_ShadowGetAccepted(const aThingName: String): Il servizio Device Shadow invia messaggi a questo topic quando una richiesta di shadow viene effettuata con successo

Subscribe_ShadowGetRejected(const aThingName: String): Il servizio Device Shadow invia messaggi a questo topic quando una richiesta per uno shadow viene rifiutata

Publish_ShadowUpdate(const aThingName, aText: String): Un oggetto o un'applicazione pubblica su questo argomento per aggiornare un'ombra

Subscribe_ShadowUpdateAccepted(const aThingName: String): Il servizio Device Shadow invia messaggi a questo argomento quando un aggiornamento viene applicato con successo a un shadow

Subscribe_ShadowUpdateRejected(const aThingName: String): Il servizio Device Shadow invia messaggi a questo topic quando un aggiornamento a uno shadow viene rifiutato

Subscribe_ShadowUpdateDelta(const aThingName: String): Il servizio Device Shadow invia messaggi a questo argomento quando viene rilevata una differenza tra le sezioni reported e desired di un shadow

Subscribe_ShadowUpdateDocuments(const aThingName: String): AWS IoT pubblica un documento di stato su questo topic ogni volta che un aggiornamento al shadow viene eseguito correttamente

 

Sessioni Persistenti

Una sessione persistente rappresenta una connessione in corso a un broker di messaggi MQTT. Quando un client si connette al broker di messaggi AWS IoT utilizzando una sessione persistente, il broker di messaggi salva tutte le sottoscrizioni effettuate dal client durante la connessione. Quando il client si disconnette, il broker di messaggi memorizza i messaggi QoS 1 non confermati e i nuovi messaggi QoS 1 pubblicati sui topic a cui il client è sottoscritto. Quando il client si riconnette alla sessione persistente, tutte le sottoscrizioni vengono ripristinate e tutti i messaggi memorizzati vengono inviati al client a una frequenza massima di 10 messaggi al secondo.

 

Si crea una sessione MQTT persistente impostando il parametro cleanSession su False nell'evento OnMQTTBeforeConnect. Se non esiste alcuna sessione per il client, viene creata una nuova sessione persistente. Se esiste già una sessione per il client, viene ripresa.

 

I dispositivi devono verificare l'attributo Session nell'evento OnMQTTConnect per determinare se è presente una sessione persistente. Se Session è True, è presente una sessione persistente e i messaggi memorizzati vengono consegnati al client. Se Session è False, non è presente alcuna sessione persistente e il client deve ri-sottoscriversi ai propri filtri di argomento.

 

Le sessioni persistenti hanno un periodo di scadenza predefinito di 1 ora. Il periodo di scadenza inizia quando il message broker rileva che un client si disconnette (disconnessione MQTT o timeout). Il periodo di scadenza della sessione persistente può essere aumentato tramite il processo standard di aumento del limite. Se un client non ha ripreso la propria sessione entro il periodo di scadenza, la sessione viene terminata e tutti i messaggi archiviati associati vengono eliminati. Il periodo di scadenza è approssimativo: le sessioni potrebbero essere mantenute fino a 30 minuti in più (ma non meno) rispetto alla durata configurata.

 

Credenziali Temporanee

AWS IoT Core può funzionare con credenziali temporanee ottenute tramite Identity Pool; esistono 2 tipi di identità:

 

 

Non autenticato

Se si utilizzano credenziali non autenticate, collegare semplicemente la policy nel ruolo UnAuthenticated creato automaticamente nel menu IAM. Quindi configurare il client impostando Access, Secret Key e Token restituiti dal servizio Cognito.

Di seguito è riportato un codice in .NET per ottenere credenziali non autenticate

 


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;

Autenticato

Credenziali autenticate, richiede di collegare la policy nell'Authenticated Role creato automaticamente nel menu IAM e di collegare la policy dell'utente nelle policy di AWS IoT Core.

Creare quindi una nuova policy nel menu delle policy di IoT Core e, ogni volta che un nuovo utente si autentica, associare questa policy a tale utente.

È possibile utilizzare il seguente comando di AWS per allegare una policy o creare una funzione lambda.

 

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

 

 

Provisioning del dispositivo

Il servizio Fleet Provisioning supporta le seguenti operazioni dell'API MQTT:

 

 

CreateCertificateFromCsr

 

Utilizzare il metodo CreateCertificateFromCsr passando il CertificateSigningRequest come parametro per creare il certificato. Per ricevere la risposta a questa richiesta, sottoscrivere prima i seguenti metodi: SubscribeCreateCertificateFromCsrResponse e SubscribeCreateCertificateFromCsrError

 

CreateKeysAndCertificate

 

Utilizzare il metodo CreateKeysAndCertificate per creare un nuovo certificato e nuove chiavi. Per ricevere la risposta a questa richiesta, sottoscrivere prima i seguenti metodi SubscribeCreateKeysAndCertificateResponse e SubscribeCreateKeysAndCertificateError

 

RegisterThing

 

Utilizzi il metodo RegisterThing per registrare un nuovo thing passando come parametri il nome del template e il payload in formato JSON. Per ricevere la risposta a questa richiesta, occorre prima iscriversi ai seguenti metodi: SubscribeRegisterThingResponse e SubscribeRegisterThingError.