Quando o IoT encontra a Inteligência Artificial: Automação residencial com Alexa e NodeMCU

Exploraremos neste tutorial, como usar a Alexa, um assistente pessoal inteligente desenvolvido pela Amazon Lab126, popularizado pelo Amazon Echo e Echo-Dot.

Alexa é capaz de interação de voz, reprodução de música, fazer listas de tarefas, configurar alarmes, transmitir podcasts, tocar audiobooks e fornecer informações meteorológicas, de trânsito e outras informações em tempo real. Alexa também pode controlar vários dispositivos inteligentes usando-se como um hub de automação residencial. Vamos usar neste projeto, o “Echo-Dot”, que permite aos usuários ativar o dispositivo usando um wake-word (no caso, “Alexa”).

echo-dot features

No espaço da Domótica (automação residencial), Alexa pode interagir com vários dispositivos diferentes como Philips Hue, Belkin Wemo, SmartThings, etc. Em nosso caso, emularemos dispositivos do tipo WeMo, como estes mostrados abaixo (mas por apenas uma fração de seu preço):

WeMo

WeMo é uma série de produtos da Belkin International, Inc. que permitem aos usuários controlar eletrônicos domésticos de qualquer lugar. A suite de produtos inclui um interruptor, sensor de movimento, Insight Switch, interruptor de luz, câmera e app. O WeMo Switch (nosso caso aqui) pode ser conectado a qualquer tomada de casa, que pode ser controlada a partir de um iOS ou Android smartphone executando o WeMo App, via rede doméstica WiFi ou rede de telefonia móvel.

O diagrama abaixo mostra o que será desenvolvido em nosso projeto:

Home Automation Block Diagram V2E o vídeo abaixo, mostra como ficará o projeto ao final:

1: Lista de materiais (BoM)

Valores referenciais em USD

  • LEDs (vermelho e verde) ($1.00)
  • 2 x Resistor (220 ohm)
  • Fonte de alimentação externa de 5V DC ou batteria

2: Emulando o WeMo Switch

 wemo_echo_upnp

Os dispositivos WeMo utilizam UPnP para executar certas funções através da rede. A função de detecção do dispositivo começa com o Echo ou Echo-Dot (em nosso caso aqui) à procura de dispositivos WeMo utilizando UPnP. O dispositivo então responde ao Echo-Dot com seu URL utilizando HTTP sobre UDP. OEcho-Dot então solicita a descrição do dispositivo utilizando esse URL HTTP. A descrição é então retornada como uma resposta HTTP. Neste ponto, o Echo-Dot já “descobriu” o dispositivo. O Echo-Dot simplesmente se conectará ao WeMo através da interface HTTP e emitirá um comando do tipo “SetBinaryState”. O WeMo então “obedecerá”, retornando uma confirmação via HTTP. O diagrama  acima resume a comunicação entre o Echo-Dote o WeMo Switch.

Este tutorial não entrará em muito mais detalhes sobre este tópico. Procurei compilar informações obtidas em vários projetos da web, simplificando sua apresentação de maneira a apresentar uma visão geral sobre o uso da Alexa na automação residencial .

Algumas excelentes fontes de informação sobre o assunto poderão ser encontradas nos links abaixo:

HOW TO MAKE AMAZON ECHO CONTROL FAKE WEMO DEVICES, escrito por Rick Osgut

Building an IoT power switch with the ESP8266 (and control it with your Amazon Echo!)  escrito por  Wai Lun

Amazon Alexa + WeMos switch made with Arduino D1 Mini , codigos desenvolvidos por Aruna Tennakoon:

Projects using NodeMCU, conjunto de códigos desenvolvidos por Christopher Kuzma

Dito isto, com o que fui aprendendo nos diversos sites acima terminei chegando a um código de teste, o qual você poderá baixar desde meu GitHib: Alexa_LED_Control_V2_EXT.ino

Verifique se você possui todas as bibliotecas necessárias para executar o código, como: ESP8266WiFi.h, ESP8266WebServer.h e WiFiUdp.h. Caso não as tenha, você poderá obtê-las aqui:  Arduino core for ESP8266 WiFi chip.

3: Criando nosso WeMo Switch com o NodeMCU

Alex and NodeMCU

Para o nosso primeiro teste, conectemos um LED ao NodeMCU pino D1 como mostrado no diagrama abaixo:

Abra o arquivo  Alexa_LED_Control_V2_EXT.ino , o qual você baixou de meu GitHub e entre com suas credenciais de rede:

const char* ssid = "YOUR SSID";
const char* password = "YOUR PASSWORD";

Confirme se você definiu corretamente o pino onde o LED está conectado e dê um nome ao seu dispositivo. Alexa reconhecerá seu dispositivo por este nome:

String device_name = "lights";  // Name of device
int relayPin = D1;              // Pin to toggle

No meu caso, o dispositivo será denominado “lights“. Aqui neste primeiro exemplo o dispositivo acionará um único LED, mas o NodeMCU poderia estar conectado a um relé que por sua vez poderia ligar as luzes de meu laboratório, por exemplo.

Carregue o código no NodeMCU.

No monitor serial você pode ver a mensagem “Connecting to UDP” e “Connection Successful”. Isso significa que do lado NodeMCU tudo está OK.

Serial Monitor1

Estarei levando em consideração que voce já tenha o Echo-Dot instalado em sua rede., bem como o Alexa App em seu smartphone. Os procedimentos para a instalação de ambos é bem simples e basta seguir as instruções da Amazon fornecidas junto com o dispositivo.

Agora, necessitamos que a Alexa encontre seu dispositivo. Existem dois métodos para isso:

  • Utilizando o aplicativo Alexa no smartphone como mostrado nas fotos abaixo. Em nosso caso, a Alexa encontrará “1 Smart Home device”:  lights WeMo Switch.

Alexa app

  • Pedindo diretamente à Alexa através de um comando de voz, como por exemplo: “Alexa, Find connected devices” como poderá ser visto no vídeo abaixo:

Uma vez que a Alexa descobriu seu dispositivo, você poderá usar comandos de voz para o acionamento do dispositivo como mostrado abaixo:

4: Acionando vários dispositivos ao mesmo tempo

IMG_1224
Aprofundando um pouco mais,  desenvolveremos um sistema um pouco mais realista que acionará vários dispositivos ao mesmo tempo. Desta maneira, o projeto poderia ser usado em projetos envolvendo automação residencial  (domótica).

Usaremos um módulo de relé de 4 canais para controlar 2 lâmpadas e 2 tomadas.

Esta parte do projeto foi baseada em grande parte no tutorial de Charles Gantt,

How To: DIY Home Automation With NodeMCU and Amazon Alexa.

Siga as instruções abaixo:

Conecte as entradas dos relés com os pinos do NodeMCU conforme descrito abaixo:

int relayOne = 14;  // NodeMCU pin D5
int relayTwo = 15;  // NodeMCU pin D8
int relayThree = 3; // NodeMCU pin RX
int relayFour = 1;  // NodeMCU pin TX

Nossos “smart devices” serão 2 lâmpadas fixas (“Lights”)e 2 tomadas para uso geral (“Outlets”). Como vimos nas etapas anteriores, deveremos emular “WeMo Devices” e para fazer isso devemos nomeá-los como abaixo:

  • Light One
  • Light Two
  • Outlet One
  • Outlet Two

Em seguida, deveremos defini-los em nosso código para que a Alexa consiga encontrá-los. Também definiremos 2 comandos (on e off) e um número de porta de comunicação (“Port”) para cada dispositivo.

O formato geral deve ser:

lightOne = new Switch("Light One", 80, lightOneOn, lightOneOff);
lightTwo = new Switch("Light Two", 81, lightTwoOn, lightTwoOff);
outletOne = new Switch("Outlet One", 82, outletOneOn, outletOneOff);
outletTwo = new Switch("Outlet Two", 83, outletTwoOn, outletTwoOff);

Agora, você deve definir as 2 funções relacionadas a cada condição do dispositivo:

Para as lampadas:

void lightOneOn() {
  Serial.print("Switch 1 turn on ...");
  digitalWrite(relayOne, LOW);   // sets relayOne on
}

void lightOneOff() {
  Serial.print("Switch 1 turn off ...");
  digitalWrite(relayOne, HIGH);   // sets relayOne off
}

void lightTwoOn() {
  Serial.print("Switch 2 turn on ...");
  digitalWrite(relayThree, LOW);   // sets relayTwo on
}

void lightTwoOff() {
  Serial.print("Switch 2 turn off ...");
  digitalWrite(relayThree, HIGH);   // sets relayTwo Off
}

E para as tomadas:

void outletOneOn() {
  Serial.print("Socket 1 turn on ...");
  digitalWrite(relayFour, LOW);   // sets relayThree on
}

void outletOneOff() {
  Serial.print("Socket 1 turn off ...");
  digitalWrite(relayFour, HIGH);   // sets relayThree off
}

void outletTwoOn() {
  Serial.print("Socket 2 turn on ...");
  digitalWrite(relayTwo, LOW);   // sets relayFour on
}

void outletTwoOff() {
  Serial.print("Socket 2 turn off ...");
  digitalWrite(relayTwo, HIGH);   // sets relayFour off
}

Uma vez que estamos utilizando relés, poderíamos testar o projeto com  qualquer tipo de dispositivo real como TVs, geladeiras, etc. Obviamente que pelas especificações dos relés utilizados neste projeto, os dispositivos estariam limitados a 250VAC/10A ou 30 VDA/30A.

Em meu caso, decidi testar os 4 WeMo switches com dispositivos alimentados externamente com 5 V DC .

  • Como “Light 1”,  usarei um LED vermelho
  • Como “Light 2”, usarei um LED verde
  • Como “Outlet 2”, usarei um pequeno buzzer (pense em um rádio, aparelho de som…)
  • Como “Outlet 1”, usarei um pequeno ventilador

Abaixo o diagrama elétrico com as conexões:

Home Automation Electric Diagram

Com o HW pronto, baixe o código de meu GitHub: NODEMCU_ALEXA_WeMos_4X_Serial_Monitor_EXT.ino

Entre com suas credenciais de rede:

const char* ssid = "YOUR SSID";
const char* password = "YOUR PASSWORD";

E pronto!

Siga o mesmo procedimento como definido na etapa anterior para permitir que a Alexa encontre seus 4 dispositivos.

O vídeo abaixo mostra uma demonstração desta etapa:

5: Aplicação em Domótica (Automação Residencial)

Neste ponto do projeto, possuímos 4 dispositivos inteligentes funcionando adequadamente, os quais podem ser ativados e desativados individualmente. Mas suponha que queremos agrupá-los de maneira a serem utilizados em nossa casa. O que deveria ser feito?

Por exemplo, suponha que a nossa casa tenha 2 cômodos:

  • Quarto (“Bed Room”)
  • Sala (“Living Room”)

Agora, suponha que você queira ter uma lâmpada e uma tomada em cada habitação. O que devemos fazer, é agrupar nossos 4 dispositivos como mostrado no diagrama de blocos da introdução:

  • IMG_9708Quarto (Bed Room)
    • Light 2
    • Outlet 1 (Ventilador)
  • Sala (Living Room)
    • Light1
    • Outlet 2 (Buzzer)

Também criaremos outro grupo para lidar com todos os dispositivos, ativando / desativando todos os dispositivos ao mesmo tempo.

Criaremos uma versão 2 de nosso código onde teremos  3 novos “switches” que agora devem ser também  ser identificados pela Alexa. Para isto, adicionemos as seguintes linhas ao nosso código:

1. Definição das “Switches”:

Switch *allDevices = NULL;
Switch *bedRoom = NULL;
Switch *livingRoom = NULL;

2. Declaração dos “callbacks” para os novos grupos de dispositivos:

void allDevicesOn();
void allDevicesOff();
void bedRoomOn();
void bedRoomOff();
void livingRoomOn();
void livingRoomOff();
  1. No setup(), devemos associar os switches com os novos callbacks and Ports (Lembre-se que temos um máximo de 14 dispositivos que podem ser manipulados com este código):
allDevices = new Switch("All Devices", 84, allDevicesOn, allDevicesOff);
bedRoom = new Switch("Bed Room", 85, bedRoomOn, bedRoomOff);
livingRoom = new Switch("Living Room", 86, livingRoomOn, livingRoomOff);
  1. Adicionando “Switches upnp Broadcast Responder” ao setup():
upnpBroadcastResponder.addDevice(*allDevices);
upnpBroadcastResponder.addDevice(*bedRoom);
upnpBroadcastResponder.addDevice(*livingRoom);
  1. Adicionando as linhas ao loop():
allDevices->serverLoop();
bedRoom->serverLoop();
livingRoom->serverLoop();
  1. E finalmente, criemos as funções para os “grupos de dispositivos” com as ações a serem executadas quando a Alexa receber um comando de voz:
void allDevicesOn() 
{
  Serial.print("All Devices turn on ...");
  digitalWrite(relayOne, LOW);   // sets relay1 on
  digitalWrite(relayTwo, LOW);   // sets relay2 on
  digitalWrite(relayThree, LOW);   // sets relay3 on
  digitalWrite(relayFour, LOW);   // sets relay4 on
}

void allDevicesOff() 
{
  Serial.print("All Devices turn off ...");
  digitalWrite(relayOne, HIGH);   // sets relay1 off
  digitalWrite(relayTwo, HIGH);   // sets relay2 off
  digitalWrite(relayThree, HIGH);   // sets relay3 off
  digitalWrite(relayFour, HIGH);   // sets relay4 off
}

void bedRoomOn() 
{
  Serial.print("Bed Room turn on ...");
  digitalWrite(relayThree, LOW);   // sets relay3 on
  digitalWrite(relayFour, LOW);   // sets relay4 on
}

void bedRoomOff() 
{
  Serial.print("Bed Room turn off ...");
  digitalWrite(relayThree, HIGH);   // sets relay3 off
  digitalWrite(relayFour, HIGH);   // sets relay4 off
}

void livingRoomOn() 
{
  Serial.print("Living Room turn on ...");
  digitalWrite(relayOne, LOW);   // sets relay1 on
  digitalWrite(relayTwo, LOW);   // sets relay2 on
}

void livingRoomOff() 
{
  Serial.print("Living Room turn off ...");
  digitalWrite(relayOne, HIGH);   // sets relay1 off
  digitalWrite(relayTwo, HIGH);   // sets relay2 off
}

Este procedimento de 6 passos poderá ser utilizado para qualquer comando, dispositivo ou grupo de dispositivos que você deseja adicionar ao seu projeto.

Você poderá baixar o código completo desta etapa. desde meu GitHub:

NODEMCU_ALEXA_WeMos_4X_V2_EXT

Depois que o código é enviado e executado pelo NodeMCU, é hora de solicitar Alexa que “localize os novos dispositivos”. Como explicado anteriormente, você poderá fazê-lo usando um comando de voz ou pelo Alexa App. Em ambos casos, o resultado poderá ser verificado no aplicativo como mostrado abaixo. Sete dispositivos (“WeMo Switches”) deverão ser encontrados agora pela Alexa:

  • IMG_1261Living Room
  • Bed Room
  • All Devices
  • Outlet One
  • Outlet Two
  • Light One
  • Light Two

O vídeo abaixo mostra como nosso projeto completo de Automação Residencial funcionará:

6: Conclusão

Como sempre, espero que este projecto possa ajudar outros a encontrarem o seu caminho no emocionante mundo da electrónica, robótica e do IoT!

Verifique o depositário no GitHub para obter os arquivos atualizados:

Home-Automation-with-Alexa-and-NodeMCU

E não deixe de visitar e seguir minha página: MJRoBot.org no Facebook

Saludos desde el sur del mundo!

Até o próximo tutorial!

Obrigado

Marcelo

Anúncios

ArduFarmBot, o livro!

Acaba de sair do forno o primeiro livro da série “Tutoriais MJRoBot”, o “ArduFarmBot: Automatizando uma horta de tomates com a ajuda da Internet das Coisas – IoT”.

O livro pode ser adquirido nas lojas do Kindle na Amazon:

book amazon

Por favor divulguem o livro e se gostaram, deixem um comentário na página da: Amazon.com.br

Caso encontrem erros ou tenham sugestões, por favor usem a area de mensagens aqui no blog, que procurarei corrigir nas próximas edições.

O livro usa o controlador eletrônico “ArduFarmBot” como base para o aprendizado de como se trabalhar tanto em HW quanto em SW, com: a) Displays do tipo LCD e OLED; b) LEDs e botões; c) Acionamento de bombas e lâmpadas via relés e d) Leitura de sensores tais como: DHT22 (temperatura e umidade relativa do ar), DS18B20 (temperatura do solo), YL69 (umidade do solo) e LDR (luminosidade).

Todas as principais etapas dos projetos são detalhadamente documentadas através de textos explicativos, diagramas de blocos, fotos coloridas de alta resolução, diagramas elétricos utilizando-se do aplicativo “Fritzing”, códigos completos armazenados no “GitHub” e vídeos do “YouTube”.

No livro, são desenvolvidas duas versões do controlador eletrônico “ArduFarmBot”, que a partir da captura de dados provenientes de uma horta de tomates, tais como temperatura do ar e solo, umidade relativa do ar, umidade do solo e luminosidade, decidem autonomamente a quantidade certa (e quando) uma plantação deve receber calor e água. O ArduFarmBot também permite a intervenção manual, tanto em forma local quanto remota via Internet, a fim de controlar o acionamento de uma bomba de água e de uma lâmpada elétrica, esta última para ser usada na geração de calor para as plantas.

O livro está dividido em 3 partes.

Na primeira parte, a partir do “Arduino Nano” de desenvolve uma versão tanto manual operada por botões, quanto automática do “ArduFarmBot”.

book1

Na segunda parte, se aprofunda no projeto da automação e introduz a operação remota através da criação de uma página na internet. O “ESP8266-01” é utilizado para a conexão “Wifi”, enviando dados para o serviço especializado em IoT, “ThingSpeak.com“.

book2

Na terceira parte, uma segunda versão do “ArduFarmBot” é desenvolvida, introduzindo o “NodeMCU ESP8266-12E”, um poderoso e versátil dispositivo para projetos em IoT, que substitui de forma integrada tanto o “Arduino Nano” quanto o “ESP8266-01”, utilizados nas partes anteriores do livro. Nesta última, se explora também uma nova plataforma de serviços do universo IoT, o “Blynk”.

book4

Espero que gostem! E se preparem para o Tutoriais MJRoBot 2: “Brincando com robôs”.

Não deixem de visitar e seguir minha página: MJRoBot.org no Facebook

Saludos desde el sur del mundo!

Nos vemos em meu próximo post!

Obrigado e um abração,

Marcelo

 

Brincando com eletrônica: Como utilizar a biblioteca “GPIO Zero” no Raspberry Pi

Uma maneira simples de aprender eletrônica é usando o Raspberry Pi e sua biblioteca “GPIO Zero”. Com poucas linhas de código em Python, você poderá facilmente controlar atuadores, ler sensores, etc. Esta biblioteca foi criada por Ben Nuttall da Raspberry Pi Foundation, Dave Jones entre outros.

Aqui, neste rápido tutorial procurarei fornecer-lhes a base para a criação de circuitos simples, controlados pelo Raspberry Pi.

Para obter maiores detalhes, consulte o link abaixo:

GPIO Zero V 1.3.1 Documentation

Você poderá também fazer o download grátis de um livro completo fornecido pela reveista MagPi, que irá guiar-lo passo a passo no desenvolvimento de vários projetos utilisando-se  da biblioteca GPIO Zero:

SIMPLE ELECTRONICS WITH GPIO ZERO

 

Neste tutorial, exploraremos os dispositivos de entrada (“sensores”):

  • Botão
  • Sensor de Detecção de Movimento

E como saída (“atuadores”):

  • LED
  • Buzzer
  • Saída digital genérica (motor de passo)

Vamos lá, mãos a obra!

1: Material utilizado

  • Raspberry Pi (Tanto a V2 quanto a V3 funcionam da mesma maneira)
  • Breadboard
  • Push-Button
  • LED
  • Resistor 330 ohms
  • Cabos para conexão (“jumpers”) do tipo “Dupont” (Fêmea/Macho and Macho/Macho)

2: Instalando a biblioteca “GPIO Zero”

A primeira coisa a fazer é atualizar sua lista de repositórios:

sudo apt-get update

Em seguida, instale o pacote de sua escolha. Tanto o Python 3 quanto o Python 2 são suportados. Python 3 é o recomendado:

sudo apt-get install python3-gpiozero

 

Numeração dos pinos do Raspberry Pi

Importante mencionar que a biblioteca GPIO Zero utiliza a numeração de pinos “Broadcom” (BCM) para seus GPIOs, em oposição à numeração física (BOARD). Qualquer pino marcado como GPIO”x”, onde “x” é seu número, no diagrama abaixo poderá ser utilizado. Por exemplo, se um LED foi anexado ao “GPIO18” você especificaria o número do pino como 18 em vez de 12, que é seu número físico.

3: “Hello World”: Piscando um LED

Agora, instalemos o LED, utilizando-se o pino físico 12 (GPIO18) conectado ao seu catodo (perna mais longa do LED ). Conecte agora o ânodo do LED ao GND do breadbord, utilizando-se um resistor de 330 ohms,  reduzindo-se assim  a corrente que será drenada do RPi,  como mostra a figura abaixo:

Uma vez que o HW esteja conectado, criemos um programa em Python para ligar o LED:

from gpiozero import LED
led = LED(18)
led.on()

Para criar e executar o programa, você poderá utilizar-se o aplicativo Python3 que aparece no menu do RPi ou utilizar qualquer editor de texto salvando o arquivo, por exemplo como “MyPgmName.py” e depois executar-lo o utilizando-se de uma linha de comando no monitor, por exemplo:

sudo Python MyPgmName.py

Como você pode ver, é muito simples de se criar um código em Python utilizando-se da biblioteca “GPIO Zero”.

Agora, vamos fazer o LED piscar  (o verdadeiro “Olá mundo” (“Hello World”), quando estamos falando de HW.

Para fazer isso, também precisaremos importar outra biblioteca que é a “time”. Vamos precisaremos dela para definir a quantidade de tempo em que o LED permanecerá ligado e/ou desligado. Em nosso caso, o LED piscará de 1 em 1 segundo (o argumento “time” da função sleep(time) está dado em segundos).

from gpiozero import LED
from time import sleep
led = LED(18)
while True:
    led.on()
    sleep(1)
    led.off()
    sleep(1)

Como alternativa, você poderá reduzir o código, utilizando-se  da função “Blink()”, incluída na biblioteca:

from gpiozero import LED
from signal import pause
red = LED(17)
red.blink()
pause()

O vídeo abaixo, mostra o programa em funcionamento:

4: Lendo um sinal digital

Uma maneira simples de se obter um comando externo, é utilizando-se de um botão e a Biblioteca GPIO Zero fornece uma maneira fácil de incluir-lo em seu projeto. Você não precisa pensar em resistores pull-up ou pull-down, etc. Em termos de HW, a única coisa a fazer é conectar uma perna de seu botão a qualquer um dos RPI GPIOs e a outra ao terra (GND), como mostrado na figura acima:

  • Perna 1 do botão para GPIO2
  • Perna 2 do botão para GND

Poderíamos escrever um simples código para ler o botão como este abaixo:

from gpiozero import Button
button = Button(2)
while True: 
    if button.is_pressed: 
        print("Button is pressed") 
    else:
        print("Button is not pressed")

Outra idéia poderia ser adicionar um LED ao circuito, de maneira que o LED acenda quando o botão é pressionado e apague quando o mesmo é liberado.

Abaixo vemos como ficaria o código em Python para executar a tarefa:

from gpiozero import LED, Button
from signal import pause
led = LED(18)
button = Button(2)
button.when_pressed = led.on
button.when_released = led.off
pause()

O vídeo mostra nosso projeto em funcionamento:

 

Agora que você domina o básico, dê uma passeada pelo documento: GPIO Zero: Button, explorando outras ideias de como utilizar botões em seus projetos.

Step 5: Motion Detection

Explorar agora outros dispositivos comuns incluídos na biblioteca. Aproveitaremos um buzzer e um sensor de movimento (PIR) que juntamente com um LED funcionarão como um alarme simples.

Como exploramos em meu último tutorial, IoT: Sensor de movimento com o NodeMCU e BLYNK, o sensor PIR gerará um pulso de nível ALTO a qualquer movimento que aconteça em seu raio de visão. Este módulo utiliza-se do sensor infravermelho passivo LHI778 e do CI BISS0001 para controlar como o movimento é detectado. O módulo possui sensibilidade ajustável que permite uma faixa de detecção de movimento de 3 a 7 metros. O módulo também inclui ajustes de atraso de tempo e seleção de gatilho que permitem um ajuste mais fino dentro de sua aplicação.

O dispositivo PIR tem uma pequena placa de circuito com três pinos: VCC, OUT e GND. VCC deve ser conectado a um pino de 5V do RPi, GND a um dos pinos de terra, e finalmente OUT a um dos GPIOs, em nosso caso: GPIO23.

Apesar que o PIR é alimentado com 5V, sua saída fornece um máximo de 3.3V, assim que é seguro conectá-lo diretamente ao pino do RPi.

O código Python abaixo mostra como podemos utilizar-lo:

from gpiozero import MotionSensor
pir = MotionSensor(23)
pir.wait_for_motion()
print("Motion detected!")

Para construir um alarme mais interessante, incluiremos um LED (conectado no GPIO18) e um buzzer (conectado ao GPIO24).

Assim, devemos importar os 3 dispositivos, como mostrado abaixo:

from gpiozero import MotionSensor, Buzzer, LED
import time
pir = MotionSensor(23)
bz = Buzzer(24)
led = LED(18)
print("Waiting for PIR to settle")
pir.wait_for_no_motion()
while True:
    led.off()
    print("Ready")
    pir.wait_for_motion()
    led.on()
    print("Motion detected!")
    bz.beep(0.5, 0.25, 8)
    time.sleep(3)

Abaixo um pequeno filme mostrando o alarme funcionando:

6: Controlando um motor de passo (“Stepper Motor”)

Usaremos 4 GPIOs como saídas digitais para acionar o motor de passos.

O motor deve ser ligado a placa controladora utilizando-se do conector especial instalado na mesma (conector branco de 5 pinos). A placa controladora tem 4 + 2 pinos que precisam ser conectados ao RPi:

Fonte (2 pinos laterais):

  • (Pin 1) ==> 5V
  • (Pin 2) ==> GND

e

Entradas digitais (4 pinos frontais):

  • IN1 ==> GPIO12
  • IN2 ==> GPIO16
  • IN3 ==> GPIO20
  • IN4 ==> GPIO21

O link abaixo lhe dará mais detalhes sobre como trabalhar com um Stepper Motor utilizando-se de um Raspberry Pi programado em Python:

Stepper Motor Control In Python

Com base no link acima, podemos recriar o código simplificando-o para usar-lo com a biblioteca GPIO Zero. Além disso, você deverá decidir entre “velocidade” ou “torque”. Este é um compromisso que você pode escolher dependendo da seqüência de passos (4 ou 8 etapas). Eu criei uma variável “mode”, para que você possa testar ambas seqüências.

Você poderá executar este programa tanto utilizando-se do aplicativo Python 3 disponível no sistema operacional Raspbian ou diretamente no monitor usando-se de uma linha de comando como mostrado abaixo:

sudo Python StepMotorCtrl.py 2

Onde o parâmetro “2” significa que o atraso ao mudar de passo será de 2ms. Se nenhum parâmetro for usado, o código assumirá 4ms (waitTime = 0.004).

import time
import sys
from gpiozero import OutputDevice as stepper
IN1 = stepper(12)
IN2 = stepper(16)
IN3 = stepper(20)
IN4 = stepper(21)
stepPins = [IN1,IN2,IN3,IN4] # Motor GPIO pins</p><p>
stepDir = -1        # Set to 1 for clockwise
                           # Set to -1 for anti-clockwise
mode = 1            # mode = 1: Low Speed ==> Higher Power
                           # mode = 0: High Speed ==> Lower Power
if mode:              # Low Speed ==> High Power
  seq = [[1,0,0,1], # Define step sequence as shown in manufacturers datasheet
             [1,0,0,0], 
             [1,1,0,0],
             [0,1,0,0],
             [0,1,1,0],
             [0,0,1,0],
             [0,0,1,1],
             [0,0,0,1]]
else:                    # High Speed ==> Low Power 
  seq = [[1,0,0,0], # Define step sequence as shown in manufacturers datasheet
             [0,1,0,0],
             [0,0,1,0],
             [0,0,0,1]]
stepCount = len(seq)
if len(sys.argv)>1: # Read wait time from command line
  waitTime = int(sys.argv[1])/float(1000)
else:
  waitTime = 0.004    # 2 miliseconds was the maximun speed got on my tests

stepCounter = 0

while True:                          # Start main loop
  for pin in range(0,4):
    xPin=stepPins[pin]          # Get GPIO
    if seq[stepCounter][pin]!=0:
      xPin.on()
    else:
      xPin.off()
  stepCounter += stepDir
  if (stepCounter >= stepCount):
    stepCounter = 0
  if (stepCounter < 0):
    stepCounter = stepCount+stepDir
  time.sleep(waitTime)     # Wait before moving on

O vídeo abaixo mostra o programa sendo executado pelo monitor e com diferentes velocidades:

7: Conclusão

Como sempre, espero que este projeto ajude outras pessoas a encontrar seu caminho no apaixonante mundo da eletrônica!

Não deixe de visitar e seguir minha página: MJRoBot.org no Facebook

“Saludos desde el sur del mundo!”😉

Um abraço e até o próximo post!

Obrigado

Marcelo

ArduFarmBot: Part 2 – “Estação Remota” – IoT

Este post é uma continuação do ArduFarmBot: Controlando um tomateiro com a ajuda de um Arduino e Internet das coisas (IoT)

Na primeira parte do projeto, criamos uma estação local de controle e captura de informações, tais como temperatura, umidade relativa do ar, luminosidade e umidade do solo. Com base nesses dados, o ArduFarmBot decidia automaticamente a quantidade certa (e quando) o tomateiro deveria receber calor e água. A estação local de controle desenvolvida na Parte 1, também permitia a intervenção manual de um operador a fim de controlar tanto a bomba de água quanto a lâmpada elétrica. Nesta  segunda parte, implementaremos uma abordagem “IoT” onde a”intervenção manual” também poderá ser feita remotamente via Internet. O diagrama de blocos mostra como faremos isso:

ardufarmbot_gal_block_diagram

Note-se que os dados capturados serão enviados a um “serviço de armazenamento na nuvem” (no nosso caso Thinkspeak.com). Além disso, um website dedicado, o “Remote Control Page” mostrado no diagrama,  irá monitorar e exibir esses dados em tempo quase real. Esta página também permitirá a ativação remota da bomba e  da lâmpada.

2.1: Lista de materiais

2.2: Completando o Hardware

Para conectar-se o ArduFarmBot à Internet, utilizaremos o ESP8266, um módulo simples, barato e fácil de programar para os projectos envolvendo a Internet das coisas (IoT). A partir da estação local desenvolvida na Parte 1, o único HW adicional necessário é o próprio ESP8266. O diagrama de blocos abaixo, mostra todas as conexões com os pinos do Arduino e dos componentes principais.

ardufarmbot_pin_diagram

O único cuidado que você deve ter é relacionado com o nível de tensão do pino RX do ESP8266, pois ele funciona com 3.3V. Assim, o Pin Rx não deve ser conectado diretamente ao pino Tx do Nano (D3). Um nívelador de tensão deve ser usado. No nosso caso, vamos construir um divisor de tensão para ser usado como um “conversor de nível de tensão”.

Voltage level Converter.png

O esquema eléctrico abaixo, mostra com mais detalhes como se conectar o ESP8266.

ardufarmbot_complete_eletr_diagram

Se você quiser saber mais sobre o ESP8266, consulte meus posts anteriores aqui no Blog:

  1. O ESP8266 parte 1 – Serial WIFI Module
  2. O ESP8266 parte 2 – Web Server
  3. O ESP8266 parte 3 – Acionando LEDs remotamente

Note que estamos usando o ESP8266 conectado aos pinos 2 (Tx) e 3 (Rx) do Nano, utilizando-se da biblioteca “SoftSerial”. Se você quiser “livrar” os pinos digitais, poderá alternadamente usar os pinos 0 e 1 do Nano (Serial por HW). Apenas lembre-se que você deve desligá-los ao enviar o código para Nano.

NOTA: Se você deseja se conectar o “Buzzer”, você deve fazê-lo no pino D17 (o mesmo que o pino A3). É bom ter um alarme sonoro quando ocorra um erro de comunicação. Eu o usei durante a fase de teste, deixando de fora no HW do projecto final (mas o código está preparado para isso). Cabe a você decidir se o insta-la ou não.

O código abaixo pode ser usado para testar e / ou configurar o  ESP8266:

2.3: Conectando o  ESP8266  a internet

Uma vez que o módulo ESP8266 está instalado, o próximo passo é aplicar um “Reset” no pino CH-PD.

/***************************************************
* Reset funtion to accept communication
****************************************************/
void reset8266(void)
{
  pinMode(CH_PD, OUTPUT);
  digitalWrite(CH_PD, LOW);

  delay(300);
  digitalWrite(CH_PD, HIGH);
  Serial.print("8266 reset OK");
  lcd.clear();
  lcd.println("8266 reset OK       ");
}

Após o reset, vamos conectá-lo à rede local  (alterar o no código: username e password, por suas credenciais ) e iniciar o módulo como “Modo Station” (CWMODE = 1):

/***************************************************
* Connect WiFi
****************************************************/
void connectWiFi(void)
{
  sendData("AT+RST\r\n", 2000, DEBUG); // reset
  sendData("AT+CWJAP=\"USERNAME\",\"PASSWORD\"\r\n", 2000, DEBUG); //Connect network
  delay(3000);
  sendData("AT+CWMODE=1\r\n", 1000, DEBUG);
  sendData("AT+CIFSR\r\n", 1000, DEBUG); // Show IP Adress
  lcd.clear();
  lcd.print("8266 Connected");
  Serial.println("8266 Connected");
}

Para enviar dados ao ESP8266, utilizamos a função sendData():

/***************************************************
* Send AT commands to module
****************************************************/

String sendData(String command, const int timeout, boolean debug)
{
  String response = "";
  esp8266.print(command);
  long int time = millis();
  while ( (time + timeout) > millis())
  {
    while (esp8266.available())
    {
      // The esp has data so display its output to the serial window
      char c = esp8266.read(); // read the next character.
      response += c;
    }
  }
  if (debug)
  {
    Serial.print(response);
  }
  return response;
}

As funções acima serão chamadas durante a “fase de setup” de nosso Código. Se tudo foi feito corretamente, você deve ver pelo monitor serial, mensagens semelhantes como as de abaixo:

Picture of The ESP8266 connection

2.4: Data Storage Cloud: O ThinkSpeak.com

Todos os dados capturados pelo ArduFarmBot serão enviados para a nuvem, utilizando-se um serviço gratuito proporcionado pelo “ThinkSpeak.com“.

No “Loop ()”, depois de capturar os dados com readSensors (),  chamaremos uma função específica para carregar os dados capturados: updateDataThingSpeak ();

/***************************************************
* Transmit data to thingspeak.com
****************************************************/
void updateDataThingSpeak(void)
{
  startThingSpeakCmd ();
  
  cmd = msg ;
  cmd += "&field1=";     //field 1 for DHT temperature
  cmd += tempDHT;
  cmd += "&field2=";    //field 2 for DHT humidity
  cmd += humDHT;
  cmd += "&field3=";    //field 3 for LDR luminosity
  cmd += lumen;
  cmd += "&field4=";    //field 4 for Soil Moisture data 
  cmd += soilMoist;
  cmd += "&field5=";    //field 5 for PUMP Status
  cmd += pumpStatus;
  cmd += "&field6=";    //field 6 for LAMP Status
  cmd += lampStatus;
  cmd += "\r\n";

  sendThingSpeakCmd();
}

A fim de enviar esses dados, a primeira coisa a fazer é iniciar a comunicação com o ThingSpeak.com. Faremos isso, usando-se da função:startThingSpeakCmd ();

/***************************************************
* Start communication with ThingSpeak.com
****************************************************/
void startThingSpeakCmd(void)
{
  cmd = "AT+CIPSTART=\"TCP\",\"";
  cmd += IP;
  cmd += "\",80";
  esp8266.println(cmd);
  delay(2000);
  if(esp8266.find("Error"))
  {
    Serial.println("ESP8266 START ERROR");
    return;
  }
  Serial.println("Thinkspeak Comm Started");
  cmd ="";
}

Uma vez que o canal esteja aberto com o ThingSpeak e o string “cmd”  esteja montado com os dados, é hora de fazer o upload de tudo isso no canal correspondente do ThingSpeak usando-se da função:sendThingSpeakCmd();

/***************************************************    
* Update channel ThingSpeak.com
****************************************************/
String sendThingSpeakCmd(void)
{
  esp8266.print("AT+CIPSEND=");
  esp8266.println(cmd.length());
  if(esp8266.find(">")){
    esp8266.print(cmd);
    Serial.println("");
    Serial.println("");
    Serial.println(cmd);
    delay(500);
   
    String messageBody = "";
    while (esp8266.available()) 
    {
      String line = esp8266.readStringUntil('\n');
      if (line.length() == 1) 
      { //actual content starts after empty line (that has length 1)
        messageBody = esp8266.readStringUntil('\n');
        Serial.print("Message received: ");
        Serial.println(messageBody);
      }
    }
    return messageBody;
  }
  else{
    esp8266.println("AT+CIPCLOSE");
    Serial.println("ESP8266 CIPSEND ERROR: RESENDING"); //Resend...
    error=1;
    return "error";
  }
}

As funções acima foram baseadas em um tutorial muito bom desenvolvido por Michalis Vasilakis. Para mais detalhes, consulte:

Arduino IOT: Temperature and Humidity ( with ESP8266 WiFi).

A foto abaixo mostra o canal ArduFarmBot no site do ThingSpeak.com:

Picture of Cloud Storage Data: The ThinkSpeak.com

2.5: Comandando os atuadores a partir da web

Neste momento, estamos subindo (“Upload”) e armazenando todos os dados coletados na nuvem. Isto é muito útil para a monitoração remota, mas o que aconteceria se com base nesses dados gostaríamos de acionar a bomba ou a Lâmpada, independente do programa automático local? Para fazer isso, além do “Upload”, também faremos um “Download” de dados a partir da nuvem, informando ao controlador como agir basedo nesses dados (comandos).

Criemos para isso, campos específicos em nosso canal ThinkSpeak para comandar os atuadores:

  • Field 7:
    • Data = 1 ==> PUMP should be Turn ON
    • Data = 0 ==> PUMP should be Turn OFF
  • Field 8:
    • Data = 1 ==> LAMP should be Turn ON
    • Data = 0 ==> LAMP should be Turn OFF

OK, mas como configurar esses campos diretamente no ThingSpeak? Podemos fazê-lo, por exemplo, escrevendo um “plugin” diretamente para ThinksPeak, ou podemos usar um site externo para fazê-lo (esta será a nossa escolha). De qualquer forma, em ambos os casos, você deverá usar um comando como o de abaixo:

api.thingspeak.com/update?key=YOUR_WRITE_KEY&field7=1

Com o comando acima, por exemplo (e usando a sua chave de escrita de canal), você vai escrever “1” no Field 7, o que significa que a bomba deve ser ligada. Você pode facilmente testá-lo, escrevendo a linha de comando acima no seu navegador, o campo correspondente no seu canal será alterado. Como um retorno, o navegador irá mostrar uma página em branco com um único número no canto superior esquerdo, correspondente à entrada de dados sequencial no seu canal.

Até agora, 50% do trabalho está feito! Agora você deverá ler este “comando” (conteúdo  do campo), na estação local do ArduFarmBot .

O comando para fazer isso é mostrado abaixo. Ele vai obter o último dado que foi escrito no campo específico (que em nosso caso será um “comando”).

api.thingspeak.com/channels/CHANNEL_ID/fields/7/last

Da mesma forma como fizemos antes, você poderá testar a linha de comando, usando seu navegador. Neste caso, o navegador irá mostrar-lhe o dado armazenado esse campo específico. Veja abaixo:

Picture of Commanding the actuators from the web

Voltando à “terra”, escreveremos uma função que lerá este “último campo”:

/***************************************************
* Read data from field7 of thingspeak.com
****************************************************/
int readLastDataField7()
{
  startThingSpeakCmd ();

  // "GET /channels/CHANNEL_ID/fields/7/last";
  cmd = msgReadLastDataField7; 
  cmd += "\r\n";

  String messageDown = sendThingSpeakCmd();
  Serial.print("Command received: ");
  Serial.println(messageDown[7]);
  
  int command = messageDown[7]-48; 
  return command;
}

A função acima retornará o dado contido no Field 7 (“1” ou “0”). Uma função semelhante deve ser escrita para o Field 8.

Uma vez que já possuímos o conteúdo de ambos os campos, devemos utilizá-los em uma função que comandará os atuadores de maneira semelhante ao que fizemos com a “função de comando manual”:

/***************************************************
* Receive Commands from thingSpeak.com
****************************************************/
void receiveCommands()
{
  field7Data = readLastDataField7();
  if (field7Data == 1) 
  {
    digitalWrite(PUMP_PIN, HIGH);
    pumpStatus = 1;
    showDataLCD();
  }
  if (field7Data == 0) 
  {
    digitalWrite(PUMP_PIN, LOW);
    pumpStatus = 0;
    showDataLCD();
  }

  delay (500); 
  
  field8Data = readLastDataField8();
  if (field8Data == 1) 
  {
    digitalWrite(LAMP_PIN, HIGH);
    lampStatus = 1;
    showDataLCD();
  }
  if (field8Data == 0) 
  {
    digitalWrite(LAMP_PIN, LOW);
    lampStatus = 0;
    showDataLCD();
  }
  delay (500); 
}

Assim, a partir de agora você pode usar a linha de comando no seu navegador para ligar / desligar a bomba e a lâmpada remotamente. Abaixo, se pode ver como o comando recebido aparecerá no monitor serial:

Download command.png

Outra consideração importante é a “coordenação” entre o comando local e o remoto. Teremos de alterar a função readLocalCmd () para que também atualizemos os Fields7  e 8 do Thinkspeak respectivamente,  como o estado da bomba e da lâmpada:

field7Data = pumpStatus;

field8Data = lampStatus;

Agora “filed7Data” e “field8Data” estão em sincronia com os comandos da página web e também com as ações de comando locais quando você pressiona um botão. Então, vamos atualizar a função aplyCmd (), que é a responsável para ligar / desligar os atuadores:

/***************************************************
* Receive Commands and act on actuators
****************************************************/
void aplyCmd()
{
  if (field7Data == 1) digitalWrite(PUMP_PIN, HIGH);
  if (field7Data == 0) digitalWrite(PUMP_PIN, LOW);
  
  if (field8Data == 1) digitalWrite(LAMP_PIN, HIGH);
  if (field8Data == 0) digitalWrite(LAMP_PIN, LOW);
}

Quando você começar seus testes,  perceberá que qualquer comando executado manualmente no local ou via web, será superado pelas ações automáticas definidas pela função autoControlPlantation(). Neste ponto, você deve considerar quem será o “chefe” (ou patroa…), o qual terá a última palavra! Em nosso caso definiremos o seguinte:

  • Em cada ciclo de loop, que é quase “sempre”, verificamos se um botão local é pressionado
  • Aproximadamente a cada minuto, faremos um “pooling” no ThinkSpeak e verificamos se existe um nova ordem manual desde lá.
  • Aproximadamente  a cada 10 minutos, leremos os sensores, atualizando os dados no ThinkSpeak e mais importante,  executamos as medidas automáticas, que serão tomadas independente das que foram selecionados manualmente e será aquela que irá ser mantido.

Você pode alterar a lógica da maneira que melhor se adapte ao seu projeto. Essa é a coisa boa sobre o uso de um processador programável, como o Arduino para controlar coisas!

Assim, 2 temporizadores serão utilizados agora, um para agrupar os comandos remotos e outro para ler os sensores (o mesmo que foi utilizado anteriormente):

long sampleTimingSeconds = 75; // ==> ******** Define Sample time in seconds to read sensores *********
int reverseElapsedTimeSeconds = 0;
long startTiming = 0;
long elapsedTime = 0;
long poolingRemoteCmdSeconds = 20; // ==> ******** Define Pooling time in seconds for new ThingSpeak commands *********
long startRemoteCmdTiming = 0;  
long elapsedRemoteCmdTime = 0;

A função loop () deverá ser reescrita da seguinte forma:

void loop() 
{
  elapsedRemoteCmdTime = millis()-startRemoteCmdTiming;   // Start timer for pooling remote commands
  elapsedTime = millis()-startTiming;   // Start timer for measurements
  reverseElapsedTimeSeconds = round (sampleTimingSeconds - elapsedTime/1000);

  readLocalCmd(); //Read local button status
  showDataLCD();

  if (elapsedRemoteCmdTime > (poolingRemoteCmdSeconds*1000)) 
  {
    receiveCommands();
    updateDataThingSpeak();
    startRemoteCmdTiming = millis();
  }
  
  if (elapsedTime > (sampleTimingSeconds*1000)) 
  {
    readSensors();
    autoControlPlantation();
    updateDataThingSpeak();
    startTiming = millis();
  }
}

2.6: Implementação de uma página Web dedicada

Neste ponto o nosso ArduFarmBot está operacional e pode ser controlado a partir da web. Você pode monitorar os dados no site do Thinkspeak e também enviar comandos utilizando-se de um navegador, mas é claro que esta “solução web” não pode ser considerado “elegante”! A melhor maneira de implementar uma solução real para IoT, é desenvolver uma página web completa, a qual além de exibir todos os dados, também possuirá botões para ativação dos atuadores.

Para hospedar nossa página web, escolhí o Byethost, um site gratuito, muito fácil e simples para lidar com suas páginas. Também aproveitei a oportunidade de aplicar neste projecto, o que eu aprendi em uma fantástica especialização on-line no Coursera / Universidade de Michigan:Learn to Design and Create Websites (Construindo um portfólio web ágil e acessível usando HTML5, CSS3 e JavaScript ).

webpage

Não entrarei em detalhes como desenvolver esse tipo de página, uma vez que este não é o propósito central deste tutorial, mas incluirei aqui o HTML, CSS e códigos fonte do JavaScript que desenvolvi exclusivamente para minha página. Uma vez que alguém tenha interesse em como cheguei ao projeto final, podemos discuti-lo utilizando-se do Painel de Comentários no final do post.

É importante reforçar que essa página não trabalha diretamente com a Estação de Controle ArduFarmBot Local. O que ela está fazendo realmente é, interagir com o canal do ThinkSpeak  como descrito abaixo:

  1. Ler dados de sensores dos Fields 1, 2, 3, 4
  2. Ler estado do atuadores  (“Echo”) dos Fields  5 e 6
  3. Armazenar dados (comandos) nos Fields 7 e 8
  4. Ler dados meteorológicos locais (Yahoo)

O item 4 acima não é realmente crucial para o projeto, mas é importante ter dados adicionais disponíveis no caso de você querer tomar algumas ações remotas independente do que está acontecendo localmente com plantação do seu tomate. Outra consideração é que você pode, por exemplo, armazenar esses dados em outro canal ThingSpeak e transferir-los para o Arduino, mostrando dados meteorológicos no display LCD local por exemplo (eu desenvolvi isto em outro projeto que funcionou bem! Deixo aqui como uma sugestão para você).

2.7: A Matriz Sensor-Atuador

Picture of Return to the brain. A Sensor-Actuator Matrix approach

Na primeira parte deste projeto, definimos algumas considerações preliminares sobre como os atuadores devem agir em função da leitura dos sensores. Naquele caso, fizemos unicamente opções simples, mas o que vai acontecer se tivermos uma situação mais complexa? Várias condições diferentes? O que iremos desenvolver aqui é uma nova abordagem que chamaremos de “Matriz Sensor – Atuador”.

Em uma planilha foram definidas para cada sensor, as diferentes condições possíveis  e como deveria ser a respectiva saída dos atuadores. O resultado pode ser observado na planilha Excel abaixo.

Matrix.png

Na planilha Excel, existem duas Tabs. Uma com filtro como mostrado acima e outra que além da seleção, te permite obter o “codigo”binário que deverá ser carregado na varíavel da Matriz.

New Matrix Setup.png

Uma vez definida, a matriz deverá ser carregada em nosso código. Um array de 18 linhas e 10 colunas foi criado para este propósito:

//                       +---SOIL----+-LIGHT-+---TEMP---+---ACTUAT----+
 //                        SL  SM  SH  LL  LH  TL  TM  TH   Pump   Lamp
boolean SDf [18] [10] = {{  1,  0,  0,  0,  1,  0,  0,  1,    1,    0 }, 
                          { 1,  0,  0,  0,  1,  0,  1,  0,    1,    0 }, 
                          { 1,  0,  0,  0,  1,  1,  0,  0,    1,    1 }, 
                          { 1,  0,  0,  1,  0,  0,  0,  1,    1,    0 }, 
                          { 1,  0,  0,  1,  0,  0,  1,  0,    1,    0 }, 
                          { 1,  0,  0,  1,  0,  1,  0,  0,    0,    1 }, 
                          { 0,  1,  0,  0,  1,  0,  0,  1,    0,    0 }, 
                          { 0,  1,  0,  0,  1,  0,  1,  0,    0,    0 }, 
                          { 0,  1,  0,  0,  1,  1,  0,  0,    0,    1 }, 
                          { 0,  1,  0,  1,  0,  0,  0,  1,    0,    0 }, 
                          { 0,  1,  0,  1,  0,  0,  1,  0,    0,    1 }, 
                          { 0,  1,  0,  1,  0,  1,  0,  0,    0,    1 }, 
                          { 0,  0,  1,  0,  1,  0,  0,  1,    0,    0 }, 
                          { 0,  0,  1,  0,  1,  0,  1,  0,    0,    0 },
                          { 0,  0,  1,  0,  1,  1,  0,  0,    0,    1 },
                          { 0,  0,  1,  1,  0,  0,  0,  1,    0,    0 },
                          { 0,  0,  1,  1,  0,  0,  1,  0,    0,    1 },
                          { 0,  0,  1,  1,  0,  1,  0,  0,    0,    1 },
                         };

Para trabalhar com a Matrix, criou-se a função defSensorStatus (). Esta função testa para cada linha se a condição das 8 primeiras colunas são TRUE. Se sim, as condições definidas nas 2 últimas colunas são executadas.

por exemplo:

if (1 and 0 and 0 and 0 and 1 and 0 and 0 and 1) { pumpStatus = 1; lampStatus = 0}

else if (1 and 0 and 0 and 0 and 1 and 0 and 1 and 0) { pumpStatus = 1; lampStatus = 0}

e assim por diante …

Dentro da função acima outra array é criada com o estado de cada leitura do sensor:

boolean snsSts[8]={0, 0, 0, 0, 0, 0, 0, 0}; // SL, SM, SH, LL, LH, TL, TM, TH

Esta array variável será também utilizado para o registo do LOG.

Segue abaixo a planilha excel:

ARDUFARMBOT SENSOR ACTUATOR MATRIX NEW.xlsx ARDUFARMBOT SENSOR ACTUATOR MATRIX NEW.xlsx

2.8: Otimizando o código

Durante o processo de desenvolvimento do ArduFarmBot percebemos que algumas mudanças na especificação original deveria ser feita:

Display LCD:

O display LCD deve estar apagado por default e a qualquer momento que uma leitura de sensores é necessária, podemos acender-lo. Esta condição foi implementada no código e o botão de leitura de sensores deve ser utilizado no modo “toggle” para ligar / desligar o LCD a qualquer momento. Acender ou apagar o LCD irá automaticamente atualizar as leituras de sensores para a exibição, mas estas leituras não serão utilizadas pelo ArduFarmBot em suas funções regulares.

Configuração inicial:

Quando o ArduFarmBot é ligado (ou depois de um Reset), o LCD irá exibirá configuração  do controlador “Initial Set-up”. Para se iniciar a execução do programa, o botão “Sensores” deve ser pressionado novamante (o LCD se apagará). A informação inicial disponível na tela inicial do LCD será:

  • COLD Temperature (i.e. 12oC)
  • DRY Soil Humidity (i.e. 30%)
  • WET Soil Humidity (i.e. 60%)
  • DARK light (i.e. 40%)
  • P_ON Pump time be ON (i.e. 10s)
  • SCAN Time to read sensors (i.e. 600s)
  • SW_Vertion (i.e. 4.1)
img_0013
Registro do Log:

Para fins de auditoria, criamos um registro com as leituras e atuações do nosso Ardu FarmBot. Em cada ciclo de leitura, a função: storeDataLogEEPROM () é executada:

/***************************************************
* Storage of Log data at Arduino EEPROM
****************************************************/
void storeDataLogEEPROM(void)
{
  for (int i = 0; i<8; i++) 
  {
    logData = logData + (snsSts[i])<< 1;
  }
  EEPROM.write (memoAddr, logData);
  memoAddr++;
  logData = 0;
  logData = logData + pumpStatus;
  logData = logData << 1;
  logData = logData + lampStatus;
  EEPROM.write (memoAddr, logData);
  EEPROM.write (0, memoAddr+1);
  logData = 0; 
  if ((memoAddr+1) == 1023) memoAddr=1;
  else memoAddr++;
} 

Como comentado na última etapa, o que será armazenada na EEPROM do Arduino é o conteúdo, bit a bit do array snsSts[] adicionado do status da bomba e lâmpada. Você pode ver abaixo, o log no monitor serial:

serial-monitor-log

Como já visto na parte 1, para facilidade de entendimento e desenvolvimento, o código para o ArduFarmBot foi divididos em vários arquivos, criados por sua especialidade. Note que 2 novos arquivos foram adicionados nesta segunda parte:

  • communication.ino (ThingSpeak and ESP8266 specific funtions)
  • stationCredentials.h (ThinkSpeak Channel ID and specific Keys for writing on the channel)

Por último, uma vez que o código acabou ficando  com um tamanho razoável, decidi armazenar as constantes na memória flash (programa), em vez de SRAM. Para isso, usei a palavra-chave PROGMEM que é um modificador de variável. Por exemplo, em vez de utilizar:

#define DHTPIN 5, usaremos:

const PROGMEM byte DHTPIN = 5;

O PROGMEN informa ao compilador  para “colocar esta informação na memória flash”, em vez de em SRAM, onde iria normalmente. Você também deve incluir a biblioteca avr/pgmspace.h no arquivo principal do seu código.

Outro bom procedimento para reduzir o uso de SRAM é comentar (ou excluir) todas as linhas de Serial.print () que você usou para a depuração durante o desenvolvimento. Você perceberá que o código usado por exemplo para mostrar o log no monitor serial estará todo “comentado” no código final.

Abaixo você pode encontrará o código completo para o ArduFarmBot. Não se esqueça de alterar os dados no file credentials.h com seu Thinkspeak Channel Id e chave de escrita. Também no communication.ino, entre com seu nome de usuário e senha para conectar o ESP 8266 na Internet.

Abaixo testes do ArduFarmBot e instalação definitiva:fvc1gl0itchfzep-large

Preparando o reservatório de água:

reservatorio

Conectando a bomba dágua: bomba

Germinação (aproximadamente 45 dias):

germinacao 

Seleção melhores mudas e transplante :

transplante

 

2.9: Conclusão

Picture of Conclusion

Isso é tudo, pessoal! ……Por agora!!

Em breve publicaremos a terceira e última parte do nosso projeto que espero seja uma boa receita de pasta ao sugo, mas com molho de tomate orgânico. 😉 !

IMG_0014.JPG

A propósito, na foto abaixo vocês podem ver os primeiros suspiros de vida na plantação do Mauricio e seus tomates alguns meses depois

img_0515

Os arquivos e documentos podem ser encontrados aqui : ArduFarmBot GitHub

Como sempre, espero que este projeto ajude outras pessoas a encontrar seu caminho no apaixonante mundo da eletrônica, do IoT e dos robôs!

Não deixe de visitar e seguir minha página: MJRoBot.org no Facebookcapa-ardufarmbot

Este projeto também está disponível em formato Kindle:

Saludos desde el sur del mundo! 😉

Um abraço e até o próximo post!

Obrigado

Marcelo e Maurício

Robô explorador de labirintos, utilizando Inteligência Artificial com Arduino

Esta é a segunda e última parte de um projeto mais complexo, que explora a potencialidade de um robô seguidor de linha. Nesta etapa, aplicaremos conceitos de inteligência artificial na exploração de labirintos, implementando algoritmos que nos ajudarão a encontrar o caminho da saída mais curto e rápido.

Este projeto foi desenvolvido a partir de meu último tutorial: “Rex”, um robô que nunca perde a linha! Depois de se conseguir desenvolver um robô com capacidade para seguir linhas, o próximo passo natural é sem dúvida, dar-lhe algum grau de inteligência. Assim, nessa etapa, o nosso querido “Rex, o Robô” tentará encontrar uma forma de escapar de um “labirinto” tomando o caminho mais curto e o mais rápido possível.
A propósito, Rex odeia o Minotauro….. 😉

A maioria dos labirintos, por mais complexa sua concepção possa parecer, foram essencialmente formados a partir de uma parede contínua com muitos cruzamentos e bifurcações. Se a parede circundante do objectivo final de um labirinto está ligado ao perímetro do labirinto na entrada, o labirinto sempre poderá ser resolvido mantendo-se uma mão em contacto com a parede, não importando os muitos desvios que possam existir. Estes labirintos “simples” são conhecido como “Simply-connected” ou “perfeitos”, ou em outras palavras, que não contêm loops.

Voltando ao nosso projeto, ele será dividido em duas partes:

1. (Primeira passada): O robô encontrará o seu caminho para sair de um labirinto perfeito desconhecido. Não importa onde você o colocar dentro do labirinto, ele sempre encontrará uma “solução de saída”.

2. (Segunda passada): Uma vez que o robô encontrou uma possível solução para sair do labirinto, ele deve otimizar-la, encontrando o caminho mais curto para ir do início ao fim.

O vídeo abaixo, mostra um exemplo do robô encontrando seu caminho para sair do labirinto (chegar “ao final”). Na primeira vez que o robô explora o labirinto, é claro que vai perder muito tempo “pensando” sobre o que fazer em todo e qualquer cruzamento. Para testar as possibilidades, ele tomará vários caminhos errados e becos sem saída, o que faz com que ele escolha caminhos mais longos e execute várias  “marcha-rés” desnecessárias.

Durante esta”1ª passada”, o robô irá acumulando experiências, “tomando notas” sobre os diferentes cruzamentos e eliminando os atalhos ruins. Em sua “segunda passada”, o robô irá direta e rapidamente ao final do labirinto sem qualquer erro ou dúvida. Ao longo deste tutorial,  exploraremos em detalhes como fazê-lo:

LISTA DE MATERIAIS:

A lista de materiais é basicamente a mesmo que usamos com robô seguidor de linha, exceto que foram incluídos 2 Sensores adicionais para uma melhor precisão na detecção da cruzamentos a esquerda e a direita. O preço final do robô, continua barato (cerca de US$85,00):

  • Corpo (pode ser adaptado para as suas necessidades):
    • 2 quadrados de madeira (80x80mm)
    • 3 Grampos de papel
    • 2 rodas de madeira (diâmetro: 50 mm)
    • 1 roda “solta” (Caster)
    • 9 Elásticos
    • Fita 3M “Command”
    • Articulações plásticas para fixação do sensor
  • Protoboard e fiação
  • 2 conjuntos de baterias (4XNi-metal hidreto) –  5V cada conjunto
  • 2 Servos de Rotação Contínua (SM-S4303R)
  • Arduino Nano
  • Módulo Bluetooth  HC-06
  • 5 sensores x Linha (TCRT5000 4CH Infrared Linha Pista Seguidor Módulo Sensor + 1 sensor de Pista independente)
  • 2 sensores ZX03 (baseado no TCRT5000) Reflective Infrared Sensors (saída analógica)
  • 1 LED
  • 1 Botão

ALTERAÇÕES AO CORPO DO ROBÔ

Retirar o conjunto original dos 5 Sensores e fixar os novos sensores reflectivos aos extremos esquerdo e direito da barra de suporte plástica.

É aconselhável manter os 7 sensores o mais alinhado possível.

Instalação e testes dos novos sensores

A nova matriz de 7 sensores, é montado de forma a que os 5 originais sejam utilizados exclusivamente para o controlo PID e detecção da “linha completa” (explicado mais adiante) e que os novos 2 sensores somente para a detecção dos cruzamentos a esquerda e a direita.

Como uma rápida revisão, vamos lembrar como os 5 sensores originais “digitais” trabalham:

Se um sensor está centrado em relação à linha preta, este irá produzir um sinal HIGH. Por outro lado, o espaço entre os sensores devem ser calculados de tal modo a permitir que dois sensores possam cobrir a largura total da linha preta produzindo assim um sinal HIGH simultaneamente em ambos os sensores.

Como os 2 novos sensores “analógicos” trabalham:

Se um dos sensores está centrado em relação à linha preta, o resultado observado na saída do ADC interno do Arduino será geralmente menor que “100” (lembre-se que o ADC produz uma saída que vai de 0 a 1023). Sobre superfícies mais claras, o valor de saída deverá ser maior (500 a 600 sobre papel branco, por exemplo). Estes valores devem ser testado em diferentes situações de luz de superfície e de materiais para se definir a constante LIMIAR (“THRESHOLD”) correta a ser usada (ver o quadro aqui).

Abaixo, o diagrama mostrando os componentes conectados ao Arduino:

Maze Solver Circuit

Olhando o código do Arduino, cada um dos sensores será definido com um nome específico:

// LFSensor more to the Left is "0"
const int lineFollowSensor0 = 12; //Using Digital input
const int lineFollowSensor1 = 18; //Using Analog Pin A4 as Digital input
const int lineFollowSensor2 = 17; //Using Analog Pin A3 as Digital input
const int lineFollowSensor3 = 16; //Using Analog Pin A2 as Digital input
const int lineFollowSensor4 = 19; //Using Analog Pin A5 as Digital input

const int farRightSensorPin = 0; //Analog Pin A0
const int farLeftSensorPin = 1; //Analog Pin A1

 

Recordando, as possíveis combinações de saída para a matriz de 5 sensores original do seguidor de linha são:

  • 0 0 0 0 1
  • 0 0 0 1 1
  • 0 0 0 1 0
  • 0 0 1 1 0
  • 0 0 1 0 0
  • 0 1 1 0 0
  • 0 1 0 0 0
  • 1 1 0 0 0
  • 1 0 0 0 0

Com a adição dos 2 novos sensores, as suas saídas possíveis são:

  • Sensor Esquerdo: Saída Analógica maior ou menor do que o valor definido de THRESHOLD
  • Sensor Direito: Saída Analógica maior ou menor do queo valor definido de THRESHOLD

A fim de armazenar os valores de cada um dos sensores uma variável tipo matriz  (Array) é criada para os sensores digitais originais 5:

int LFSensor[5]={0, 0, 0, 0, 0};

E duas variáveis do tipo inteiras para os 2 novos sensores analógicos:

int farRightSensor = 0;
int farLeftSensor = 0;

As variáveis serão constantemente actualizadas dependendo do estado de cada um dos sensores:

LFSensor[0] = digitalRead(lineFollowSensor0);
 LFSensor[1] = digitalRead(lineFollowSensor1);
 LFSensor[2] = digitalRead(lineFollowSensor2);
 LFSensor[3] = digitalRead(lineFollowSensor3);
 LFSensor[4] = digitalRead(lineFollowSensor4);
 
 farRightSensor = analogRead(farRightSensorPin);
 farLeftSensor = analogRead(farLeftSensorPin);

Possuindo 5 sensores, como se viu no projeto do Robô seguidor de linha, se permite a geração de uma “variável de erro” que ajudará a controlar a posição do robô sobre a linha. Essa variável de erro será mantida e uma nova denominada “mode” será incluída para saber se o robô está seguindo uma linha, sobre uma linha contínua, uma intersecção ou fora da linha.

Esta variável “mode” será usada também com os novos sensores de esquerda e direita. Consideremos que os novos sensores da esquerda e da direita geraram 3 estados possíveis: H (maior do que THRESHOLD), L (menor do que oTHRESHOLD) e X (irrelevante). Para as saídas digitais, manteremos “0”, “1” e também introduziremos o “X”:

Intersection

X 1  1  1  1  1 X ==> mode = CONT_LINE; error = 0;
H 0 X X X X L ==> mode = RIGHT_TURN; error = 0;  (Veja o exemplo na imagem acima)
L X X X X 0 H ==> mode = LEFT_TURN; error = 0;
X 0 0 0 0 0 X ==> mode = NO_LINE; error = 0;
H 0 0 0 0 1 H ==> mode = FOLLOWING_LINE; error = 4;
H 0 0 0 1 1 H ==> mode = FOLLOWING_LINE; error = 3;
H 0 0 0 1 0 H ==> mode = FOLLOWING_LINE; error = 2;
H 0 0 1 1 0 H ==> mode = FOLLOWING_LINE; error = 1;
H 0 0 1 0 0 H ==> mode = FOLLOWING_LINE; error = 0;
H 0 1 1 0 0 H ==> mode = FOLLOWING_LINE; error = -1;
H 0 1 0 0 0 H ==> mode = FOLLOWING_LINE; error = -2
H 1 1 0 0 0 H ==> mode = FOLLOWING_LINE; error = -3;
H 1 0 0 0 0 H ==> mode = FOLLOWING_LINE; error = -4;

Assim, a implementação da lógica acima na função:

void readLFSsensors()

irá retornar as variáveis “mode” e “error” que serão utilizados na lógica do programa.

É importante testar a lógica dos sensores antes de seguir com o projeto. A função de teste abaixo está incluída no código e poderá ser usado para fins de ensaio:

void testSensorLogic(void) 
{
    Serial.print (farLeftSensor);
    Serial.print (" <== LEFT RIGH==> ");
    Serial.print (farRightSensor);
    Serial.print (" mode: ");
    Serial.print (mode);
    Serial.print (" error:");
    Serial.println (error);
}

Resolvendo o labirinto – a regra da mão esquerda

Como discutido na introdução deste tutorial, a maioria dos labirintos são essencialmente formados a partir de uma parede contínua com muitos cruzamentos e desvios.

Pesquisando na Wikipedia, aprendemos que “o seguidor da parede” é o algorítmo mais conhecido para percorrer labirintos. É também conhecido como “regra da mão esquerda” ou a “regra da mão direita”. Se o labirinto é simplesmente conectado, isto é, todos as suas paredes são ligadas entre si, mantendo-se uma mão em contato com uma das paredes do labirinto é garantido que chegará a uma saída. Usaremos aqui a “Regra da mão esquerda”.

Em resumo, a regra da mão esquerda pode ser descrito como:

  • Coloque a mão esquerda na parede.
  • Comece a andar para a frente
  • Em cada cruzamento, e ao longo do labirinto, manter a sua mão esquerda tocando na parede à sua esquerda.
  • Eventualmente, você vai chegar ao final do labirinto. Você provavelmente não vai seguir o caminho mais curto e mais direto, mas você chegará lá.

Portanto, a chave aqui é identificar as intersecções, definindo que medidas tomar com base nas regras acima. Especificamente no nosso tipo de labirinto em 2D , podemos encontrar 8 tipos diferentes de intersecções :

Intersections types

Olhando a imagem acima, podemos perceber que as possíveis ações nos cruzamentos são:

  1. Em um “cruzamento tipo cruz”
    • Vá para a esquerda ou
    • Vá para a direita ou
    • Siga em frente ou
  2. Em um “T”:
    • Vá para a esquerda ou
    • Vá para a direita
  3. Em um “virar somente a direita”:
    • Vá para a direita
  4. Em um “virar somente a esquerda”:
    • Vá para a esquerda
  5. Em um a frente ou esquerda:
    • Vá para a esquerda ou
    • Siga em frente
  6. Em um a frente ou ou a direita:
    • Vá para a direita ou
    • Siga em frente
  7. Em um beco sem saída:
    • Volte
  8. No final do labirinto:
    • Pare

Aplicando-se  a “regra da mão esquerda” a lista acima, será reduzida a apenas uma opção para uma cada uma das possibilidades:

  1. Em um “cruzamento tipo cruz”
    • Vá para a esquerda
  2. Em um “T” (Transversal):
    • Vá para a esquerda
  3. Em um “virar somente a direita”:
    • Vá para a direita
  4. Em um “virar somente a esquerda”:
    • Vá para a esquerda
  5. Em um a frente ou esquerda:
    • Vá para a esquerda
  6. Em um a frente ou ou a direita:
    • Siga em frente
  7. Em um beco sem saída:
    • Volte
  8. No final do labirinto:
    • Pare

Estamos quase lá. Quando o robô atinge um beco sem saída é fácil identificá-lo, porque não existem situações ambíguas (já implementamos essa ação com o Robô seguidor de linha). O problema está quando o robô encontra uma “linha” por exemplo, pois a linha pode ser parte de um cruzamento tipo “cruz” (1) ou de um “T” (2) ou mesmo um “Final” (8). Além disso, quando o robô chega a um “virar à esquerda ou à direita”, esses cruzamentos podem ser os do tipo simples (opções 3 ou 4) ou opções que podem ir para a frente (5 ou 6). Para se descobrir exatamente em que tipo de intersecção está o robô, é necessário incorporar um passo adicional: o robô deve “dar um passinho a frente” ou seja rodar o que chamamos de “extra inch” e assim “ver” o que vem por adiante:

Maze4
Em termos de fluxo, todas as acções possíveis podem ser descritas como:

  • Em um beco sem saída:
    • Volte
  • Em uma linha:
    • Executar uma polegada extra
    • Se há uma linha:
      • É uma “cruz” ==> Ir para ESQUERDA
    • Se não houver nenhuma linha:
      • é um “T” ==> Ir para ESQUERDA
    • Se houver outra linha:
      • É o fim de Maze ==> PARAR
  • Em uma curva à direita:
    • Executar uma polegada extra
    • se há uma linha:
      • É ir a frente ou virar a direita ==> ir direto
    • Se não houver nenhuma linha:
      • é um virar obrigatoriamente a direita==> Ir para DIREITA
  • Em uma curva à esquerda:
    • Executar uma polegada extra
    • se há uma linha:
      • É ir a frente ou virar a esquerda ==> virar a esquerda
    • Se não houver nenhuma linha:
      • é um virar obrigatoriamente a esquerda==> Ir para ESQUERDA

Note-se que de facto, no caso de um “virar à esquerda”, você poderá pular o teste porque o robô tomará à esquerda de qualquer maneira. Deixei a explicação mais genérica somente para claridade. No código real ignorarei este teste.

IMG_3474

 

A foto ao lado , é de um labirinto bem simples que desenhei no chão do meu laboratório, usando fita isolante de 18mm (3/4) que uso para testes (ainda bem que minha mãe não viu!!!! ;-0:

Aplicando o algorítmo “Left Hand on the Wall” ao código do Arduino

Uma vez que já temos a função  readLFSsensors () modificada, para incluir os 2 sensores adicionais, podemos também re-escrever a função “Loop” introduzindo o algoritmo como descrito anteriormente:

void loop() 
{
     readLFSsensors(); 
     switch (mode)
     { 
         case NO_LINE: 
            motorStop();
            goAndTurn (LEFT, 180);
            break;
         case CONT_LINE: 
            runExtraInch();
            readLFSsensors();
            if (mode == CONT_LINE) mazeEnd();
            else goAndTurn (LEFT, 90); 
            break;
         case RIGHT_TURN: 
            runExtraInch();
            readLFSsensors();
            if (mode == NO_LINE) goAndTurn (RIGHT, 90); 
            break; 
         case LEFT_TURN: 
            goAndTurn (LEFT, 90); 
            break; 
         case FOLLOWING_LINE: 
            followingLine();
            break; 
 
     }
}

Algumas funções importantes aparecem aqui.

followingLine() é a mesma utilizada com o robô seguidor de linha, que quando se está apenas seguindo uma linha, deve-se: calcular o PID e controlar os motores, dependendo dos valores dos ganhos da malha de controle usando a função: motorPIDcontrol ();

runExtraInch (): vai empurrar o robô para a frente um pouquinho. Quanto o robô se moverá, dependerá do tempo que você usa na função delay(), antes que mande parar os motores.

void runExtraInch(void)
{
    motorPIDcontrol();
    delay(extraInch);
    motorStop();
}

goAndTurn (direction, angle): Esta função é importante, porque você na verdade não pode virar o robô, tão logo perceba o tipo de intersecção em que está. Lembre-se que projectamos um robô do tipo diferencial que, quando faz curvas, “gira em torno do seu eixo”. Assim, para sair de um cruzamento, girar 90 graus e continuar movendo-se sobre a linha, o centro das rodas deve obrigatoriamente estar alinhado com o centro da intersecção. Uma vez que a linha dos sensores está à frente do eixo das rodas, o robô deve mover-se para a frente para alinhá-los. Os moteres devem funcionar por um tempo “t” dependendo da distância entre a linha dos sensores e o eixo dos motores ( “d”), velocidade e tamanho das rodas. Esta constante de tempo “t” é  no código: adjGoAndTurn, que deve ser ajustada dependendo de seu projeto:

Turn_Explanation

void goAndTurn(int direction, int degrees)
{
    motorPIDcontrol();
    delay(adjGoAndTurn);
    motorTurn(direction, degrees);
}

Neste ponto, o robô já está “resolvendo um labirinto”!  Você acabou de terminar o “Primeiro Passo”. Não importa onde você começar dentro de um labirinto, você chegará ao final.

Abaixo, um vídeo mostrando um teste para esta fase do projeto:

Tomando nota do Caminho

Consideremos o exemplo abaixo:

Maze1

Partindo do ponto escolhido, o robô encontrará 15 Interseções antes de chegar ao final do labirinto:

  1. Esquerda (L)
  2. Back (B)
  3. Esquerda (L)
  4. Esquerda (L)
  5. Esquerda (L)
  6. Back (B)
  7. Reto (S)
  8. Back (B)
  9. Esquerda (L)
  10. Esquerda (L)
  11. Back (B)
  12. Reto (S)
  13. Esquerda (L)
  14. Esquerda (L)
  15. Fim

O que deve ser feito em qualquer um desses cruzamentos é “salvar a decisão tomada” na mesma sequência em que aconteça. Para isso, vamos criar uma nova variável (matriz) que irá armazenar o caminho que o robô tenha tomado:

A variável path[] e 2 índices variáveis, serão utilizados em conjunto para se gravar os passos:

char path[100] = "";
unsigned char pathLength = 0; // the length of the path
int pathIndex = 0; // used to reach an specific array element.

Voltando ao exemplo, uma vez percorrido todo o circuito, as variáveis ficariam:

path = [LBLLLBSBLLBSLL] e  pathLengh = 14

Simplificando (otimizando) o Caminho

Voltemos ao nosso exemplo. Olhando para o primeiro grupo de cruzamentos, percebemos que o primeiro ramo esquerdo é na verdade um “Dead End”, e assim, se o robô em vez de um “lateral-esquerdo-esquerdo” apenas tivesse seguido reto nesse primeiro cruzamento, uma grande quantidade de energia e tempo seriam salvas! Em outras palavras, uma sequência do tipo “LBL”, de facto, seria o mesmo que “S”.

Maze2

Isso é exatamente como o caminho completo pode ser otimizado. Se você analisar todas as possibilidades onde um “U turn” (back) é utilizado, o conjunto de 3 cruzamentos onde o”U-Turn” ( “B”) aparece  (“xBx”) poderá ser reduzido para apenas um.

A descrição acima é apenas um exemplo, abaixo podemos encontrar a lista completa de possibilidades:

LBR = B
LBS = R
RBL = B
SBL R =
SBS = B
LBL = S

Aplicando-se as substituições acima para o caminho completo de nosso exemplo, podemos reduzi-lo a:

path = [LBLLLBSBLLBSLL] ==> LBL = S

path = [SLLBSBLLBSLL] ==> LBS = R

path = [SLRBLLBSLL] ==> RBL = B

path = [SLBLBSLL] ==> LBL = S

path = [SSBSLL] ==> SBS = B

path = [SBLL] ==> SBL = R

path = [RL]

Olhando para o exemplo, é muito claro que se o robô gira para a DIREITA logo no primeiro cruzamento e depois disso, à esquerda, ele chegará ao final do labirinto pelo caminho mais curto!

Maze3

A primeira passada será consolidada na função mazeSolve (). Esta função é, de facto, a função loop () utilizada anteriormente, onde se incorporaram as etapas de armazenamento e otimização do caminho. Quando a primeira passada termina, a variavel path[] conterá o caminho já optimizado.

Uma nova variável é introduzida para sinalizar o final da “passada”:

unsigned int status = 0; // solving = 0; reach end = 1

Abaixo a função completa para a primeira etapa do programa:

void mazeSolve(void)
{
   while (!status)
   {
      readLFSsensors(); 
      switch (mode)
      { 
         case NO_LINE: 
            motorStop();
            goAndTurn (LEFT, 180);
            recIntersection('B');
            break;
         case CONT_LINE: 
            runExtraInch();
            readLFSsensors();
            if (mode != CONT_LINE) {goAndTurn (LEFT, 90); recIntersection('L');} // or it is a "T" or "Cross"). In both cases, goes to LEFT
            else mazeEnd(); 
            break;
         case RIGHT_TURN: 
            runExtraInch();
            readLFSsensors();
            if (mode == NO_LINE) {goAndTurn (RIGHT, 90); recIntersection('R');}
            else recIntersection('S');
            break; 
         case LEFT_TURN: 
            goAndTurn (LEFT, 90); 
            recIntersection('L');
            break; 
         case FOLLOWING_LINE: 
            followingLine();
            break; 
 
       }
   }
}

Aqui uma nova função foi introduzida: recIntersection (direction)

Esta função será a usada para armazenar as decisões tomadas nos cruzamentos e também para chamar outra importante função: simplifyPath(), que irá otimizando “em tempo real” o grupo de 3 cruzamentos envolvendo “U-Turn”, como vimos anteriormente.

void recIntersection(char direction)
{
    path[pathLength] = direction; // Store the intersection in the path variable.
    pathLength ++;
    simplifyPath(); // Simplify the learned path.
}

O crédito para a criação da função simplifyPath () é de Patrick McCabe. Eu apenas a incluí ao meu código (para mais detalhes, visite patrickmccabemakes.com):

void simplifyPath()
{
   if(pathLength < 3 || path[pathLength-2] != 'B') // only simplify the path if the second-to-last turn was a 'B'
   return;
   int totalAngle = 0;
   int i;
   for(i=1;i<=3;i++)
   {
     switch(path[pathLength-i])
     {
        case 'R':
           totalAngle += 90;
           break;
        case 'L':
           totalAngle += 270;
           break;
        case 'B':
           totalAngle += 180;
           break;
      }
    }
    totalAngle = totalAngle % 360;  // Get the angle as a number between 0 and 360 degrees.
    switch(totalAngle) // Replace all of those turns with a single one.
    {
       case 0:
          path[pathLength - 3] = 'S';
          break;
       case 90:
          path[pathLength - 3] = 'R';
          break;
       case 180:
          path[pathLength - 3] = 'B';
          break;
       case 270:
          path[pathLength - 3] = 'L';
          break;
     }
     pathLength -= 2; // The path is now two steps shorter.
}

A segunda passada: resolvendo o labirinto o mais rápido possível!

O programa principal: loop () é bem simples:

void loop() 
{
    ledBlink(1);
    readLFSsensors(); 
    mazeSolve(); // First pass to solve the maze
    ledBlink(2); // end 1st pass
    
    while (digitalRead(buttonPin) && !mode) { }
    pathIndex = 0;
    status = 0;
    mazeOptimization(); //run the maze as fast as possible
    ledBlink(3); // end of 2nd pass. Maze solved!
}

Assim, quando a primeira passada termina, o que devemos fazer é apenas “alimentar” o robô com o caminho otimizado. Ele vai começar a percorrer o labirinto novamente e quando uma intersecção for encontrado, ele não mais tomará decisões, mas simplesmente seguirá o que está armazenado na variável path [].

Para a segunda passada usamos a função mazeOptimization(), que por sua vez, chama a função mazeTurn(path[]) que comandará os movimentos do robô nessa segunda passada:

void mazeOptimization (void)
{
   while (!status)
   {
      readLFSsensors(); 
      switch (mode)
      {
         case FOLLOWING_LINE:
            followingLine();
            break; 
         case CONT_LINE:
            if (pathIndex >= pathLength) mazeEnd (); 
            else {mazeTurn (path[pathIndex]); pathIndex++;}
            break; 
         case LEFT_TURN:
            if (pathIndex >= pathLength) mazeEnd (); 
            else {mazeTurn (path[pathIndex]); pathIndex++;}
            break; 
         case RIGHT_TURN:
            if (pathIndex >= pathLength) mazeEnd (); 
            else {mazeTurn (path[pathIndex]); pathIndex++;}
            break; 
      } 
   } 
}
void mazeTurn (char dir) 
{
   switch(dir)
   {
      case 'L': // Turn Left
         goAndTurn (LEFT, 90); 
         break; 
      case 'R': // Turn Right
         goAndTurn (RIGHT, 90); 
         break; 
      case 'B': // Turn Back
         goAndTurn (RIGHT, 180); 
         break; 
      case 'S': // Go Straight
         runExtraInch(); 
         break;
    }
}

A segunda passada está feita!

O vídeo abaixo mostra o exemplo trabalhado aqui completo, onde Rex encontra seu caminho para livrar-se do Minotauro! 😉

O código Arduino completo para este projeto, poderá ser encontrado no link abaixo:

Codigo para o Arduino

Usando o aplicativo Android para o ajuste

A App Android desenvolvido para o projeto do Robô seguidor de Linha também pode ser usado aqui.  O código Arduino apresentado na última etapa já inclui comunicação com o dispositivo Android, mas se não quiser usar-lo não há problema, porque o código é “transparente”.

Eu usei bastante o dispositivo Android durante o projeto para enviar dados de teste do robô para o dispositivo, utilizando-se o campo de “Mensagem recebida”.

Diversas variáveis devem ser bem definidas, a fim de garantir que o robô gire corretamente. Os mais importantes estão abaixo (os marcados em negrito tive mudar-los várias vezes):

const int power = 250;
const int iniMotorPower = 250;
const int adj = 0;
float adjTurn = 8;
int extraInch = 200;
int adjGoAndTurn = 800;
const int THRESHOLD = 150;
float Kp=50;
float Ki=0;
float Kd=0;

CONCLUSÃO

Esta é a segunda e última parte de um projeto complexo, explorando a potencialidade de um robô seguidor de linha, onde aplicando-se conceitos de inteligência artificial se conseguiu explorar labirintos,  encontrando o caminho da saída mais curto e rápido.

Espero que esse trabalho possa contribuir para que outras pessoas possam aprender mais sobre eletrônica, robôs, Arduino, etc.

Não deixe de visitar e seguir minha página: MJRoBot.org no Facebook

Saludos desde el sur del mundo! 😉

Um abraço e até o próximo post!

Obrigado

Marcelo

“Rex”, um robô que nunca perde a linha!

O objetivo deste projeto é construir um robô seguidor de linha com controle PID. Também usaremos um dispositivo Android para poder configurar mais facilmente os principais parâmetros (ganhos) da malha de controle.

Introdução:

Esta é a primeira parte de um projeto mais complexo, explorando a potencialidade de um robô seguidor de linha. Construiremos um robô com controle PID e também aprenderemos como usar um dispositivo Android na configuração mais dos principais parâmetros (ganhos) de sua malha de controle. Na segunda parte deste projeto Robô explorador de labirintos, utilizando Inteligência Artificial com Arduino, aplicaremos conceitos de inteligência artificial para explorar labirintos,  encontrando o caminho da saída mais curto e rápido.

Abaixo um vídeo que mostra o robô seguindo um circuito básico:

Lista de Materiais:

A lista de materiais necessários é simples e o preço final do robô, muito barato (cerca de US$ 75,00):

  • Corpo (pode ser adaptado para as suas necessidades):
    • 2 quadrados de madeira (80x80mm)
    • 3 Grampos de papel
    • 2 rodas de madeira (diâmetro: 50 mm)
    • 1 roda “solta” (Caster)
    • 9 Elásticos
    • Fita 3M “Command”
    • Articulações plásticas para fixação do sensor
  • Protoboard e fiação
  • 2 conjuntos de baterias (4XNi-metal hidreto) –  5V cada conjunto
  • 2 Servos de Rotação Contínua (SM-S4303R)
  • Arduino Nano
  • Módulo Bluetooth  HC-06
  • 5 sensores x Linha (TCRT5000 4CH Infrared Linha Pista Seguidor Módulo Sensor + 1 sensor de Pista independente)
  • 1 LED
  • 1 Botão

Ajuste dos motores

Como motores, utilizei 2 servos em modo contínuo (SM-S4303R). Eles devem ser “colados”, formando um único e sólido bloco como você pode ver na foto (use a fita 3M Command, cola ou fita dupla face). Esses servos girarão a uma dada velocidade dependendo da largura do pulso que receba em sua entrada de dados. Para este servo específico, a largura do pulso varia de 1.0 ms a 2.0ms (outros tipos de servos podem trabalhar com larguras de pulso diferentes).

Arduino3

Olhando em detalhes:

  • Um pulso de 1.5ms comandará o servo para que fique em sua posição neutra, ou “parado”.
  • Um pulso de 1.0 ms comandará o servo para que funcione em sua velocidade máxima (cerca de 70 RPM) em uma direção e 2.0ms na direção oposta.
  • Pulsos entre 1.0 e 1.5ms ou 1.5ms e 2.0ms, farão os servos girarem em velocidades intermediárias e proporcionais.

Arduino4

 

A primeira coisa que se deve fazer, é enviar um pulso de 1.5ms (1500us) para verificar se os motores ficam realmente”parados”. Caso isto não ocorra, os servosdevem ser ajustados (procure o parafuso amarelo, na lateral do servo). É claro que se o seu servo não possui esse ajuste, tente alterar o valor “1500ms” até obter o ponto final.

Monte o Arduino e os servos como mostrado abaixo:

Motorflb5wsqin3edek6-large

O código abaixo, para o Arduino deve ser usado para o ajuste:

#include Servo // este código linha deve ser mudado, por favor, olhe o código anexado
Servo leftServo;
Servo rightServo;
void setup ()
{
   leftServo.attach (5);
   rightServo.attach (3);
   leftServo.writeMicroseconds (1500);
   rightServo.writeMicroseconds (1500);
}

void loop ()
{
}

Montagem do Corpo e motores para teste de movimento

fk1e3mein3ef60l-medium

 

1. Com a fita 3M Command, fixar os 2 Servos à uma das peças de madeira quadrado.

 

 

fd75kabin3ef63f-small2. Fixar o segundo quadrado de madeira ao anterior, utilizando os grampos de papel. Ajuste o comprimento da plataforma para suas necessidades.

fb16lqlin3ef65n-small

 

 

3. Fixe o caster utilizando-se o grampo de papel.

 

ftmc0s8in3ee81d-medium4. A alimentação para os motores será fornecida por um dos conjuntos de baterias (5V). Este conjunto de baterias será instalado entre o protoboard e a plataforma do corpo.

 

 

fog5h6ein3ee14w-small
5. Conecte a bateria a ser utilizada com os servos: deixe uma linha de alimentação lateral do protoboard exclusivamente para os servos

6. Conecte o Arduino Nano ao protoboard

7. Conecte os terras do Protoboard ao GND do Arduino.

8. Conecte os Servos ao Arduino: LEFT => Pin 5;   RIGHT => Pin 3

9. Ligue o LED ao Arduino Pino 13

10. Ligue o botão para Arduino Pin 9

 

Note que, devido a maneira com que os servos estão montados (em oposição) as faixa de variação de velocidade em função da largura de pulso são diferentes:

  • RIGHT Servo: velocidade de avanço frontal vai de 1500us (parado) a 2000us (velocidade máxima)
  • LEFT Servo: velocidade de avanço frontal vai de 1500us (parado) a 1000us (velocidade máxima)

Um LED externo é adicionar ao pin13, com finalidade de sinalização e testes (você pode usar o LED interno do Arduino se quiser, mas leve em consideração que será difícil de vê-lo no meio dos cabos).

Além disso, um botão é ligado ao pino 9. Este botão será muito útil para testes e para o comando de início, quando o robô estiver posicionado no início do circuito de linha.
Por exemplo:

while (digitalRead (buttonPin)) {}
    motorTurn (LEFT, 500);
    motorTurn (RIGHT, 500);

Note que as 2 linhas que irão comandar o robô para virar à esquerda, esperar 500ms e virar à direita, só serão executadas depois que você pressionar o botão (buttonPin = 0). Antes disso, o programa ficará executando um “loop infinito”.

O código Motor_Test.ino, disponível no final deste tutorial, poderá ser usado como uma base para um teste de motores mais completo (para frente, para trás, parada completa, virar à esquerda, virar à direita). Se necessário você deverá ajustar os atrasos (“delays”) para obter um ângulo de virada correto.. Também, dependendo de seus motores, os valores definidos para a largura de pulso devem ser um pouco diferentes para compensar qualquer falta de alinhamento entre os mesmos.

Instalando o módulo Bluetooth (opcional)

O módulo Bluetooth HC-06 deverá ser instalado no protoboard. A biblioteca SoftSerial do Arduino será utilizada.

Motor&amp;BTf4fe9iqin3eedct-medium

Abaixo as conexões de pino HC-06:

  • Tx Pin para Arduino pino 10 (Rx)
  • RX Pin para Arduino pino 11 (Tx)
  • VCC / GND para Arduino 5V / GND

 

O robô funcionará com ou sem o módulo Bluetooth. O código será construído de uma maneira que caso você não ative o BT, os parâmetros utilizados serão os padrões. Na última parte deste tutorial, vou explorar como usar um aplicativo Android para enviar dados para um melhor ajuste dos parâmetros do robô e mover-lo em modo manual. O uso da App Android é um opcional no caso que alguém deseje explorar mais o uso de um Robô seguidor de linha em competições, por exemplo.

Adicionando os sensores de linha


fqq0sfhin3ees26-large

1. Fixe os 5 sensores em uma barra de plástico, como mostrado nas fotos

 

 

fw9zenoin3eeqyx-large

2. É aconselhável para rotular os sensores para fins de teste. O nome sensores vai de de “0” (mais à esquerda) para “4” (mais à direita)

 

f9d55psin3eer0w-large

3. Passe os cabos sob o quadro, usando os elásticos para corrigi-los (tome cuidado para não misturar-se com as rodas ou Caster.

 

f5c3vfgin3eersy-large

4. Conecte os cabos de pinos do Arduino, como abaixo:

  • Sensor 0 = 12
  • Sensor 1 = 18
  • Sensor 2 = 17
  • Sensor 3 = 16
  • Sensor 4 = 19

fmhzshlin3ecx2e-large

 

5. Fixe o segundo conjunto de baterias 5V e conecte ao pino Vin do Arduino.

Line Follow full circuit

 

 

 

Em meu caso, estou usando um módulo com 4 sensores integrados + 1 extra. Todos são compatíveis. Para simplificar, no diagrama abaixo, I inclui 5 sensores individuais conectados juntos. O resultado final é o mesmo em ambas as configurações.

 

Implementando a Lógica dos Sensores de linha

IRsensorCada sensor é constituído por um LED e um fotodiodo, ambos infravermelhos. A luz emitida pelo LED atinge a superfície e é reflectida de volta para o fotodiodo. O fotodiodo em seguida, gera uma tensão de saída proporcional à reflectância da superfície.

Track SensorsNo caso dos sensores utilizados, um circuito integrado no módulo gera um sinal de saída digital simples (ALTO: escuro; BAIXO: Luz). Um potenciômetro instalado no módulo (ver foto) irá ajustar o nível correto de luz para ser considerada “dark” ou não. Quando a luz reflectida é conLine Follower Sensorsiderada “escura”, a saída fica ALTA ( “1”) e BAIXA ( “0”) para outra cor mais clara. Eu usei aqui um módulo integrado com 4 sensores e e módulo extra com um sensor único (forma diferente, mas mesma lógica). A combinação é uma matriz de 5 sensores que eu considero apropriado para este tipo de controle, como explicado abaixo.

Sensor1_Cover

A matriz de 5 sensores  é montada de forma que, se apenas um sensor está centrado em relação à linha preta, ele irá produzir um “1”.

 

Sensor1_&amp;2_Cover

Por outro lado, o espaço entre os sensores devem ser calculados para permitir que 2 sensores possam cobrir a largura total da linha preta simultaneamente,também produzindo assim um “1” em ambos os sensores (ver os quadros aqui).
As possíveis combinações de saída do conjunto de sensores são:

  • 0 0 0 0 1
  • 0 0 0 1 1
  • 0 0 0 1 0
  • 0 0 1 1 0
  • 0 0 1 0 0
  • 0 1 1 0 0
  • 0 1 0 0 0
  • 1 1 0 0 0
  • 1 0 0 0 0

Trabalhar com 5 sensores, permite a geração de uma “variável de erro” que ajudará a controlar a posição do robô sobre a linha, como mostrado abaixo:
Vamos considerar que a condição ideal é quando o robô está centrado, tendo a linha apenas abaixo do “sensor do meio” (Sensor 2). A saída da matriz será: 0 0 1 0 0  e nesta situação, o “erro” será “zero”.

Se o robô começa a derivar para a esquerda (a linha “parece mover-se” para a direita) o erro deverá aumentar e com um sinal positivo. Se o robô começar a mover-se para a direita (a linha” parece mover-se “para a esquerda), da mesma maneira, o erro tem de aumentar, mas agora com um sinal negativo.
A variável de erro, relacionada com o estado do sensor será:

  • 0 0 0 0 1 ==> erro = 4
  • 0 0 0 1 1 ==> erro = 3
  • 0 0 0 1 0 ==> erro = 2
  • 0 0 1 1 0 ==> erro = 1
  • 0 0 1 0 0 ==> erro = 0
  • 0 1 1 0 0 ==> erro = -1
  • 0 1 0 0 0 ==> erro = -2
  • 1 1 0 0 0 ==> erro = -3
  • 1 0 0 0 0 ==> erro = -4

Olhando o código Arduino, cada um dos sensores será definido com um nome específico (considere que o Sensor mais à esquerda deve ser atribuído com uma etiqueta de “0”):

const int lineFollowSensor0 = 12;
const int lineFollowSensor1 = 18;
const int lineFollowSensor2 = 17;
const int lineFollowSensor3 = 16;
const int lineFollowSensor4 = 19;

A fim de armazenar os valores de cada um dos sensores uma variável tipo Array será criada:

int LFSensor [5] = {0, 0, 0, 0, 0};

Cada posição da matriz irá ser constantemente actualizada com a saída de cada um dos sensores:

LFSensor [0] = digitalRead (lineFollowSensor0);
LFSensor [1] = digitalRead (lineFollowSensor1);
LFSensor [2] = digitalRead (lineFollowSensor2);
LFSensor [3] = digitalRead (lineFollowSensor3);
LFSensor [4] = digitalRead (lineFollowSensor4);

Uma vez armazenado o valor de cada um dos sensores, uma lógica deve ser implementada para gerar a variável de erro:

if ((LFSensor [0] == 0) && (LFSensor [1] == 0) && (LFSensor [2] == 0) && (LFSensor [3] == 0) && (LFSensor [4] == 1 )) erro = 4;
else if ((LFSensor [0] == 0) && (LFSensor [1] == 0) && (LFSensor [2] == 0) && (LFSensor [3] == 1) && (LFSensor [4] == 1)) erro = 3;
else if ((LFSensor [0] == 0) && (LFSensor [1] == 0) && (LFSensor [2] == 0) && (LFSensor [3] == 1) && (LFSensor [4] == 0)) erro = 2;
else if ((LFSensor [0] == 0) && (LFSensor [1] == 0) && (LFSensor [2] == 1) && (LFSensor [3] == 1) && (LFSensor [4] == 0)) erro = 1;
else if ((LFSensor [0] == 0) && (LFSensor [1] == 0) && (LFSensor [2] == 1) && (LFSensor [3] == 0) && (LFSensor [4] == 0)) erro = 0;
else if ((LFSensor [0] == 0) && (LFSensor [1] == 1) && (LFSensor [2] == 1) && (LFSensor [3] == 0) && (LFSensor [4] == 0)) erro = - 1;
else if ((LFSensor [0] == 0) && (LFSensor [1] == 1) && (LFSensor [2] == 0) && (LFSensor [3] == 0) && (LFSensor [4] == 0)) erro = -2;
else if ((LFSensor [0] == 1) && (LFSensor [1] == 1) && (LFSensor [2] == 0) && (LFSensor [3] == 0) && (LFSensor [4] == 0)) erro = -3;
else if ((LFSensor [0] == 1) && (LFSensor [1] == 0) && (LFSensor [2] == 0) && (LFSensor [3] == 0) && (LFSensor [4] == 0)) erro = -4;

Controlando a direção (Proporcional Control – P)

Perfeito! Neste ponto, o robô está montado e operacional. Você deve aproveitar e executar alguns testes básicos com os motores, ler a saída de sensores e testá-los através de uma linha. O que está faltando ainda, é o verdadeiro “cérebro”, ou seja fazer o Robô executar a tarefa de “andar na linha” por seus próprios meios. Vamos conseguir isso, através da implementação de uma lógica de controle que garantirá ao robô seguir uma linha, seja qual for o desenho.

PD Control
Suponha que o robô esteja “andando” sobre uma linha e a saída do Array de Sensores é: “0 0 1 0 0”.  Neste caso, o erro correspondente é “0” e ambos os motores estarão empurrando o robô para a frente com uma velocidade constante. Um código para essa condição seria:

rightServo.writeMicroseconds (1500 + iniMotorPower);
leftServo.writeMicroseconds (1500 - iniMotorPower);

Por exemplo, com iniMotorSpeed = 250,  significa que LEFT Servo receberá pulsos de 1,1250us e o RIGHT Servo 1750us, o que fará com que o robô avance com metade de sua velocidade máxima. Lembre-se que a velocidade de avanço do Servo direito irá variar com larguras de pulso que variam de 1500us (parado) a 2000us (velocidade máxima) e o Servo esquerdo de 1500us (parado) a 1000us (velocidade máxima).

Suponha agora que o robô comece a derivar para a esquerda ( a “linha vai para a direita”), fazendo com que o sensor 3 també, fique sobre a linha. Neste caso, a saída do Array de sensores será: “0 0 1 1 0” e o erro = 1. Nesta situação, o que necessitamos é virar o robô para a direita. Para fazer isso, devemos diminuir a velocidade do Servo direito o que significa diminuir a largura do pulso. Além disso, a velocidade do Servo esquerdo deve aumentar, o que significa diminuir a largura do pulso. Para isso, precisamos alterar a função de controle do motor:

rightServo.writeMicroseconds (1500 + iniMotorPower - erro);  // Erro positivo: velocidade diminue
leftServo.writeMicroseconds (1500 - iniMotorPower - erro);  //Erro positivo: velocidade aumenta

A lógica acima é correcta, mas é facil compreender que a adição ou subtracção de apenas “1” microssegundo na duração dos pulsos não irá gerar a correcção necessária, ao menos em um tempo realista. É intuitivo que o número a se adicionar ou subtrair deve ser maior, por exemplo, 50, 100, etc. Para conseguir isso, o “erro” deve ser multiplicado por uma constante “K”. Uma vez que a influência dessa constante será proporcional ao erro, vamos chamar-la “Constante proporcional: Kp. No meu caso, testei o robô com uma constante Kp=50 e ele funcionou muito bem.

A função final para os motores será:

int Kp = 50;
rightServo.writeMicroseconds (1500 + iniMotorPower - Kp * erro);
leftServo.writeMicroseconds (1500 - iniMotorPower - Kp * erro);

Podemos resumir o que acontecerá com os motores, como mostrado abaixo:

  • Array de Sensores: 0 0 1 0 0
    • erro = 0
    • Servo direito: pulso = 1750us
    • Servo esquerdo: pulso  = 1250us
    • (ambos os motores a mesma velocidade)
  • Array de Sensores: 0 0 1 1 0
    • erro = 1
    • Servo direito: pulso = 1700us (mais lento)
    • Servo esquerdo: pulso = 1200us (mais rápido)

Se a situação é o oposto e o robô vai para a direita, o erro seria “negativo” e a velocidade dos servos deve mudar:

  • Array de Sensores: 0 0 1 0 0
    • erro = 0
    • Servo direito: pulso = 1750us
    • Servo esquerdo: pulso = 1250us
    • (ambos os motores a mesma velocidade)
  • Array de Sensores: 0 1 1 0 0
    • erro = -1
    • Servo direito: pulso = 1800us (mais rápido)
    • Servo esquerdo: pulso = 1300us (mais lento)

Neste ponto fica claro que quanto mais o robô “escorregue” para um lado, maior será o erro (1, 2, 3 ou 4) e mais rápido ele deve retornar ao centro (valores a serem adicionados a largura do pulso: 50, 100, 150, 200). A velocidade com que o robô irá reagir ao erro será proporcional ao mesmo.

Controle PID

Controle PID (Proporcional, Derivativo e Integral) é um dos sistemas de controle mais comuns que existem. A maioria dos sistemas de controle industriais utilizam algum tipo do controle PID. Há muitas maneiras de ajustar uma malha PID, incluindo a técnica “tentativa e erro”, que é a que usaremos nesse projeto.

Pense no PID como uma mola simples. Uma mola tem um comprimento original (setpoint), que quando perturbada, pela expansão ou contração, tende a recuperar o seu comprimento original no menor tempo possível. Controladores PID são utilizados onde quer que haja a necessidade de controlar uma quantidade física e a torná-la igual a um valor pré-especificado. Por exemplo, controlador de velocidade em carros, robôs, reguladores de temperatura, reguladores de tensão, etc.

Como o PID funciona?
O sistema calcula o “ePIDrro” ou “desvio” da quantidade física em relação ao set point, medindo o valor atual dessa quantidade física usando um sensor. Para voltar ao set point, este “erro” deve ser minimizado, idealmente igual a zero. Além disso, esse processo deve ocorrer o mais rapidamente possível (também idealmente esse tempo deveria ser zero).
Mais informações, acesse:  http://en.wikipedia.org/wiki/PID_controller

Implementando PID

  1. erro (e):
    Este valor é igual à diferença entre o ponto de regulação e o valor actual da quantidade a ser controlada.
    error = set_point – CURRENT_VALUE (no nosso caso é a variável de erro começa a partir da posição do robô sobre a linha
  2. Termo Proporcional (P):
    Este termo é proporcional ao erro.
    P = error
    Este valor é responsável pela magnitude da mudança necessária na quantidade física para atingir o ponto de ajuste. O termo proporcional é o que determina o tempo de subida da malha de controle ou o quão rápido ele vai chegar ao set point.
  3. Termo Integral (I):
    Este termo é a soma de todos os valores de erro anterior.
    I = I + error
    Este valor é o responsável pela rapidez de resposta do sistema para a mudança do ponto de ajuste. O termo integral é utilizado para eliminar o erro de estado estacionário exigido pelo termo proporcional. Normalmente, pequenos robôs não usam o termo integral porque não estamos preocupados com erro de estado estacionário e isso pode complicar o ajuste.
  4. Diferencial ou termo derivativo (D):
    Este termo é a diferença entre o erro instantâneo do ponto de ajuste, e o erro a partir do instante anterior.
    D = error – previousError
    Este valor é responsável para diminuir (“slow down”) a taxa de variação da quantidade física quando nos aproximamos do ponto de ajuste. O termo derivativo é utilizado a reduzir o “overshoot” ou o quanto o sistema “super corrige”.

Equação:

PIDvalue = (Kp * P) + (Ki * I) + (Kd * D)

Onde:

  • Kp é a constante usada para fazer variar a magnitude da mudança necessária para atingir o ponto de ajuste.
  • Ki é a constante utilizada para variar a velocidade com que a mudança deve ser apresentado na quantidade física para atingir o ponto de ajuste.
  • Kd é a constante utilizada para variar a estabilidade do sistema.

Uma abordagem para a obtenção das constantes é deixar Ki de fora  e trabalhar com o Controle PD (manter Ki = 0);  definir a variável Kd como 0 e sintonizar o termo Kp sozinho pela primeira vez. Kp de 25 é um bom lugar para começar no nosso caso aqui e ir subindo seu valor. Na última etapa foi utilizado um Kp de 50 que funciona muito bem com meu robô. Se o robô reagir muito lentamente, voce deve aumentar o valor. Se o robô parece reagir muito rapidamente tornando-se instável, diminua o valor. Uma vez que o robô responda razoavelmente, sintonize a parte derivativa da malha de controle. Primeiro defina o valor Kp e Kd cada um para a 1/2 do valor Kp. Por exemplo, se a resposta do robô é razoável com um Kp = 50, definir Kp = 25 e Kd = 25 para iniciar. Aumente o ganho de Kd para diminuir o “overshoot ou o diminua se o robô se tornar instável.

Um outro componente do loop a ser considerado é a taxa real de amostra / loop. Acelerar-la ou para retardar-la pode fazer uma diferença significativa no desempenho do robô. Isso é definido pelas declarações de “Delay” que você tem em seu código. Use o método tentativa-erro para obter o melhor resultado.

Com base na abordagem anterior, implementamos a função abaixo:

void calculatePID () 
{
   P = error;
   I = I + error;
   D = error - previousError;
   PIDvalue = (Kp * P) + (Ki * I) + (Kd * D);
   previousError = error;
}

A constante Kp simples usada na última etapa será substituída agora por PIDvalue, mais completa:

void motorPIDcontrol () 
{
   int leftMotorSpeed = 1500 - iniMotorPower - PIDvalue; 
   int rightMotorSpeed = 1500 + iniMotorPower - PIDvalue;
   leftServo.writeMicroseconds (leftMotorSpeed);
   rightServo.writeMicroseconds (rightMotorSpeed);
}

Mas note que se você tem Kd e Ki = 0, PIDvalue = Kp * error, exatamente como na etapa anterior onde usamos só o controle Proporcional.

Adicionando condições especiais ao código final

 

FullSizeRender 13

Neste ponto do projeto, o robô já pode seguir sem parar um circuito de linha do tipo “loop constante”.

A função Loop do programa de ciclo seria simplesmente:

void loop ()
{
   readLFSsensors (); // Ler sensores, armazenar os valores no Array de sensores e calcular o "erro"
   calculatePID (); 
   motorPIDcontrol ();
}

Mas, para uma operação mais completa e real, é importante acrescentar, pelo menos um par de “comandos básicos de linha”.

Por exemplo, vamos introduzir uma nova variável: "mode", definindo 3 estados para esta variável:

#define STOPPED 0
#define FOLLOWING_LINE 1
#define NO_LINE 2

Se todos os sensores de encontrar uma linha preta, a saída do Array de Sensor seria: 1 1 1 1 1. Nesta condição, podemos definir o modo como “PARADO” e o robô deve realizar um “Full Stop”.

if ((LFSensor [0] == 1) && (LFSensor [1] == 1) && (LFSensor [2] == 1) && (LFSensor [3] == 1) && (LFSensor [4] == 1 )) {mode = STOPPED;}

Outra situação comum com robôs seguidores de linha, é quando o mesmo não encontra “nenhuma linha”, ou a saída do Array de Sensores é: 0 0 0 0 0. Neste caso, podemos programá-lo para girar 180 graus (ou girar em ângulos pequenos até que uma linha é encontrada e a condição normal FOLLOWING_LINE é retomada.

else if ((LFSensor [0] == 0) && (LFSensor [1] == 0) && (LFSensor [2] == 0) && (LFSensor [3] == 0) && (LFSensor [4] == 0)) {mode = NO_LINE;)

A função loop completa seria:

void loop () 
{
   readLFSsensors ();
   switch (mode)
   {
     case STOPPED:
        motorStop();
        break;
     case NO_LINE:
        motorStop ();
        motorTurn (LEFT, 180);
        break;
     case FOLLOWING_LINE:
        calculatePID ();
        motorPIDcontrol ();
        break;
   }
}

O código final incluirá integrar lógica adicional e também algumas variáveis devem ser inicializadas, etc. Durante as explicações , deixei estes detalhes de fora para simplificar a explicação, mas acredito que tudo fique claro dando uma olhada no código final.

Usando o App Android para ajustar os ganhos do controlador PID

No código do Arduino, você poderá encontrar no arquivo “robotDefines.h” as seguintes definições para as constantes “default” serem usadas com o controle PID.

float Kp = 50;
float Ki = 0;
float Kd = 0;

Como explicado anteriormente,  a melhor maneira de definir os ganhos corretos (Constantes Kd, Ki e Kd) é usar a metodologia “Tentativa-e-erro”. O lado ruim disso é que você deve re-compilar o programa a cada vez que você defina uma constante nova. Uma maneira de acelerar o processo é usar o App Android para enviar as constantes na fase de setup do programa.

Picture1

Eu desenvolvi um aplicativo Android exclusivamente para isso:

o MJRoBot Line Follower PID Control

Em resumo o aplicativo possui:

fkhjj4uin699t7j-medium

  • Comandos manuais tradicionais:
    • FW, BW, esquerda, direita e parar onde o aplicativo irá enviar ao módulo BT HC-o6, respectivamente:  ‘f’, ‘b’, ‘l’, ‘r’ e ‘s’.

 

fpjpy8zin699t7l-medium

  •  3sliders , um para cada constantes PID:
    • Kp: “p / XXX”
    • Ki: “i / XXX”
    • Kd: “d / XXX”
      • Em que “XXX” é um número de 0 a 100.

 

 

  • Um botão extra (“Play”) que funciona exatamente como o botão conectado ao pino 9 do Arduino. Você poderá utilizar um ou o outro, não importa.

No final deste tutorial, você encontrará o arquivo .aia que pode ser modificado no MIT AppInventor e o arquivo .apk para ser instalado diretamente no seu dispositivo Android.

  • MJRoBot_Line_Follower_PID_Control.aia
  • MJRoBot_Line_Follower_PID_Control.apk

Alterar o código para o ajuste remoto dos ganhos do PID

Durante a fase de setup do programa, introduziremos um “loop” onde você poderá enviar os parâmetros PID para o robô antes de colocá-lo sobre a linha:

while (digitalRead (buttonPin) && !mode) 
{
   checkBTcmd (); // Verificar se um comando é recebido via controle remoto BT
   manualCmd (); // Executar o comando
   command = "";
}
checkPIDvalues (); // Enviar as constantes PID para o dispositivo Android (apenas para verificação)
mode = STOPPED;

A função de comando manual, será:

void manualCmd () 
{
   switch (command [0])
   {
      case 'g':
        mode = FOLLOWING_LINE;
        break;
      case 's':
        motorStop (); // Desligar ambos motores
        break;
      case 'f':
        motorForward ();
        break;
      case 'r':
        motorTurn (RIGHT, 30);
        motorStop ();
        break;
      case 'l':
        motorTurn (LEFT, 30);
        motorStop ();
        break;
      case 'b':
        motorBackward ();
        break;
      case 'p':
        Kp = command [2];
        break;
      case 'i':
        Ki = command [2];
        break;
      case 'd':
        Kd = command [2];
        break;
    }
}

No vídeo, você poderá ver alguns testes usando o App Android:

Video Robô seguidor de linha

Abaixo o código final para o robô:

Arduino & Android codes

Conclusão

Este é o primeiro tutorial de um projeto mais complexo, explorando a potencialidade de um robô seguidor de linha. No próximo tutorial, desenvolverei um robô com habilidade para resolver um labirinto, ou seja não só encontrar o caminho de saída, mas também qual seria o caminho mais curto e rápido.

Espero que esse trabalho possa contribuir para que outras pessoas possam aprender mais sobre eletrônica, robôs, Arduino, etc.

Um abraço e até o próximo post!
Obrigado

O “Mars Rover” tupiniquim – Protótipo de uma estação móvel para captura de dados ambientais

Descrição geral:

A idéia deste projecto é o desenvolvimento de um protótipo totalmente funcional para uma estação móvel  usada na coleta de dados ambientais tais como: temperatura, umidade e luminosidade.  Este protótipo foi desenvolvido somente para fins didáticos e fez parte de meu projeto final no curso de especialização do Coursera em parceria com a University of California, Irvine:  “Uma Introdução à Programação da Internet of Things (IOT)“.

Considerações do projeto:
  • O Rover será controlado remotamente por um dispositivo Android com capacidade Bluetooth. Os dados serão continuamente capturados e transmitidos independentemente se o Rover está parado ou em movimento.
  • O usuário deve receber um feedback visual (streaming de vídeo ao vivo)
  • Os dados capturados serão analisados através de um site público (neste caso: thingspeak.com)
  • Os dados estarão disponíveis para os usuários em um formato gráfico e tabela
  • Alarmes via Twitter serão gerados localmente pela estação ou pelo website
  • O Rover terá capacidade autónoma para evitar obstáculos a fim de proteger-se em caso de mau controle por parte do usuário.
Opções de projeto:

Com base nos requisitos, inicialmente 2 opções foram consideradas para este projeto.

  1. Um único processador responsável por todas as tarefas, que neste caso deveria ser um Raspberry Pi.
  2. Um processador dual , sendo as funções divididos entre eles ( Arduino e RPI ) :
    • Processor 1: RPi
      • Captura de dados
      • Comunicação com a Web
      • Transmissão de vídeo
      • Envío de mensagens via mídia social
    • Processor 2: Arduino
      • Controle dos motores (movimento e posicionamento da câmera)
      • Evasão de obstáculos
      • Comunicação com o controle remoto

Em termos de custos, utilizar 2 processadores é de fato menos custoso do que a opção de um único processador. Isso ocorre porque o Arduino é um item muito barato e portanto mais acessível que a opção de “Servo Hat”, necessária para o RPi controlar os servos de maneira adequada. Outra diferença é o módulo de BT. Para o Arduino, um módulo barato como o HC – 06 BT 3.0 é suficiente, sendo que o mesmo custa a metade do preço do “BT Dongle” a ser adicionado ao Rpi.  Assim,a opção escolhida foi o projeto com processador dual.

diag Block complete

A Lista de materiais:

BoM

Instalação e testes da Camera (Pi-Cam) no RPi
  1. Instale PIP
    • sudo apt- get install python- pip
  2. Instalar a biblioteca picamera :
    • pip install picamera
  3. Instalar a biblioteca flask Python:
    • sudo pip install flask
  4. Baixar projeto de streaming de vídeo Flask:
  5. Na pasta do projeto editar o arquivo app.py , comente esta linha:
    • #from camera import Camera
  6.  Tirar o comentario da linha:
    • from camera_pi import Camera
  7. Salve o arquivo app.py
  8. Executar ifconfig para descobrir o endereço IP local do seu Raspberry Pi “yourLocalIPaddress ” .
  9. Inicie o servidor Flask executando este comando :
    • python app.py 
  10. Uma mensagem será impressa no monitor:
  11. Abra um navegador e acesse este endereço :
    • ” YourLocalIPaddress ” : 5000
Instalando o sensor de temperatura e humidade: HC-11

DH11-RPiDH11-RPi hw

  1. Em primeiro lugar, obter a Biblioteca do Github :
  2. Instalação da Biblioteca :
    • sudo apt-get update
    • sudo apt- get install build-essential python -dev python- openssl
    • cd / Home / Pi / Adafruit_Python_DHT
    • sudo python setup.py install
  3. Teste o sensor, executando o programa: AdafruitDHT.py no monitor. Entre os parâmetros como : 11 ( sensor DHT11 ) e 4 ( GPIO onde o sensor está ligado )
    • sudo Python AdafruitDHT.py 11 4
  4. O resultado deve ser a temperatura ea humidade lido pelo sensor

DH11-RPi monitor

Enviando dados para a internet

Para a configuração básica do sensor DH- 11 com a RPI e envio dos dados à internet , uma grande ajuda foi começar a partir deste tutorial:

Plotting DHT11 sensor data at ThingSpeak.com using Raspberry Pi

Passos:

  • A definição de um canal em ThingSpeak.com :

ThinkSpeak1

  • Execução do códigoPython para testes:
    • sudo Python temp_hum_test.py
    • NOTA: Todos os códigos fonte estão disponíveis ao final do post.

ThinkSpeak2

ThinkSpeak3

Adicionando o sensor de luz:

Um tutorial muito bom que serviu de base para esta parte do projeto, pode ser visto aqui:

Continue Lendo “O “Mars Rover” tupiniquim – Protótipo de uma estação móvel para captura de dados ambientais”