Relógio com Raspberry Pi Pico W Deixe um comentário

Neste artigo vamos alavancar as capacidade de IoT da Raspberry Pi Pico W e construir um Relógio Inteligente que apresenta em um display gráfico colorido a hora atual e a previsão do tempo (obtidos da Internet) e a temperatura local (obtida de um sensor) .

Relógio com Raspberry Pi Pico W
Relógio com Raspberry Pi Pico W

Materiais Necessários para montar o Relógio com Raspberry Pi Pico W 

Para criar um Relógio Inteligente com Raspberry Pi Pico W, você irá precisar dos seguintes componentes:

Preparativos – Instalando o Interpretador MicroPython

Siga os passos abaixo para preparar a sua Pi Pico para usar o MicroPython:

  • Baixe e instale no seu computador a IDE Thonny (https://thonny.org/).
  • Entre em Tools Options, selecione a aba Interpreter e escolha MicroPython (Raspberry Pi Pico).
Configuração do Relógio com Raspberry Pi Pico W na IDE Thonny
Configuração do Relógio com Raspberry Pi Pico W na IDE Thonny
  • Aberte o botão BOOTSEL da Pi Pico e, mantendo o botão apertado, conecte a Pi Pico ao seu PC. Aguarde a placa ser reconhecida como uma unidade de disco.
  • Clique no link “Install or update firmware”
Instalação do firmware para programar nosso relógio com Raspberry Pi Pico W
Instalação do firmware para programar nosso relógio com Raspberry Pi Pico W
  • Selecione “Raspberry Pi Pico W / Pico WH”
  • Click no botão Install e aguarde aparecer a mensagem Done!
  • Click no botão Close

Instalando o Driver do Display e Fontes

Para escrever no display precisamos instalar um driver e de dois fontes (um deles com os ícones para a previsão de tempo). Para realizar a instalação, execute o Thonny com a Pico W conectada ao micro e siga os passos abaixo:

  • Baixe os arquivos necessários de  https://github.com/dquadros/RelogioPico/archive/refs/heads/main.zip
  • Expanda o zip em um diretório do seu micro
  • No Thonny, use View Files para apresentar os diretórios no micro e na placa
  • Navega na área de aquivo do micro para o diretório onde estão os arquivos da biblioteca, clique com o botão direito e escolha Focus into
  • Clique com o botão direito no diretório lib nos arquivos do micro e escolha Upload to /

Obtendo a URL de Consulta  à Previsão do Tempo

Vamos obter a previsão do tempo do serviço Open-Meteo acessa a página https://open-meteo.com/en/docs e faça as seguintes seleções:

  • Coloque o nome da sua cidade em Select City (ou forneça a latitude e longitude):
  • Desmarque Temperature em Hourly Weather Variables:
  • Marque Weathercode, Sunrise e Sunset em Daily Weather Variables:
  • Em Settings, selecione o fuso horário (Timezone)
  • Copie e salve a API URL

Esquemático relógio com Raspberry Pi Pico W 

A figura abaixo mostra a montagem do projeto.

Esquemático do Relógio Inteligente com o Pico W
Esquemático do Relógio Inteligente com o Pico W

Obs: ao invés do sensor DHT-11 avulso, você pode usar um módulo com ele (como este aqui). Neste caso, acerte as conexões conforme a pinagem do módulo, não é necessário o resistor de 4.7KΩ pois ele já está no módulo.

Programação do relógio com Raspberry Pi Pico W 

O nosso programa vai estar dividido em vários módulos. Cada módulo deve ser copiado no Thonny e salvo na placa com o nome indicado (ou você pode copiar diretamente do micro para a placa a partir do diretório onde você colocou o driver do display.

O primeiro módulo, secrets.py contem o nome e a senha da rede WiFi que será usada:

ESSID = 'MinhaRede'
PASSWD = 'segredo'

O módulo weather.py contém as funções para obter a previsão de tempo.

# Módulo para consulta do Clima em https://open-meteo.com/
import network
import urequests
import json
import time

url = 'https://api.open-meteo.com/v1/forecast?latitude=-23.53&longitude=-46.79&daily=weathercode,sunrise,sunset&timezone=America%2FSao_Paulo'

# converte hora de formato ISO para interno
def _conv_time(iso):
    return time.mktime((int(iso[0:4]), int(iso[5:7]), int(iso[8:10]),
                        int(iso[11:13]), int(iso[14:16]), 
                        0, 0, 0))

# obtem a previsão de tempo para hoje
def previsao():
    r = urequests.get(url)
    if r.status_code == 200:
        resposta = json.loads(r.text)
        r.close()
        sunrise = _conv_time(resposta['daily']['sunrise'][0])
        sunset = _conv_time(resposta['daily']['sunset'][0])
        return resposta['daily']['weathercode'][0], sunrise, sunset
    else:
        r.close()
        print ('ERRO: {0}'.format(r.status_code));
        return None, 0, 0

from display_ips import DISPLAY_IPS
WHITE = DISPLAY_IPS.rgb(255,255,255)
GRAY = DISPLAY_IPS.rgb(127,127,127)
BLACK = DISPLAY_IPS.rgb(0,0,0)
BLUE = DISPLAY_IPS.rgb(0,0,255)
YELLOW = DISPLAY_IPS.rgb(255, 216, 0)

# conversão do código do tempo em caracter e cores
tabWeather = {
    0: [ (0x0d, YELLOW), (0x2e, WHITE) ], # clear sky
    1: [ (0x0d, YELLOW), (0x2e, WHITE) ], # mainly clear
    2: [ (0x02, YELLOW), (0x86, WHITE) ], # partly cloudy
    3: [ (0x13, WHITE), (0x13, WHITE) ], # overcast
    45: [ (0x14, WHITE), (0x14, WHITE) ], # fog
    48: [ (0x14, WHITE), (0x14, WHITE) ], # depositing rime fog
    51: [ (0x17, WHITE), (0x17, WHITE) ], # light drizzle
    53: [ (0x17, WHITE), (0x17, WHITE) ], # moderate drizzle
    55: [ (0x17, WHITE), (0x17, WHITE) ], # dense drizzle
    56: [ (0x17, WHITE), (0x17, WHITE) ], # light freezing drizzle
    57: [ (0x17, WHITE), (0x17, WHITE) ], # dense freezing drizzle
    61: [ (0x19, WHITE), (0x19, WHITE) ], # slight rain
    63: [ (0x19, WHITE), (0x19, WHITE) ], # moderate rain
    65: [ (0x19, WHITE), (0x19, WHITE) ], # heavy rain
    66: [ (0x19, WHITE), (0x19, WHITE) ], # light freezing rain
    67: [ (0x19, WHITE), (0x19, WHITE) ], # heavy freezing rain
    71: [ (0x1b, WHITE), (0x1b, WHITE) ], # slight snow
    73: [ (0x1b, WHITE), (0x1b, WHITE) ], # moderate snow fall
    75: [ (0x1b, WHITE), (0x1b, WHITE) ], # heavy snow fall
    77: [ (0x1b, WHITE), (0x1b, WHITE) ], # snow grains
    80: [ (0x19, WHITE), (0x19, WHITE) ], # slight rain shower
    81: [ (0x19, WHITE), (0x19, WHITE) ], # moderate rain shower
    82: [ (0x19, WHITE), (0x19, WHITE) ], # violent rain shower
    85: [ (0x1b, WHITE), (0x1b, WHITE) ], # slight snow shower
    86: [ (0x1b, WHITE), (0x1b, WHITE) ], # heavy snow shower
    95: [ (0x1e, GRAY), (0x1e, GRAY) ], # thunderstorm
    96: [ (0x1e, GRAY), (0x1e, GRAY) ], # thunderstorm with slight hail
    99: [ (0x1e, GRAY), (0x1e, GRAY) ] # thunderstorm with heavy hail
}

def decodeWeather(code, ehDia):
    if code in tabWeather:
        fundo = BLUE if ehDia else BLACK
        ind = 0 if ehDia else 1
        return tabWeather[ind][0], tabWeather[ind][1], fundo
    else:
        return None, 0, 0

O módulo dht.py contém uma classe para acesso ao sensor DHT11 (extraída do artigo “Como utilizar o PIO da Raspberry Pi Pico para Comunicar sensores DHT11 ou DHT22”).

# Módulo para leitura de sensor DHt11/DHT22
import utime
import rp2 
from rp2 import PIO, asm_pio
from machine import Pin
 
# Programa para o PIO
# coloca automaticamente na fila a cada 8 bits recebidos
@asm_pio(set_init=(PIO.OUT_HIGH),autopush=True, push_thresh=8) 
def DHT_PIO():
    # aguarda uma solicitação do programa
    pull()
     
    # mantem dado em 0 pelo tempo informado pelo programa
    set(pindirs,1)              #set pin to output  
    set(pins,0)                 #set pin low
    mov (x,osr)
    label ('waitx')
    nop() [25] 
    jmp(x_dec,'waitx')          # espera tempo*26/clock=x
      
    # inicia leitura da resposta
    set(pindirs,0)              # muda o pino para entrada
    wait(1,pin,0)               # aguarda voltar ao nível alto
    wait(0,pin,0)               # aguarda pulso inicial
    wait(1,pin,0)
    wait(0,pin,0)               # aguarda inicio do primeiro bit
 
    # lê os bits
    label('readdata')
    wait(1,pin,0)               # espera o sinal ir para nivel alto
    set(x,20)                   # registrador x é o timeout para descer
    label('countdown')
    jmp(pin,'continue')         # continua contando se sinal permanece alto
     
    # pino foi para o nível baixo antes da contagem terminar -> bit 0
    set(y,0)                 
    in_(y, 1)                   # coloca um 'zero' no resultado
    jmp('readdata')             # ler o próximo bit
     
    # pino continua no nível alto
    label('continue')
    jmp(x_dec,'countdown')      # decrementar a contagem
 
    # contagem terminou -> bit 1
    set(y,1)                  
    in_(y, 1)                   # coloca um 'um' no resultado
    wait(0,pin,0)               # espera voltar ao nível baixo
    jmp('readdata')             # ler o próximo bit
 
DHT11 = 0
DHT22 = 1
 
class DHT:
 
    # Construtor
    # dataPin: pino de dados
    # modelo:  DHT11 ou DHT22
    # smID:    identificador da máquina de estados
    def __init__(self, dataPin, modelo, smID=0):
        self.dataPin = dataPin
        self.modelo = modelo
        self.smID = smID
        self.sm = rp2.StateMachine(self.smID)
        self.ultleitura = 0
        self.data=[]
     
    # faz uma leitura no sensor
    def leitura(self):
        data=[]
        self.sm.init(DHT_PIO,freq=1400000,set_base=self.dataPin,in_base=self.dataPin,jmp_pin=self.dataPin)
        self.sm.active(1)
        if self.modelo == DHT11:
            self.sm.put(969)     # espera 18 milisegundos
        else:
            self.sm.put(54)      # espera 1 milisegundo
        for i in range(5):       # lê os 5 bytes da resposta
            data.append(self.sm.get())
        self.sm.active(0)
        total=0
        for i in range(4):
            total=total+data[i]
        if data[4] == (total & 0xFF):
            # checksum ok, salvar os dados
            self.data = data
            self.ultleitura = utime.ticks_ms()
            return True
        else:
            return False
 
    # le ou usa dados já existentes
    def obtemDados(self):
        # garante ter dados
        while len(self.data) == 0:
            if not self.leitura():
                utime.sleep_ms(2000)
             
        # só tenta ler se já passou pelo menos 2 segundos da última leitura
        agora = utime.ticks_ms()
        if self.ultleitura > agora:
            self.ultleitura = agora  # contador deu a volta
        if (self.ultleitura+2000) < agora:
            self.leitura()
     
    # informa a umidade
    def umidade(self):
        self.obtemDados()
        if self.modelo == DHT11:
            return self.data[0] + self.data[1]*0.1
        else:
            return ((self.data[0] << 8) + self.data[1]) * 0.1
 
    # informa a temperatura
    def temperatura(self):
        self.obtemDados()
        if self.modelo == DHT11:
            return self.data[2] + self.data[3]*0.1
        else:
            s = 1
            if (self.data[2] & 0x80) == 1:
                s = -1
            return s * (((self.data[2] & 0x7F) << 8) + self.data[3]) * 0.1

Por último, o módulo main.py contém o programa principal.

# Relogio Inteligente - Módulo Principal
import rp2
import network
import time
import gc

import secrets
import dht

from sys import exit
from machine import Pin, SPI

TIMEOUT = 20

# Inicia o display
from display_ips import DISPLAY_IPS
import fonts.freesans20 as freesans20
import fonts.weather_font as wfont

pdc = Pin(14, Pin.OUT, value=0)
prst = Pin(13, Pin.OUT, value=1)
pcs = Pin(9, Pin.OUT, value=1)
gc.collect()  # Precaution before instantiating framebuf
spi = SPI(1, sck=Pin(10), mosi=Pin(11), baudrate=1_000_000)
disp = DISPLAY_IPS(spi, pcs, pdc, prst, 80, 160)

WHITE = DISPLAY_IPS.rgb(255,255,255)
GRAY = DISPLAY_IPS.rgb(127,127,127)
BLACK = DISPLAY_IPS.rgb(0,0,0)
BLUE = DISPLAY_IPS.rgb(0,0,255)
YELLOW = DISPLAY_IPS.rgb(255, 216, 0)

disp.fill(BLUE)
disp.print(20, 30, "Conectando...", freesans20, WHITE, BLUE)
disp.show()

# Prepara acesso ao sensor
dht_data = Pin(15, Pin.IN, Pin.PULL_UP)
sensor = dht.DHT(dht_data, dht.DHT11, 0)

# Conecta à rede WiFi
rp2.country('BR')
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(secrets.ESSID, secrets.PASSWD)

print ('Conectando...')
timeout = time.ticks_add(time.ticks_ms(), TIMEOUT*1000)
while not wlan.isconnected() and wlan.status() >= 0 and \
      time.ticks_diff(timeout, time.ticks_ms()) > 0:
    time.sleep(0.2)

if not wlan.isconnected(): 
    disp.fill(BLUE)
    disp.print(0, 30, "SEM CONEXAO", freesans20, WHITE, BLUE)
    disp.show()
    print ('Não conseguiu conectar, abortando')
    exit()

print ('Conectado')
print('IP: '+wlan.ifconfig()[0])

# Obter a hora atual
import ntptime
UTC_OFFSET = -3 * 60 * 60
ntptime.settime()

# Iniciacoes para o laco principal
import weather
clima = None
atlWeather = time.time() + UTC_OFFSET
INTERVALO_HORA = 20 # tempo entre atualizacoes da hora
INTERVALO_WEATHER = 5*60 # tempo entre atualizacoes do clima

# Laco Principal
while True:
    # Obtem a hora atual
    now = time.time() + UTC_OFFSET
    agora = time.localtime(now)
    hora =  "{:02}:{:02}".format(agora[3], agora[4])

    # Obtem previsão do tempo
    if now >= atlWeather:
        code,sunrise,sunset = weather.previsao()
        if not code is None:
            print ("Weathercode: {}".format(code))
            clima, frente, fundo = weather.decodeWeather(code,
                        (now > sunrise) and (now < sunset))
        atlWeather = atlWeather + INTERVALO_WEATHER
        
    # Obtem a temperatura
    t = sensor.temperatura()
    temp = "{:.1f} C".format(t)

    # Atualiza a tela
    disp.fill(BLUE)
    disp.print(80, 10, hora, freesans20, WHITE, BLUE)
    disp.print(80, 40, temp, freesans20, GRAY, BLUE)
    if not clima is None:
        disp.print(20, 4, chr(clima), wfont, frente, fundo)
    disp.show()

    # Dá um tempo entre atualizações da hora
    time.sleep (INTERVALO_HORA)

Note que o programa principal utiliza o módulo ntptime (que já vem instalado no MicroPython) para obter a hora atual da internet.

Como o programa principal foi salvo com o nome main.py, ele será automaticamente executado quando a Pi Pico for ligada. Para interromper a execução, digite Control C no Thonny.

Conclusão

Neste artigo vimos como usar o MicroPython para alavancar o recurso de comunicação da Pi Pico e construir um relógio que apresenta um agregado de informações obtidas via internet (hora e clima) com obtidas localmente (temperatura).

Algumas sugestões de mudanças para você implementar:

  • Apresentar a informação de umidade do sensor DHT11
  • Apresentar outras informações obtidas da open-meteo
  • Acrescentar outros sensores locais (como um sensor de pressão BMP280) e apresentar suas informações
  • Acrescentar um buzzer e implementar uma função de alarme
  • Apresentar outras informações obtidas de outras APIs disponíveis na internet

E então, gostou deste projeto? Deixe um comentário abaixo contando se você pretende montá-lo (ou já montou) e o que acrescentaria nele.

Anunciada a Raspberry Pi 5

Faça seu comentário

Acesse sua conta e participe