Raspberry Pi

GUI para projetos com Raspberry Pi

Eletrogate 27 de junho de 2024

O que é uma GUI?

O Raspberry Pi é uma plataforma muito popular entre entusiastas de tecnologia, educadores e hobbistas, devido à sua versatilidade, baixo custo e capacidade de rodar diversas aplicações, como automação residencial, estações meteorológicas e robótica. Uma forma de tornar seus projetos em Raspberry Pi ainda mais incríveis e interativos é criando uma GUI (Graphical User Interface) ou interface gráfica de usuário. Assim, em vez de depender de comandos de terminal, uma GUI permite que os usuários interajam com o sistema de maneira visual e amigável, utilizando botões, menus e outros elementos gráficos, de forma intuitiva e acessível. Além disso, uma GUI pode tornar seus projetos mais apresentáveis e profissionais, facilitando o uso por pessoas que não têm familiaridade com a linha de comando.

Nesse artigo, vamos explorar uma biblioteca popular chamada Tkinter, para criar GUIs para projetos no Raspberry Pi. Acompanhe:


GUI vs. Aplicação Web: Qual Escolher?

Quando se trata de criar interfaces para projetos em Raspberry Pi, existem duas abordagens principais: GUIs locais/ Desktop e aplicações web. Cada uma tem suas vantagens e desvantagens. Nesse artigo, trataremos da criação de Desktops.

Uma aplicação Desktop é executada diretamente no Raspberry Pi e pode interagir diretamente com o hardware, sem a necessidade de intermediários. Isso permite um controle mais simples, e eficiente de sensores, atuadores e outros componentes conectados ao Raspberry Pi.

Além disso, Desktops têm acesso direto aos recursos do sistema, como arquivos locais, dispositivos conectados, e funcionalidades nativas do sistema operacional. Isso permite uma integração mais profunda e funcionalidades que podem ser difíceis de implementar em uma aplicação web. As aplicações Desktops também são muito mais simples de implementar, geralmente envolvendo menos configuração em comparação com uma aplicação web. Não há necessidade de configurar servidores web, gerenciar certificados SSL ou resolver problemas de compatibilidade entre navegadores. As GUIs são offlines, não requerindo uma conexão com a internet, tornando-as ideais para aplicações em áreas remotas ou onde a conectividade é limitada.

Porém, aplicações desktop tem várias desvantagens em relação à web, como por exemplo, ficam limitadas ao dispositivo onde estão instaladas, a manutenção e a atualização podem ser mais trabalhosas e  a escalabilidade pode ser um desafio, especialmente quando se trata de gerenciar dados centralizados e compartilhados entre vários usuários ou dispositivos.  Portanto, é preciso avaliar bem qual o melhor tipo de aplicação para cada cenário. Normalmente, você vai querer fazer uma aplicação desktop no Raspberry Pi para projetos pessoais de pequena escala, prototipagem e teste rápidos, projetos de monitoramento, projetos customizados para ambientes específicos ou para projetos em equipamentos StandAlone (auto-suficientes) embutidos, onde a coleta, processamento, análise e display visual dos dados acontecem na mesma máquina, sem depender de conexão com outros dispositivos.


Resumo do Projeto: Controle de acesso de funcionários

Nesse artigo vamos mostrar como, com algumas ferramentas simples e um pouco de código, você pode desenvolver interfaces gráficas intuitivas e funcionais. Vamos explorar uma biblioteca popular chamada Tkinter, que é integrada ao Python, uma linguagem de programação amplamente usada com o Raspberry Pi.

Para ilustrar a criação de uma GUI local para um projeto em Raspberry Pi, vamos desenvolver uma interface gráfica para uma aplicação de controle de acesso de funcionários. Para isso, vamos utilizar o leitor RFID JT308. Quando um cartão RFID é aproximado do leitor JT30 conectado no Raspberry Pi via USB, o leitor lê as informações do cartão e envia esses dados para o Raspberry Pi. Quando os dados são recebidos, eles são interpretados de forma a “liberar o acesso” ou “bloquear o acesso”, dependendo se o cartão estiver previamente cadastrado com as informações de um funcionário ou não.


O Leitor RFID JT308

Falando um pouco sobre o leitor RFID JT308, é um dispositivo utilizado para efetuar a leitura de tags RFID em diversas aplicações, como controle de acesso, identificação de produtos, rastreamento e automação. Opera na frequência de 125 kHz, uma das frequências mais comuns para dispositivos RFID de baixa frequência (LF), e compatível com tags padrão EM4100 e outras de 125 kHz. Com uma interface USB, a sua integração com computadores e outros sistemas, como o Raspberry Pi, é extremamente simples, oferecendo uma experiência plug-and-play ao usuário e o melhor: sem necessidade de drivers adicionais na maioria dos sistemas operacionais. A distância de leitura típica varia de 0 a 20 mm, dependendo da tag e das condições ambientais.


Materiais Necessários

Para esse projeto, você irá precisar de:

  • 1x Raspberry Pi (qualquer versão) com cartão de memória, fonte chaveada, dissipadores de calor e cabo HDMI (placas semelhantes também podem ser usadas, como a Orange Pi);
  • 1x Display (estou utilizando 7 polegadas com definição do hardware de 1024 × 600, mas qualquer um com conexão via cabo HDMI vai funcionar) ;
  • 1x Leitor de Cartão RFID JT308 + Cabo USB (para execução do exemplo);
  • Algumas unidades do Cartão RFID 125kHz (para execução do exemplo);


Passos para o Projeto: Configurações Iniciais

  1. Configurar o Raspberry Pi e o Raspibian OS. Para esse passo, você pode conferir esse artigo. Se estiver utilizando placas de outras fabricantes, como a Orange Pi ou Banana Pi, utilize o sistema padrão de cada uma.
  2. Instalar o Python. O Raspibian vem com uma versão do Python 3.11.2, por padrão. Para se certificar que o python está instalado, digite o seguinte comando no terminal:
    python-V
  3. Instale a biblioteca serial do python no Raspberry Pi
    pip install pyserial
  4. Conecte o cabo USB do Leitor de Cartão RFID JT30 a uma porta USB disponível no Raspberry Pi.

O código python para o controle de acesso no terminal deve ficar assim:

import serial
# Configuração da porta serial (Tenta acessar a porta 0-10, onde está conectado o leitor RFID JT30)

for tent in range(10):
      try:
          ser = serial.Serial('/dev/ttyUSB'+tent, 9600, timeout=1)
            ser.reset_input_buffer()
print("porta"+tent+"encontrada")

        except:
print('porta não encontrada')

# Dicionário de funcionários cadastrados

Employees = {
  '0013087914': {
      "name": "Júlia Z. Schwartz",
      "position": "Dev Backend",
      "last": "Today"
    },

  '0003866073': {
      "name": "Maria da Silva",
      "position": "Data Engineer",
      "last": "Today"
    },

  '0002710499': {
      "name": "João Da Silva",
      "position": "DevOps",
      "last": "Yesterday"
  }
}


try:
  while True:
      if ser.in_waiting > 0:
#lê a informação da serial, se existente
          rfid_data = ser.readline().decode('utf-8').strip()
            print(f"ID do Cartão RFID: {rfid_data}")

          # Verifica se o ID do RFID está no dicionário de funcionários
          if rfid_data in Employees:
              employee = Employees[rfid_data]
              print(f"Acesso concedido a: {employee['name']}")
              print(f"Cargo: {employee['position']}")
              print(f"Última Presença: {employee['last']}")
                access_granted = True

          else:
              print("Acesso negado: ID de Cartão RFID não reconhecido.")
                access_granted = False


   
except KeyboardInterrupt:
    print("Leitura de RFID interrompida")

finally:
  ser.close()

Crie um arquivo .py contendo o código acima no diretório de sua preferência. No terminal, navegue até o diretório onde está o arquivo e utilize o comando abaixo para executar o arquivo de código.

sudo python nome_do_arquivo.py

Rodando o código, a saída no Raspberry Pi fica assim:


Construindo a Interface Visual

Por enquanto, nosso projeto está apenas fazendo o print dos resultados no terminal, o que queremos é fazer uma tela para tornar o processo interativo e visual! Chegou a hora de adicionarmos a interface gráfica. Para isso, precisaremos de algumas configurações adicionais, que você acompanha na sequência:

O pacote Tkinter está presente por padrão no python, portanto, não precisamos instalá-lo. Porém, precisaremos instalar uma ferramenta chamada Tkinter Designer, que integra o editor Figma com o Tkinter. Essa ferramenta permite que você crie o layout da sua interface no Figma e transforme automaticamente em código Tkinter. Isso elimina a necessidade de escrever manualmente todo o código para a interface, acelerando o desenvolvimento e reduzindo a margem de erro.

A descrição e documentação do Projeto Tkinter Designer está nesse repositório: https://github.com/ParthJadhav/Tkinter-Designer/blob/master/docs/README.pt-BR.md

Para instalar o Tkinter Designer há duas formas:

1 – Através do pip:

   pip install tkdesigner

2 –Através do git, baixando do código fonte:

    git clone https://github.com/ParthJadhav/Tkinter-Designer.git

Tudo certo até então, porém, antes de usar o Tkinter Designer, você precisará criar um arquivo no Figma seguindo as instruções abaixo.


 Faça o Design da sua GUI

  1. Crie uma conta no Figma;
  2. Crie um novo arquivo Figma Design;
  3. Comece a criar o design da sua GUI.

    Caso queira usar o design de exemplo e copiar para seu projeto, pode acessá-lo aqui.
    Obs: Comece seu projeto já pensando no tamanho e resolução da tela que será usada em seu raspberry.  A minha tela tem 7’’ e configurei a resolução para 720 x 480, portanto, meu projeto figma terá mais ou menos essas dimensões também, na altura e largura.

Você pode conferir  a resolução da sua tela no Raspberry Pi em Preferências -> Screen Configuration -> Disposição -> Resolução:

 

Formatando seu Design :

Para gerar o código pelo Tkinter Designer, a forma com que você vai nomear os elementos do seu design Figma é importante, pois o Tkinter Designer identifica esses nomes para criar os widgets Tkinter. Portanto, você precisa seguir um padrão para nomear seus elementos no design. Para renomear os elementos dentro do figma, clique duas vezes no nome deles no painel esquerdo do layout.

O padrão de nomeação deve seguir o esquema dessa tabela

Nome do Elemento no Figma      Elemento no Tkinter
Button      Button
Text      Qualquer nome
Rectangle      Rectangle
Line      Line
TextArea      Text Area
TextBox      Entry
Image      Image

Ou seja, por exemplo, se você desenhou um botão no figma, e quer que ele realmente seja reconhecido como um elemento de botão dentro do python, para que depois possa determinar triggers, eventos e outras ações à ele, você deve nomeá-lo, obrigatoriamente, como Button. Isso vale para os outros elementos também, então se você quer que um elemento seja reconhecido como um input de texto, coloque o nome de “TextBox“, e assim por diante.

Além disso, seu design deve ter apenas 1 frame e não conter agrupamentos  (a menos que o nome do grupo seja o de um componente reconhecido pelo Tkinter designer) .

Dica 1 : Acesse o design de exemplo e observe o nome dos componentes para entender melhor.

Dica 2: Comece seu design sem se preocupar com agrupamentos, frames e nomeação dos componentes. Deixe para organizar essa etapa no final, quando o design estiver pronto.

Para desagrupar os componentes, clique no nome do componente com o botão direito do mouse + “Ungroup”.

Para renomear os componentes, clique no nome do componente com o botão direito do mouse + “Rename” (ou Ctrl+ R).

Quando estiver tudo pronto, clique em “share”, no canto superior direito.

Clique na opção de copiar o link do projeto:

 

Agora você precisa também do seu Token Figma, para que o Tkinter Designer possa acessar seu projeto e gerar o código Python a partir do seu design: 

Para isso, clique no ícone do seu perfil e vá em Settings

, 

E na aba Account, role até encontrar “Personal access Tokens”. Então clique em “Generate new token”.

Lembre-se de guardar essa chave token muito bem, pois não será mais possível visualizá-la.

 


Gerando o código

Agora vamos abrir o Tkinter Designer instalado previamente:   

cd Tkinter-Designer
cd gui
python3 gui.py

Ele deverá abrir essa tela:

  1. Cole seu token Figma no campo Token ID.
  2. Cole o link no campo URL do arquivo no Tkinter Designer.
  3. Clique em “Output Path” para selecionar o caminho de saída.
  4. Clique em Generate.

OBS: Um update recente do figma gerou um Bug nessa etapa, portanto, se sua URL é: “https://www.figma.com/design/XX/YY“, mude “design” para “file”, no campo de URL, para que fique: https://www.figma.com/file/XX/YY

Assim ele vai começar a gerar o arquivo .py que conterá sua GUI, baseado no seu design figma. Você poderá acompanhar esse processo no terminal. Se o processo de nomeação e grupamento dos componentes estiver correto, esse processamento deve demorar alguns segundos, então o arquivo .py e as dependências serão geradas dentro de uma nova pasta, chamada build, localizada na pasta de destino que você selecionou previamente em “Output Path”.

Parabéns, você agora criou sua GUI do Tkinter usando o Tkinter Designer!

Para visualizar, só executar o arquivo gui.py.

cd build python3 gui.py

OBS: Talvez as fontes apresentadas não sejam as mesmas. Nesse caso, certifique-se que você tem a fonte instalada no raspberry Pi, na pasta /usr/share/fonts/ e também na pasta /home/pi/.fonts. No meu caso, eu usei a fonte Inter. Para isso, fiz o download de toda a família da fonte e suas variantes no google fonts:

https://fonts.google.com/specimen/Inter?query=inter

Agora, vamos editar esse arquivo gui.py para adicionar algumas modificações:

No início do arquivo, vamos adicionar esse import, para permitir a comunicação com o leitor RFID via USB serial:

import serial

No final do arquivo, vamos adicionar esses dois atributos:

window.resizable(True, True),

window.attributes("-fullscreen", True)

 O primeiro vai garantir que o usuário consiga ajustar o tamanho da janela da GUI. O segundo argumento permite que a janela ocupe a tela inteira, sem apresentar a barra de ferramentas e a barra superior da GUI.

IMPORTANTE: Isso só será garantido se o projeto estiver no mesmo tamanho da resolução da tela, portanto, se atente à isso.

Você pode conferir a resolução da aplicação gerada no atributo window.geometry:

Também vamos adicionar uma função para fechar a tela ao apertar a tecla ESC, já que a aplicação ocupará a tela inteira

def on_escape(event=None):

    print("escaped")

    window.destroy()

window.bind("<Escape>", on_escape)

Vá até os Widgets Canvas no seu código python que contêm as informações que você quer alterar na tela quando um novo cartão se aproximar do leitor. Nesse exemplo, vamos fazer com que os campos de Número, Nome, Cargo e “Último Registro” sejam re-preenchidos para cada funcionário. Portanto, esses widgets devem ser mapeados dentro do código para que possamos alterar o texto dentro deles, dependendo da informação de cartão que estamos recebendo. Para isso, vou nomear esses widgets alvo, criando variáveis, para que eu possa depois alterar seu valor.

A diferença é que agora não faremos mais o print(), e sim o comando:

canvas.itemconfig(id_do_widget, valor)

esse comando vai alterar a imagem/texto de cada parte do nosso layout de acordo com a informação que estamos recebendo. Assim, se eu recebo o número “0003866073” do leitor RFID, todos os campos alvo devem mudar para os valores referentes ao dicionário desse número (“João da Silva”, “Devops”, etc…). Da mesma forma, se eu receber um valor ‘não cadastrado” (não presente no dicionário), os campos alvos devem ser preenchidos com uma string vazia, e a imagem do banner de bem vindo deve ser alterada para uma imagem de banner de “bloqueado”.

Podemos adicionar o código para isso no final do nosso arquivo gui.py (mas dentro do window.mainloop()) como abaixo:

Employees = {
  '0013087914': {
    "name" : "Júlia Z. Schwartz",
    "position" : "Dev Backend",
     "last": "Today"
  },
  '0003866073': {
    "name" : "Maria da Silva",
    "position" : "Data Engineer",
     "last": "Today"
  },
  '0002710499': {
    "name" : "João Da Silva",
    "position" : "DevOps",
    "last": "Yesterday"
  }
}

for tent in range(10):

        try:
            ser = serial.Serial('/dev/ttyUSB'+tent, 9600, timeout=1)

            ser.reset_input_buffer()
           
            print("porta"+tent+"encontrada")

        except:
            print('porta não encontrada')

# if ser.in_waiting > 0:
    #   line = ser.readline().decode('utf-8').rstrip();
try:
    line = Employees[ser]
    canvas.itemconfig(number, text=ser)
    canvas.itemconfig(name, text=line['name'])
    canvas.itemconfig(position, text=line['position'])
    canvas.itemconfig(last, text=line['last'])
    block = PhotoImage(file=relative_to_assets("free.png"))
    canvas.itemconfig(banner, image=block)

except:
    canvas.itemconfig(number, text=ser)
    canvas.itemconfig(name, text='Não identificado')
    canvas.itemconfig(position, text='')
    canvas.itemconfig(last, text='')
    block = PhotoImage(
        file=relative_to_assets("blocked.png"))
    canvas.itemconfig(banner, image=block)

O arquivo gui.py completo para esse exemplo fica assim:

from pathlib import Path 
import serial  #adicionamos esse import para permitir a comunicação com o leitor RFID via USB serial

from tkinter import Tk, Canvas, Entry, Text, Button, PhotoImage


OUTPUT_PATH = Path(__file__).parent
ASSETS_PATH = OUTPUT_PATH / Path(r"E:\Downloads\figmas\build\build\assets\frame0")


def relative_to_assets(path: str) -> Path:
    return ASSETS_PATH / Path(path)

def on_escape(event=None):

    print("escaped")

    window.destroy()

window = Tk()

window.geometry("716x490")
window.configure(bg = "#FFFFFF")

window.bind("<Escape>", on_escape)


canvas = Canvas(
    window,
    bg = "#FFFFFF",
    height = 490,
    width = 716,
    bd = 0,
    highlightthickness = 0,
    relief = "ridge"
)

canvas.place(x = 0, y = 0)
canvas.create_rectangle(
    272.0,
    0.0,
    716.0,
    490.0,
    fill="#F5F5F5",
    outline="")

canvas.create_rectangle(
    0.0,
    0.0,
    272.0,
    490.0,
    fill="#000000",
    outline="")

canvas.create_text(
    298.0,
    29.0,
    anchor="nw",
    text="Access Control  Application",
    fill="#515156",
    font=("Inter Bold", 25 * -1)
)

canvas.create_text(
    300.0,
    62.0,
    anchor="nw",
    text="Quarta, 01 Maio, 2024, 13:09 ",
    fill="#A2A2A2",
    font=("Inter", 14 * -1)
)

image_image_1 = PhotoImage(
    file=relative_to_assets("image_1.png"))
image_1 = canvas.create_image(
    67.0,
    285.0,
    image=image_image_1
)

canvas.create_text(
    91.0,
    347.0,
    anchor="nw",
    text="Settings",
    fill="#FFFFFF",
    font=("Inter", 16 * -1)
)

image_image_2 = PhotoImage(
    file=relative_to_assets("image_2.png"))
image_2 = canvas.create_image(
    112.0,
    226.0,
    image=image_image_2
)

canvas.create_text(
    89.0,
    273.0,
    anchor="nw",
    text="Past Records",
    fill="#FFFFFF",
    font=("Inter", 16 * -1)
)

canvas.create_rectangle(
    55.0,
    325.0,
    232.0,
    326.0,
    fill="#FFFFFF",
    outline="")

image_image_3 = PhotoImage(
    file=relative_to_assets("image_3.png"))
image_3 = canvas.create_image(
    129.0,
    61.0,
    image=image_image_3
)

image_image_4 = PhotoImage(
    file=relative_to_assets("image_4.png"))
image_4 = canvas.create_image(
    122.9100341796875,
    166.0,
    image=image_image_4
)

image_image_5 = PhotoImage(
    file=relative_to_assets("image_5.png"))
image_5 = canvas.create_image(
    68.0,
    360.0,
    image=image_image_5
)

image_image_6 = PhotoImage(
    file=relative_to_assets("image_6.png"))
image_6 = canvas.create_image(
    485.0,
    239.0,
    image=image_image_6
)

number =canvas.create_text(
    358.0,
    156.0,
    anchor="nw",
    text="90020034530",
    fill="#646474",
    font=("Inter Medium", 18 * -1)
)
print(number)

name = canvas.create_text(
    356.0,
    199.0,
    anchor="nw",
    text="Júlia Z. Schwartz",
    fill="#01150C",
    font=("Inter Bold", 23 * -1)
)

print(name)

canvas.create_text(
    360.0,
    299.0,
    anchor="nw",
    text="Last time checked: ",
    fill="#515156",
    font=("Inter ExtraLight", 14 * -1)
)

canvas.create_text(
    569.0,
    159.0,
    anchor="nw",
    text="Employee",
    fill="#646474",
    font=("Inter", 9 * -1)
)

last = canvas.create_text(
    569.0,
    300.0,
    anchor="nw",
    text="Today",
    fill="#006710",
    font=("Inter Light", 14 * -1)
)

image_image_7 = PhotoImage(
    file=relative_to_assets("image_7.png"))
image_7 = canvas.create_image(
    408.0,
    258.0,
    image=image_image_7
)

position =canvas.create_text(
    369.0,
    246.0,
    anchor="nw",
    text="Dev Backend",
    fill="#212123",
    font=("Inter Light", 13 * -1)
)

print(position);

free = PhotoImage(
    file=relative_to_assets("free.png"))
banner = canvas.create_image(
    514.0,
    383.0,
    image=free
)

window.resizable(True, True),
# window.attributes("-fullscreen", True)


Employees = {
  '0013087914': {
    "name" : "Júlia Z. Schwartz",
    "position" : "Dev Backend",
     "last": "Today"
  },
  '0003866073': {
    "name" : "Maria da Silva",
    "position" : "Data Engineer",
     "last": "Today"
  },
  '0002710499': {
    "name" : "João Da Silva",
    "position" : "DevOps",
    "last": "Yesterday"
  }
}

for tent in range(10):

        try:
            ser = serial.Serial('/dev/ttyUSB'+tent, 9600, timeout=1)

            ser.reset_input_buffer()
           
            print("porta"+tent+"encontrada")

        except:
            print('porta não encontrada')
 if ser.in_waiting > 0:
     line = ser.readline().decode('utf-8').rstrip();
     
try:     #se o funcionário estiver cadastrado ( estiver no dicionário), preenche os dados do card e apresenta o banner de "Bem Vindo"
    line = Employees[ser]
    canvas.itemconfig(number, text=ser)
    canvas.itemconfig(name, text=line['name'])
    canvas.itemconfig(position, text=line['position'])
    canvas.itemconfig(last, text=line['last'])
    block = PhotoImage(file=relative_to_assets("free.png"))
    canvas.itemconfig(banner, image=block)

except:   #se o funcionário estiver cadastrado (não estiver no dicionário), preenche os dados do card com uma string vazia e apresenta o banner de "Bloqueado"
    canvas.itemconfig(number, text=ser)
    canvas.itemconfig(name, text='Não Cadastrado')
    canvas.itemconfig(position, text='')
    canvas.itemconfig(last, text='')
    block = PhotoImage(
        file=relative_to_assets("blocked.png"))
    canvas.itemconfig(banner, image=block)


window.mainloop()

Para acessar o código completo com os assets gerado no exemplo, acesse aqui


Extra: Autoinicializando o programa

E se eu quiser que essa tela abra sozinha toda a vez que eu inicializar o Raspberry Pi?

É Simples! Basta navegue até home/pi/.config/autostart e criar um novo arquivo com a extensão .Desktop

Coloque esse conteúdo dentro dele:

[Desktop Entry] 
Type=Application 
Encoding=UTF-8 
Name=AutoInit
Comment=Exemplo
Exec=/home/pi/CAMINHO_PARA_SEU_ARQUIVO/gui.py
Terminal=True
StartupNotify=False

Feito isso, reinicie o Raspberry.


Funcionamento

Com todos os passos acima sendo executados corretamente, o programa deve rodar de forma interativa, dessa forma:

 


Conclusão

Esperamos que tenha entendido todo o passo a passo da criação de uma GUI para seus projetos usando o Raspberry Pi ou similares. Fique ligado no blog, temos muitas novidades vindo aí!


Sobre o Autor


Julia Zamith

Bióloga, desenvolvedora Backend e entusiasta de eletrônica, projetos maker e IoT.


Eletrogate

27 de junho de 2024

A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.

Conheça a Metodologia Eletrogate e Lecione um Curso de Robótica nas Escolas da sua Região!

Eletrogate Robô

Cadastre-se e fique por
dentro de novidades!