Mise en oeuvre d’AWS IoT et Alexa avec un module ESP32

Cette activité va permettre d’aborder les notions suivantes :

L’ESP32

Notre objet connecté sera architecturé autour d’un ESP32.

L’ESP32 est le successeur de l’ESP8266 auquel on a ajouté le Bluetooth. Il existe différents modules.

Liens :

Autres activités :

AWS (Amazon Web Services)

AWS (Amazon Web Services) est un ensemble de services de cloud computing fourni par Amazon.com et essentiellement destiné aux entreprises.

Depuis 2017, AWS propose plus de 90 services, comprenant le calcul, le stockage, le réseau, la base de données, l’analyse de données, des services applicatifs, du déploiement, de la gestion de système, de la gestion d’applications mobiles, des outils pour les développeurs et pour l’internet des objets. [Source : https://fr.wikipedia.org/wiki/Amazon_Web_Services]

AWS fournit notamment des services IoT pour des solutions industrielles, grand public et commerciales :

Remarque : D’après une enquête de la Fondation Eclipse, AWS serait la plateforme cloud privilégiée par 34 % des développeurs de l’IoT devant Azure (23%) et Google Cloud Platform (20%) .

Il faudra créer un compte sur AWS. Puis, rechercher et sélectionner AWS IoT.

Il existe plusieurs moyens pour gérer les service dans AWS :

  • l’AWS Console qui est l’interface graphique web proposée par Amazon
  • aws-cli qui est une interface en ligne de commandes

AWS IoT

On s’intéressera ici à AWS IoT Core qui est un service basé sur le cloud permettant aux appareils connectés d’interagir avec d’autres appareils et applications cloud. L’AWS IoT Core prend en charge les protocoles HTTP, WebSockets et MQTT. AWS IoT Core assure l’authentification et le chiffrement de bout en bout.

Vous pouvez utiliser l’une des deux méthodes suivantes pour gérer les objets connectés dans AWS IoT :

  • l’AWS Console qui est l’interface graphique web proposée par Amazon (long et fastidieux)
  • aws-cli qui est une interface en ligne de commandes (rapide et efficace)

Quelque soit la méthode utilisée, le principe est le suivant :

  • créer un nouvel objet
  • créer une nouvelle stratégie (policy) pour contrôler les accès
  • créer un certificat X.509 et sa clé privée RSA 2048 bits
  • associer l’ensemble

AWS Console

Lien : Enregistrement d’un appareil

À partir de l’AWS Console, qui est l’interface graphique proposée par Amazon, on va créer notre objet connecté architecturé ici autour d’un ESP32 :

Remarque : quelque soit votre objet connecté, ce sera toujours la même procédure que celle décrite ci-dessous. Donc, idem pour une Raspberry Pi.

On va ensuite créer un certificat X.509 pour notre objet :

Attention, c’est la phase essentielle car il faut :

  • télécharger le certificat de l’objet xxxxx.cert.perm (on peut le télécharger plus tard)
  • télécharger la clé privée xxxxx.private.key (on ne pourra pas le télécharger plus tard)
  • télécharger Amazon Root CA 1 (Clé RSA 2048 bits) : $ curl https://www.amazontrust.com/repository/AmazonRootCA1.pem > root-CA.crt
  • l’activer

Remarque : Le lien Télécharger “CA racine pour AWS IoT” permet seulement d’accéder à la page Certificats X.509 et AWS IoT sur laquelle il faudra choisir un certificat CA.

Maintenant, il faut créer et associer une stratégie à notre objet via le certificat :

Les stratégies permettent de gérer les accès dans AWS. On les attachent ensuite à des identités IAM (utilisateurs, groupes d’utilisateurs ou rôles) ou des ressources AWS. Une stratégie est un objet dans AWS qui, lorsqu’il est associé à une identité ou à une ressource, définit les autorisations de ces dernières. AWS évalue ces stratégies lorsqu’une entité mandataire (utilisateur ou rôle) envoie une demande.

Les autorisations dans les stratégies déterminent si la demande sera acceptée ou refusée. La plupart des stratégies sont stockées dans AWS sous forme de documents JSON. Dans notre cas, on donnera les droits d’effectuer des subscribe et publish MQTT sur cet objet avec ce certiciat et on aura :

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:*",
      "Resource": "*"
    }
  ]
}

Remarque : Évidemment ces paramètres seraient trop permissifs pour un environnement de production. Il faudra alors les resteindre.

Lien : Stratégies et autorisations AWS

Pour rappel, l’objectif était le suivant :

On va vérifier notre objet : (j’ai recréé un certificat)

Il y a un certificat pour cet objet :

Il y a une stratégie attachée à ce certificat :

Il y a un objet associé à cette stratégie :

Dans tous les cas, les informations pour se connecter à notre objet se trouvent ici :

AWS CLI

Installation d’aws-cli :

$ pip3 install awscli --upgrade --user

Puis, vérifiez que aws-cli a été installé correctement :

$ aws --version
aws-cli/1.16.144 Python/3.6.7 Linux/4.15.0-47-generic botocore/1.12.134

Allez sur la console IAM pour récupérer l’AWS Access Key ID et l’AWS Secret Access Key.

Pour une utilisation générale, le moyen le plus rapide pour configurer l’installation d’aws-cli est :

$ aws configure
AWS Access Key ID [None]: ****************2WNF
AWS Secret Access Key [None]: ****************z6O3
Default region name [None]: eu-central-1
Default output format [None]: json

Puis, vérifiez que aws-cli a été configuré correctement :

$ aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************2WNF shared-credentials-file
secret_key     ****************z6O3 shared-credentials-file
    region             eu-central-1      config-file    ~/.aws/config

Liens :

Il est possible maintenant de lister nos objets connectés :

$ aws iot list-things
{
    "things": [
        {
            "thingName": "monesp32",
            "thingArn": "arn:aws:iot:eu-central-1:494915339550:thing/monesp32",
            "attributes": {},
            "version": 1
        }
    ]
}

Remarque : l’objet monesp32 apparaîtra si vous l’avez créé avec AWS Console dans l’étape précédente.

Il est aussi possible de créer un nouvel objet :

$ aws iot create-thing --thing-name "UnNouvelObjet"
{
    "thingName": "UnNouvelObjet",
    "thingArn": "arn:aws:iot:eu-central-1:494915339550:thing/UnNouvelObjet",
    "thingId": "2d12ea83-1b3d-4d76-99c5-d0a3b22a9986"
}

$ aws iot describe-thing --thing-name "UnNouvelObjet"
{
    "defaultClientId": "UnNouvelObjet",
    "thingName": "UnNouvelObjet",
    "thingId": "2d12ea83-1b3d-4d76-99c5-d0a3b22a9986",
    "thingArn": "arn:aws:iot:eu-central-1:494915339550:thing/UnNouvelObjet",
    "attributes": {},
    "version": 1
}

Ce qui donnera :

Il faut ensuite créer une paire de clés RSA 2048 bits et un certificat X.509 :

$ aws iot create-keys-and-certificate --set-as-active --certificate-pem-outfile certificate.pem.crt --public-key-outfile public.pem.key --private-key-outfile private.pem.key
{
    "certificateArn": "arn:aws:iot:eu-central-1:494915339550:cert/2946f548241cebdbb770d0a1b83d32218f09292b4736f9752702a6ebe46faecc",
    "certificateId": "2946f548241cebdbb770d0a1b83d32218f09292b4736f9752702a6ebe46faecc",
    "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDWTCCAkGgAwIBAgIUYzUnmu/76OeLZH7qoWXg9lBa62gwDQYJKoZIhvcNAQEL\n
    BQAwTTFLMEkGA1UECwxCQW1hem9uIFdlYiBTZXJ2aWNlcyBPPUFtYXpvbi5jb20g\n
  ...
  /FIcV\nt4+6r64vKqprpdCbSaa85JgJAHr080PYhA0QQ+BotiaeUwPKaqYfjiyI9NsG\n-----END CERTIFICATE-----\n",
    "keyPair": {
        "PublicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz4eOHLJQvzD1yD22DY2/\nzgebI/17IxRBhlDycJOcLtRT4DivWVBMmIozvZYWepWO6GXmyACSjFWT7Dt6V2iB\nVXxzj3/LvGMKXlssUmYAjnAbyp2EUdv3QyobBAmvjv5RA5z8VVlamd9osuzni01Y\n
...
        JCsU1U5+yUlKdwwvXwb4mvMCFiIt40cfhtwMZk26AKaZw7H1yaEXQaU=\n-----END RSA PRIVATE KEY-----\n"
    }
}

$ ls -l
-rw------- 1 tv tv 1,2K avril 22 09:01 certificate.pem.crt
-rw------- 1 tv tv 1,7K avril 22 09:01 private.pem.key
-rw------- 1 tv tv  451 avril 22 09:01 public.pem.key

$ curl https://www.amazontrust.com/repository/AmazonRootCA1.pem > root-CA.crt

Comme on l’a vu précedemment, un objet doit posséder un certificat X.509 pour pouvoir communiquer avec AWS IoT. Pour l’attacher à un certificat, il faut utiliser la commande :

$ aws iot attach-thing-principal --thing-name "UnNouvelObjet" --principal "arn:aws:iot:eu-central-1:494915339550:cert/2946f548241cebdbb770d0a1b83d32218f09292b4736f9752702a6ebe46faecc"

Ce qui donnera :

Il reste à créer une stratégie et à l’associer à l’objet.

$ aws iot create-policy --policy-name "MaNouvelleStrategie" --policy-document "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"iot:*\",\"Resource\":\"*\"}]}"
{
    "policyName": "MaNouvelleStrategie",
    "policyArn": "arn:aws:iot:eu-central-1:494915339550:policy/MaNouvelleStrategie",
    "policyDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"iot:*\",\"Resource\":\"*\"}]}",
    "policyVersionId": "1"
}

Ce qui donnera :

Pour finir, on va attacher cette nouvelle stratégie au certificat associé au nouvel objet :

$ aws iot attach-policy --policy-name "MaNouvelleStrategie" --target "arn:aws:iot:eu-central-1:494915339550:cert/2946f548241cebdbb770d0a1b83d32218f09292b4736f9752702a6ebe46faecc"

Et on obtient :

Pour rappel, l’objectif était le suivant :

aws-cli fournit les commandes pour lister l’ensemble de nos ressources IoT :

  • objet(s), stratégie(s) et certificat(s) :
$ aws iot list-things
{
    "things": [
        {
            "thingName": "UnNouvelObjet",
            "thingArn": "arn:aws:iot:eu-central-1:494915339550:thing/UnNouvelObjet",
            "attributes": {},
            "version": 1
        },
        ...
    ]
}

$ aws iot list-policies
{
    "policies": [
        {
            "policyName": "MaNouvelleStrategie",
            "policyArn": "arn:aws:iot:eu-central-1:494915339550:policy/MaNouvelleStrategie"
        },
        ...
    ]
}

$ aws iot list-certificates
{
    "certificates": [
        {
            "certificateArn": "arn:aws:iot:eu-central-1:494915339550:cert/2946f548241cebdbb770d0a1b83d32218f09292b4736f9752702a6ebe46faecc",
            "certificateId": "2946f548241cebdbb770d0a1b83d32218f09292b4736f9752702a6ebe46faecc",
            "status": "ACTIVE",
            "creationDate": 1555916468.752
        },
        ...
    ]
}
  • les attachements réalisés :
$ aws iot list-attached-policies --target "arn:aws:iot:eu-central-1:494915339550:cert/2946f548241cebdbb770d0a1b83d32218f09292b4736f9752702a6ebe46faecc"
{
    "policies": [
        {
            "policyName": "MaNouvelleStrategie",
            "policyArn": "arn:aws:iot:eu-central-1:494915339550:policy/MaNouvelleStrategie"
        }
    ]
}

$ aws iot list-principal-things --principal "arn:aws:iot:eu-central-1:494915339550:cert/2946f548241cebdbb770d0a1b83d32218f09292b4736f9752702a6ebe46faecc"
{
    "things": [
        "UnNouvelObjet"
    ]
}

$ aws iot list-targets-for-policy --policy-name "MaNouvelleStrategie"
{
    "targets": [
        "arn:aws:iot:eu-central-1:494915339550:cert/2946f548241cebdbb770d0a1b83d32218f09292b4736f9752702a6ebe46faecc"
    ]
}

$ aws iot list-thing-principals --thing-name "UnNouvelObjet"
{
    "principals": [
        "arn:aws:iot:eu-central-1:494915339550:cert/2946f548241cebdbb770d0a1b83d32218f09292b4736f9752702a6ebe46faecc"
    ]
}

Dans tous les cas, les informations pour se connecter à notre objet se trouvent ici :

$ aws iot describe-endpoint
{
    "endpointAddress": "XXXXXXXXXXXXXX.iot.eu-central-1.amazonaws.com"
}

Bilan : En 5 commandes avec aws-cli, on a réalisé la même chose qu’AWS Console !

Tests (MQTT)

Tests avec AWS Console

Il est possible de tester notre objet directement dans AWS Console. Ici j’ai choisi un kit de connexion en Python sous Linux :

Vous pouvez accéder aussi à un client MQTTdans AWS :

Et surveiller votre objet connecté :

Tests avec MQTT Mosquitto

On va maintenant tester avec le client MQTT Mosquitto :

$ sudo apt-get install mosquitto-clients

Pour faire un publish :

$ mosquitto_pub --cert f183df69c8-certificate.pem.crt --key f183df69c8-private.pem.key --cafile root-CA.crt -h XXXXXXXXXXXXXX-ats.iot.eu-central-1.amazonaws.com -p 8883 -t 'test/thing' -m "Hello from Mosquitto"

On obtient :

Maintenant on va faire un publish à partir de la console AWS et un subscribe avec le client Mosquitto :

$ mosquitto_sub --cert f183df69c8-certificate.pem.crt --key f183df69c8-private.pem.key --cafile root-CA.crt -h XXXXXXXXXXXXXX-ats.iot.eu-central-1.amazonaws.com -p 8883 -t 'test/+'
{
  "message": "Hello from AWS IoT console"
}

Autre client MQTT : MQTT.fx

Tests avec HTTP

Pour finir, on va aussi tester avec HTTP :

$ curl --tlsv1.2 --cacert root-CA.crt --cert f183df69c8-certificate.pem.crt --key f183df69c8-private.pem.key -X POST -d "{ \"message\": \"Hello from HTTP\" }" "https://XXXXXXXXXXXXXX-ats.iot.eu-central-1.amazonaws.com:8443/topics/test/thing"

{"message":"OK","traceId":"ccf5f94d-06c9-18df-60ff-737b0afc9ee8"}

On obtient :

Exemple ESP32

On va créer un projet sous PlatformIO pour une carte LOLIN32 avec le framework Arduino :

Pour notre objet connecté ESP32, on va utiliser directement une bibliothèque fournie pour cela : AWS_IOT.

Remarque : pour une Raspberry Pi, on suivra le guide du kit SDK pour Embedded C AWS IoT.

Bibliothèque AWS_IOT

On sélectionne donc la bibliothèque AWS_IOT pour ESP32 :

Le fichier de projet platformio.ini sera le suivant :

[env:lolin32]
platform = espressif32
board = lolin32
framework = arduino
lib_deps =
  AWS_IOT
upload_port = /dev/ttyUSB0
upload_speed = 115200

Il faut maintenant importer les certificats pour assurer une connexion. Pour cela, il faut éditer le fichier .piolibdeps/AWS_IOT_ID2071/src/aws_iot_certficates.c et faire un copier/coller du contenu des différents fichiers téléchargés précedemment de la manière suivante :

const char aws_root_ca_pem[] = {"-----BEGIN CERTIFICATE-----\n"
"MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\n"
...
"rqXRfboQnoZsG4q5WTP468SQvvG5\n"
"-----END CERTIFICATE-----\n"};

const char certificate_pem_crt[] = {"-----BEGIN CERTIFICATE-----\n"
"MIIDWTCCAkGgAwIBAgIUAYNodFlLN9FU57uRs79VIUYL414wDQYJKoZIhvcNAQEL\n"
...
"RBC05RL60muyhwXTxmGzeo9bWP1hty8D4tYQHnqi7pf29PX/xqX9Mkd6hiJS\n"
"-----END CERTIFICATE-----\n"};

const char private_pem_key[] = {"-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEogIBAAKCAQEAyD3RMwUkFVkXbtsA+C+9ycaI+aEWqzNhx/1CLxSqtbvte6M/\n"
...
"eHV2rMmVnevhvOfytSucGhuTNOf7SiA1XRAOUqegOiQSlxsUPhk=\n"
"-----END RSA PRIVATE KEY-----\n"};

La bibliothèque fournit plusieurs exemples à exploiter : Examples AWS_IOT.

Le principe est le suivant :

  • instancier un objet de la classe AWS_IOT :
#include <Arduino.h>
#include <AWS_IOT.h>

AWS_IOT aws;
  • définir les variables pour la connexion :
char HOST_ADDRESS[] = "XXXXXXXXXXXXXX-ats.iot.eu-central-1.amazonaws.com";
char CLIENT_ID[] = "monesp32-aws-iot"; // un id différent pour chaque client

char TOPIC_NAME_PUBLISH[] = "$aws/things/monesp32/shadow/update";
char TOPIC_NAME_SUBSCRIBE[] = "$aws/things/monesp32/shadow/#";
  • Une fois le WiFi initialisé, il faut se connecter à AWS (et éventuellement à s’abonner à un topic) :
void setup()
{
  Serial.begin(115200);
  while (!Serial);

  // initialiser le WiFi ...

  if(aws.connect(HOST_ADDRESS, CLIENT_ID)== 0)
  {
      Serial.println(F("AWS connexion ok"));
      delay(1000);

      if(aws.subscribe(TOPIC_NAME_SUBSCRIBE, mySubCallBackHandler) == 0)
      {
          Serial.println(F("AWS subscribe ok"));
      }
      else
      {
          Serial.println(F("AWS subscribe erreur !"));
      }
  }
  else
  {
      Serial.println(F("AWS connexion erreur !"));
  }
}
  • Une fois connecté à AWS, il est possible de publier sur un topic :
int msgCount = 0;
char payload[64];

void loop()
{
    sprintf(payload, "Hello from ESP32 : %d", msgCount++);
    if(aws.publish(TOPIC_NAME_PUBLISH, payload) == 0)
    {
        Serial.print(F("AWS publish : "));
        Serial.println(payload);
    }
    else
    {
        Serial.println(F("AWS publish erreur !"));
    }
    
    // etc ...
}
  • La réception de données sur un topic se fait par une fonction de callback :
void mySubCallBackHandler (char *topicName, int payloadLen, char *payLoad)
{
  Serial.print(F("AWS received topic : "));
  Serial.print(topicName);
  Serial.print(F(" -> "));
  Serial.println(payLoad);
}

Device Shadow

Une Device Shadow est une représentation persistante des objets et de leur état dans le cloud AWS.

Il est possible d’utiliser le service Device Shadow pour obtenir et/ou définir l’état d’un appareil via MQTT (ou HTTP), que l’appareil soit connecté ou non à Internet. Chaque shadow d’appareil sera identifié de façon unique par le nom de l’objet correspondant :

$aws/things/monesp32/shadow/

AWS IoT fournit trois méthodes à utiliser avec un shadow d’appareil : UPDATE (crée un shadow d’appareil s’il n’existe pas, ou met à jour le contenu avec les données fournies dans la demande), GET (récupère le dernier état stocké dans le shadow d’appareil) et DELETE (supprime un shadow d’appareil, avec tout son contenu).

Le service Device Shadow utilise ensuite des rubriques MQTT pour faciliter la communication entre les applications et l’objet connecté. On utilisera le topic pour mettre à jour l’état de l’appareil :

$aws/things/monesp32/shadow/update

AWS IoT répondra en publiant dans les topics $aws/things/monesp32/shadow/update/accepted ou $aws/things/monesp32/shadow/update/rejected.

Lien : Liste des rubriques shadow MQTT

Pour simplifier, on pourra s’abonner avec le filtre de rubrique suivant : $aws/things/monesp32/shadow/#

Un shadow d’appareil est en fait un document JSON utilisé pour stocker et extraire les informations sur l’état en cours d’un objet connecté :

  • Exemple d’envoi vers l’objet connecté pour définir la couleur et l’état de la Led :
{
    "state" : {
        "desired" : {
            "couleur" : "rouge",
            "etat" : "on"
         }
     }
}
  • Exemple d’envoi par l’objet connecté :
{
    "state" : {
        "reported" : {
            "couleur" : "rouge",
            "etat" : "on"
         }
     }
}

Pour les tests, j’ai utilisé une carte ESP32-Weather qui, autour d’un ESP32, intégre un capteur d’éclairement lumineux TSL 2591, un capteur DHT22 de température et d’humidité et une Led Bicolore. Les mesures sont affichées périodiquement sur l’écran OLED de la carte. (Le brochage des différents composants est fourni dans le code source).

Pour l’exemple, on va publier les données JSON de la manière suivante :

char payload[512];
const char payloadFormat[] = "{\"state\":{\"reported\":{\"temperature\":%.1f,\"ressentie\":%.1f,\"luminosite\":%u,\"humidite\":%u}}}";
sprintf(payload,payloadFormat,getTemperature(),getRessentie(),getLuminosite(),getHumidite());
if(aws.publish(TOPIC_NAME_PUBLISH, payload) == 0)
{
  Serial.print(F("AWS publish :"));
  Serial.println(donneesSonde);
}
else
{
  Serial.println(F("AWS publish erreur !"));
}

On obtient :

AWS connexion ok
AWS subscribe ok

AWS publish : {"state":{"reported":{"temperature":24.5,"ressentie":24.1,"luminosite":8252,"humidite":42}}}

AWS received topic : $aws/things/monesp32/shadow/update -> {"state":{"reported":{"temperature":24.5,"ressentie":24.1,"luminosite":8252,"humidite":42}}}

AWS received topic : $aws/things/monesp32/shadow/update/accepted -> {"state":{"reported":{"temperature":24.5,"ressentie":24.1,"luminosite":8252,"humidite":42}},"metadata":{"reported":{"temperature":{"timestamp":1555841961},"ressentie":{"timestamp":1555841961},"luminosite":{"timestamp":1555841961},"humidite":{"timestamp":1555841961}}},"version":1,"timestamp":1555841961} 

Avec aws-cli, les commandes iot-data permettent une communication bidirectionnelle sécurisée entre les objets connectés à Internet et le cloud AWS :

Lien : Les commandes iot-data

$ aws iot-data get-thing-shadow --thing-name "monesp32" "output.txt" && cat "output.txt"
{"state":{"reported":{"temperature":24.3,"ressentie":24.0,"luminosite":0,"humidite":45}},"metadata":{"reported":{"temperature":{"timestamp":1555874715},"ressentie":{"timestamp":1555874715},"luminosite":{"timestamp":1555874715},"humidite":{"timestamp":1555874715}}},"version":556,"timestamp":1555921093}

Alexa

Alexa est un assistant personnel intelligent développé par Amazon.com et rendu populaire par l’appareil Echo. Il est capable d’interaction vocale, de lire de la musique, faire des listes de tâches, régler des alarmes, lire des podcasts et des livres audio, et donner la météo, le trafic et d’autres informations en temps réel.

Amazon Echo est une enceinte connectée, conçue par Amazon, ayant la capacité d’obéir à la voix humaine, de parler, et, dans une certaine mesure, d’interagir avec un humain. L’appareil peut être connecté à des objets domotiques qui peuvent ainsi être contrôlés par la voix humaine.

Alexa fournit un ensemble de fonctionnalités intégrées, appelées skills (compétences). Un(e) Skill Alexa peut être considéré(e) comme une application (ou un service).

Avec ASK (Alexa Skills Kit), il est possible de développer ses propres skills et donc d’ajouter des fonctionnalités à Alexa.

Il vous faut un compte (gratuit) sur https://developer.amazon.com/alexa. Un compte amazon.fr sera reconnu. Ce compte permet d’accéder à la console de développement Alexa et à ask, la ligne de commande Alexa (CLI).

Il vous faut aussi un compte AWS (aws.amazon.com). Vous aurez besoin de la clé d’accès et la clé secrète d’un utilisateur IAM de votre compte AWS.

Remarque : ce sont des comptes différents.

Principe

Un appareil Amazon Echo s’active quand il reconnait le mot “Alexa” par défaut. C’est le wake-word qui permet déclencher le traitement vocal. Il en existe d’autres : Amazon, Echo, Ordinateur.

Les sons capturés sont ensuite envoyés vers un serveur d’Amazon (cloud).

  • Étape n°1 : Reconnaissance Automatique de la Parole (Automatic Speech Recognition - ASR). L’ASR transforme les sons en texte. Le résultat d’ASR est une chaîne de caractères.

  • Étape n°2 : Compréhension du Langage Naturel (Natural Language Understanding - NLU). NLU transforme le texte en intentions et entités. Le résultat du NLU est un document JSON.

  • Étape n°3 : Traitement de la requête par un service qui sera capable de répondre à la question ou la commande. S’il s’agit d’une commande destinée à une skill, la requête JSON sera envoyée à un web service du développeur de la skill (endpoint). Le web service doit traiter la requête et produire une répondre sous la forme d’un document JSON qui contiendra le texte qu’Alexa “lira” à l’utilisateur.

Remarque : L’IA qui gère ASR et NLU est de type d’apprentissage supervisé. Elle a besoin de données pour pouvoir extrapoler le résultat d’une analyse.

Exemple : “Alexa, joue Ac-Dc sur Dezzer”

  • Intention : jouer de la musique (“PlayMusic” par exemple)
  • Entités : “Ac-Dc” (Artiste) et “Dezzer” (Source)

Le développeur de la skill devra fournir un Modèle de Conversation (Language Model). C’est la liste des intentions comprises par la skill et les entités associées aux intentions (types et valeurs).

  • invocation (invocation) : le (ou les) mot(s) que l’utilisateur devra prononcer pour appeler la skill
  • intentions (intents) : fonctionnalités de la skill déclenchées par une ou plusieurs utterances
  • énoncés (utterances) : phrases prononcées par l’utilisateur et associées à une intention
  • entité (slot) : partie variable d’une utterance
  • endpoint : adresse du web service qui traitera la requête

Exemple : “demande à mon esp quelle est la température”

  • invocation (invocation) : “mon esp”
  • intention (intent) : GetTemperature
  • énoncé (utterance) : “quelle est la température”
  • entité (slot) : “température”

Remarque : Alexa ne veut pas du point d’interrogation dans les questions ! Dans la phrase ci-dessus, “demande à” est le déclencheur. C’est un mot de liaison qui précède le nom d’invocation de la skill. Il existe plusieurs mots déclencheurs qui permettent d’utiliser Alexa de façon naturelle : “Dis à …”, “Lance …”, “Va sur …”, …

Le endpoint peut être hébergé soit sur AWS Lambda ou sur un serveur HTTPS.

Remarque : AWS Lambda est un service informatique qui vous permet d’exécuter du code sans nécessiter la mise en service ni la gestion de serveurs.

En résumé, pour créer une skill Alexa, il faudra :

Installation et configuration de ASK CLI

Installer NodeJS :

$ sudo apt-get install nodejs

Si besoin (problème de EACCES permission denied) :

$ mkdir ~/.npm-global

$ npm config set prefix '~/.npm-global'

$ vim ~/.profile
export PATH=~/.npm-global/bin:$PATH

$ source ~/.profile

Puis :

$ npm install -g ask-cli

Il faut ensuite initialiser l’environnement avec le compte AWS et Développeur Amazon :

$ ask init
This command will initialize the ASK CLI with a profile associated with your Amazon developer credentials.
------------------------- Step 1 of 2 : ASK CLI Initialization -------------------------
Switch to "Login with Amazon" page and sign-in with your Amazon developer credentials.
If your browser did not open the page, run the initialization process again with command "ask init --no-browser".
ASK Profile "default" was successfully created. The details are recorded in ask-cli config ($HOME/.ask/cli_config).
Vendor ID set as YYYYYYYYYYYY.

------------------------- Step 2 of 2 : Associate an AWS Profile with ASK CLI -------------------------
? If you want to host your skill's backend code in AWS Lambda (recommended), you must associate an AWS profile with the ASK CLI. Do you want to associate an AWS profile? Yes

Please follow the instruction from https://developer.amazon.com/docs/smapi/set-up-credentials-for-an-amazon-web-services-account.html
Fill in the AWS Access Key ID and AWS Secret Access Key below. CLI will generate/modify the AWS credential file for you.
? AWS Access Key ID:  XXXXXXXXXXXXXXXXXXXX
? AWS Secret Access Key:  zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
AWS profile "ask_cli_default" was successfully created. The details are recorded in aws credentials file ($HOME/.aws/credentials).
AWS profile "ask_cli_default" was successfully associated with your ASK profile "default".

------------------------- Initialization Complete -------------------------
Here is the summary for the profile setup: 
  ASK Profile: default
  AWS Profile: ask_cli_default
  Vendor ID: YYYYYYYYYYYY

Création de la skill (NodeJS)

On utilise la commande ask new pour créer une skill. Il faut choisir l’environnement d’exécution de la skill (NodeJS ou Python) puis un exemple GitHub depuis une liste (le squelette de l’exemple Hello World suffira ici) :

$ ask new
? Please select the runtime Node.js V8
? List of templates you can choose Hello World
? Please type in your skill name:  skill-esp32
Skill "skill-esp32" has been created based on the chosen template

$ cd skill-esp32/
$ ls -l
drwxr-xr-x 2 tv tv  4096 avril 12 07:11 hooks
drwxr-xr-x 2 tv tv  4096 avril 12 07:11 instructions
drwxr-xr-x 3 tv tv  4096 avril 12 07:11 lambda
-rw-r--r-- 1 tv tv 11358 avril 12 07:11 LICENSE.txt
drwxr-xr-x 2 tv tv  4096 avril 12 07:11 models
-rw-r--r-- 1 tv tv  2033 avril 12 07:11 README.md
-rw-r--r-- 1 tv tv   784 avril 12 07:11 skill.json

On trouve notamment :

  • .ask : répertoire caché qui contient le fichier de configuration de ASK CLI
  • hooks : répertoire qui contient les scripts post_new_hook et pre_deploy_hook à exécuter si besoin avant et après un déploiement.
  • lambda : répertoire qui contient le code source de la fonction.
  • models : répertoire qui contient les modèles de conversation de la skill par langue. Il existe par défaut celui pour l’anglais en-US.json.
  • skill.json : le fichier qui contient les métadonnées de la skill, nécessaire pour publication sur le store.

On rappelle que pour créer une skill, il faudra :

On va donc modifier 3 fichiers :

  • Le modèle de conversation fr-FR.json avec le nom d’invocation, les phrases et les valeurs des entités en français ;
  • Les métadonnées de la skill skill.json avec le nom et sa la description ;
  • Le code de la fonction Lambda index.js avec les réponses qui seront lues avec la voix d’Alexa.

Extrait de ces fichiers :

$ cp models/en-US.json models/fr-FR.json
$ vim models/fr-FR.json
{
  "interactionModel": {
    "languageModel": {
      "invocationName": "mon esp",
      "intents": [
        {
          "name": "AMAZON.CancelIntent",
          "samples": []
        },
        {
          "name": "AMAZON.HelpIntent",
          "samples": []
        },
        {
          "name": "AMAZON.StopIntent",
          "samples": []
        },
        {
          "name": "GetTemperature",
          "samples": [
            "quelle est la température actuelle",
            "quelle est la température",
            "quelle température",
            "dis moi la température actuelle",
            "dis moi la température",
            "dis moi température",
            "dis la température actuelle",
            "dis la température",
            "dis température",
            "donne moi la température actuelle",
            "donne moi la température",
            "donne moi température",
            "donne la température actuelle",
            "donne la température",
            "donne température"
          ]
        },
        ...
        ]
    }
  }
}

Remarque : pour cette première skill, j’ai décidé de ne pas utiliser de slot (température, humidité, …). Et ce n’est vraiment pas une bonne idée ! Voir : Slot Type Reference

$ vim skill.json 
{
  "manifest": {
    "publishingInformation": {
      "locales": {
        "fr-FR": {
          "summary": "Description courte",
          "examplePhrases": [
            "Alexa ouvre mon esp",
            "mon esp",
            "aide"
          ],
          "name": "skill-esp32",
          "description": "Description longue"
        }
      },
      "isAvailableWorldwide": true,
      "testingInstructions": "Sample Testing Instructions.",
      "category": "KNOWLEDGE_AND_TRIVIA",
      "distributionCountries": []
    },
    "apis": {
      "custom": {
        "endpoint": {
          "sourceDir": "lambda/custom",
          "uri": "ask-custom-skill-esp32-default"
        }
      }
    },
    "manifestVersion": "1.0"
  }
}

Le code de la skill se trouve dans le fichier lambda/custom/index.js :

$ vim lambda/custom/index.js 
/* eslint-disable  func-names */
/* eslint-disable  no-console */

const Alexa = require('ask-sdk-core');

// Configuration AWS IoT
var config = {};
config.IOT_BROKER_ENDPOINT      = "<your-broker-endpoint>".toLowerCase();
config.IOT_BROKER_REGION        = '<broker-region>';
config.IOT_THING_NAME           = '<the-thing-name>';
config.IOT_ACCESS_KEY_ID        = '<your-access-key-id>';
config.IOT_SECRET_ACCESS_KEY    = '<your-access-key-secret>';
config.params                   = { thingName: config.IOT_THING_NAME };

// Bibliothèque AWS SDK
var AWS = require('aws-sdk');

// Initialisation AWS
AWS.config.region = config.IOT_BROKER_REGION;
AWS.config.update({accessKeyId: config.IOT_ACCESS_KEY_ID, secretAccessKey: config.IOT_SECRET_ACCESS_KEY});
var iotData = new AWS.IotData({endpoint: config.IOT_BROKER_ENDPOINT});

...

const GetTemperatureHandler = {
  canHandle(handlerInput) {
    console.log("GetTemperatureHandler [canHandle] request.type -> " + handlerInput.requestEnvelope.request.type);
    if(handlerInput.requestEnvelope.request.type === 'IntentRequest')
        console.log("GetTemperatureHandler : [canHandle] request.intent.name -> " + handlerInput.requestEnvelope.request.intent.name);
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && handlerInput.requestEnvelope.request.intent.name === 'GetTemperature';
  },
  async handle(handlerInput) {
    const responseBuilder = handlerInput.responseBuilder;
    const reponse = await getTemperature();
    console.log("GetTemperatureHandler : reponse -> " + reponse);
    return responseBuilder
      .speak(reponse)
      .withSimpleCard('mon esp', reponse)
      .getResponse();
  },
};

...

const skillBuilder = Alexa.SkillBuilders.custom();

exports.handler = skillBuilder
  .addRequestHandlers(
    LaunchRequestHandler,
    GetTemperatureHandler,
    GetTemperatureRessentieHandler,
    GetHumiditeHandler,
    GetLuminositeHandler,
    AllumerLedHandler,
    EteindreLedHandler,
    HelpIntentHandler,
    CancelAndStopIntentHandler,
    SessionEndedRequestHandler
  )
  .addErrorHandlers(ErrorHandler)
  .lambda();

function getTemperature() {
  return new Promise(((resolve, reject) => {
    iotData.getThingShadow(config.params, function (err, data) {
        let speechText = '';
        if (err) 
        {
            console.log(err, err.stack);
            speechText = "Impossible d\'obtenir la température";
            reject(speechText);
        }
        else
        {
            console.log(data);
            var payload = JSON.parse(data.payload);
            var temperature = payload.state.reported.temperature;
            console.log("temperature : " + temperature);
            speechText = "La température est de " + temperature + " degrés celcius";
            resolve(speechText);
        }
    });
  }));
}

...

function allumerLed() {
    var payloadObj = { "state":
                          { "desired":
                                   {"led":1}
                          }
                     };

    var paramsUpdate = {
        "thingName" : config.IOT_THING_NAME,
        "payload" : JSON.stringify(payloadObj)
    };

    // Topic : $aws/things/monesp32/shadow/update/delta
    iotData.updateThingShadow(paramsUpdate, function(err, data) {
      if (err){
        console.log(err, err.stack);
      }
    });
}

function eteindreLed() {
    var payloadObj = { "state":
                          { "desired":
                                   {"led":0}
                          }
                     };

    var paramsUpdate = {
        "thingName" : config.IOT_THING_NAME,
        "payload" : JSON.stringify(payloadObj)
    };

    // Topic : $aws/things/monesp32/shadow/update/delta
    iotData.updateThingShadow(paramsUpdate, function(err, data) {
      if (err){
        console.log(err, err.stack);
      }
    });
}


$ cd lambda/custom/
$ npm install
$ cd ../..

Déploiement :

$ ask deploy
Profile for the deployment: [default]
-------------------- Update Skill Project --------------------
Skill Id: amzn1.ask.skill.46fca081-3306-485e-9e89-76e7e87cd374
Skill deployment finished.
Model deployment finished.
Lambda deployment finished.
Lambda function(s) updated:
  [Lambda ARN] arn:aws:lambda:us-east-1:494915339550:function:ask-custom-skill-esp32-default
[Info]: No in-skill product to be deployed.
Your skill is now deployed and enabled in the development stage. Try simulating your Alexa skill using "ask dialog" command.

Sur la console ASK, on peut voir que la skill a été créee :

Et son modèle de conversation aussi :

On va aussi vérifier que notre fonction lambda a bien été créée à partir de la console AWS :

On va s’assurer que la fonction lambda est bien liée avec la skill d’Alexa et le service AWS IoT :

Il faudra évidemment que l’utilisateur IAM de votre compte AWS possèdent les autorisations nécessaires (ici en version développement) :

Test de la skill :

$ ask dialog --locale fr-FR
  User  >  demande à mon esp quelle est la luminosité
  Alexa >  La luminosité est de 0 lux
  User  >  demande à mon esp quelle est la température ressentie
  Alexa >  La température ressentie est de 24 degrés celcius
  User  >  demande à mon esp quelle humidité
  Alexa >  L'humidité est de 45 pourcent
  User  >  demande à mon esp allume led
  Alexa >  d'accord
  User  >  demande à mon esp eteindre led
  Alexa >  d'accord
  User  >  demande à mon esp quelle luminosité
  Alexa >  La luminosité est de 8134 lux
  User  >  demande à mon esp allume led
  Alexa >  d'accord
  ...
  User  >  ouvre mon esp
  Alexa >  Bienvenue sur mon esp, vous pouvez me demander la température, l'humidité ou la luminosité.
  User  >  température
  Alexa >  La température est de 25.2 degrés celcius

Sur l’ESP32 :

...
AWS réception [delta] : {"version":668,"timestamp":1556033949,"state":{"led":1},"metadata":{"led":{"timestamp":1556029994}}}
AWS réception [delta] : {"version":669,"timestamp":1556033995,"state":{"led":0},"metadata":{"led":{"timestamp":1556033995}}}

On peut accéder aux logs à partir de la console AWS CloudWatch :

Codes sources

Vidéos

Liens

Et les notions :

  • Règles pour AWS IoT : Les règles offrent aux objets connecté la possibilité d’interagir avec les services AWS.
  • Tâches AWS IoT : Des tâches AWS IoT peuvent être utilisées pour définir un ensemble d’opérations distantes qui sont envoyées vers un ou plusieurs objets connectés.

Voir aussi

Retour au sommaire