Curso sobre Python e Arduino – Parte 3 – Arduino “escutando” o Python

Fala galera, tudo bem com vocês? Esse é o nosso terceiro artigo do nosso mini curso sobre Python e Arduino! Seguindo o nosso projetinho de criar uma IA (assistente virtual, veja mais detalhes na parte do 2), hoje vamos fazer a parte do Arduino “escutando” o Python! Iremos construir a comunicação entre o assistente virtual (código python) e o Arduino! Lembrando que para acompanhar este tutorial você precisa ler as partes 1 (introdução ao Python) e 2 (construindo o assistente virtual com Python)!

Contextualização

O que vamos fazer é semelhante a uma inteligência artificial como a Alexa e o Google Assistente, na qual o nosso IA programado em Python, sob nossa ordem, comunica-se com o Arduino que por sua vez executa uma ação. No fim do projeto poderemos dizer que o Arduino está “escutando” o Python!

Hoje em dia se tornou comum o uso de IA em várias áreas, sejam elas em SAC de empresas, assistência jurídica, pesquisas médicas, entre outros. O próprio Google tem várias IA com funções mais diversas, desde pesquisas sobre seus gostos pelo seu histórico de compras, por exemplo, para enviarem publicidades mais direcionadas, até uma assistente pessoal (Google Assistente).

Mergulhando no código

Código no Arduino

Abaixo segue o código que será carregado na IDE do Arduino:

String inputString = "";
bool stringComplete = false;

void setup() {
  Serial.begin(115200);
  inputString.reserve(200);

  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  if(stringComplete){
    Serial.println("Assistente Virtual Arduino Falando: ");
    Serial.println(inputString);

    if(inputString.startsWith("ligar")){
      digitalWrite(LED_BUILTIN, HIGH);
    }
    if(inputString.startsWith("desligar")){
      digitalWrite(LED_BUILTIN, LOW);
    }
    inputString = "";
    stringComplete = false;
  }
}

void serialEvent(){
  while (Serial.available()){
    char inChar = (char)Serial.read();
    inputString += inChar;
    if(inChar == '\n'){
      stringComplete = true;
    }
  }
}



Agora vamos detalhar o código acima, para podermos compreender melhor tudo o que está acontecendo!

Primeiro vamos criar uma variável do tipo String(uma palavra/cadeia de caracteres) para receber o comando (frase enviada pelo código Python) do assistente, depois crie uma variável stringComplete e declare como tipo bool (verdadeiro ou falso) para verificar se o estado da palavra recebida está completa, ou seja, se já recebeu toda a frase enviada pelo Python.

String inputString = "";
bool stringComplete = false;

Depois vamos inicializar algumas configurações na função setup, primeiro vamos inicializar a comunicação serial com uma taxa de transmissão de 115.200 baunds. Depois vamos reservar 200bytes para a variável inputString e por último vamos definir o pino 13 do Arduino como saída:

void setup() {
  Serial.begin(115200);
  inputString.reserve(200);

  pinMode(LED_BUILTIN, OUTPUT);
}

Agora vamos passar para a função void. No if(stringComplete) verificamos se chegou uma nova linha de texto, se sim imprimimos: ” Assistente Virtual Arduino Falando: ” mais o conteúdo do texto recebido.

void loop() {
  // put your main code here, to run repeatedly:
  if(stringComplete){
    Serial.println("Assistente Virtual Arduino Falando: ");
    Serial.println(inputString);

Depois no segundo if verificamos se o texto que chegou começa com “ligar”, e sim, usamos o digitalWrite para ascender o LED.

 if(inputString.startsWith("ligar")){
      digitalWrite(LED_BUILTIN, HIGH);
    }

Caso o texto não comece com “ligar”, então verificamos se começa com “desligar”, e em caso positivo, dessa vez usamos o digitalWrite para desligar o LED.

E para finalizar o loop void, limpamos a variável inputString e setamos a variável stringComplete como false.

Agora vamos falar da função serialEvent, ela é chamada sempre que novos dados chegam no RX do hardware. Ela é executada entre cada vez que o loop é executado, porntanto, o atraso no loop pode gerar um atraso na resposta. Vários bytes de dados podem estar disponíveis.

void serialEvent(){
  while (Serial.available()){
    char inChar = (char)Serial.read();
    inputString += inChar;
    if(inChar == '\n'){
      stringComplete = true;
    }
  }
}

A primeira coisa feita na função serialEvent é verificar se houve alguma interação com a comunicação serial, e enquanto houver a interação, obtemos o número de bytes (caracteres) disponíveis na porta serial. São dados que já chegaram e são armazenados no buffer de recebimento serial (que contém 64 bytes).

Vamos detalhar este processo, usando a função Serial.read(), obtemos um novo byte(que no caso é um novo caractere/letra) e salvamos na variável inChar:

char inChar = (char)Serial.read();

Depois concatenamos (somamos) o caractere salvo na variável inchar na variável inputString:

inputString += inChar;

Depois verificamos se o caractere recebido é uma nova linha (‘\n’ é o caractere de nova linha), se sim, setamos a variável stringComplete como true, para que o loop principal possa continuar na próxima atividade, ou seja, toda vez que recebemos uma nova linha, entramos no if(stringComplete) que está lá na função void loop na linha 11 do código! E é isso! Agora já estamos na metade do nosso objetivo, e daqui a pouco poderemos dizer que o Arduino está “escutando” o Python.

Código Python

Agora vamos escrever um código de exemplo no Python, para enviar mensagens para o Arduino e finalmente fazê-lo piscar o LED.

Abaixo segue o código completo:

import serial
import threading
import time
import speech_recognition as sr
import pyttsx3

conectado = False
porta = 'COM10' # linux ou mac em geral -> ‘/dev/ttyS0’
velocidadeBaud = 115200

mensagensRecebidas = 1;
desligarArduinoThread = False

try:
    SerialArduino = serial.Serial(porta, velocidadeBaud, timeout=0.2)
except:
    print("Verificar porta serial ou religar arduino")

def handle_data(data):
    global mensagensRecebidas
    print("Recebi " + str(mensagensRecebidas) + ": " + data)
    mensagensRecebidas += 1

def read_from_port(ser):
    global conectado, desligarArduinoThread
    while not conectado:
        conectado = True
        while True:
            reading = ser.readline().decode()
            if reading != "":
                handle_data(reading)
            if desligarArduinoThread:
                print("Desligando Arduino")
                break

lerSerialThread = threading.Thread(target = read_from_port, args=(SerialArduino,))
lerSerialThread.start()

print("Preparando Arduino")
time.sleep(2)
print("Arduino Pronto")

engine = pyttsx3.init()
r = sr.Recognizer()
mic = sr.Microphone()

with mic as fonte:
    r.adjust_for_ambient_noise(fonte)
    print("Para me ativar, me chame pelo meu nome.")
    engine.say("Para me ativar, me chame pelo meu nome.")
    engine.runAndWait()
    engine.stop()
    audio = r.listen(fonte)
    print("Enviando para reconhecimento")
    try:
        text = r.recognize_google(audio, language= "pt-BR")
        print("Voce disse::{}".format(text))
        while(text=='Bob'):
            r.adjust_for_ambient_noise(fonte)
            engine.say("Olá professor, o que posso ajudar?")
            engine.runAndWait()
            engine.stop()
            print("Olá professor, o que posso ajudar?")
            audio = r.listen(fonte)
            print("Enviando para reconhecimento")
            try:
                text2 = r.recognize_google(audio, language="pt-BR")
                engine.say("Voce disse::{ }".format(text2))
                engine.runAndWait()
                engine.stop()
                print("Voce disse::{ }".format(text2))
                if (text2 == "ligar"):
                    print("Enviando")
                    SerialArduino.write('ligar led\n'.encode())
                    time.sleep(2)
                elif (text2 == "desligar"):
                    print("Enviando")
                    SerialArduino.write('desligar led\n'.encode())
                    time.sleep(2)
                elif (text2 == "dispensado"):
                    text = text2
                    engine.say("Ok, irei embora. Adeus.")
                    engine.runAndWait()
                    engine.stop()
                    print("Ok, irei embora. Adeus.")
                print("")
            except:
                engine.say("Não entendi o que você disse.")
                engine.runAndWait()
                engine.stop()
                print("Não entendi o que você disse.")
            print(" ")
    except:
        engine.say("Ok, então você não quer falar comigo. Adeus.")
        engine.runAndWait()
        engine.stop()
        print("Ok, então você não quer falar comigo. Adeus.")

Agora vamos entender melhor todo esse código Python. Na primeira linha importamos o pacote serial para estabelecer a comunicação entre a porta USB do computador e comunicação serial do Arduino:

import serial

Depois vamos importar o pacote threading, pois precisamos trabalhar com os threads em Python que são fluxos de programas que executam em paralelo dentro de uma aplicação, isto é, uma ramificação de uma parte da aplicação que é executada de forma independente e escalonada independentemente do fluxo inicial da aplicação.

import threading

O pacote time é necessário para trabalharmos com a gestão de tempo dentro do programa.

import time

Depois vamos escrever entre aspas simples a porta COM que seu computador reconheceu o Arduino, então volte na IDE do Arduino e identifique qual porta que está selecionada.

porta = 'COM10' # linux ou mac em geral -> ‘/dev/ttyS0’

Para comunicação com um computador use uma destas taxas: 300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 57600, 115200. Logo optamos por usar a maior taxa de transmissão ofertada, assim a comunicação ficaria mais eficiente e rápida, 31 lembrando que o valor que for setado aqui no Python deverá ser igual no Arduino.

velocidadeBaud = 115200

A função “serial.Serial” exige 3 parâmetros que são: A porta COMO que será realizada a comunicação serial, a taxa de transmissão e o tempo de espera para tentar nova conexão, o timeout=1, ele serve para no caso de tentarmos conectar e o dispositivo não responder, o programa vai ficar ocioso aguardando, de forma blocante, como usamos timeout=0.2 tem-se um tempo de 0.2s para a conexão responder.

try:
    SerialArduino = serial.Serial(porta, velocidadeBaud, timeout=0.2)
except:
    print("Verificar porta serial ou religar arduino")

O “def” é a chamada para uma nova função criada no programa Python, uma função é uma sequência de comandos que executa alguma tarefa e que tem um nome. A sua principal finalidade é nos ajudar a organizar programas em pedaços que correspondam a como imaginamos uma solução do problema.

Neste caso essa função tem como objetivo manipular os dados que estão sendo recebidas do Arduino:

def handle_data(data):

Em Python, as variáveis definidas ficam acessíveis às funções declaradas dentro de um mesmo escopo, ou seja, a variável que você declara só é reconhecida dentro do bloco declarado, mas há a possibilidade de declararmos globalmente, ou seja, onde irá valer para todo o programa, global e “meio global” — no caso algo mais específico da versão 3.x do Python.

def handle_data(data):
    global mensagensRecebidas
    print("Recebi " + str(mensagensRecebidas) + ": " + data)
    mensagensRecebidas += 1

A função abaixo tem o objetivo de ler os dados recebidos na porta serial:

def read_from_port(ser):
    global conectado, desligarArduinoThread

Quando usamos a sintaxe “while conectado” estamos criando um laço de repetição onde enquanto a variável conectado for “true”, ou seja, verdadeira o programa iria continuar nesse bloco, porém se você voltar um pouco neste e-book irá observar que iniciamos o conectado com “false”, logo este é o motivo de incluir o “not” neste teste, assim a lógica ficou enquanto não conectado entre neste bloco, logo entraria na primeira passagem por ele.

Porém ao entrar no bloco, o primeiro comando é alterar a variável conectado para “True”, assim quando retornasse para o teste while acima o programa não entraria mais.

while not conectado:
        conectado = True

Na linha abaixo criou-se um loop infinito (while True), até que o break seja executado:

while True:

Este comando ler a linha recebida na comunicação serial, decodifica e carrega em uma variável reading:

reading = ser.readline().decode()

Se a variável não estiver carregada com o uma string vazia ele entra neste bloco e chama a função “handle_data” já estudada acima e executa os comandos lá.

  if reading != "":
                handle_data(reading)

Se a variável “desligarArduinoThread” for verdadeira, mais na frente iremos ver quando esta variável vira verdadeira, este bloco é executa e o break realizado fazendo com que saia do while e pare o programa.

if desligarArduinoThread:
                print("Desligando Arduino")
                break

Parece estranho, mas essa linha é executada antes dos blocos anteriores, essa 33 é uma das características em trabalhar com funções, o seu código não é linear, ele executa os blocos de acordo com as solicitações.

Basicamente essa linha é a que manda o Python ler a porta serial em paralelo pelo pacote threading, funciona assim: é invocado o método Thread da biblioteca threading onde esse método leva dois parâmetros, que são necessários para a execução de sua atividade, esse parâmetros estão dentro dos parênteses e são o target que traduzindo é alvo, e o args que é o argumento. Ou seja, ele executa a leitura da porta serial e escrita dentro da função read_from_port levando para lá as configurações da comunicação serial com a variável SerialArduino que foi configurada no início do programa.

lerSerialThread = threading.Thread(target = read_from_port, args=(SerialArduino,))

Inicia o objeto lerSerialThread criado na linha anterior:

lerSerialThread.start()

print("Preparando Arduino")
time.sleep(2)
print("Arduino Pronto")

Agora vamos para a parte final do projeto (estamos perto de poder dizer que o Arduino está escutando o Python!), o nosso assistente será acionado quando dizermos a palavra “Arduino”.

with mic as fonte:
    r.adjust_for_ambient_noise(fonte)
    print("Para me ativar, me chame pelo meu nome.")
    engine.say("Para me ativar, me chame pelo meu nome.")
    engine.runAndWait()
    engine.stop()
    audio = r.listen(fonte)
    print("Enviando para reconhecimento")
    try:
        text = r.recognize_google(audio, language= "pt-BR")
        print("Voce disse::{}".format(text))
        while(text=='Arduino'):

Repare no bloco acima que algumas funções já explicamos na parte 2 do nosso curso, como _with mic as fonte, r.adjust_for_ambient_noise(fonte), r.recognize_google(audio, language= “pt-BR”), etc.

Depois vem a parte do reconhecimento do que foi dito:

try:
                text2 = r.recognize_google(audio, language="pt-BR")
                engine.say("Voce disse::{ }".format(text2))
                engine.runAndWait()
                engine.stop()
                print("Voce disse::{ }".format(text2))

Em seguida vamos acionar a função SerialArduino.write para comunicar com o Arduino (enviar dados via comunicação serial), se falarmos “Ligar” ou “Desligar”, repassaremos esse comando para o Arduino, se falarmos “Dispensado” encerraremos a interação.

if (text2 == "ligar"):
                    print("Enviando")
                    SerialArduino.write('ligar led\n'.encode())
                    time.sleep(2)
                elif (text2 == "desligar"):
                    print("Enviando")
                    SerialArduino.write('desligar led\n'.encode())
                    time.sleep(2)
                elif (text2 == "dispensado"):
                    text = text2
                    engine.say("Ok, irei embora. Adeus.")
                    engine.runAndWait()
                    engine.stop()
                    print("Ok, irei embora. Adeus.")
                print("")

E por fim, em caso de algum erro ou resultado inesperado encerramos a interação:

except:
        engine.say("Ok, então você não quer falar comigo. Adeus.")
        engine.runAndWait()
        engine.stop()
        print("Ok, então você não quer falar comigo. Adeus.")


Conclusão

Enfim finalizamos o nosso assistente virtual usando Python e Arduino! Agora podemos dizer que o Arduino está “escutando” o Python! Como muitos de vocês podem ter percebido, é um assistente simples, que só entente os comandos ligar e desligar, mas com base nesse projeto você já é capaz de evoluir este código e partir para soluções mais sofisticadas! Continuem acompanhando o nosso mini curso de Arduino e Python, dúvidas e sugestões é só escrever nos comentários abaixo! Abração e até a próxima!

Sobre Sandro Mesquita 19 artigos
Prof. Sandro Mesquita, Msc. Eng de Software, CREA - 44680 .

Seja o primeiro a comentar

Deixe uma resposta

O seu endereço de email não será publicado.


*