Carta de apresentação
Sou um desenvolvedor FullStack sénior com mais de 25 anos de experiência, apaixonado por tecnologia e por trabalhar com pessoas. Ao longo da minha carreira desenvolvi soluções em múltiplas linguagens e ambientes (PHP, C#, JavaScript), administrei bases de dados como SQL Server, MySQL e PostgreSQL, e gerei servidores Linux e Windows.
A minha trajetória inclui também direção técnica e liderança de projetos em Espanha e Brasil, o que me deu uma visão ampla, madura e adaptável do mundo do software.
Destaco-me pela minha forma de trabalhar: resolvo problemas complexos com calma, especialmente em situações críticas, e mantenho sempre uma atenção na arquitetura limpa, eficiência do código e desempenho. Integro-me facilmente nas equipas, colaboro, escuto, ajudo e mantenho uma atitude positiva mesmo em momentos difíceis.
Para além das minhas competências técnicas, trago uma dimensão humana que valorizo tanto como o código: responsabilidade, empatia, boa comunicação e capacidade para gerar confiança e um bom ambiente. Falo espanhol, catalão, inglês e português com fluência, o que me permite colaborar de forma natural em ambientes multiculturais.
Em resumo: sou um profissional sénior com valores, trato humano e uma abordagem colaborativa, alguém com experiência real e com quem é fácil e agradável trabalhar.
Idiomas
Experiência
-
Senior Fullstack Developer — Biogyne SpainPHP Laravel Vue.js Bootstrap Node.js
-
CTO e Senior Developer — Denusa (Destilaria Nova União S/A - Brasil)PHP C# Laravel Zend Framework Bootstrap Vue.js
-
Senior Developer — Segundamano.esPHP C C++ Perl Bash JavaScript C#
-
Responsável de TI — Plastificados de TerrassaPHP C#
-
Empresa Familiar / Desenvolvedor — NELOSAPHP Visual Basic
Sobre Mim
Educação
- Licenciatura em Engenharia Informática
Competências
Idiomas
Contato
Drone autónomo com deteção de pessoas por IA
Um quadcopter construído do zero: um controlador de voo Pixhawk com ArduCopter, uma Raspberry Pi 4 como cérebro embarcado, uma câmara de IA (Sony IMX500) que deteta pessoas no próprio chip, vídeo em direto e controlo de voo completo a partir de uma app móvel própria — tudo por WiFi local, sem rádio-comando tradicional nem serviços na nuvem.
Um projeto pessoal para aprender de ponta a ponta: eletrónica e montagem do hardware, firmware de voo (ArduCopter), inteligência artificial embarcada e desenvolvimento de software — um servidor em Python/FastAPI e uma app móvel em React Native.
O objetivo final é um quadcopter totalmente controlado a partir do telemóvel: vídeo em direto com deteção de pessoas em tempo real, telemetria e controlos de voo (descolar, aterrar, mover, voltar a casa, paragem de emergência), tudo a comunicar com o Pixhawk via MAVLink — sem rádio-comando tradicional envolvido, embora venha a ser adicionado um como reserva.
Esta página resume a arquitetura, o estado real de cada peça e alguns dos bugs mais difíceis de depurar que encontrei pelo caminho.
Arquitetura
Hardware
- Controlador de voo Pixhawk 2.4.8 com firmware ArduCopter 4.6.3
- Raspberry Pi 4 Model B como computador de bordo (companion computer)
- Câmara Raspberry Pi AI (Sony IMX500) — inferência de IA no próprio sensor
- 4× motores brushless A2212 1000KV + 4× ESC 30A (ICQUANZX) + hélices de 10"
- Bateria LiPo 3S 5000mAh (11.1V nominal) — escolhida para respeitar a especificação do motor/ESC atual, ainda por comprar
- PDB Matek PDB-XT60, com BEC integrado de 5V/1.5A — insuficiente por si só para alimentar a Raspberry Pi
- UBEC dedicado 5V/5A (entrada 2S-6S) + condensador de 220-470µF entre 5V e GND, para a Raspberry Pi — ainda por comprar e montar
- Módulo de potência 3DR, para o Pixhawk medir tensão e corrente da bateria
- GPS u-blox NEO-M9N — ainda por ligar ao Pixhawk
- Ecrã OLED SSD1306 (provavelmente por I2C) — ainda por ligar
- Buzzer e botão/interruptor de segurança
- Comando RadioMaster Zorro preto (ExpressLRS, EdgeTX) + recetor ELRS a condizer (RadioMaster RP1/RP2 ou Happymodel EP1/EP2) — ainda por comprar
- Frame próprio desenhado à medida e impresso em 3D: pernas, braços, tampa, base e espaçadores (ficheiros STL mais abaixo)
- Suporte de câmara impresso em 3D, desenhado para a fixar e descartar a vibração como causa dos retângulos de deteção desalinhados
- Cabo de telemetria entre o Pixhawk e a Raspberry Pi através do conector JST-GH do Pixhawk — refeito à mão depois de localizar uma falha no fio de GND
- Extensões de cabo soldadas entre os ESC e os motores, porque os cabos de fábrica não chegam
Software
- comando_server.py — servidor FastAPI na Raspberry Pi que comunica com o Pixhawk por MAVLink (dronekit/pymavlink): descolar, aterrar, movimento, RTL, paragem de emergência, telemetria e logs em direto
- deteccion_personas_ai.py — captura vídeo com Picamera2 e corre um modelo SSD MobileNetV2 diretamente no chip IMX500 para detetar pessoas em tempo real, desenha os retângulos e publica o stream por RTSP/MediaMTX
- dronecam-app — app móvel própria (Expo / React Native) com vídeo em direto, telemetria, consola de logs e controlos de voo, a comunicar por WiFi local com a Raspberry Pi
Código no GitHub
Como funciona, do início ao fim
- A app móvel (dronecam-app) liga-se à Raspberry Pi por WiFi local — sem passar pela internet nem por qualquer servidor externo.
- A câmara IMX500 capta vídeo continuamente; cada fotograma é analisado no próprio chip da câmara, que já tem a inferência de IA integrada, pelo que a Raspberry Pi nunca tem de suportar esse trabalho.
- O deteccion_personas_ai.py recolhe essas deteções, desenha um retângulo verde sobre cada pessoa e envia o vídeo já anotado para o MediaMTX, que o serve como HLS para a app — tudo com cerca de 6 segundos de atraso.
- Em paralelo, o comando_server.py expõe uma API (FastAPI) com os controlos de voo: descolar, aterrar, mover, voltar a casa ou parar de emergência.
- Esse servidor traduz cada ordem para MAVLink e envia-a ao Pixhawk, que é quem realmente controla os motores — a Raspberry Pi nunca pilota o drone diretamente, apenas lhe diz o que fazer.
- Um watchdog corta o voo e força o regresso a casa se a app deixar de responder durante mais de 5 segundos, como rede de segurança.
Configuração da Raspberry Pi
Sistema e arranque
- Raspberry Pi OS (64 bits) numa Raspberry Pi 4 Model B
- O encaixe do cartão microSD tem uma avaria física: faz contacto com a caixa posta (que pressiona o cartão) mas perde-o sem ela, que é como vai ficar montada no drone
- Solução em curso: arrancar a partir de uma USB em vez do cartão microSD, para evitar totalmente o encaixe partido
sudo apt update && sudo apt upgrade -y
# permissões para aceder à câmara e à porta série sem sudo
sudo usermod -a -G video,dialout $USER
sudo reboot
Telemetria com o Pixhawk
- Ligada à porta TELEM2 do Pixhawk através do UART de hardware da Raspberry Pi
- 921600 baud em ambos os lados (antes estava mal configurado em 115200)
- UART ativado (enable_uart=1) e o Bluetooth integrado desativado (dtoverlay=disable-bt) para libertar o UART de hardware completo, sem o partilhar com a consola série
- Ligação confirmada com um heartbeat MAVLink real e estável, lido com pymavlink
- A Raspberry Pi esteve primeiro na TELEM1; foi movida para a TELEM2 para deixar a TELEM1 livre e pronta para o futuro recetor de rádio ELRS
# /boot/config.txt — no final do ficheiro
dtoverlay=disable-bt
enable_uart=1
# /boot/cmdline.txt — remover "console=serial0,115200"
sudo systemctl disable bluetooth
sudo reboot
# depois de reiniciar, confirmar que a porta existe:
ls /dev/serial0
Servidor de comandos (comando_server.py)
- Implementado em ~/Documents/_Codes/drone-env, sincronizado por git
- FastAPI + dronekit/pymavlink, a escutar na porta 8000
- Configuração num ficheiro .env (copiado de .env.example) — sem ele, o servidor ligava por defeito a um simulador (SITL) em vez do Pixhawk real
- Watchdog interno: se não receber um /ping da app em 5 segundos, força o regresso a casa como rede de segurança
- Expõe estes endpoints HTTP à app: /ping, /despegar, /aterrizar, /rtl, /mover, /parar_movimiento, /parada_emergencia, /motor_test/iniciar, /motor_test/detener, /ejecutar_script, /detener_script, /logs e /telemetria
- Por fazer: montá-lo também como serviço systemd para que arranque sozinho depois de um reinício
cd ~/Documents/_Codes/drone-env
pip install fastapi uvicorn dronekit pymavlink python-dotenv
# .env (copiado de .env.example):
DRONE_CONN=/dev/serial0:921600
DRONE_WATCHDOG_TIMEOUT=5
python comando_server.py # arranca em 0.0.0.0:8000
# verificação rápida a partir de outra máquina na mesma rede:
curl -X POST http://<ip-rpi>:8000/ping
Câmara e deteção de pessoas
- O deteccion_personas_ai.py corre a inferência de IA diretamente no chip Sony IMX500 da câmara, sem sobrecarregar o CPU da Raspberry Pi
- Publica o vídeo por ffmpeg → RTSP (rtsp://localhost:8554/drone) → MediaMTX → HLS para a app
- Resolução 640×480 a 10 fps, com um buffer de apenas 2 fotogramas na câmara — antes eram 12, e essa fila é que causava o minuto de atraso (ia-se enchendo de fotogramas cada vez mais antigos)
- Limiar de confiança da deteção: 0.6 (só se desenha a pessoa se o modelo ultrapassar essa confiança)
- Intervalo de keyframe do ffmpeg fixado em 1 segundo (-g, -keyint_min e -sc_threshold a 0) para manter a latência em direto baixa
sudo apt install -y rpicam-apps python3-picamera2 imx500-all
rpicam-hello --timeout 0 # confirmar que a câmara responde
# /boot/firmware/config.txt
camera_auto_detect=1
# modelo de IA usado pelo deteccion_personas_ai.py:
# /usr/share/imx500-models/imx500_network_ssd_mobilenetv2_fpnlite_320x320_pp.rpk
# comando ffmpeg real que o próprio script lança (fotogramas via stdin -> RTSP):
ffmpeg -y -hide_banner -loglevel warning -nostats \
-use_wallclock_as_timestamps 1 \
-f rawvideo -pixel_format bgr24 -video_size 640x480 -framerate 10 \
-i - \
-c:v libx264 -preset ultrafast -tune zerolatency \
-g 10 -keyint_min 10 -sc_threshold 0 \
-pix_fmt yuv420p \
-f rtsp rtsp://localhost:8554/drone
Serviços systemd (arrancam sozinhos com a Raspberry Pi)
- mediamtx.service — o servidor MediaMTX que recebe o RTSP e o serve como HLS para a app
- drone-camera.service — corre o deteccion_personas_ai.py, que por sua vez lança o ffmpeg; depende do mediamtx.service (After / Requires) para nunca arrancar demasiado cedo
- Restart=on-failure: se o processo cair, o systemd reinicia-o sozinho ao fim de 2 segundos
# /etc/systemd/system/drone-camera.service
[Unit]
Description=Streaming da camara IA (deteção de pessoas) para o MediaMTX
After=mediamtx.service
Requires=mediamtx.service
[Service]
WorkingDirectory=/home/emiki/Documents/_Codes/drone-env
ExecStart=/home/emiki/venv-ardupilot/bin/python3 /home/emiki/Documents/_Codes/drone-env/deteccion_personas_ai.py
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now mediamtx.service drone-camera.service
# confirmar que estão bem / ver os seus logs:
sudo systemctl status drone-camera.service mediamtx.service
sudo journalctl -u drone-camera.service -f
Implementação e atualização do código
- O código da Raspberry Pi vive no mesmo repositório dos dois PCs do utilizador: nada de copiar ficheiros soltos à mão
- Cada alteração é enviada a partir do PC e atualizada na Raspberry Pi com um git pull, para não haver risco de uma cópia ficar desatualizada
cd ~/Documents/_Codes/drone-env
git pull
# se foi alterado o comando_server.py, reiniciá-lo à mão (ainda não é systemd):
# Ctrl+C no seu terminal e voltar a lançar:
python comando_server.py
# se foi alterado o deteccion_personas_ai.py, o serviço reinicia-se sozinho:
sudo systemctl restart drone-camera.service
Rede e acesso a partir da app
- Tudo por WiFi local — sem passar pela internet nem por nenhum servidor externo
- O IP da Raspberry Pi muda consoante a casa (Barcelona ou Segur); a app móvel centraliza esse IP num único ficheiro de configuração para não ter de mexer no código sempre
Desafios técnicos que me custou resolver
Ao migrar o streaming para passar pelo chip de IA (de rpicam-vid para deteccion_personas_ai.py), o atraso do vídeo na app passou de alguns segundos para 2-3 minutos!
Causa: Descartei primeiro o pipeline de vídeo e a própria app — reproduzindo o mesmo RTSP no VLC tinha o mesmo atraso, pelo que o problema estava antes. A causa real: o ffmpeg não fixava o intervalo de keyframe, pelo que usava um valor por defeito de cerca de 25 segundos. O MediaMTX precisa de um keyframe para poder fechar cada segmento de vídeo (HLS), por isso era obrigado a esperar minutos antes de conseguir servir algo 'em direto'.
Correção: Ao forçar um keyframe a cada segundo (as flags -g, -keyint_min e -sc_threshold do ffmpeg), o atraso baixou de imediato para cerca de 6 segundos.
Depois de limpar a deteção para só marcar pessoas, o retângulo verde saía quase sempre esticado, cobrindo praticamente todo o vídeo em vez de encaixar sobre a pessoa.
Causa: Eram dois bugs encadeados: primeiro, eu estava a reescalar as coordenadas manualmente sem ter em conta que o modelo de IA raciocina sobre um quadrado de 320×320 píxeis, não sobre a resolução real da câmara. Segundo, mesmo usando a função oficial de conversão de coordenadas do picamera2, esta espera as coordenadas já normalizadas entre 0 e 1, e o modelo devolvia-as em píxeis da sua própria entrada.
Correção: Apliquei a mesma normalização usada no exemplo oficial do picamera2/IMX500 antes de converter as coordenadas, e o retângulo passou a encaixar corretamente sobre a pessoa. (Bónus: uma tentativa de correção a meio caminho tentou modificar uma propriedade só de leitura da biblioteca e derrubou o serviço num ciclo de reinícios — foi detetado de imediato nos logs do systemd e revertido na hora.)
A Raspberry Pi não conseguia obter um heartbeat MAVLink estável do Pixhawk pela porta série — ou zero bytes, ou bytes corrompidos.
Causa: Fui descartando um a um: portas alternativas, baud rates, configuração UART da Raspberry Pi, orientação dos cabos TX/RX, e até o próprio hardware UART com testes de loopback. No final, a causa era puramente física: o cabo GND tinha saído da sua cavidade no conector JST-GH do Pixhawk.
Correção: Um cabo novo, bem encaixado, e a correção de um baud rate mal configurado (estava a 115200 em vez de 921600), resolveram o bloqueio numa única tarde.
Estado do projeto
Feito
- Descartadas primeiro as causas de software: reinícios, configuração UART, orientação dos cabos TX/RX e o próprio hardware UART (testado com loopback)
- Encontrada a causa real: o cabo de GND tinha saído da sua cavidade no conector JST-GH
- Cabo novo montado e testado
- Corrigido o baud rate, que estava mal configurado em 115200 em vez de 921600
- Heartbeat MAVLink confirmado de forma estável com pymavlink
- Movida a porta da Raspberry Pi para TELEM2, deixando a TELEM1 pronta para o futuro recetor de rádio
- Confirmado que o servidor de comandos já leva dias a funcionar sem problemas com o Pixhawk real
Por fazer
Nada — fechado.
Feito
- Diagnosticada e corrigida uma atualização automática do Expo Go que quebrou a compatibilidade com o projeto
- Revisado o código: já implementa todos os controlos (descolar, mover, motor test, telemetria, logs, scripts) contra os endpoints reais
- Criado um ficheiro de configuração central para o IP da Raspberry Pi, que muda consoante a casa
- Confirmada a ligação real app↔servidor: vídeo da câmara visível e botões a chegar à Raspberry Pi
- Adicionado o controlo de altitude (subir/descer) ao D-pad no ecrã
- Consola de logs organizada (mais recente primeiro) e limitada às últimas 10 linhas
- Anotado o IP da Raspberry Pi para quando estiver em Barcelona
Por fazer
- Novo painel de teste manual de motores (botões ±10% por motor e para todos) — pendente de testar com o Pixhawk real
Feito
- Pipeline completo construído e verificado em produção
- Montado como serviços systemd persistentes após reinício
- Migrado o streaming para incluir a deteção de pessoas no próprio chip IMX500
- Diagnosticado e corrigido um bug grave de latência (2-3 minutos) causado por um intervalo de keyframe do ffmpeg sem fixar
- Confirmada a descida da latência para cerca de 6 segundos (por vezes 3s) depois da correção
Por fazer
- Decidir entre baixar ainda mais o keyframe, aceitar os ~6s atuais, ou migrar para WebRTC mais tarde
Feito
- Implementado na Raspberry Pi e ligado de verdade ao Pixhawk real
- Implementados os endpoints de descolar, aterrar, RTL, mover (com controlo de altitude), paragem de emergência, motor test, telemetria e logs em direto
- Adicionado um watchdog que força o regresso a casa se não receber sinal da app em 5 segundos
- Melhorado o registo: agora ficam também no log os comandos normais de voo, não só os scripts lançados
- Confirmado que os botões de subir/descer velocidade da app usam sempre uma velocidade vertical fixa (0.5 m/s), sem possibilidade de ajuste
- Esclarecido que os testes de motores em bancada devem usar os endpoints dedicados de teste de motores, não os controlos normais de mover, para evitar problemas de controlo
Por fazer
- Montá-lo como serviço systemd para que persista após reinícios
- Os avisos PreArm atuais (por falta de comando e recetor) resolvem-se sozinhos assim que essa peça for comprada
- Teste manual de motores por passos (botões ±10%, por motor ou para todos) já programado — pendente de subir para a Raspberry Pi e testar com hardware real
Feito
- Patas de aterragem — validadas
- Asas / braços — validados
- Tampa superior — validada
- Base — validada
- Separadores — validados
Por fazer
- Verificar como vai encaixar o suporte do GPS
Feito
- Identificado que o BEC do PDB não é suficiente para a Raspberry Pi
- Decidida a arquitetura: UBEC dedicado mais condensador
- Resolvido o conflito de voltagem entre a bateria e o motor/ESC, baixando de 4S para 3S para respeitar a especificação do ESC atual
Por fazer
- Comprar a bateria 3S 5000mAh, o UBEC 5V/5A (2S–6S) e o condensador de 220–470µF
- Montar o UBEC e testá-lo em bancada antes de voar (ver Montagem)
Feito
- Subido o limiar de confiança para reduzir falsos positivos
- Removido o desenho de retângulos para objetos que não são pessoas
- Corrigido o escalamento do retângulo com a função oficial de conversão de coordenadas do picamera2
- Corrigida a normalização das coordenadas antes dessa conversão
- Confirmado que o retângulo já encaixa bem sobre a pessoa na maioria dos casos
- Desenhada uma peça de suporte para montar a câmara fixa e isolar se o que resta é vibração ou o movimento de segurar a câmara à mão
Por fazer
- Imprimir o suporte da câmara
- Testar com a câmara fixa sobre uma mesa, num espaço mais aberto, e confirmar se isso resolve a inconsistência que resta
Feito
- Escolhido o comando: RadioMaster Zorro preto, com protocolo ExpressLRS (ELRS)
- Deixada preparada a porta do Pixhawk onde vai ligar o recetor
Por fazer
- Comprar o comando e um recetor ELRS compatível
- Atualizar ambos para a mesma versão de firmware antes de os emparelhar
- Emparelhá-los (bind)
- Cablar e soldar o recetor ao Pixhawk
- Configurar o failsafe do recetor para «sem pulsos»
- Calibrar o rádio e atribuir os canais de voo
- Configurar o failsafe de perda de sinal no ArduCopter
- Confirmar que o aviso «recetor não encontrado» desaparece e testar armar sem hélices
Feito
- Diagnosticado o sintoma exato: o cartão funciona com a caixa posta, mas perde o contacto sem ela
Por fazer
- Decidir entre: arrancar por USB (evita totalmente o encaixe partido), um remendo mecânico com espuma ou fita (rápido mas pouco fiável com as vibrações de voo), ou trocar a Raspberry Pi por outra
- Trabalho em pausa até essa decisão
Feito
Nada ainda.
Por fazer
- Soldar o PDB
- Fazer extensões aos cabos dos ESC para chegarem aos motores
- Ligar o GPS e o ecrã OLED ao Pixhawk / Raspberry Pi
- Fixar os ESCs à tampa do frame
- Montar na tampa o buzzer, o PDB, a câmara e o OLED
- Montar o UBEC e testar em bancada (motores a 15%) que a Raspberry Pi não reinicia, antes de voar
Peças do frame (impressão 3D)
O frame é um design próprio, desenhado à medida e iterado em mais de uma dezena de versões até todos os componentes encaixarem. Estes são os ficheiros STL das peças atuais, prontos a imprimir:
Ainda em decisão
- Latência de streaming: manter os ~6s atuais, reduzir mais, ou migrar para WebRTC mais tarde?
- Onde colocar a bateria e o módulo de potência 3DR dentro do frame
- Marca/modelo exatos de alguns componentes, ainda por anotar
Porque abracei este desafio
Sou developer há mais de 25 anos, mas este projeto tira-me da zona de conforto de uma forma muito saudável: aqui um bug não vive só no código, pode estar numa soldadura solta, num cabo mal encaixado, ou na forma como um chip de IA normaliza as suas coordenadas. Obriga-me a raciocinar por camadas — hardware, firmware, backend e app — e essa mistura de eletrónica, IA embarcada e desenvolvimento de software é exatamente o que mais gosto de aprender.