Neste trabalho será abordado e elaboração de um sensor de queda de árvores, destinado ao monitoramento de florestas e combate ao desmatamento, utilizando o kit de desenvolvimento da DuoDigit, o Edukit Redfox. O artigo trará a abordagem teórica de configuração do sensor e montagem, firmware de funcionamento e criação e configurações da plataforma TagoIO para tratamento dos dados.

 

FUNDAMENTAÇÃO TEÓRICA

O princípio deste sensor se baseia em um acelerômetro que através da força da gravidade possibilita definir o posicionamento de um objeto. Após a amostragem dos dados de posicionamento, estes são enviados à nuvem através do Edukit Redfox que utiliza a rede Sigfox para transferir a informação do sensor para a plataforma Sigfox.

Após chegar aos servidores da Sigfox, os dados são encaminhados para a TagoIO onde serão transformadas em dados visivelmente interpretáveis

 

LISTA DE MATERIAIS

- Edukit Redfox

- Arduino Nano

- Cabos

- Sensor MPU6050

- Computador

 

Edukit Redfox – Firmware e hardware

O Edukit Redfox é composto por um circuito dedicado à interface Sigfox e um arduino nano que intermedia os comandos entre a o barramento serial e o circuito Sigfox. Para o posicionamento foi definido o acelerômetro e giroscópio MPU6050 que possui uma gama de bibliotecas e funções disponíveis para o arduino. A ligação entre estes componentes é exibida na Figura (1) a seguir:

 

Figura (1) – Ligação entre o Edukit Redfox e o sensor MPU6050.
Figura (1) – Ligação entre o Edukit Redfox e o sensor MPU6050. | Clique na imagem para ampliar |

 

 

Com o circuito montado pode se passar para a gravação do código. Este código foi desenvolvido para realizar o envio de 1 mensagem de 12 bits a cada 2 horas, assim cumprindo a especificação da Sigfox com a máxima transferência de bits por dia de um dispositivo.

Os 12 bits enviados são a informação pura dos 3 eixos de aceleração do sensor que serão tratadas posteriormente no payload parser da TagoIO. As funções de envio de dados foram baseadas nas funções disponibilizadas pelo livro do Kit da DuoDigit.

O código a seguir exibe o firmware desenvolvido para este sensor:

 

/*-------------------------------------------------------------------------*
 * Firmware:  Deflorest Sensor Rev. 1.2                                    *
 * Autor:     Vitor Vitalino                                               *
 * Data:      28/12/2021                                                   *
 * Este programa utiliza o Edukit Redfox para realizar a leitura           *
 * dos dados de um acelerômetro MPU6050 e enviar para os servidores Sigfox *
 *-------------------------------------------------------------------------*/

//Inclui Bibliotecas Necessárias para o funcionamento 
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <SoftwareSerial.h>
#include <stdio.h>
#include <stdlib.h>
#include<Wire.h>

//Endereço I2C do sensor acelerômetro MPU6050
#define MPU6050_ADDR 0x68

//Definição de pinos referente ao Edukit Redfox 
#define RESET 4  //Reset no HT32SX
#define TX  2    //Serial TX
#define RX  3    //Serial RX
#define LED 13   //LED do arduino Nano
#define SW1 5    //Boão da placa edukit

//Criação de itens para comunicação e para o sensor
SoftwareSerial serial_HT(RX,TX);
Adafruit_MPU6050 mpu;

//Criação de variáveis
char buffer[60];

//Estrutura contendo variáveis que armazenaram os dados  
typedef struct{
  int16_t AccX,AccY,AccZ;
  int16_t GyroX,GyroY,GyroZ;
  int16_t Temp;
} MPU6050_Data;

//Declaração de estrutura do tipo de dados acima
MPU6050_Data ReadMPUData;

/*---------------------------------------------------------*
 * Função: setup()                                         *
 * Esta função inicializa todas as configurações referente *
 * a pinos e comunicação relacionados ao arduino           * 
 *---------------------------------------------------------*/
void setup() {
    //Configuração do pino de reset
    digitalWrite(RESET, HIGH);
    pinMode(RESET, OUTPUT);
    //Configuração dos pinos para o botão e o led
    pinMode(SW1, INPUT_PULLUP);
    pinMode(LED, OUTPUT);

    //Inicialização da serial primária (Usuário - Arduino)
    Serial.begin(115200); // Sensor precisa ser 115200
    while (!Serial)  delay(10);

    //Inicialização da serial secundária (Arduino - HT32)
    serial_HT.begin(9600);  //Abre serial do software conectada ao HT32SX
    reset_HT();     //Reseta o hardware HT
    delay(8000);

    serial_HT.print("AT+CFGRCZ=2;"); //Configura HT para região RC2
    delay(2000);
    
    //Inicializa a comunicação com o acelerômetro
    do{
      if (!mpu.begin()){
        Serial.println("Failed to find MPU6050 chip");
        delay(100);
      }
      else Serial.println("MPU6050 Found!");
    }while(!mpu.begin());

    //Configurações basicas doacelerômetro
    mpu.setAccelerometerRange(MPU6050_RANGE_16_G);
    mpu.setGyroRange(MPU6050_RANGE_500_DEG);
    //mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);

    Serial.println("");
    delay(100);
    
} //Fim do setup

 /*--------------------------------------------------------*
 * Função: loop()                                          *
 * Esta função contem a rotina principal de leitura e      *
 * ações que serão executadas periódicamente               * 
 *---------------------------------------------------------*/
void loop(){

    while (true)
    {
        MPU6050_ReadRawData(&ReadMPUData);
    
        sprintf(buffer, "AccX= %04X | AccY= %04X | AccZ= %04X | Temp= %04X", ReadMPUData.AccX, ReadMPUData.AccY, ReadMPUData.AccZ, ReadMPUData.Temp);
        Serial.println(buffer);    
        delay(100);
        
        Serial.println("Enviando MSG - SIGFOX");
        sprintf(buffer,"AT+SEND=0:%04x%04x%04x;", ReadMPUData.AccX, ReadMPUData.AccY, ReadMPUData.AccZ);
        
        Serial.println(buffer);
        serial_HT.print(buffer);
        delay_msg();
        delay(7200000); //Delay de 2 horas 
    }
}

 /*--------------------------------------------------------*
 * Função: reset_HT()                                      *
 * Esta função reseta o CI HX                              * 
 *---------------------------------------------------------*/
void reset_HT(){
    digitalWrite(RESET,HIGH);
    delay(1000);
    digitalWrite(RESET,LOW);
    delay(100);
} //Fim da função reset_HT

 /*--------------------------------------------------------*
 * Função: Char2Int()                                      *
 * Esta função converte dados de char para o formato int   * 
 *---------------------------------------------------------*/
int Char2Int(char c){
    int x;
    if(c >= '0' && c <='9')         x = c - '0';
    else if(c >= 'a' && c <= 'f')   x = (c - 'a') + 10;
    else                            return(-1);

    return(x);
}//Fim da função Char2Int


 /*--------------------------------------------------------*
 * Função: delay_msg()                                     *
 * Esta função separa e envia a informação do sensor para  *
 * o HX enviar através da rede Sigfox                      * 
 *---------------------------------------------------------*/
void delay_msg(){
  int Temp_H, Temp_L; 
  int AccX_H, AccX_L;
  int AccY_H, AccY_L;
  int AccZ_H, AccZ_L;
  int x, xPos;
  uint16_t i;
  char c;
  char buf_aux[80];
  
  digitalWrite(LED, HIGH);
  Serial.println("Aguarde 45s ou até o led apagar");
    
  for(i = 0; i<45000; i++)
  {
    if(serial_HT.available()) //Verifica se existe algum dado do HT
    {
      String resposta = serial_HT.readString(); //Recebe o Dado do HT
      Serial.print(resposta); //Exibe o dado recebido do HT
      
      if(resposta.indexOf("Customer resp:")> 0) // Verifica se dentro da string resposta, após a frase, se existe alguma informação
      {


        
        //--------AQUISIÇÃO AccX_H-------------------------------------
        xPos = resposta.indexOf('x'); //Procura o primeiro 'x' dentro da frase e retorna um int com a pos começando em zero 
        c = resposta[xPos+1]; // char C recebe o primeiro valor após o 'x'
        AccX_H = Char2Int(c); // int Cont converte o primeiro valor recebido após o 'x'
        c = resposta[xPos+2]; // char C recebe o segundo valor após o 'x'
        
        if(c != ',')// Compara o valor c, se não é a virgula que separa os dados
        {
          x = Char2Int(c);
          AccX_H *= 16;
          AccX_H += x;  
        }
        //--------AQUISIÇÃO AccX_L-------------------------------------
        xPos = resposta.indexOf('x', xPos+1);
        c = resposta[xPos+1];
        AccX_L = Char2Int(c);
        c = resposta[xPos+2];
        
        if(c != ',')
        {
          x = Char2Int(c);
          AccX_L *= 16;
          AccX_L += x;  
        }
        
        //--------AQUISIÇÃO AccY_H-------------------------------------
        xPos = resposta.indexOf('x', xPos+1); //Procura o primeiro 'x' dentro da frase e retorna um int com a pos começando em zero 
        c = resposta[xPos+1]; // char C recebe o primeiro valor após o 'x'
        AccY_H = Char2Int(c); // int Cont converte o primeiro valor recebido após o 'x'
        c = resposta[xPos+2]; // char C recebe o segundo valor após o 'x'
        
        if(c != ',')// Compara o valor c, se não é a virgula que separa os dados
        {
          x = Char2Int(c);
          AccY_H *= 16;
          AccY_H += x;  
        }
        //--------AQUISIÇÃO AccY_L-------------------------------------
        xPos = resposta.indexOf('x', xPos+1);
        c = resposta[xPos+1];
        AccY_L = Char2Int(c);
        c = resposta[xPos+2];
        
        if(c != ',')
        {
          x = Char2Int(c);
          AccY_L *= 16;
          AccY_L += x;  
        }
        
        //--------AQUISIÇÃO AccZ_H-------------------------------------
        xPos = resposta.indexOf('x'); //Procura o primeiro 'x' dentro da frase e retorna um int com a pos começando em zero 
        c = resposta[xPos+1]; // char C recebe o primeiro valor após o 'x'
        AccZ_H = Char2Int(c); // int Cont converte o primeiro valor recebido após o 'x'
        c = resposta[xPos+2]; // char C recebe o segundo valor após o 'x'
        
        if(c != ',')// Compara o valor c, se não é a virgula que separa os dados
        {
          x = Char2Int(c);
          AccZ_H *= 16;
          AccZ_H += x;  
        }
        //--------AQUISIÇÃO AccZ_L-------------------------------------
        xPos = resposta.indexOf('x', xPos+1);
        c = resposta[xPos+1];
        AccZ_L = Char2Int(c);
        c = resposta[xPos+2];
        
        if(c != ',')
        {
          x = Char2Int(c);
          AccZ_L *= 16;
          AccZ_L += x;  
        }
     
        ReadMPUData.AccX = AccX_H;
        ReadMPUData.AccX = ReadMPUData.AccX * 256;
        ReadMPUData.AccX = ReadMPUData.AccX + AccX_L;

        ReadMPUData.AccY = AccY_H;
        ReadMPUData.AccY = ReadMPUData.AccY * 256;
        ReadMPUData.AccY = ReadMPUData.AccY + AccY_L;

        ReadMPUData.AccZ = AccZ_H;
        ReadMPUData.AccZ = ReadMPUData.AccZ * 256;
        ReadMPUData.AccZ = ReadMPUData.AccZ + AccZ_L;

        sprintf(buf_aux, "Valor Recebido: AccX= 0x%4x, AccY= 0x%4x, AccZ= 0x%04x", ReadMPUData.AccX, ReadMPUData.AccY, ReadMPUData.AccZ);
        Serial.println(buf_aux);
      }
      if(resposta.indexOf("AT_cmd_Waiting...")> 0 )   i = 50000;   
      Serial.println(" ");
    }
    delay(1);
  }
  digitalWrite(LED, LOW);
  Serial.println("Pronto para receber novo comando");
  Serial.println(" ");
}//Fim da função delay_msg()


 /*--------------------------------------------------------*
 * Função: MPU6050_ReadRawData()                           *
 * Esta função realiza a leitura de dados provenientes do  *
 * sensor MPU 6050 e estão sem nenhum tipo de tratamento   *
 * no formato complemento de dois de 16 bits               *
 *---------------------------------------------------------*/

void MPU6050_ReadRawData(MPU6050_Data* ReadMPUDatas){
  
    Wire.beginTransmission(MPU6050_ADDR);
    Wire.write(0x3B);
    Wire.endTransmission(false);
    
    Wire.requestFrom(MPU6050_ADDR,14,true);
    ReadMPUDatas->AccX  = Wire.read() <<8 | Wire.read();
    ReadMPUDatas->AccY  = Wire.read() <<8 | Wire.read();
    ReadMPUDatas->AccZ  = Wire.read() <<8 | Wire.read();
    ReadMPUDatas->Temp  = Wire.read() <<8 | Wire.read();
    ReadMPUDatas->GyroX = Wire.read() <<8 | Wire.read();
    ReadMPUDatas->GyroY = Wire.read() <<8 | Wire.read();
    ReadMPUDatas->GyroZ = Wire.read() <<8 | Wire.read();
}

 

 

 

Callback Sigfox

Antes que o dispositivo realize a comunicação efetiva com a rede e servidores Sigfox, existe uma etapa de validação e ativação do kit, todo esse procedimento foi realizado seguindo as orientações provenientes do livro IoT Sigfox com Edukit Redfox.

Após a etapa de validação do kit foi feita a criação de um call-back, que é um serviço da Sigfox que encaminha os dados recebidos do sensor para a plataforma TagoIO. Para este sensor o call-back foi desenvolvido na seguinte forma descrita pela Figura (2) a seguir:

 

Figura (2) – Estrutura do call-back desenvolvida para o sensor de queda de árvore.
Figura (2) – Estrutura do call-back desenvolvida para o sensor de queda de árvore. | Clique na imagem para ampliar |

 

 

Com esta configuração o Backend Sigfox estará enviando os dados contendo informações sobre o valor da força gravitacional distribuídas entre o eixo x, y e z, cada eixo está associado a uma variável de 4 bits e enviados nesta respectiva sequência.

Para enviar estes dados da forma correta deve-se inserir a estrutura JSON de acordo com o código a seguir.

[
     {
          "variable": "seqNumber",
          "value": "{seqNumber}",
          "serie": "{time}"
     },
     {
          "variable": "time",
          "value": "{time}",
          "serie": "{time}"
     },
     {
          "variable": "device",
          "value": "{device}",
          "serie": "{time}"
     },
     {
          "variable": "location",
          "value": "Sensor_Position",
          "location": {
               "lat": -12.123,
               "lng": -12.123
               }
     },
     {
          "variable": "AccX",
          "value": "{customData#AccX}",
          "serie": "{time}"
     },
     {
          "variable": "AccY",
          "value": "{customData#AccY}",
          "serie": "{time}"
     },
     {
          "variable": "AccZ",
          "value": "{customData#AccZ}",
          "serie": "{time}"
     }
]

 

 

TagoIO

Com o dispositivo e o Backend Sigfox configurados, podemos partir para a etapa onde os dados serão transformados e exibidos em um dashboard.

Na plataforma TagoIO, para receber os dados de um sensor e interpretá-los, primeiramente devemos cadastrá-los neste portal, a Figura(3) exibe como foi feito o cadastro deste sensor.

 

Figura (3) – Cadastro do dispositivo Edukit Redfox na plataforma TagoIO.
Figura (3) – Cadastro do dispositivo Edukit Redfox na plataforma TagoIO. | Clique na imagem para ampliar |

 

 

Além do cadastro também deve-se gerar uma autorização, a qual é inserida no Backend Sigfox que permite a inserção dos dados neste sistema.

Após a criação do dispositivo, para este sensor é necessário uso do Payload Parser, para realizar um prévio tratamento das informações dos dados, visto que estes valores quando chegam da Sigfox ainda estão no formato de complemento de dois de 16 bits.

Na seção Payload Parser deve se habilitar o a opção “Run your own payload parser” e inserir o seguinte código:

{
    // Calculate number of bits of N-1;
    let x = parseInt(Math.log(n) / Math.log(2)) ;
    let m = 1 << x;
    m = m | m - 1;
    n = n ^ m;
    return n;
}

const ignore_vars = ['rf_chain', 'channel', 'modulation', 'app_id', 'dev_id', 'time', 'gtw_trusted', 'port', 'seqNumber', 'time', 'device'];

// Remove unwanted variables.
payload = payload.filter(x => !ignore_vars.includes(x.variable));

const AccelerationX = payload.find(x => x.variable === "AccX");
if(AccelerationX){
  if((AccelerationX.value & 0x8000) == 0x8000){
      AccelerationX.value = invertBits(AccelerationX.value);
      AccelerationX.value = (AccelerationX.value + 1)*(-1)/2048;
      //AccelerationX.value = (AccelerationX.value + 1)*(-1);
  }
  else{
    AccelerationX.value = (AccelerationX.value)/2048;
  }
}

const AccelerationY = payload.find(x => x.variable === "AccY");
if(AccelerationY){
  if((AccelerationY.value & 0x8000) == 0x8000){
    AccelerationY.value = invertBits(AccelerationY.value);
    AccelerationY.value = (AccelerationY.value + 1)*(-1)/2048;
    //AccelerationY.value = (AccelerationY.value + 1)*(-1);
  }
  else{
    AccelerationY.value = (AccelerationY.value)/2048;
 }
}

const AccelerationZ = payload.find(x => x.variable === "AccZ");
if(AccelerationZ){
  if((AccelerationZ.value & 0x8000) == 0x8000){
    AccelerationZ.value = invertBits(AccelerationZ.value);
    AccelerationZ.value = (AccelerationZ.value + 1)*(-1)/2048;
    //AccelerationZ.value = (AccelerationZ.value + 1)*(-1);
  }
  else{
    AccelerationZ.value = (AccelerationZ.value)/2048;
  }
}

 

Agora que todas as configurações estão de acordo, podemos prosseguir com a construção do dashboard com as informações que são relevantes. Para este projeto acredito que sejam relevantes as seguintes informações: Valores de aceleração no eixo x, y e z, localização do dispositivo e uma imagem descrevendo se a árvore esta em pé ou se caiu. As Figuras (4a e 4b) a seguir exemplificam como ficará o dashboard.

 

Figura (4a) – Dashboard com a árvore boa e em pé.
Figura (4a) – Dashboard com a árvore boa e em pé. | Clique na imagem para ampliar |

 

 

Figura (4b) – Dashboard com a árvore derrubada.
Figura (4b) – Dashboard com a árvore derrubada. | Clique na imagem para ampliar |

 

 

 

Aceleração nos eixos:

A aceleração em cada eixo deve estar dentro de um range de -1 a 1. Considerando que o sensor seja instalado na superfície de uma árvore e que este sensor fique paralelo com o tronco e perpendicular ao solo, como padrão o eixo y apresentará um valor próximo a 1g enquanto os dois outros eixos valores próximos a 0g.

 

 

Localização:

Sabendo que quando o sensor for instalado em uma arvore sua localização não mudará. Com isso foram passados os parâmetros de localização no widget de mapa, o que permite verificar a localização da planta caso esta for derrubada e auxiliar sua localização.

 

Imagem representativa:

A imagem representativa exibe o status o qual a árvore que está sendo monitorada se encontra, se estiver tudo bem a o dashboard exibirá uma árvore em pé, caso contrário o sistema exibirá uma árvore derrubada.

 

CONSIDERAÇÕES FINAIS

Com o desenvolvimento desta primeira versão de sensor, voltado a monitorar a integridade de uma árvore pode-se observar que a proposta cumpre os requisitos do monitoramento online, informando o posicionamento e a localização da árvore online com atualização de cada 2 horas na plataforma TagoIO.

Para futuros trabalhos existe uma sequência de sugestões que podem ser executadas. A primeira e mais impactante é a substituição de um acelerômetro com a função “fall detector” que permite o sistema atuar somente se a árvore sofrer uma queda, estendendo muito a bateria do sistema.

A segunda sugestão é uma implementação otimizada do posicionamento da árvore nos dashboards da TagoIO, inserindo mais figuras que representem o atual posicionamento da árvore e também convertendo os valores de aceleração para ângulo em graus.

Para finalizar as sugestões, com relação ao firmware, pode-se implementar um sleep mode para economia de bateria ao invés do delay que foi implementado atualmente.

 

AGRADECIMENTOS

Gostaria de deixar os meus mais profundos agradecimentos a todos os envolvidos neste projeto que promoveu a propagação de conhecimento e tecnologia, entre eles o Instituto NCB, a Mouser, DuoDigit e Sigfox. Também quero agradecer minha esposa pelo companheirismo e apoio em todos os momentos em que precisei dedicar meu tempo a este projeto.

 

REFERÊNCIAS

BERNARDES, Luiz H. Corrêa. IoT Sigfox com Edukit Redfox. São Paulo: Agbook, 2021.

www.community.tago.io

www.support.sigfox.com/docs/callbacks-documentation

www.docs.tago.io/en