Início

ZONY Field Stack™

Docker · FastAPI · MQTT · CAN Bus · Simulador

Docker Compose
FastAPI
MQTT Mosquitto
Python CAN
Nginx

Stack em produção no campo

Web UI :3000API FastAPI :8000MQTT Broker :1883Gateway CANSimuladorMáquina
[Web UI] ──► [API FastAPI]
                 │
                 ▼
           [MQTT Broker]
                 │
       ┌─────────┴─────────┐
       ▼                   ▼
[Gateway CAN (Python)]  [Simulator]
       │                   │
       └──────► CAN Bus ◄──┘
                    │
                [Máquina]

1. Arquitetura

Em produção no campo
API
:8000

Orquestra comandos e telemetria

MQTT
:1883

Tempo real de baixo consumo

Gateway
host

Lê/escreve CAN Bus da máquina

Simulator
internal

Testa sem hardware físico

Web UI
:3000

Controla tudo pelo browser

Máquina
CAN

Trator / plantadeira / drone

2. Docker Compose

Stack completo
docker-compose.yml
YAML
version: "3.9"

services:
  api:
    build: ./api
    ports:
      - "8000:8000"
    depends_on:
      - mqtt
    environment:
      - MQTT_HOST=mqtt

  mqtt:
    image: eclipse-mosquitto
    ports:
      - "1883:1883"

  gateway:
    build: ./gateway
    network_mode: host
    privileged: true
    depends_on:
      - mqtt
    environment:
      - MQTT_HOST=localhost

  simulator:
    build: ./simulator
    depends_on:
      - mqtt
    environment:
      - MQTT_HOST=mqtt

  web:
    build: ./web
    ports:
      - "3000:3000"
    depends_on:
      - api
Rodar tudo: docker-compose up --build

3. API (FastAPI + MQTT)

api/main.py
api/main.py
Python
# api/main.py
from fastapi import FastAPI
import paho.mqtt.publish as publish
import os

app = FastAPI()
MQTT_HOST = os.getenv("MQTT_HOST", "localhost")

@app.post("/plant")
def plant(machine_id: str, depth: int, spacing: float):
    payload = {
        "machine_id": machine_id,
        "cmd": "PLANT",
        "depth": depth,
        "spacing": spacing
    }
    publish.single("zony/commands", str(payload), hostname=MQTT_HOST)
    return {"status": "sent"}

@app.post("/stop")
def stop(machine_id: str):
    publish.single("zony/commands", f"{machine_id}:STOP", hostname=MQTT_HOST)
    return {"status": "stopped"}
api/Dockerfile
Dockerfile
# api/Dockerfile
FROM python:3.11
WORKDIR /app
COPY . .
RUN pip install fastapi uvicorn paho-mqtt
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
POST
/plantPublica comando PLANT no MQTT
POST
/stopPublica STOP — parada imediata

4. Gateway CAN (Real)

gateway/gateway.py
gateway/gateway.py
Python
# gateway/gateway.py
import can
import paho.mqtt.client as mqtt
import os

MQTT_HOST = os.getenv("MQTT_HOST", "localhost")

bus = can.interface.Bus(channel='can0', bustype='socketcan')

def on_message(client, userdata, msg):
    data = eval(msg.payload.decode())

    if data["cmd"] == "PLANT":
        send_can(0x100, [1, data["depth"], int(data["spacing"]*100)])

    if data["cmd"] == "STOP":
        send_can(0x101, [0])

def send_can(arbid, data):
    message = can.Message(arbitration_id=arbid, data=data, is_extended_id=False)
    bus.send(message)

client = mqtt.Client()
client.connect(MQTT_HOST, 1883)
client.subscribe("zony/commands")
client.on_message = on_message

print("Gateway ativo...")
client.loop_forever()
gateway/Dockerfile
Dockerfile
# gateway/Dockerfile
FROM python:3.11
WORKDIR /app
COPY . .
RUN pip install python-can paho-mqtt
CMD ["python", "gateway.py"]

⚙️ Fluxo do Gateway

MQTT subscribeDecodifica comandoMonta CAN framebus.send()Máquina reage

5. Simulador (Sem Hardware)

simulator/sim.py

Testa todo o stack sem precisar de máquina física. Recebe os comandos MQTT e imprime no terminal.

simulator/sim.py
Python
# simulator/sim.py
import paho.mqtt.client as mqtt

def on_message(client, userdata, msg):
    print("SIMULADOR RECEBEU:", msg.payload.decode())

client = mqtt.Client()
client.connect("mqtt", 1883)
client.subscribe("zony/commands")
client.on_message = on_message

print("Simulador ativo...")
client.loop_forever()
web/index.html
HTML
<!-- web/index.html -->
<!DOCTYPE html>
<html>
<body>
  <h2>ZONY Control</h2>
  <button onclick="plant()">Iniciar Plantio</button>
  <button onclick="stop()">Parar</button>

  <script>
    function plant() {
      fetch("http://localhost:8000/plant?machine_id=tractor_01&depth=5&spacing=0.5",
        { method: "POST" });
    }
    function stop() {
      fetch("http://localhost:8000/stop?machine_id=tractor_01",
        { method: "POST" });
    }
  </script>
</body>
</html>
web/Dockerfile
Dockerfile
# web/Dockerfile
FROM nginx:alpine
COPY . /usr/share/nginx/html

6. Protocolo CAN ZONY v1

CAN Frame Spec
ID CANFunçãoByte 0Byte 1Byte 2
0x100Iniciar plantioCmd (0x01)Profundidade cmEspaçamento cm
0x101Parar tudoCmd (0x00)——
0x102Ajustar prof.Cmd (0x02)Nova depth cm—
0x103Ajustar espaç.Cmd (0x03)—Novo spacing cm

Exemplo real — plantio

[0x01, 0x05, 0x32]

0x01 → plantar

0x05 → 5 cm de profundidade

0x32 → 50 cm de espaçamento

7. Campo Real — Checklist

CRÍTICO antes de conectar

Validar protocolo CAN da máquina

Cada fabricante pode ter IDs e bauds diferentes.

Usar isolador CAN

Protege o Raspberry Pi de surtos elétricos.

Testar em modo seguro

Desconectar implemento físico nos primeiros testes.

Implementar STOP físico

Botão de emergência independente do software.

🔥 Resultado Final

Rodando em Docker
Controle via Web
Gateway CAN real
Simulador incluso
Protocolo ZONY

"Do clique na tela ao movimento da máquina — em tempo real."

— ZONY Field Stack™

base44
Edit with Base44