jueves, 15 de febrero de 2018

AWS IoT con ESP8266

Una manera fácil de integrar un ESP8266 en el Internet de las cosas es a través de la infraestructura que Amazon Web Services nos ofrece con AWS IoT.






En este articulo voy a mostrar los pasos a seguir para conectar un ESP8266 con AWS IoT.


Parte 1 - AWS

Registrarse en AWS

Para acceder a AWS IoT hay que registrarse en AWS. Solicitan una tarjeta de crédito, los primeros 12 meses se dispone de una capa gratuita, para quedarse más tranquilo se pueden comprobar los precios, fijándose especialmente en la capa gratuita.

Acceder a la consola de AWS IoT

Una vez registrados en AWS vamos a acceder ala consola de AWS IoT:


Registrar una cosa

Una vez que hemos accedido a la consola de AWS IoT, vamos a registrar nuestra primera cosa.

En el menú de la izquierda podemos acceder a la administración de cosas ("things" en inglés) desde Manage > Things, en donde veremos el botón Register a thing.


Una vez pulsado el botón nos da a elegir entre crear una sola cosa o crear muchas cosas, elegimos crear una sola cosa (Create a single thing).

Crear una cosa se compone de tres pasos, en el primer paso lo único que haremos será ponerle un nombre a nuestra cosa.



Al pulsar sobre Next accedemos al segundo paso, en donde hay que añadir un certificado a nuestra cosa. La manera más fácil es usar la opción recomendada (One-click certificate creation).



Una vez creado el certificado nos invitan a realizar 4 descargas: el certificado de la cosa, una clave pública, una clave privada y un certificado raíz para AWS IoT. Descargamos, por lo menos, el certificado y la clave privada. Hemos de activar el certificado pulsando sobre el correspondiente botón.



El tercer paso nos lo saltamos y terminamos pulsando el botón Done. Si todo ha ido bien, en Manage > Things podremos ver nuestra cosa ya registrada.



Ahora vamos a crear una política (policy) para nuestra cosa. Vamos a Secure > Policies y seleccionamos Create a policy. Hemos de poner un nombre a la política. Hemos de definir que tipo de acciones se pueden hacer sobra cada recurso, para simplificar vamos a permitir que todas las acciones se puedan realizar sobre cualquier recurso. Para ello en Action pondremos iot:* y en Resource ARN pondremos *, por último, en Effect seleccionamos Allow.


Pulsamos el botón Create para terminar.

Ahora que hemos definido nuestra política, es necesario asociarla al certificado. Para ello vamos a Secure > Certificates, seleccionamos el certificado que habiamos creado, y al mostrarse el certificado vamos a un desplegable que está arriba a la derecha y seleccionamos Attach Policy. Se nos mostrará un dialogo en el que debemos escoger la política que hemos creado y pulsar Attach.



Con esto, ya tenemos la configuración mínima para poder empezar a probar desde un ESP8266.

Parte 2 - ESP8266

Para realizar esta parte me han sido muy útiles los ejemplos de Evandro Luis Copercini.

La parte de ESP8266 se va a implementar usando Arduino IDE. Debido a que AWS IoT exige TLS 1.2 es importante que la versión de Arduino Core sea la 2.4.0-rc1, o superior, ya que es donde se introdujo el soporte para TLS 1.2.

Librería MQTT

Para comunicarnos con AWS IoT emplearemos el protocolo MQTT. Usaremos la librería de Nick O'Leary que implementa un cliente MQTT para Arduino. Se puede instalar desde el gestor de librerias de Arduino IDE.

Nos vamos a basar en el ejemplo para ESP8266 que tiene la librería.

Como la otra librería necesaria es la de ESP8266 los includes quedarán así:


#include <ESP8266WiFi.h>
#include <PubSubClient.h> //https://pubsubclient.knolleary.net/

Certificados a hexadecimal

AWS IoT emplea TLS 1.2 para cifrar las comunicaciones y para autenticar los clientes. Por ello habrá que incorporar certificados en nuestro código.

Los certificados que hemos descargado están en formato PEM (Privacy Enhanced Mail), codificados en Base64. Vamos a necesitar, en primer lugar, convertirlos a formato DER (Distinguish Encoding Rules), que es una codificación binaria. Para ello vamos a usar OpenSSL, en los sistemas Linux es habitual tenerlo instalado. En caso de usar Windows hay que descargarse OpenSSL para Windows.

Las instrucciones para realizar la conversión, sustituyendo previamente las x según el nombre de nuestros ficheros, son las siguientes:

$ openssl x509 -in xxxxxxxxxx-certificate.pem.crt -out cert.der -outform DER

$ openssl rsa -in xxxxxxxxxx-private.pem.key -out private.der -outform DER 

Ahora tenemos los ficheros en formato binario, nos queda pasarlos a hexadecimal. Para ello emplearemos el comando de Linux xxd:

$ xxd -i cert.der > cert.hex

$ xxd -i private.der > private.hex

La opción -i hace que el resultado este en un array de C.

Como en Windows no estará instalado xxd habrá que buscarse una alternativa para convertir a hexadecimal. Puede ser con un conversor online, aunque habria que valorar la seguridad de subir unos certificados a un sistema externo. Otra posibilidad es usar un ejecutable que haga la conversión.

Podemos extraer la salida de xxd para tener definidos los certificados en nuestro código:


unsigned char private_der[] = {
  0x30, 0x82, 0x04, 0xa3, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00,
  0xc4, 0x3f, 0x37, 0xd8, 0x1a, 0x75, 0xcc, 0xfe, 0x32, 0x5d, 0x71, 0x26,
  0x8d, 0x6c, 0xd5, 0x04, ...
};
unsigned int private_der_len = 1191;

unsigned char cert_der[] = {
  0x30, 0x82, 0x03, 0x59, 0x30, 0x82, 0x02, 0x41, 0xa0, 0x03, 0x02, 0x01,
  0x02, 0x02, 0x14, 0x4c, 0xe9, 0x68, 0x02, 0xae, 0x23, 0x3e, 0xee, 0x8c,
  0xf3, 0xd1, 0x42, 0xe8, ...
};

unsigned int cert_der_len = 861;

Endpoint

Hemos de indicar la dirección a la que el cliente MQTT se ha de conectar, la podemos encontrar yendo a Manage > Things, pulsamos sobre la cosa que hemos definido, y luego sobre Interact. En el apartado HTTPS nos indica lo que AWS conoce como Rest API Endpoint.


En el código quedará así:

const char* mqtt_server = "a18fbw2ljplhhm.iot.eu-west-1.amazonaws.com";

En el setup hay que configurar el cliente HTTPS con los certificados:

void setup_wifiClient() {
  wifiClient.setCertificate(cert_der, cert_der_len);
  wifiClient.setPrivateKey(private_der, private_der_len);
}

Al cliente MQTT hay que indicarle la dirección y el puerto del servidor, así como la función callback en donde se recibirán los mensajes:

void setup_mqttClient() {
  mqttClient.setServer(mqtt_server, 8883);
  mqttClient.setCallback(callback);  
}

Para suscribirse a un topic:


mqttClient.subscribe("inTopic");


Para publicar:

mqttClient.publish("outTopic", msg);


Código completo

#include <ESP8266WiFi.h>
#include <PubSubClient.h> //https://pubsubclient.knolleary.net/

//incomplete array
unsigned char private_der[] = {
  0x30, 0x82, 0x04, 0xa3, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00,
  0xae, 0x59, 0x05, 0xf3, 0x01, 0x27, 0x6f, 0xab, 0x3d, 0x6c, 0xdf, 0x1a,
  0x2b, 0x42, 0x06, 0x5e, ...
};
unsigned int private_der_len = 1191;

//incomplete array
unsigned char cert_der[] = {
  0x30, 0x82, 0x03, 0x5a, 0x30, 0x82, 0x02, 0x42, 0xa0, 0x03, 0x02, 0x01,
  0x02, 0x02, 0x15, 0x00, 0xba, 0x70, 0x77, 0x25, 0xdd, 0x0f, 0x56, 0xcb,
  0x6c, 0xb7, 0x17, 0x0f, ...
};
unsigned int cert_der_len = 862;

const char* ssid = "your_ssid";
const char* password = "your_password";
const char* mqtt_server = "a18fbw2ljplhhm.iot.eu-west-1.amazonaws.com";

WiFiClientSecure wifiClient;
PubSubClient mqttClient(wifiClient);
long lastMsg = 0;
char msg[50];
int value = 0;

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++)
    Serial.print((char)payload[i]);

  Serial.println();
}

void reconnect() {
  // Loop until we're reconnected
  while (!mqttClient.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (mqttClient.connect(clientId.c_str())) {
      Serial.println("connected");
      mqttClient.subscribe("inTopic");
    } else {
      Serial.print("failed, rc=");
      Serial.print(mqttClient.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void setup_wifiClient() {
  wifiClient.setCertificate(cert_der, cert_der_len);
  wifiClient.setPrivateKey(private_der, private_der_len);
}

void setup_mqttClient() {
  mqttClient.setServer(mqtt_server, 8883);
  mqttClient.setCallback(callback);  
}

void setup() {
  Serial.begin (115200);
  
  setup_wifi();

  setup_wifiClient();

  setup_mqttClient();
}

void loop() {
  
  if (!mqttClient.connected()) {
    reconnect();
  }
  mqttClient.loop();

  long now = millis();
  if (now - lastMsg > 10000) {
    lastMsg = now;
    ++value;
    snprintf (msg, 75, "hello world #%ld", value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    mqttClient.publish("outTopic", msg);
  }
}

El código también esta disponible en GitHub

Parte 3 - Test

Ahora que tenemos todo preparado en AWS y en nuestro ESP8266 podemos probarlo.

Conectamos el ESP8266 y leemos la salida por el puesto serie. 



Vemos que periódicamente va publicando mensajes.

Si no conecta dará un código de error. Por mi experiencia, el error -2 me ha salido cuando alguno de los certificados no es correcto y el -4 cuando los permisos están mal definidos en AWS IoT.

Volvemos a la consola de AWS IoT, en Test > Subscribe to a topic > Subscription topic introducimos el nombre de nuestro topic (outTopic).


Pulsamos sobre el botón Subscribe to topic y podemos empezar  a ver los mensajes que va publicando nuestro ESP8266.


Para publicar desde AWS IoT y que llegue al ESP8266 vamos a Test > Publish to a topic escribimos el nombre de nuestro topic (inTopic) y si queremos escribimos el mensaje que queremos enviar, o dejamos el que nos propone.


Podremos ver como el mensaje llega a nuestro ESP8266.



Con esto tenemos un ejemplo de como conectarse desde una ESP8266 a AWS IoT que puede ser la base para muchos proyectos de Internet de las cosas.

2 comentarios:

  1. Very nice! Thanks for providing your information AWS Online Course

    ResponderEliminar
  2. Muy buen aporte! Tengo una pregunta...Cuando me suscribo a un tema que creo yo todo funciona correctamente, no obstante, cuando publico un mensaje vacío en el tema $aws/things/miobjeto/shadow/get para actualizar la sombra del dispositivo miobjeto y me suscribo al tema $aws/things/miobjeto/shadow/get/acepted para recibir la actualización, el mensaje no me llega y sospecho que es porque el contenido del mensaje es un JSON ¿Alguna solución?

    ResponderEliminar