Tu primer proyecto IoT Cloud (II): Registro en AWS IoT y puesta a punto de M5Stack

Ismael Rihawi    10 septiembre, 2020

En el primer de los capítulos de esta serie planteamos un reto: tangibilizar las aproximaciones teóricas acerca del estado del arte del Internet de las Cosas y los servicios que la nube provee para aproximar disciplinas y unir capacidades. Definida la visión global de proyecto y los requisitos imprescindibles para su implementación (¡tiempo se ha concedido para equiparse!), no queda otra que ponernos en marcha.

1. AWS IoT Device Management: things y buenas prácticas

Para poder establecer comunicación con un dispositivo ESP32, el primer requerimiento es el de dar de alta el mismo en la nube como representación virtual del objeto físico. Este procedimiento permite generar certificados únicos de seguridad, así como especificar sobre qué topics dispondrá de permisos de publicación o suscripción de mensajes. Para ello, es necesario recrear los siguientes pasos:

  • Inicie sesión con sus credenciales de acceso en la consola de administración de AWS, navegue hasta el servicio AWS IoT Device Management. Ya allí, observará en el panel lateral izquierdo un listado de múltiples opciones, destacando el apartado “Manage” desplegado. Haga clic sobre “Things” (de normal seleccionada por defecto) y en “Register a thing” sobre la ventana principal, tal como se muestra en la siguiente imagen:
Figura 2. Registro de nuestro dispositivo ESP32 M5Stack en AWS IoT Device Management
Figura 2. Registro de nuestro dispositivo ESP32 M5Stack en AWS IoT Device Management
  • En la siguiente ventana del asistente, haga clic en “Create a single thing“.
  • Es tiempo de añadir información identificativa de la cosa, necesaria para el registro de la entidad y su shadow en AWS IoT. Establezca por nombre m5stack-fire-01, manteniendo el resto de campos restantes con sus valores por defecto. Opcionalmente, es recomendable llevar a cabo una serie de buenas prácticas para adecuarse al dimensionamiento de proyecto real de Internet de las Cosas, en donde múltiples dispositivos de distinta naturaleza pudieran estar conviviendo cumpliendo distintas finalidades, a destacar:
    • Aplicar un tipo (“thing type“) para simplificar la administración de aquellos que sean de la misma familia, marca o recursos. Esta agrupación permite asociar características en común (atributos y tags), facilitando la identificación en masa para labores de mantenimiento, auditoría y/o inventariado de hardware. A modo de propuesta, crearemos un tipo de nombre event-recognition-unit, descripción “Registry counter units for technology current events.” y tag clave-valor “device-type“: “sender“.
    • Los grupos de cosas (“thing groups“) facilitan la gestión remota IoT, sintetizando la ejecución de tareas: envío de ficheros, actualización de SO, aplicativos, etc.. Continuando con nuestro ejemplo, crearemos un nuevo grupo representativo del siguiente evento tecnológico del que el microcontrolador formará parte, de nombre “current-technology-event“, conservando el resto de campos tal como figuran por defecto.
Figura 3. Definición de nombre de dispositivo, tipo y grupo de cosas
Figura 3. Definición de nombre de dispositivo, tipo y grupo de cosas
  • Escoge “One-Click certificate creation“; una vez generados, descargar únicamente el certificado del thing (el primero en la lista, de extensión .pem), la clave privada (.key) y la Amazon Root CA 1. Aparte, es fundamental hacer clic en el botón “Activate” para habilitarlo en el registro y posibilitar la conectividad. Tras ello, haga clic en “Attach a policy“.
  • Omitir agregar una política y seleccionando “Register Thing“. Esto dará por concluido el asistente, regresando al panel principal de AWS IoT Device Management con el nuevo periférico dado de alta.
  • En el menú lateral de la consola de AWS IoT, esta vez haga clic en “Secure“, “Policies” y “Create“. Establezca por nombre “M5StackPolicy“, y seleccione la pestaña “Advanced mode” para añadir la siguiente plantilla de política genérica:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:Connect",
      "Resource": [
        "arn:aws:iot:REGION:ACCOUNT_ID:client/M5StackFire01"
      ],
      "Condition": {
        "ForAllValues:StringEquals": {
          "iot:ConnectAttributes": [
            "PersistentConnect"
          ]
        }, 
        "Bool": {
          "iot:Connection.Thing.IsAttached": [
            "true"
          ]
        }
      }
    },
    {
      "Effect": "Allow",
      "Action": "iot:Subscribe",
      "Resource": [
        "arn:aws:iot:REGION:ACCOUNT_ID:topicfilter/m5stack/sub"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "iot:Receive",
      "Resource": [
        "arn:aws:iot:REGION:ACCOUNT_ID:topic/m5stack/sub"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "iot:Publish",
      "Resource": [
        "arn:aws:iot:REGION:ACCOUNT_ID:topic/m5stack/pub"
      ]
    }
  ]
}
  • Sobre la plantilla, reemplazar REGION y ACCOUNT_ID (este segundo, sin guiones medios) por los valores correspondientes definidos en su cuenta AWS. Pueden encontrarse encontrar en la esquina superior derecha de la ventana de la consola de administración de AWS.
Figura 4. Definición de nombre de dispositivo, tipo y grupo de cosas
Figura 4. Definición de nombre de dispositivo, tipo y grupo de cosas
  • Haga clic en “Create“.
  • Nuevamente en la consola de AWS IoT, elija “Secure“,y sobre el menú desplegado en “CertificatesSeleccione el creado para su dispositivo y haga clic sobre “Actions” y “Attach policy“.
  • Elija M5StackPolicy sobre el listado de políticas de seguridad y seleccione “Attach“.

Llegados a este punto, el dispositivo IoT se encuentra registrado y con permisos de autenticación sobre el Hub de AWS IoT, pudiendo publicar en la cola de mensajes m5stack/pub y suscribirse sobre los topis emitidos en m5stack/sub.

Por último, es conveniente obtener el endpoint MQTT de la cuenta AWS, punto de acceso único a compartir con todo thing con el que desee aprovisionar la solución (en este caso, únicamente uno). Esta información debe resguardarse junto a los certificados generados y descargados anteriormente para incorporar en siguientes pasos dentro de la lógica (código) del artefacto hardware.

Figura 5. Endpoint MQTT necesario para la interconexión IoT Cloud
Figura 5. Endpoint MQTT necesario para la interconexión IoT Cloud

2. Arduino IDE: Puesta a punto

Contando ya con el entorno de desarrollo instalado en su equipo, llevaremos a cabo una serie de ajustes previos de configuración indispensables. Con ellos, el IDE reconocerá al kit de desarrollo y prototipado M5Stack-Fire como módulo compatible para su programación, así como la instalación de librerías requeridas en tiempo de compilación.

  • Abra Arduino IDE, y en el menú superior de opciones, seleccione “Archivo” y dentro, sobre la opción “Preferencias”.
  • Al final de la ventana emergente encontrará la opción “Gestor de URLs Adicionales de Tarjetas“, agregar la siguiente url, presionando en última instancia el botón “OK“:
https://dl.espressif.com/dl/package_esp32_index.json
  • Ahora diríjase sobre el menú principal a “Herramientas“, “Placas” y “Gestor de tarjetas“. En la barra de búsqueda, escriba esp32 e instale la última versión. Una vez se complete el proceso, haga clic en el botón “Cerrar“. Cuando volvamos a abrir el gestor de tarjetas veremos que se han añadido en la parte inferior las tarjetas instaladas. Seleccionaremos la tarjeta que vamos a utilizar; en nuestro caso la tarjeta de la familia ESP32 Arduino M5Stack Fire:
Figura 6. Elección de modelo de microcontrolador elegido para nuestra PoC desde Arduino IDE
Figura 6. Elección de modelo de microcontrolador elegido para nuestra PoC desde Arduino IDE
  • Únicamente resta para completar este setup inicial la instalación de dependencias de software para habilitar el protocolo de conectividad MQTT y el tratamiento de mensajes en JavaScript Object Notation (JSON). Por ello, sobre el menú superior, haga clic en esta ocasión sobre “Programa“, luego en “Incluir Librería” y “Administrar Bibliotecas…“; sobre el cuadro de opciones en la ventana emergente, instala las últimas versiones de MQTT (de Joel Gaehwiler), ArduinoJson y Adafruit Neopixel sobre la barra de búsqueda.

3. “…Developers, Developers, Developers…!”

Apartado enfocado en la codificación del desarrollo a incorporar en M5Stack, encargado de ordenar peticiones de servicio según tipología de asistente al evento tecnológico hacia la nube, así como procesar cualquier mensaje que se quiera transmitir en sentido inverso.

  • Abra Arduino IDE en su equipo, haga clic en Archivo, Nuevo para crear un nuevo sketch o boceto (de extensión .ino).
  • Es necesario agregar además una nueva pestaña, asignarle por nombre “secrets.h“. Representa el fichero cabecera con todos los componentes requeridos; en este caso, hay que pegar la plantilla de código que figura a continuación, y sobre ella, rellenar la siguiente información:
    • Nombre del thing registrado en AWS IoT Device Management (en constante de nombre “THINGNAME“): m5stack-fire-01
    • Parámetros de conectividad al punto de acceso disponible WiFi. (constantes “WIFI_SSID” y “WIFI_PASSWORD“).
    • Endpoint MQTT único de nuestra cuenta AWS (“AWS_IOT_ENDPOINT”).
    • Copy-paste de los 3 certificados descargados: Amazon Root CA 1 (“AWS_CERT_CA“), certificado (“AWS_CERT_CRT“) y clave privada del dispositivo (“AWS_CERT_PRIVATE“).
#include <pgmspace.h>

#define SECRET
#define THINGNAME "m5stack-fire-01"

const char WIFI_SSID[] = "YOUR WIFI NETWORK NAME (SSID)";
const char WIFI_PASSWORD[] = "WIFI PASSWORD";
const char AWS_IOT_ENDPOINT[] = "xxxxx.amazonaws.com";

// Amazon Root CA 1
static const char AWS_CERT_CA[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
)EOF";

// Device Certificate
static const char AWS_CERT_CRT[] PROGMEM = R"KEY(
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
)KEY";

// Device Private Key
static const char AWS_CERT_PRIVATE[] PROGMEM = R"KEY(
-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----
)KEY";
  • Ahora seleccione la pestaña original con el fichero Sketch, copie el siguiente código y guarde los cambios, asignándole como nombre event-handler.ino. Posteriormente pasaremos a describir brevemente las secciones más relevantes:
#include "secrets.h"
#include "WiFi.h"
#include <WiFiClientSecure.h>
#include <MQTTClient.h>
#include <ArduinoJson.h>
#include <stdio.h>
#include <stdlib.h>
#include <ezTime.h>
#include <time.h>
#include <M5Stack.h>
#include <Adafruit_NeoPixel.h>

// How many times we should attempt to connect to WiFi
#define WIFI_MAX_RECONNECT_TRIES 15

// The MQTT topics that this device should publish/subscribe
#define AWS_IOT_PUBLISH_TOPIC "m5stack/pub"
#define AWS_IOT_SUBSCRIBE_TOPIC "m5stack/sub"

// How many times we should attempt to connect to AWS
#define AWS_MAX_RECONNECT_TRIES 10

#define NOTE_D1 294
#define NOTE_DL1 147
#define NOTE_DH1 589

#define M5STACK_FIRE_NEO_NUM_LEDS 10
#define M5STACK_FIRE_NEO_DATA_PIN 15
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(M5STACK_FIRE_NEO_NUM_LEDS, M5STACK_FIRE_NEO_DATA_PIN, NEO_GRB + NEO_KHZ800);

WiFiClientSecure net = WiFiClientSecure();
MQTTClient client = MQTTClient(256);

void connectToWiFi()
{
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  M5.Lcd.print("Connecting to Wi-Fi");

  // Only try 10 times to connect to the WiFi
  int retries = 0;
  while (WiFi.status() != WL_CONNECTED && retries < WIFI_MAX_RECONNECT_TRIES){
    delay(500);
    Serial.print(".");
    retries++;
  }
  
  while (WiFi.status() != WL_CONNECTED){
    M5.Lcd.print(".");
    esp_sleep_enable_timer_wakeup(1 * 60L * 1000000L);
    esp_deep_sleep_start();
  }
  M5.Lcd.println("");
}

void connectToAWSIoT()
{
  // Configure WiFiClientSecure to use the AWS IoT device credentials
  net.setCACert(AWS_CERT_CA);
  net.setCertificate(AWS_CERT_CRT);
  net.setPrivateKey(AWS_CERT_PRIVATE);

  // Connect to the MQTT broker on the AWS endpoint we defined earlier
  client.begin(AWS_IOT_ENDPOINT, 8883, net);

  // Create a message handler
  client.onMessage(messageHandler);
  M5.Lcd.print("Connecting to AWS IoT");
  int attempts = 0;
  while (!client.connect(THINGNAME) && attempts < AWS_MAX_RECONNECT_TRIES) {
    delay(50);
    M5.Lcd.print(".");
    attempts++;
  }
  M5.Lcd.println("");

  if(!client.connected()){
    M5.Lcd.println("AWS IoT Timeout!");
    return;
  }

  // Subscribe to a topic
  client.subscribe(AWS_IOT_SUBSCRIBE_TOPIC);
  M5.Lcd.println("AWS IoT Connected!");
  initPixel();

  M5.Lcd.println("ESP32&AWS: Tutorial from scratch solucion IoT Cloud");
  M5.Lcd.println("https://empresas.blogthinkbig.com/esp32-y-aws-tutorial-from-scratch-de-solucion-iot-cloud");
  M5.Lcd.println("Author: Ismael Rihawi Aragon");
  M5.Lcd.println("");
  M5.Lcd.println("========================================");
  M5.Lcd.println("WELCOME TO IOT&BIG DATA TECH EVENT 2021!");
  M5.Lcd.println("- What kind of assistant are you?");
  M5.Lcd.println("A)Regular attender B)Sponsor C) Speaker");
  M5.Lcd.println("========================================");
}

void publishMessage(String attender)
{
  StaticJsonDocument<200> doc;

  doc["type_attender"] = attender;
  doc["quantity"] = 1;
  doc["access_time"] = UTC.dateTime();
  
  char jsonBuffer[512];
  serializeJson(doc, jsonBuffer);
  client.publish(AWS_IOT_PUBLISH_TOPIC, jsonBuffer);
}

void messageHandler(String &topic, String &payload) {
  StaticJsonDocument<200> doc;
  deserializeJson(doc, payload);
  const char* message = doc["message"];
  M5.Lcd.println(message);
}

void initPixel() {
  int i, j;
  for (i = 1; i < 2; ++i) {
    pixels.begin();
    for (j = 1; j < 11; ++j) {
      pixels.setPixelColor(j, pixels.Color(255, 0, 0));   
      pixels.show();
      delay(100);
    }
    pixels.clear();
    pixels.show();
  } 
}

void setup() {
  M5.begin();
  M5.Lcd.fillScreen(WHITE);
  M5.Lcd.setTextColor(BLUE);
  pixels.begin();
  Serial.begin(9600);
  connectToWiFi();
  connectToAWSIoT();
}

void loop() {
  if(M5.BtnA.wasReleased()) {
    M5.Speaker.tone(NOTE_D1, 200);
    M5.Lcd.println("You pressed REGULAR ATTENDER!");
    publishMessage("Regular attender");
    delay(1000);
  } else if(M5.BtnB.wasReleased()) {
    M5.Speaker.tone(NOTE_DH1, 200);
    M5.Lcd.println("You pressed SPONSOR ATTENDER!");
    publishMessage("Sponsor");
    delay(1000);
  } else if(M5.BtnC.wasReleased()) {
    M5.Speaker.tone(NOTE_DL1, 200);
    M5.Lcd.println("You pressed SPEAKER ATTENDER!");
    publishMessage("Speaker");
    delay(1000);
  }  

  // update button state
  M5.update();
  client.loop();
}
  • Sobre la plantilla propuesta, cabe resaltar:
    • La definición de los topics MQTT en los que llevar a cabo la emisión de mensajes o la recepción por suscripción, asignación recogida en las constantes AWS_IOT_PUBLISH_TOPIC y AWS_IOT_SUBSCRIBE_TOPIC. Por otra parte, dicha información concuerda con lo establecido dentro de la política de seguridad adjunta al certificado del dispositivo IoT en AWS IoT Device Management.
    • La función connectToWiFi() intenta establecer conexión con las credenciales de punto de conectividad proporcionados, entrando en modo de suspensión en caso no conseguirlo al cabo de un número de intentos n para eficientar el uso de la batería.
    • La función connectToAWSIoT() contiene la creación de instancias de cifrado punto a punto en el paso de mensajes (clase WiFiClientSecure) y canal de comunicación MQTT (clase MQTTClient). Los certificados codificados y el endpoint de nuestra cuenta AWS entran en juego en la configuración de los objetos para el posterior intento de establecimiento de conexión. Este proceso se encuentra acotado a un número de intentos máximos para eficientar también los recursos energéticos del dispositivo IoT; en caso afirmativo, se procede a la suscripción del topic definido y a la renderización por pantalla LCD de textos de bienvenida y menú de opciones disponibles.
    • La función publishMessage(String attender) tiene por objetivo construir un mensaje JSON a objeto de publicación por el topic correspondiente; recoge información simple para facilitar la comprensión, resaltando la inclusión del tipo de asistente al evento tecnológico seleccionado. Las librerías internas de M5Stack nos permiten capturar en todo momento si el usuario presiona el botón físico izquierdo del frontal (en cuyo caso, indica que se trata de un “Regular attender“), botón central (“Sponsor“) o derecho (“Speaker“). Este evento acciona ipso facto la creación del objeto mensaje descrito, que acto seguido se emite hacia la nube para su consumo por parte de otros suscriptores y/o accionado de reglas de negocio en cadena.
    • La función initPixel() únicamente tiene por misión iluminar con una transición de color rojo las 2 barras LED laterales que porta M5Stack cuando se conecta correctamente a AWS IoT, en claro homenaje a un icono generacional.

Flasheado de módulo ESP32

Ya sólo resta alojar nuestro código en el propio thing para dar por completada la labor de programación del tutorial, y que posibilita en esencia la interoperabilidad completa entre componentes mínimos fundamentales en una solución IoT Cloud.

  • Conecte M5Stack a un puerto USB del equipo en el que tenemos instalado y configurado (placa y librerías) la última versión de Arduino IDE.
  • Abrir herramienta de desarrollo y sketch propuesto con la lógica incorporada (fichero principal de extensión .ino y cabecera .h).
  • Cerciorarse de que se conserva la elección de la placa ESP 32 Ardunio M5Stack Fire (recordamos que se realiza en el menú superior, haciendo clic en “Herramientas“, “Placas“, “Gestor de tarjetas” y “ESP32 Arduino”).
  • Seleccionar el botón “Subir” para iniciar las etapas de compilación y volcado del código. Un log similar al siguiente debiera mostrarse por la terminal de salida del IDE, evidenciando la finalización del proceso.
Figura 7. Trazas de correcta escritura (flashing) de boceto en M5Stack Fire desde Arduino IDE
Figura 7. Trazas de correcta escritura (flashing) de boceto en M5Stack Fire desde Arduino IDE

Al instante veremos iluminarse la pantalla LCD del dispositivo, ejecutando la secuencia de acciones programadas:

Figura 8. M5Stack conectado por WiFi a AWS IoT. ¡Todo preparado para el evento del año!
Figura 8. M5Stack conectado por WiFi a AWS IoT. ¡Todo preparado para el evento del año!

Hasta aquí llega la segunda parte del tutorial. En el tercer post que completa la serie, definiremos ciertos automatismos sobre servicios Cloud de AWS recurrentes originados por los mensajes IoT emitidos. Cerraremos con una gran validación final, certificando el correcto funcionamiento de nuestro flujo de trabajo end-to-end. Stay tuned!


Todos los post de esta serie:

Tu primer proyecto IoT Cloud (I): Tutorial para solución E2E con ESP32 y AWS IoT

Tu primer proyecto IoT Cloud (II): Registro en AWS IoT y puesta a punto de M5Stack

Tu primer proyecto IoT Cloud (III y fin): Procesamiento en streaming y validación final

Para mantenerte al día con el área de Internet of Things de Telefónica visita nuestra página web o síguenos en TwitterLinkedIn YouTube.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *