Arduino Day Univag 2018: IOT - Comunicação entre máquinas


No dia 31 de março aconteceu em Cuiabá o Arduino Day Univag 2018. Centenas de pessoas estiveram no Centro Universitário Univag para compartilhar ideias e experiências sobre Arduino e o mundo maker.

O evento organizado pela Univag e pelos canais Arduino BrasilEletrônica Fácil,  contou com a presença de makers de renome como: Rodolpho Oliveira, Nascimento Júnior, Waldyr Reis, Alvaro Viebrantz, Fábio Souza, Gedeane Kenshima e eu Marcelo Maximiano, que recebi com imensa alegria o convite de participar desse evento grandioso.

Eu apresentei uma palestra com o tema "Como transformar um protótipo com Arduino em um produto final" e uma oficina prática "IoT - Comunicação entre Máquinas", que apresento neste artigo.


IOT - Comunicação entre máquinas


O objetivo da oficina era mostrar uma implementação simples de IoT (Internet das Coisas), onde um Arduino comandasse remotamente um motor de passo.
O diagrama abaixo mostra a implementação onde um Arduino envia comandos (definidos por um protocolo simples) via comunicação serial para um ESP8266, esse se conecta a um broker MQTT e publica uma mensagem com esse comando no feed "motor".
O ESP8266 no módulo receptor, faz subscrição do mesmo tópico, e assim que recebe a mensagem, a envia via comunicação serial para o Arduino que comanda através do driver de motor ULN2003, um motor de passo modelo 28BYJ-48. 

Para simplificar o exercício, utilizamos placas MBZ Wifi, pela facilidade de ter o Arduino integrado com o ESP8266. Além disso, no módulo receptor, o ULN2003 foi integrado à placa, ficando assim uma montagem única.
 
Protocolo de Comando do Motor

A mensagem de comando do motor segue um protocolo simples, definido para esse exercício, composto por 5 comandos:

I - Id do usuário
S - velocidade de movimento do motor. Valores válidos: 10-60
R - girar para a direita X graus. Valores válidos: 1-360
L - girar para a esquerda X graus. Valores válidos: 1-360
P - pausa X milisegundos. Valores válidos: 1-2000


Desta forma uma sequencia complexa de movimentos pode ser enviada em uma única transmissão para o módulo receptor, que fará o parsing da mensagem e executará comando a comando a sequencia de movimentos.
Exemplo:

I:3 S:20 R:180 P:500 L:180 S:60 R:360 

ID 3, velocidade 20, gira para a direita 180 graus, pausa por 500ms, gira para a esquerda 180 graus, aumenta a velocidade para 60 e gira para a direita 360 graus.


Criar Feed no serviço IoT

Para poder publicar os dados na nuvem, será necessário criar uma conta e configurar conforme detalhado abaixo:
1) crie uma conta no site iot.adafruit.com 
2) crie um feed com o nome "motor"


Programas 
Seguem abaixo os programas, numerados conforme o diagrama acima:
Lembre-se de instalar a biblioteca "Adafruit MQTT Library"


Módulo Emissor:
1) Arduino: envia o comando para o ESP8266
2) ESP8266: recebe o comando e envia para o MQTT broker

Módulo Receptor:
3) ESP8266: faz subscrição no MQTT broker e envia o comando para o Arduino
4) Arduino: recebe o comando e faz a movimentação do motor de passo

Programa 1 - para rodar no ATMega328P: send_atmega.ino
/*******************************************************************
  MBZ MQTT example (ATMEGA328P)
  -----------------------------
  Este programa envia comandos para girar um motor de passo para
  o módulo ESP8266.
  O ESP8266 recebe os dados e os envia o serviço de IOT da Adafruit 
  via protocolo MQTT (Message Queuing Telemetry Transport
********************************************************************/
#include <SoftwareSerial.h>

#define rxPin 2
#define txPin 3
SoftwareSerial EspSerial(rxPin, txPin); // RX, TX

#define PIN_ENABLE_ESP8266 4

void setup() {
  // seta a velocidade das portas seriais
  Serial.begin(19200);
  EspSerial.begin(19200);

  // inicializa o ESP8266
  Serial.println("Inicializando o ESP8266");
  pinMode(PIN_ENABLE_ESP8266, OUTPUT);
  digitalWrite(PIN_ENABLE_ESP8266, LOW);
  delay(200);
  digitalWrite(PIN_ENABLE_ESP8266, HIGH);
  delay(1000);

  String motorCommand = "I:3 S:20 R:180 P:500 L:180 S:60 R:360 ";
  EspSerial.print(motorCommand);
}

void loop() {
}


Programa 2 - para rodar no ESP8266: send_esp.ino 
/*******************************************************************
  MBZ MQTT example (ESP8266)
  -----------------------------
  Este programa recebe os dados uma sequencia de comandos para 
  para girar um motor de passo, e os envia para o serviço 
  IOT da Adafruit via protocolo MQTT
********************************************************************/
#include <ESP8266WiFi.h>
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"

#define SSID "redewifi"            // id do roteador WIFI
#define PWD  "senha"               // senha do roteador WIFI

#define SERVER "io.adafruit.com"   // servidor MQTT
#define PORT   1883                // porta do servidor
#define USR    "AIO_USER"          // usuário do IO Adafruit
#define KEY    "AIO_KEY"           // chave de acesso do IO Adafruit

WiFiClient client;
Adafruit_MQTT_Client mqtt(&client, SERVER, PORT, USR, KEY);

Adafruit_MQTT_Publish MOTOR = Adafruit_MQTT_Publish(&mqtt, USR "/feeds/motor");  // feed 1: luz

String serialData;

void setup() {
  Serial.begin(19200);

  // Conecta no WIFI
  Serial.println();
  Serial.print("Conectando no WIFI: ");
  Serial.println(SSID);

  WiFi.begin(SSID, PWD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();

  Serial.println("WiFi conectado");
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());
  Serial.println();

}

void loop() {
  if (getSerialData()) {
    if (mqtt.connect() == 0) {
      char buff[100];
      serialData.toCharArray(buff,100);
      // envia dados para o servidor MQTT
      MOTOR.publish(buff);
    }
  }
  serialData = "";
  delay(100);
}

bool getSerialData() {
  bool ret = false;

  if (Serial.available()) {
    // lê porta serial
    serialData = Serial.readString();
  }

  if (!serialData.equals("")) {
    ret = true;
  }

  return ret;
}

int getValue(String str, String tk) {
  String buff = "";
  int p0, p1;

  p0 = str.indexOf(tk);

  if (p0 != -1) {
    p0 = str.indexOf(":", p0) + 1;
    p1 = str.indexOf(";", p0);
    buff = str.substring(p0, p1);
  }

  return buff.toInt();
}

void MQTT_connect() {
  int8_t ret;

  // Stop if already connected.
  if (mqtt.connected()) {
    return;
  }

  Serial.print("Connecting to MQTT... ");

  uint8_t retries = 3;
  while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
    Serial.println(mqtt.connectErrorString(ret));
    Serial.println("Retrying MQTT connection in 5 seconds...");
    mqtt.disconnect();
    delay(5000);  // wait 5 seconds
    retries--;
    if (retries == 0) {
      // basically die and wait for WDT to reset me
      while (1);
    }
  }
  Serial.println("MQTT Connected!");
}



Programa 3 - para rodar no ESP8266: rec_esp.ino 
/********************************************************************
  MBZ MQTT example (ESP8266)
  -----------------------------
  Este programa faz a subscrição do feed motor e quando
  recebe a mensagem, a envia para o Arduino
*********************************************************************/
#include <ESP8266WiFi.h>
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"

#define SSID "redewifi"            // id do roteador WIFI
#define PWD  "senha"               // senha do roteador WIFI

#define SERVER "io.adafruit.com"   // servidor MQTT
#define PORT   1883                // porta do servidor
#define USR    "AIO_USER"          // usuário do IO Adafruit
#define KEY    "AIO_KEY"           // chave de acesso do IO Adafruit

WiFiClient client;
Adafruit_MQTT_Client mqtt(&client, SERVER, PORT, USR, KEY);

Adafruit_MQTT_Subscribe motor = Adafruit_MQTT_Subscribe(&mqtt, USR "/feeds/motor");

void setup() {
  Serial.begin(19200);

  // Conecta no WIFI
  Serial.println();
  Serial.print("Conectando no WIFI: ");
  Serial.println(SSID);

  WiFi.begin(SSID, PWD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();

  Serial.println("WiFi conectado");
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());
  Serial.println();

  // subscreve o feed motor
  mqtt.subscribe(&motor);
}

void loop() {
  // conecta no servidor 
  MQTT_connect();

  // verifica se há dados
  Adafruit_MQTT_Subscribe *subscription;
  while ((subscription = mqtt.readSubscription(5000))) {
    if (subscription == &motor) {
      Serial.print((char *)motor.lastread);
    }
  }
  
  delay(100);
}

void MQTT_connect() {
  int8_t ret;

  // Stop if already connected.
  if (mqtt.connected()) {
    return;
  }

  Serial.print("Connecting to MQTT... ");

  uint8_t retries = 3;
  while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
       Serial.println(mqtt.connectErrorString(ret));
       Serial.println("Retrying MQTT connection in 5 seconds...");
       mqtt.disconnect();
       delay(5000);  // wait 5 seconds
       retries--;
       if (retries == 0) {
         // basically die and wait for WDT to reset me
         while (1);
       }
  }
  Serial.println("MQTT Connected!");
}


Programa 4 - para rodar no ATMega328P: rec_atmega.ino 
/*******************************************************************
  MBZ MQTT example (ATMega328P)
  -----------------------------
  Este programa recebe a mensagem do ESP8266 e comanda o motor
********************************************************************/
#include <Stepper.h>
#include <Softwareserial.h>

#define PIN_ENABLE_ESP8266 4

#define rxPin 2
#define txPin 3
SoftwareSerial EspSerial(rxPin, txPin); // RX, TX

const int stepsPerRevolution = 512;

Stepper myStepper(stepsPerRevolution, 8, 10, 9, 11);

void setup()
{
  // seta a velocidade das portas seriais
  Serial.begin(19200);
  EspSerial.begin(19200);

  // inicializa o ESP8266
  Serial.println("Inicializando o ESP8266");
  pinMode(PIN_ENABLE_ESP8266, OUTPUT);
  digitalWrite(PIN_ENABLE_ESP8266, LOW);
  delay(200);
  digitalWrite(PIN_ENABLE_ESP8266, HIGH);
  delay(5000);
}

void loop()
{
  String st = "";

  st = getSerialData(5000);

  if (st != "") {
    Serial.print("Motor Cmd = ");
    Serial.println(st);
    processCmd(st);
  }
}

void processCmd(String cmd) {
  int p = 0;
  int ln = 0;
  int id = 0;
  int p0 = 0;
  int p1 = 0;
  int value = 0;
  String ch = "";
  String buff = "";

  // exemplo
  // I:3 S:30 R:10 S:50 L:30 P:500 S:60 R:180

  cmd.trim();
  cmd.toUpperCase();
  ln = cmd.length();

  while (p < ln) {
    ch = cmd.substring(p, p + 1);
    if ((ch == "I") || (ch == "S") || (ch == "R") || (ch == "L") || (ch == "P")) {
      p0 = cmd.indexOf(":", p) + 1;
      p1 = cmd.indexOf(" ", p0);
      if (p1 == -1) {
        p1 = ln;
      }
      buff = cmd.substring(p0, p1);
      value = buff.toInt();
      p = p1;

      if (ch == "I") {
        id = value;
      }
      
      if (ch == "S") {
        // limita a velocidade: 10 - 60
        if (value < 10) {
          value = 10;
        }
        if (value > 60) {
          value = 60;
        }
        myStepper.setSpeed(value);
      }
      
      if (ch == "R") {
        turnMotor(value, 1);
      }

      if (ch == "L") {
        turnMotor(value, 0);
      }

       if (ch == "P") {
        // limita a pausa: 1 - 2000
        if (value < 1) {
          value = 1;
        }
        if (value > 2000) {
          value = 2000;
        }
        delay(value);
      }     
    }
    p++;
  }
}

void turnMotor(long degrees, int direction) {
  long steps;

  // pega o valor absoluto
  degrees = abs(degrees);

  // converte de graus para passos
  steps = (2048 * degrees) / 360;

  // se a direcao for esquerda, deixa steps negativo
  if (direction == 0)  { // girar para a esquerda
    steps = steps * -1;
  }

  myStepper.step(steps);
}

int getValue(String str, String tk) {
  String buff = "";
  int p0, p1;

  p0 = str.indexOf(tk);

  if (p0 != -1) {
    p0 = str.indexOf(":", p0) + 1;
    p1 = str.indexOf(";", p0);
    buff = str.substring(p0, p1);
  }

  return buff.toInt();
}

String getSerialData(const int timeout)
{
  String strBuffer = "";
  long int time = millis();

  while ( (time + timeout) > millis())
  {
    if (EspSerial.available() > 0)
    {
      strBuffer = EspSerial.readString();
      break;
    }
  }

  return strBuffer;
}

0 comentários :

Postar um comentário