Esse repositório é um template para um módulo de sensor.
Para criar um novo módulo, siga os seguintes passos:
- Crie um repositório no GitHub nesta organização (
open-weather-iot
) - Selecione o template
template-module
- Dê um nome no formato
{sensor}-module
. Exemplo:temperature-module
- Não é necessário escrever uma descrição
- Deixe o projeto público. Não faça upload de nenhum arquivo com informações sensíveis
Após a inicialização do repositório:
- Reescreva o arquivo README.md com a descrição do projeto, incluindo o que é, informações necessárias, detalhes de execução e o restante que julgar pertinente.
- Inclua os arquivos necessários nas pastas descritas em na seção seguinte (Organização de pastas e arquivos)
altium/
: arquivos do Altiumdocuments/
: especificações e PDFs utilizados para o desenvolvimento do sub-projetogerber_files/
: export de arquivos Gerberimg/
: imagens utilizadas na descrição do README.mdsrc/
: diretório do código-fonte. Todo código deverá ser colocado aqui, exceto quando é utilizado exclusivamente para testesexample.py
: módulo de sensor exemplo
test/
: diretório com os arquivos para execução de testesmain.py
: arquivo principal da rotina de execução de testes. Importa as classes e funções do diretório do código-fonte (src/
)
util/
: códigos comuns utilizados por vários módulos de sensores ("utilitários").bus.py
: utilitário de barramentos SPI, Serial e I2C
Os arquivos .gitkeep
existem nas pastas vazias para que elas sejam reconhecidas pelo git e incluídas no template. Após popular seu conteúdo, esses arquivos devem ser removidos.
A base para um sensor é uma classe, a qual obedece aos seguintes métodos (funções):
__init__(self, *, ...)
: o construtor da classe deve receber os parâmetros nomeados necessários e o barramento utilizado (seja SPI, Serial ou I2C)setup(self)
(opcional): contém as rotinas de inicialização do módulo do sensor, caso necessárioread(self)
(obrigatório): retorna um dicionário com as seguintes chaves:raw
: contém um dicionário com os valores puros que foram lidos do sensor que se está trabalhandovalue
: representa o valor final após conversão de unidades para ser apresentado diretamente ao usuáriounit
: unidade de medida do campovalue
. Exemplo:'Celsius'
class Example:
# deve receber os parâmetros nomeados necessários e o barramento utilizado (seja SPI, Serial ou I2C)
def __init__(self, i2c_bus):
self.i2c_bus = i2c_bus
# ...
# método **OPCIONAL** da classe que realiza a inicialização do sensor
def setup(self):
pass
# método **OBRIGATÓRIO** da classe que realiza leituras do sensor
def read(self):
return { 'raw': {}, 'value': 0.0, 'unit': '' }
Alguns utilitários básicos são definidos na pasta util/
.
Utilitário de barramentos SPI, Serial e I2C.
A classe respectiva para o barramento SPI obedece à seguinte especificação:
- construtor
SPI(port)
: select()
: ativa o dispositivo SPIdeselect()
: desativa o dispositivo SPIread(nbytes, *, auto_select=False)
: lê a quantidadenbytes
de bytes do dispositivo SPI. Retorna um objetobytes
com o dado que foi lido.write(buf, *, auto_select=False)
: Escreve os bytes contidos embuf
. RetornaNone
._spi
: Atributo utilizado internamente que armazena a instânciamachine.SPI
. Não é recomendável utilizar diretamente esse atributo, exceto nos casos de bibliotecas de componentes que recebem uma instânciamachine.SPI
._cs_pin
: Atributo utilizado internamente que armazena o número do pino de chip select. Não é recomendável utilizar diretamente esse atributo, exceto nos casos de bibliotecas de componentes que recebem o número do pino de chip select._cs
: Atributo utilizado internamente que armazena a instânciamachine.Pin
(output) do chip select. Não é recomendável utilizar diretamente esse atributo, exceto nos casos de bibliotecas de componentes que recebem uma instânciamachine.Pin
.
from util.bus import SPI
from xyz42 import XYZ42_SPI
spi = SPI(port=1)
# (1) ativa o dispositivo SPI, (2) escreve os bytes 12345678, (3) desativa o dispositivo SPI
spi.select()
spi.write(b'12345678')
spi.deselect()
# escreve os bytes 12345678 (automaticamente ativa e desativa o dispositivo para esse comando específico)
spi.write(b'12345678', auto_select=True)
# lê 5 bytes (automaticamente ativa e desativa)
reading_5 = spi.read(5, auto_select=True)
with spi: # ativa e desativa o dispositivo SPI automaticamente dentro desse contexto/bloco
spi.write(b'12345678')
MSB = spi.read(1)
LSB = spi.read(1)
# expondo para o módulo hipotético XYZ42 a instância interna `machine.SPI` e o `machine.Pin` do chip select
xyz = XYZ42_SPI(spi=spi._spi, cs=spi._cs)
Não importe módulos inteiros.
❌
from example import *
✅
from example import Example
Evite utilizar variáveis e instruções globais para prover uma melhor modularização do código.
❌
# src/blink_led.py from time import sleep_ms from machine import Pin led_builtin = Pin(25, Pin.OUT) led_builtin.value(1) interval = 1000 def blink(): while True: led_builtin.toggle() sleep_ms(interval) # test/main.py from src.blink_led import blink blink()
✅
# src/blink_led.py from time import sleep_ms from machine import Pin def blink(): led_builtin = Pin(25, Pin.OUT) led_builtin.value(1) interval = 1000 while True: led_builtin.toggle() sleep_ms(interval) # test/main.py from src.blink_led import blink if __name__ == "__main__": blink()
💡 Note que com essa alteração, agora é possível parametrizar o pino do led e intervalo, deixando o código mais genérico e personalizável.
# src/blink_led.py from time import sleep_ms from machine import Pin def blink(*, led_pin=25, interval=1000): led = Pin(led_pin, Pin.OUT) led.value(1) while True: led.toggle() sleep_ms(interval)
Tome cuidado para identar o código com 4 espaços.
❌ Exemplo: 3 espaços
def test(): return 42
✅
def test(): return 42
Quando uma classe ou função tem objetivo de comunicar com o usuário final, dê preferência a receber e passar parâmetros pelo nome, em vez de pela ordem de passagem de parâmetros, principalmente quando há muitos parâmetros.
Essa recomendação propõe que os nomes dos parâmetros sejam expostos ao usuário da classe ou função, assim, facilitando a leitura dos parâmetros sem a necessidade de abrir o código e analisar a ordem dos parâmetros.
❌
class Example: # Inicialização da classe Example # recebe os pinos utilizados para o SPI def __init__(self, clk_pin, sdi_tx_pin, sdo_rx_pin, cs_pin, spi_id): pass # ... ex = Example(10, 11, 12, 13, 1)
✅
class Example: # Inicialização da classe Example # recebe os pinos utilizados para o SPI def __init__(self, *, clk_pin, sdi_tx_pin, sdo_rx_pin, cs_pin, spi_id): pass # ... ex = Example(clk_pin=10, sdi_tx_pin=11, sdo_rx_pin=12, cs_pin=13, spi_id=1)
💡 Note que basta colocar
*
na posição a partir da qual deseja-se que os parâmetros seguintes sejam passados pelo nome, não por ordem
💡 Não é sempre necessário utilizar parâmetros nomeados, por exemplo, quando há poucos parâmetros e os valores são auto-descritivos
# src/max31865.py class MAX31865: def __init__(self, spi_bus): self.spi_bus = spi_bus # test/main.py from src.max31865 import MAX31865 from util.bus import SPI def main(): sensors = { "t1": MAX31865(SPI(port=1)) } # ... if __name__ == "__main__": main()
Valores padrão podem ser utilizados para parâmetros que definam comportamentos do módulo/função. Porém, não utilize valores padrão quando se refere ao hardware (por exemplo, um pino).
Essa recomendação facilita no momento de integração de códigos e resolução de conflito de pinagem.
❌
class Example: # Inicialização da classe Example # recebe os pinos utilizados para o SPI def __init__(self, *, clk_pin=10, sdi_tx_pin=11, sdo_rx_pin=12, cs_pin=13, spi_id=1): pass
✅
class Example: # Inicialização da classe Example # recebe os pinos utilizados para o SPI def __init__(self, *, clk_pin, sdi_tx_pin, sdo_rx_pin, cs_pin, spi_id): pass
✅
# interval: intervalo de envio de dados (em segundos) def send(interval=10): pass