quarta-feira, 28 de março de 2018

Testando MQTT-SN com Arduino + XBEE

Introdução:

    Este tutorial tem como objetivo montar um ambiente em que se possa testar o protocolo MQTT-SN. Para isso utilizaremos o RSMB (Really Small Message Broker) como base para testes de conexão, publicações e inscrições.


Materiais Necessários:

1x Arduino Mega
1x XBEE Arduino Shield
1x Arduino XBee (Configurado como Coordenator em MeshBee)
1x XBEE Explorer
1x Arduino XBee (Configurado como Router em MeshBee)

Obs: Seguir o tutorial Rede Mesh Xbee (https://bytespassivos.blogspot.com.br/2018/03/rede-mesh-xbee.html) para configurar os XBEE.


Passos:

1) Instalar a lib mqttsn
2) Fazer upload do código da Arduino
3) Compilar e instalar o RSMB
4) Criar arquivo de configuração do broker
5) Fazer o setup do ambiente
6) Testar a comunicação com o broker mqtt



1) Instalar a lib mqttsn

1. Fazer o download da lib mqttsn (https://github.com/boriz/MQTT-SN-Arduino)
2. Instalar na pasta de bibliotecas da Arduino (No meu caso Arduino/libraries)

$ cd ~/Downloads
$ git clone https://github.com/boriz/MQTT-SN-Arduino
$ cp MQTT-SN-Arduino/Arduino/libraries/mqttsn/ ~/Arduino/libraries/

2) Fazer upload do código da Arduino

1. Fazer o upload do código abaixo para a arduino:


#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <mqttsn-messages.h>

//////////////////////////////////////// MESHBEE.H DEFINES////////////////////////////////////////

#define API_DATA_LEN  20
#define API_PAY_LEN  (API_DATA_LEN + 5)
#define API_FRAME_LEN  (API_DATA_LEN + 9)

#define API_DATA_PACKET  0x02
#define API_START_DELIMITER  0x7E
#define OPTION_CAST_MASK  0x40   //option unicast or broadcast MASK
#define OPTION_ACK_MASK  0x80   // option ACK or not MASK

//////////////////////////////////////// MAIN DEFINES ////////////////////////////////////////

#define XBEE Serial3

#define API_DATA_LEN  20
#define API_PAY_LEN  (API_DATA_LEN + 5)
#define API_FRAME_LEN  (API_DATA_LEN + 9)


#define TOPIC_PUB "arduino/temp"
#define TOPIC_SUB "arduino/temp"

//////////////////////////////////////// MESHBEE.H ////////////////////////////////////////

uint8_t FrameID = 0;

// Create MeshBee frame (return frame lenght)
int MB_FrameCreate(uint8_t* data, int data_len, uint16_t dest_addr, uint8_t* frame, int frame_max_len, bool broadcast)
{
  // frame buffer is big enough?
  if ( frame_max_len < API_FRAME_LEN )
  {
    return -1;
  }
  
  // data is too long?
  // TODO: Split in multiple packets?
  if (data_len > API_DATA_LEN)
  {
    return -2;
  }
  
  // Frame buffer is fine. Clear it
  memset (frame, 0, frame_max_len); 
  
  // Header
  frame[0] = API_START_DELIMITER;  // Delimiter
  frame[1] = API_PAY_LEN;    // Length of the payload
  frame[2] = API_DATA_PACKET;    // API ID
  
  // Payload
  uint8_t cs = 0;  // CS=Sum of the payload
  cs += frame[3] = FrameID++;   // frame id  
  if (broadcast)
  {
    cs += frame[4] = OPTION_CAST_MASK; // option
  }
  cs += frame[5] = data_len; // data length
  
  // Data
  for (int i=0; i<data_len; i++)
  {
    cs += frame[6 + i] = data[i];    
  }
  cs += frame[6 + API_DATA_LEN] = (dest_addr >> 8) && 0xFF;  // Unicast address (16 bits)
  cs += frame[7 + API_DATA_LEN] = (dest_addr >> 0) && 0xFF;  // Unicast address (16 bits)
  frame[8 + API_DATA_LEN] = cs;
  
  return API_FRAME_LEN;
}


// Parse MeshBee frame (return data length)
int MB_FrameParse(uint8_t* frame, int frame_len, uint8_t* data, int data_max_len, uint16_t* src_addr)
{
  // got a full frame?
  if ( frame_len != API_FRAME_LEN )  
  {
    // TODO: May be a valid frame, keep reading?
    return -1;  
  }
  
  // Delimeter?
  if (frame[0] != API_START_DELIMITER)  // Delimiter
  {
    return -2;
  }
  
  // Payload length?
  if (frame[1] != API_PAY_LEN)  // Length of the payload
  {
    return -3;
  }
  
  // Right API ID?
  if (frame[2] != API_DATA_PACKET)  // API ID
  {
    return -4;
  }  
  
  // Payload
  int len;
  uint8_t cs = 0;  // CS=Sum of the payload
  cs += frame[3];   // frame id
  cs += frame[4];  // option
  
  // Enough space in the payload buffer?
  cs += len = frame[5];
  if (len > data_max_len)
  {
    return -5;
  }
  
  // Clear output buffer
  memset (data, 0, data_max_len);
  
  // Copy data to the buffer
  for (int i=0; i<API_DATA_LEN; i++)
  {
    if (i<len)
    {
      cs += data[i] = frame[6 + i];    
    }
    else
    {
      // Rest of the payload data (for checksum) 
      cs += frame[6 + i];
    }
  }
  *src_addr = (frame[6 + API_DATA_LEN] << 8) | frame[7 + API_DATA_LEN];  // Unicast address (16 bits)
  cs += frame[6 + API_DATA_LEN]; 
  cs += frame[7 + API_DATA_LEN];
  
  // Verify checksum
  if (cs != frame[8 + API_DATA_LEN])
  {
    return -6;
  }
  
  return len;
}


//////////////////////////////////////// MAIN ////////////////////////////////////////


MQTTSN mqttsn;
uint16_t u16TopicPubID;
uint16_t u16TopicSubID;
uint8_t u8Counter;

uint8_t FrameBufferIn[API_FRAME_LEN];
uint8_t FrameBufferOut[API_FRAME_LEN];

void setup()
{
  Serial.begin(9600);
  XBEE.begin(9600);
  
  ADMUX = 0xC8; // turn on internal reference, right-shift ADC buffer, ADC channel = internal temp sensor
  delay(100);  // wait a sec for the analog reference to stabilize
  
  u8Counter = 1;
  Serial.println("Setup done");   
}

 
void loop()
{ 
  // Check serila data
  CheckXBEE();
  
  // ------- MQTT-SN publis logic -------
  // Busy?
  if (mqttsn.wait_for_response())
  {
    // Busy. exit
    return;
  }
  
  // Connected?
  if (!mqttsn.connected())
  {
    // Not connected - connect
    mqttsn.connect(0, 10, "test1758");  // Flags=0, Duration=10
    Serial.println("Connect sent");   
    return;
  }
  
  // Topic registered?
  uint8_t index;
  u16TopicPubID = mqttsn.find_topic_id(TOPIC_PUB, &index);
  if (u16TopicPubID == 0xffff)
  {
    // Topic is not registered yet
    mqttsn.register_topic(TOPIC_PUB);
    Serial.println("Reg top sent");    
    return;  
  }
   
  // Topic is alredy registered, publish
  char str[50];
  char temp[10];
  float t = averageTemperature();
  itoa(t, temp, 10);
  sprintf(str, "%s (%i)", temp, u8Counter);
  Serial.print("Top id:");      
  Serial.print(u16TopicPubID);    
  Serial.print("; Val:");      
  Serial.println(str);      
  mqttsn.publish(0, u16TopicPubID, str, strlen(str));  // Flags=0         
  u8Counter++;
  delay(2000);
  
  // ------- MQTT-SN subscribe logic -------
  u16TopicSubID = mqttsn.find_topic_id(TOPIC_SUB, &index);
  if (u16TopicSubID == 0xffff)
  {
    // Topic is not registered yet
    mqttsn.subscribe_by_name(0, TOPIC_SUB);  // Flags=0   
    Serial.println("Sub top sent");    
    return;  
  }
  delay(2000);  
}


// gwinfo message callback
void MQTTSN_gwinfo_handler(const msg_gwinfo* msg)
{
  // Got a gateway response
  // The frame is still in the buffer - parse it
  
  Serial.println("GW info hand"); 
}


void MQTTSN_publish_handler(const msg_publish* msg)
{
  Serial.println("Sub pub hand"); 
}


// Callback funciton to send XBEE data
void MQTTSN_serial_send(uint8_t* message_buffer, int length)
{
  // Assuming that our gateway is at address 0 (coordinator)
  int len = MB_FrameCreate (message_buffer, length, 0x0000, FrameBufferOut, sizeof(FrameBufferOut), false);
  if (len > 0)
  {
    XBEE.write(FrameBufferOut, len);
    XBEE.flush();
  }
}


// XBEE event interrupt
void CheckXBEE()
{
  if (XBEE.available() > 0) 
  {
    // Wait till we got the whole packet or timeout
    long Start = millis();   
    int cnt =0;
    while (cnt < API_FRAME_LEN && (millis() - Start) < 100) 
    {
        while (XBEE.available() > 0) 
        {
            FrameBufferIn[cnt++] = (uint8_t)XBEE.read();
            
            // Reset timeout counter
            Start = millis();
        }
    }
    
    if (cnt == API_FRAME_LEN)
    {
      // Got the whole packet
      Serial.print("From ser:");  
      for (int i=0; i<cnt; i++)
      {        
        Serial.print (FrameBufferIn[i], HEX);
        Serial.print (" ");
      }
      Serial.println();   
  
      uint8_t pay[API_DATA_LEN];
      uint16_t src_addr;
      int pay_len = MB_FrameParse(FrameBufferIn, cnt, pay, sizeof(pay), &src_addr);
      if (pay_len > 0)
      {
        Serial.print("Ser payload:");  
        for (int i=0; i<pay_len; i++)
        {        
          Serial.print (pay[i], HEX);
          Serial.print (" ");
        }
        Serial.println(); 
        
        // Valid frame. Pare it
        mqttsn.parse_stream(pay, pay_len);
      }
    }
    else
    {
      // Timeout. 
      Serial.println("Chec ser timeout");  
    }
  }
}



// =================== Measure temperature =================== 
int readTemperature() 
{
  /*ADCSRA |= _BV(ADSC); // start the conversion
  while (bit_is_set(ADCSRA, ADSC)); // ADSC is cleared when the conversion finishes
  return (ADCL | (ADCH << 8)) - 342; // combine bytes & correct for temp offset (approximate)} 
  */
  return 42;
}

float averageTemperature()
{
  /*readTemperature(); // discard first sample (never hurts to be safe)

  float averageTemp; // create a float to hold running average
  for (int i = 1; i < 1000; i++) // start at 1 so we dont divide by 0
    averageTemp += ((readTemperature() - averageTemp)/(float)i); // get next sample, calculate running average

  return averageTemp; // return average temperature reading*/
  return readTemperature();
}


3) Compilar e instalar o RSMB

1. Fazer o download do RSMB (https://github.com/eclipse/mosquitto.rsmb)
2. Compilar o broker_mqtts da pasta mosquitto.rsmb/rsmb/src
3. Instalar o broker copiando o arquivo compilado broker_mqtts para o diretório /usr/bin/

$ cd ~/Downloads
$ git clone https://github.com/eclipse/mosquitto.rsmb
$ cd mosquitto.rsmb/rsmb/src/
$ make broker_mqtts

4) Criar arquivo de configuração do broker  

1. Crie um arquivo broker.cfgcom as confugurações abaixo:

$ nano ~/broker.cfg 

Obs: Copie e cole o conteúdo do broker.cfg abaixo. 
ctrl+o: Salva o arquivo
ctrl+x: Sai do editor. 
broker.cfg:
# will show you packets being sent and received
trace_output protocol      

#listener 1883 INADDR_ANY mqtt //Listen for MQTT Connections "Normal Broker"

listener 1883 INADDR_ANY mqtts //Listen for MQTT-SN Connections "Sensor Network Broker"

connection MY_MQTTSN_GATEWAY
 protocol mqtt
 address 198.41.30.241:1883 # 198.41.30.241 is the ip of iot.eclipse.org
 #address 127.0.0.1:1887 #local mosquitto broker
 topic # out


5) Fazer o setup do ambiente

1. Abra o primeiro terminal (ctrl+alt+t)
2. Encerre qualquer server que possa estar usando a porta 1883. No meu caso, quase sempre é o Mosquitto que esta ocupando essa porta.


 $ sudo killall mosquitto
3. Execute o broker_mqtts com o arquivo de configuração criado

$ cd ~/Downloads/mosquitto.rsmb/rsmb/src/
$ ./broker_mqtts ~/broker.cfg
4. Conecte a Arduino Mega (com o XBEE Router montado) ao PC (anote a porta serial ao qual está conectada)

5. Conecte o XBEE Explorer (com o XBEE Coordenator montado) ao PC (anote a porta serial ao qual está conectada)
 
5. Abra um segundo terminal

6. Execute o  Bridge Serial-UDP (~/Downloads/MQTT-SN-Arduino/serial-mqtts/ser_redir.py)
Obs: Antes de executar é necessário fazer as seguintes modificações no código:
SerPort = "/dev/ttyUSB0" (Deve ser a porta ao qual o XBEE Explorer está conectado)
Baud = 9600 (Baudrate configurado do XBEE Explorer)
BrokerHost = "127.0.0.1"
BrokerPort = 1883


$ cd ~/Downloads/MQTT-SN-Arduino/serial-mqtts/
$ python ser_redir.py
7. Aperte o botão de reset no shield XBEE.
Obs: Isso vai fazer com que a Arduino inicie o processo de conexão com o Gateway e sucessivamente com o broker online (iot.eclipse.org)

6) Testar a comunicação com o broker mqtt
1. Abra um terceiro terminal
2. Lance um cliente que se inscreva no tópico "arduino/temp"

$ mosquitto_sub -h iot.eclipse.org -p 1883 -t arduino/temp 
3. Se esse cliente estiver recebendo as publicações deste tópico então a conexão com o broker MQTT e o Gateway MQTT-SN estão funcionando corretamente.


Fontes:
[Para postar o código no post]
http://hilite.me/
https://www.engineersgarage.com/Tutorials/IoT-Communication-MQTT-SN-RSMB-Mosquitto-Broker
https://www.engineersgarage.com/Tutorials/RSMB-Broker-for-MQTT-SN

Nenhum comentário:

Postar um comentário