Raspberry Pi

Executar um script Python MQTT na inicialização com systemd no Raspberry Pi

Transforme um cliente MQTT em Python em um serviço systemd de verdade no Raspberry Pi: inicie depois que a rede e o relógio estiverem prontos, reinicie automaticamente, guarde as credenciais em um arquivo de ambiente e desligue de forma limpa.

Terminal do Raspberry Pi mostrando um serviço systemd iniciando um cliente Python no boot, ilustrando como executar script python mqtt na inicialização
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

A forma confiável de executar um script Python MQTT na inicialização é um serviço systemd: ele inicia seu cliente depois que a rede está disponível, reinicia quando ele trava, captura os logs e o encerra de forma limpa no desligamento. Neste tutorial você vai transformar um script paho-mqtt em um serviço no Raspberry Pi, guardar as credenciais fora do código, contornar o problema do relógio no boot que quebra o TLS e aprender os comandos da operação do dia a dia. O mesmo arquivo de unidade funciona em qualquer distribuição Linux moderna.

Por que systemd e não cron ou rc.local

Há várias formas de iniciar um script no boot, e elas não são equivalentes:

Método Reinicia após travar Espera a rede Logs Desligamento limpo
@reboot no crontab Não Não Só se você redirecionar a saída Não
/etc/rc.local Não Não confiável Não Não
Autostart do desktop Não Não Não Só com sessão gráfica
Serviço systemd Sim, configurável Sim, com ordenação journald integrado Sim, SIGTERM e depois SIGKILL

Uma entrada no crontab parece mais simples, mas o resultado é um gateway MQTT que morre silenciosamente às 3 da manhã e nunca mais volta. O systemd já roda no Raspberry Pi OS, então não há nada a instalar.

Passo 1: prepare o script

Um script que roda como serviço deve ler a configuração de variáveis de ambiente, reconectar sozinho e encerrar de forma limpa quando o systemd enviar SIGTERM. Este exemplo publica um heartbeat com o uptime a cada 30 segundos e usa um Last Will para que os assinantes vejam quando o dispositivo fica offline.

#!/usr/bin/env python3
import json
import os
import signal
import threading
import time

import paho.mqtt.client as mqtt

HOST = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")
PASSWORD = os.getenv("MQTT_PASSWORD")
CLIENT_ID = os.environ["MQTT_CLIENT_ID"]            # obrigatório e único
PREFIX = os.getenv("TOPIC_PREFIX", "connectmq/demo/s8t4n6/pi1")
INTERVAL = int(os.getenv("PUBLISH_INTERVAL", "30"))

stop = threading.Event()
signal.signal(signal.SIGTERM, lambda *_: stop.set())
signal.signal(signal.SIGINT, lambda *_: stop.set())


def on_connect(client, userdata, flags, reason_code, properties):
    print(f"connected to {HOST}:{PORT} ({reason_code})", flush=True)
    client.publish(f"{PREFIX}/status", "online", qos=1, retain=True)


def on_disconnect(client, userdata, flags, reason_code, properties):
    print(f"disconnected ({reason_code}), will retry", flush=True)


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
if USERNAME:
    client.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
    client.tls_set()
client.will_set(f"{PREFIX}/status", "offline", qos=1, retain=True)
client.reconnect_delay_set(min_delay=1, max_delay=60)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.connect_async(HOST, PORT, keepalive=30)
client.loop_start()

start = time.monotonic()
while not stop.wait(INTERVAL):
    payload = {"uptime_s": int(time.monotonic() - start), "ts": int(time.time())}
    client.publish(f"{PREFIX}/heartbeat", json.dumps(payload), qos=1)

print("stopping", flush=True)
client.publish(f"{PREFIX}/status", "offline", qos=1, retain=True).wait_for_publish(3)
client.disconnect()
client.loop_stop()

connect_async() faz diferença aqui. Um connect() simples lança uma exceção se o broker estiver inacessível naquele instante, o que é comum nos primeiros segundos após o boot. connect_async() entrega a conexão ao loop de rede, que tenta de novo com o backoff definido por reconnect_delay_set().

Passo 2: instale em um local estável

Não rode serviços a partir da pasta Downloads do seu diretório pessoal. Dê ao gateway seu próprio diretório, ambiente virtual e usuário de sistema:

sudo useradd --system --home /opt/mqtt-client --shell /usr/sbin/nologin mqttclient
sudo mkdir -p /opt/mqtt-client
sudo cp heartbeat.py /opt/mqtt-client/
sudo python3 -m venv /opt/mqtt-client/.venv
sudo /opt/mqtt-client/.venv/bin/pip install "paho-mqtt>=2.0"
sudo chown -R mqttclient:mqttclient /opt/mqtt-client

Se o script usa GPIO, I2C ou portas seriais, adicione o usuário ao grupo correspondente, por exemplo sudo usermod -aG gpio,i2c,dialout mqttclient.

Passo 3: guarde as credenciais em um arquivo de ambiente

sudo mkdir -p /etc/mqtt-client
sudo tee /etc/mqtt-client/env > /dev/null <<'EOF'
MQTT_HOST=broker.connectmq.com
MQTT_PORT=1883
MQTT_CLIENT_ID=pi1-heartbeat-s8t4n6
TOPIC_PREFIX=connectmq/demo/s8t4n6/pi1
PUBLISH_INTERVAL=30
EOF
sudo chmod 600 /etc/mqtt-client/env

Para o primeiro teste, isto aponta para o servidor MQTT público, que não exige credenciais e serve apenas para testes e protótipos. O systemd lê o arquivo como root antes de iniciar o processo, então ele pode ficar legível apenas pelo root.

Passo 4: escreva o arquivo de unidade para executar o script Python MQTT na inicialização

Crie /etc/systemd/system/mqtt-client.service:

[Unit]
Description=Python MQTT client
Wants=network-online.target time-sync.target
After=network-online.target time-sync.target
StartLimitIntervalSec=0

[Service]
Type=simple
User=mqttclient
Group=mqttclient
WorkingDirectory=/opt/mqtt-client
EnvironmentFile=/etc/mqtt-client/env
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/mqtt-client/.venv/bin/python /opt/mqtt-client/heartbeat.py
Restart=always
RestartSec=5
TimeoutStopSec=15
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes

[Install]
WantedBy=multi-user.target

O que cada linha importante faz:

  1. Wants/After=network-online.target adia o início até que a rede esteja configurada, e não apenas até o serviço de rede ter iniciado.
  2. time-sync.target espera pelo relógio (veja a observação sobre TLS abaixo).
  3. StartLimitIntervalSec=0 desativa o limite de taxa de inicialização, para que o systemd continue tentando para sempre em vez de desistir após algumas falhas rápidas.
  4. Restart=always e RestartSec=5 reiniciam o script cinco segundos após qualquer encerramento, incluindo travamentos e saídas limpas.
  5. PYTHONUNBUFFERED=1 faz a saída de print() aparecer imediatamente no journal.
  6. As opções Protect* isolam o processo em sandbox; o checklist de segurança para Raspberry Pi IoT as explica melhor.

Habilite e inicie:

sudo systemctl daemon-reload
sudo systemctl enable --now mqtt-client.service
systemctl status mqtt-client.service

Passo 5: verifique e opere

Acompanhe os logs ao vivo e depois assine a partir de outra máquina para confirmar que as mensagens chegam:

journalctl -u mqtt-client.service -f
mosquitto_sub -h broker.connectmq.com -p 1883 -i boot-viewer-s8t4n6 -t "connectmq/demo/s8t4n6/pi1/#" -v

Agora reinicie o Pi com sudo reboot. O assinante vê offline (do desligamento limpo) e, quando o Pi volta, online e os heartbeats. Para testar a recuperação após travamento, mate o processo com sudo systemctl kill -s SIGKILL mqtt-client.service e veja o systemd reiniciá-lo depois de cinco segundos.

Comandos que você vai usar no dia a dia:

Tarefa Comando
Reiniciar após mudar código ou env sudo systemctl restart mqtt-client
Parar temporariamente sudo systemctl stop mqtt-client
Desabilitar no boot sudo systemctl disable mqtt-client
Logs desde o último boot journalctl -u mqtt-client -b
Mostrar a unidade efetiva systemctl cat mqtt-client

A armadilha do TLS e do relógio

A maioria dos modelos de Raspberry Pi não tem relógio de tempo real com bateria (o Pi 5 tem RTC, mas precisa de uma bateria para manter a hora sem energia). No boot, o relógio parte do último horário salvo até o NTP sincronizar. Se estiver muito errado, a validação do certificado TLS falha porque o certificado parece ainda não válido ou expirado, e seu script registra erros de conexão repetidos na porta 8883.

A ordenação por time-sync.target só ajuda se algum serviço realmente esperar pela sincronização. Habilite o que faz isso:

sudo systemctl enable systemd-time-wait-sync.service
timedatectl status

Com ele habilitado, o cliente MQTT só inicia depois que o relógio estiver sincronizado. Como o script também reconecta sozinho, ele se recupera mesmo sem isso, só que mais devagar.

Solucionando um serviço que não fica de pé

Quando um serviço se comporta mal, systemctl status e o journal quase sempre contêm a resposta. Estas são as falhas mais vistas com clientes MQTT em Python:

  1. status=203/EXEC: o systemd não consegue executar o ExecStart. O caminho para o Python do venv está errado ou o venv foi criado por outro usuário e não pode ser lido. Use caminhos absolutos e confira com ls -l.
  2. ModuleNotFoundError: paho: a unidade roda o Python do sistema em vez do venv. Aponte o ExecStart para /opt/mqtt-client/.venv/bin/python, não para python3.
  3. KeyError: 'MQTT_CLIENT_ID': falta uma variável no arquivo de ambiente ou há um erro de digitação. Lembre que as linhas de EnvironmentFile são KEY=value simples, sem export.
  4. PermissionError em um arquivo ou dispositivo: ProtectSystem=strict deixou o caminho somente leitura ou o usuário do serviço não está em um grupo necessário. Adicione o diretório a ReadWritePaths= ou o usuário ao grupo certo.
  5. "connection refused" ou "not authorised" repetidos: porta, credenciais erradas ou client ID duplicado. Dois processos com o mesmo client ID ficam desconectando um ao outro, então confirme que nenhum teste manual continua rodando.
  6. Reinícios a cada poucos segundos, sem parar: o script encerra imediatamente. Rode à mão o comando exato do ExecStart como usuário do serviço, com sudo -u mqttclient, para ver o erro no seu terminal.

Migrando para um broker privado

Quando o serviço estiver funcionando, mude para credenciais de produção. Crie um broker no ConnectMQ e edite /etc/mqtt-client/env:

MQTT_HOST=broker.connectmq.com
MQTT_PORT=8883
MQTT_USERNAME=YOUR_USERNAME
MQTT_PASSWORD=YOUR_PASSWORD
MQTT_CLIENT_ID=YOUR_CLIENT_ID
TOPIC_PREFIX=site1/gateways/pi1

Reinicie o serviço e o script se conecta via TLS. Um broker MQTT gerenciado mantém o lado do servidor funcionando enquanto o systemd mantém o lado do cliente, e juntos eles cobrem a maioria das quedas. Para travamentos que parecem congelamentos em vez de encerramentos, adicione um watchdog de aplicação, como descrito nas dicas de confiabilidade para Raspberry Pi, e guarde os dados durante quedas de rede com o buffer offline de mensagens MQTT em Python. O manual do systemd.service lista todas as opções.

Novo em MQTT no Pi? Comece pelo tutorial de MQTT com Raspberry Pi e depois volte aqui para tornar seu cliente permanente.

Mantenha seu Pi conectado com o ConnectMQ

Cadastre-se em app.connectmq.com no plano Serverless gratuito, sem cartão de crédito, e cole as credenciais geradas no seu arquivo de ambiente. O portal ConnectMQ lista seus dispositivos, então após a próxima reinicialização você pode confirmar que o serviço voltou a ficar online sem precisar entrar no Pi.

Perguntas frequentes

Por que meu serviço inicia antes de a rede estar pronta?

After=network.target só espera pelo serviço de rede, não por uma conexão funcionando. Use Wants= e After=network-online.target e deixe o script reconectar sozinho com connect_async().

Por que não vejo nenhuma saída no journalctl?

O Python faz buffer do stdout quando ele não é um terminal. Defina PYTHONUNBUFFERED=1, rode o Python com -u ou passe flush=True para print().

Devo usar um serviço de usuário em vez de um serviço de sistema?

Serviços de usuário só rodam enquanto aquele usuário tem uma sessão, a menos que o lingering esteja habilitado. Um serviço de sistema com um User= dedicado é mais simples e inicia no boot sem nenhum login.

Como rodo vários scripts MQTT?

Crie um arquivo de unidade por script ou use uma unidade template como mqtt-client@.service com um arquivo de ambiente separado por instância. Cada instância precisa do seu próprio client ID único.

Artigos relacionados

Registro de Acessos Raspberry Pi RFID MQTT com o Leitor RC522

Ligue um leitor RFID RC522 a um Raspberry Pi, leia tags em Python, confira-as em uma lista de permissões local e publique cada tentativa de acesso como um evento MQTT que loggers, dashboards e ferramentas de alerta podem consumir.

8 min de leitura

Confiabilidade do Raspberry Pi: Rodando um Pi 24/7 como Gateway IoT

Um guia prático para manter um gateway MQTT com Raspberry Pi rodando por meses: desgaste do armazenamento, sistemas de arquivos somente leitura, alimentação, refrigeração, watchdogs de hardware e software e monitoramento remoto de saúde.

9 min de leitura

Pico W vs Pico 2 W para MQTT: Desempenho, Memória e TLS do RP2040 vs RP2350

O Pico 2 W mantém o formato e o chip Wi-Fi do Pico W, mas troca o RP2040 pelo RP2350, mais rápido. Esta comparação mostra o que isso significa para dispositivos MQTT: CPU, memória, TLS, recursos de segurança, suporte de software e qual placa comprar para cada projeto.

9 min de leitura

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.