A ideia deste post é explicar como enviar parâmetros para o Arduino inserindo valores em uma janela, de forma que não seja necessário alterar toda vez a programação via IDE para mudar esses parâmetros.
Para isso, criaremos uma interface gráfica em Python que envia as informações inseridas ao Arduino, que por sua vez as identifica e salva na EEPROM (a EEPROM é uma memória que mantém a informação salva quando a placa é desligada, como um pequeno HD. Utilizaremos a biblioteca EEPROM, que permite ler e gravar valores em posições especificadas da EEPROM).
Assim, quando o Arduino é conectado ao computador e roda-se o programa aqui descrito (em Python), uma janela se abre, através da qual podemos enviar valores que servirão como um comando para que o Arduino execute algo, como ligar e desligar o LED por um determinado tempo, por exemplo. Quando desconectamos o Arduino, ele continuará executando a função de acordo com os parâmetros salvos.
O módulo Tkinter permite o desenvolvimento de interfaces gráficas em Python. Utilizaremos o Tkinter nestes exemplos para criar as interfaces que enviam informações à EEPROM do Arduino por meio da biblioteca pySerial.
Lista de Materiais:
- Python instalado (estou usando a versão 3.4)
- Módulo Python-Serial (pySerial) - Versão 2.7 para Python 3.x
https://pypi.python.org/pypi/pyserial - Download
http://pyserial.sourceforge.net/pyserial.html - Documentação em inglês
- Arduino (estou usando o Arduino Leonardo)
- IDE Arduino instalada
O código abaixo é o programa mais básico possível utilizando o Tkinter, que cria uma janela que exibe o texto “Hello, world!”.
Explicação do código:
Para usar o Tkinter, começamos importando o módulo Tkinter, que contém todas as classes, funções e outras coisas necessárias para trabalhar com o “Tk toolkit”:
from tkinter import *
Se você está usando a versão 3.x do Python, use a primeira letra minúscula (tkinter). Caso contrário, a primeira letra deve ser maiúscula (Tkinter).
Para inicializar o Tkinter, temos que criar uma janela, normalmente chamada de raíz (“root”):
root = Tk()
Na sequência, criamos um widget do tipo “Label” como “filha” da janela raíz. A palavra “widget” designa componentes de interface gráfica com o usuário de um modo geral, incluindo elementos como janelas, botões, menus, campos de entrada, barras de rolagem, etc. Um widget do tipo Label pode ser utilizado para exibir um texto ou imagem. Aqui exibimos o texto “Hello, world!”.
w = Label(root, text="Hello, world!")
Estamos utilizando o gerenciador de geometria pack. Existem também os gerenciadores grid e place.
w.pack()
Por fim, vem a parte que executa o método mainloop deste objeto raiz - que torna a janela visível esperando que eventos aconteçam. O programa ficará no loop até que fechemos a janela.
root.mainloop()
Exemplo 2
Este código cria uma janela através da qual enviaremos informações que serão gravadas na EEPROM do Arduino, utilizando o módulo pySerial - que realiza o acesso à porta serial. Para que o programa se comunique corretamente com o Arduino, é necessário que o firmware abaixo esteja gravado no Arduino e que a porta à qual ele está conectado esteja devidamente identificada na linha 6 do código em Python.
- Python instalado (estou usando a versão 3.4)
- Módulo Python-Serial (pySerial) - Versão 2.7 para Python 3.x
https://pypi.python.org/pypi/pyserial - Download
http://pyserial.sourceforge.net/pyserial.html - Documentação em inglês
- Arduino (estou usando o Arduino Leonardo)
- IDE Arduino instalada
Exemplo 1
O código abaixo é o programa mais básico possível utilizando o Tkinter, que cria uma janela que exibe o texto “Hello, world!”.
from tkinter import * root = Tk w = Label (root, text= "Hello, world!") w.pack() root.mainloop
Explicação do código:
Para usar o Tkinter, começamos importando o módulo Tkinter, que contém todas as classes, funções e outras coisas necessárias para trabalhar com o “Tk toolkit”:
from tkinter import *
Se você está usando a versão 3.x do Python, use a primeira letra minúscula (tkinter). Caso contrário, a primeira letra deve ser maiúscula (Tkinter).
Para inicializar o Tkinter, temos que criar uma janela, normalmente chamada de raíz (“root”):
root = Tk()
Na sequência, criamos um widget do tipo “Label” como “filha” da janela raíz. A palavra “widget” designa componentes de interface gráfica com o usuário de um modo geral, incluindo elementos como janelas, botões, menus, campos de entrada, barras de rolagem, etc. Um widget do tipo Label pode ser utilizado para exibir um texto ou imagem. Aqui exibimos o texto “Hello, world!”.
w = Label(root, text="Hello, world!")
Estamos utilizando o gerenciador de geometria pack. Existem também os gerenciadores grid e place.
w.pack()
Por fim, vem a parte que executa o método mainloop deste objeto raiz - que torna a janela visível esperando que eventos aconteçam. O programa ficará no loop até que fechemos a janela.
root.mainloop()
Exemplo 2
Este código cria uma janela através da qual enviaremos informações que serão gravadas na EEPROM do Arduino, utilizando o módulo pySerial - que realiza o acesso à porta serial. Para que o programa se comunique corretamente com o Arduino, é necessário que o firmware abaixo esteja gravado no Arduino e que a porta à qual ele está conectado esteja devidamente identificada na linha 6 do código em Python.
Na janela há 2 campos de entrada. O código em Python envia estes valores ao Arduino, que os lê e salva EEPROM. O código salvo no Arduino, além de conter a instrução de salvar na EEPROM as informações recebidas, identificará a informação salva na posição 0 da EEPROM (que vem do primeiro campo de entrada) como o tempo que o LED fica aceso, e informação salva na posição 1 da EEPROM (que vem do segundo campo de entrada) como o tempo que ele fica apagado - fazendo-o piscar.
Como a comunicação funciona:
Gravar:
Código em Python:
Quando clicamos no botão "Grava", é enviada a letra "w" (escolhida arbitrariamente) e na sequência a letra "a" + o valor do campo de entrada 1 (campo superior - tempo ligado) e a letra "b" + o valor do campo de entrada 2 (campo inferior - tempo desligado). Por exemplo, se escolhermos os tempos 10 (ligado) e 5 (desligado), ao clicar em "Grava" o programa enviará: w a10 b5.
Firmware:
O código salvo no Arduino possui a condição de que, caso receba a letra "w", salva na posição 0 da EEPROM o valor que vem precedido pela letra "a" e na posição 1 o valor precedido pela letra "b".
Ler:
Código em Python:
Quando clicamos no botão "Lê", é enviada a letra "r" e lidos os valores que serão recebidos na sequência - que por sua vez são inseridos pelo programa nos campos de entrada 1 e 2.
Firmware:
Caso receba a letra "r", envia os valores das posições 0 e 1 da EEPROM precedidos pelas letras "a" e "b", respectivamente.
Firmware:
#include < eeprom.h > char var = 0; char var2 = 0; int led = 13; void setup(){ Serial.begin(115200); } void loop(){ if(Serial.available()) { var = Serial.read(); var2 = Serial.read(); switch (var){ case 'r': //envia os valores Serial.print('a'); Serial.println(EEPROM.read(0)); // envia o valor do campo 0 da EEPROM precedida pela letra a Serial.print('b'); Serial.println(EEPROM.read(1)); // envia o valor do campo 1 da EEPROM precedida pela letra b break; case 'w': //salva os valores if (var2 == 'a') EEPROM.write(0, Serial.parseInt()); //se receber a letra a precedida por w, salva o valor na posição 0 da EEPROM else if (var2 == 'b') EEPROM.write(1, Serial.parseInt()); //se receber a letra B precedida por w, salva o valor na posição 1 da EEPROM break; } } digitalWrite(led, HIGH); // Liga o Led delay(EEPROM.read(0)*100); // Espera o número de milisegundos definido no programa (salvo na posição 0 da EEPROM) digitalWrite(led, LOW); // Desliga o Led delay(EEPROM.read(1)*100); // Espera o número de milisegundos definido no programa (salvo na posição 1 da EEPROM) }
Código Python:
from tkinter import * import tkinter.messagebox import serial import time ser = serial.Serial('COM9', 115200, timeout=0) # Definir a porta na qual está conectado o Arduino (no caso, COM9) class GUIFramework(Frame): def __init__(self,master=None): self.root = Tk() Frame.__init__(self,master) self.master.title("Interface Python - Arduino") self.grid(padx=10,pady=10) self.CreateWidgets() def CreateWidgets(self): self.Entrada1 = Entry(self, width=4) # Campo de entrada 1 self.Entrada1.grid(row=1, column=1) self.Entrada2 = Entry(self, width=4)# Campo de entrada 2 self.Entrada2.grid(row=2, column=1) # Note que aqui utilizando o gerenciador .grid, no qual especificamos as linhas e colunas onde desejamos posicionar os widgets. Label(self, text = "CONTROLE DO LED", justify = RIGHT).grid (row=0, column=2) Label(self, text = "Tempo Ligado (s) ", justify = LEFT).grid (row=1, column=2) Label(self, text = "Tempo Desigado (s)", justify = LEFT).grid (row=2, column=2) Botao_Grava = Button(self, text=" Grava ", command=self.Write) # Botão para gravar os valores na EEPROM do Arduino Botao_Grava.grid(row=3, column=1) Botao_Le = Button(self, text=" Lê ", command=self.Read) # Botão para ler os valores da EEPROM do Arduino Botao_Le.grid(row=3, column=2) def Write(self): #Através da bibliteca serial, envia os valores dos campos de entrada para o Arduino if self.Entrada1.get() == ("") or self.Entrada2.get() == "": tkinter.messagebox.showwarning("Aviso", "Campo vazio") #Se os campos estão vazios, exibe um aviso if (int(self.Entrada1.get()) + int(self.Entrada2.get()) > 20): tkinter.messagebox.showwarning("Aviso", "A soma dos campos não deve ultrapassar 20") #Caso contrário a leitura é muito demorada else: ser.write(("wa"+self.Entrada1.get()).encode()) # Envia os valores precedidos por letras ser.write(("wb"+self.Entrada2.get()).encode()) # para que o programa no Arduino identifique a informação tkinter.messagebox.showwarning("Upload", "O upload foi realizado com sucesso!") def Read(self): self.Entrada1.delete(0, END) # Limpa os valores dos campos de entrada self.Entrada2.delete(0, END) ser.write(("r").encode()) time.sleep (3) var = ser.readline().decode() valor = var[1:].rstrip() # Extrai os valores a partir da 2a posição e tira o espaço do final self.Entrada1.insert(END, valor) var = ser.readline().decode() valor = var[1:].rstrip() self.Entrada2.insert(END, valor) tkinter.messagebox.showwarning("Read", "O arquivo foi lido com sucesso!") if __name__ == "__main__": GUIFramework().mainloop()
Este código cria uma janela similar à anterior, com algumas pequenas modificações:
- Identifica as portas nas quais há dispositivos conectados, e possui um menu no qual pode-se selecionar diretamente a porta desejada, sem necessidade de especificação da porta no código em si;
- Salva e lê as informações em um arquivo .csv.
Não há necessidade de alteração do firmware.
Código Python:
import os import csv from tkinter import * import tkinter.messagebox import serial import time class GUIFramework(Frame): def __init__(self,master=None): self.root = Tk() Frame.__init__(self,master) self.master.title("Interface Python - Arduino") self.grid(padx=10,pady=10) self.Porta() self.Menu() self.CreateWidgets() def Menu(self): # Cria o Menu menubar = Menu(self.root) filemenu = Menu(menubar, tearoff=0) menubar.add_cascade(label="Arquivo", menu=filemenu) filemenu.add_command(label="Sair", command=self.root.destroy) toolsmenu = Menu (menubar, tearoff=0) menubar.add_cascade(label="Ferramentas", menu=toolsmenu) portaMenu = Menu (menubar, tearoff = 0) toolsmenu.add_cascade(label="Portas", menu=portaMenu) for self.port in self.result: portaMenu.add_radiobutton (label = self.port , variable = self.Portas, command= lambda arg0=self.port: self.Port(arg0) ) self.root.config(menu=menubar) def Porta(self): # Lista as portas disponiveis self.Portas = IntVar() #variavel para listar as portas if sys.platform.startswith('win'): self.ports = ['COM' + str(i + 1) for i in range(256)] # testei apenas no windows - nao sei se nos demais funciona corretamente elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'): self.ports = glob.glob('/dev/tty[A-Za-z]*') elif sys.platform.startswith('darwin'): self.ports = glob.glob('/dev/tty.*') else: raise EnvironmentError('Unsupported platform') self.result = [] for self.port in self.ports: try: s = serial.Serial(self.port) s.close() self.result.append(self.port) except (OSError, serial.SerialException): pass def Port(self, arg0): # Define a Porta print ("Voce selecionou a Porta", arg0) self.ser = serial.Serial(str(arg0), 115200, timeout=1) def CreateWidgets(self): self.Entrada1 = Entry(self, width=4) # Campo de entrada 1 self.Entrada1.grid(row=1, column=1) Label(self, text = "Tempo Ligado (s) ", justify = LEFT).grid (row=1, column=2) self.Entrada2 = Entry(self, width=4) # Campo de entrada 2 self.Entrada2.grid(row=2, column=1) Label(self, text = "Tempo Desigado (s)", justify = LEFT).grid (row=2, column=2) Botao_Grava = Button(self, text=" Grava ", command=self.Write) # Botao para gravar os valores na EEPROM do Arduino Botao_Grava.grid(row=3, column=1) Botao_Le = Button(self, text=" Lê ", command=self.Read) # Botao para ler os valores da EEPROM do Arduino Botao_Le.grid(row=3, column=2) Botao_Grava = Button(self, text=" Grava CSV ", command=self.WriteCSV) # Botao para gravar os valores em um arquivo .csv Botao_Grava.grid(row=4, column=1) Botao_Le = Button(self, text=" Lê CSV ", command=self.ReadCSV) # Botao para ler os valores em um arquivo .csv Botao_Le.grid(row=4, column=2) def Write(self): # Envia os valores dos campos de entrada para o Arduino if hasattr (self, 'ser'): # checa se o objeto self tem o atributo "ser" (ou seja, se a porta foi definida) if self.Entrada1.get() == ("" or 1) or self.Entrada2.get() == "": tkinter.messagebox.showwarning("Aviso", "Campo vazio") #Se os campos estao vazios, exibe um aviso if (int(self.Entrada1.get()) + int(self.Entrada2.get()) > 20): tkinter.messagebox.showwarning("Aviso", "A soma dos campos nao deve ultrapassar 20") #Caso contrario a leitura eh muito demorada else: self.ser.write(("wa"+self.Entrada1.get()).encode()) # Envia os valores precedidos por letras self.ser.write(("wb"+self.Entrada2.get()).encode()) # para que o firmware identifique a informacao tkinter.messagebox.showwarning("Upload", "O upload foi realizado com sucesso!") else: tkinter.messagebox.showwarning("Aviso", "Selecione uma porta no menu Ferramentas > Portas") def Read(self): # Recebe os valores do Arduino if hasattr (self, 'ser'): # checa se o objeto self tem o atributo "ser" (ou seja, se a porta foi definida) self.Entrada1.delete(0, END) # Limpa os valores dos campos de entrada self.Entrada2.delete(0, END) self.ser.write(("r").encode()) #envia a letra "r", que sera interpretada pelo firmware como instrucao para enviar os valores da EEPROM time.sleep (3) #aguarda, pois a resposta pode demorar alguns segundos, dependendo do tempo enviado para piscar o led var = self.ser.readline().decode() #le os valores enviados pelo Arduino valor = var[1:].rstrip() # Extrai os valores a partir da 2a posicao e tira o espaco do final self.Entrada1.insert(END, valor) #Insere os valores nos campos de entrada var = self.ser.readline().decode() #Novamente, para a segunda posicao valor = var[1:].rstrip() self.Entrada2.insert(END, valor) tkinter.messagebox.showwarning("Read", "O arquivo foi lido com sucesso!") else: tkinter.messagebox.showwarning("Aviso", "Selecione uma porta no menu Ferramentas > Portas") def WriteCSV(self): # Salva os valores em formato .csv filename = tkinter.filedialog.asksaveasfilename() output_file = open(filename+".csv", 'w', newline='') data = csv.writer(output_file) valor = self.Entrada1.get() send_value = (chr(ord('a') ) + (valor)) data.writerow([send_value]) valor = self.Entrada2.get() send_value = (chr(ord('a')+1 ) + (valor)) data.writerow([send_value]) tkinter.messagebox.showwarning("Upload", "O Arquivo foi salvo com sucesso!") def ReadCSV(self): # Lê os valores do arquivo .csv self.Entrada1.delete(0, END) # Limpa os valores dos campos de entrada self.Entrada2.delete(0, END) filename = tkinter.filedialog.askopenfilename() input_file = open(filename, 'r', newline='') data = csv.reader(input_file, delimiter='\t', quoting=csv.QUOTE_NONE) line = next(data) var = line[0] self.Entrada1.insert (END, var[1:]) line = next(data) var = line[0] self.Entrada2.insert (END, var[1:]) if __name__ == "__main__": GUIFramework().mainloop()Há muito mais possibilidades de widgets que podem ser utilizados. Para uma documentação mais detalhada, veja as seguintes referências (em português):
- Python - Módulo C - Tkinter: http://www.fem.unicamp.br/~labaki/Python/ModuloC.pdf
- Pensando em Tkinter: http://www.dcc.ufrj.br/~fabiom/mab225/PensandoTkinter.pdf
Grande parte da documentação encontrada para o Tkinter utiliza o Python versão 2.x. Segue uma lista das alterações necessárias para utilizar os módulos na versão 3.x:
Versão 2 > Versão 3:
Tkinter > tkinter
tkMessageBox > tkinter.messagebox
tkColorChooser > tkinter.colorchooser
tkFileDialog > tkinter.filedialog
tkCommonDialog > tkinter.commondialog
tkSimpleDialog > tkinter.simpledialog
tkFont > tkinter.font
Tkdnd > tkinter.dnd
ScrolledText > tkinter.scrolledtext
Tix > tkinter.tix
ttk > tkinter.ttk