Contribuindo com o Nectar SDK¶
Discuta mudanças significativas via GitHub Issues ou Discussions antes de implementar, e leia primeiro o Código de Conduta.
Issues e Pedidos de Funcionalidade¶
Relatando Bugs¶
Crie relatos de bug que sejam:
- Reproduzíveis: inclua os passos para reproduzir o problema
- Específicos: números de versão, SO, detalhes de hardware
- Únicos: procure primeiro nas issues existentes
- Delimitados: um bug por relato
Pedidos de Funcionalidade¶
Verifique as Discussions por conversas em andamento antes de abrir um novo pedido.
Configuração de Desenvolvimento¶
Git LFS¶
Este repositório usa o Git LFS para rastrear arquivos binários grandes (imagens, vídeos, pesos de modelo etc.). Instale o Git LFS antes de clonar:
O Git LFS trata automaticamente os arquivos grandes definidos em .gitattributes. Ao
adicionar imagens, vídeos ou outros arquivos grandes, eles são automaticamente rastreados
pelo LFS.
Nota: se você clonou antes do Git LFS ser configurado, talvez precise baixar os arquivos LFS:
Ambiente Python¶
O SDK instala as dependências Python com uv em uma venv
compartilhada do workspace ($WORKSPACE/.venv), reutilizada por todos os pacotes do
workspace. Veja o guia de instalação.
Hooks de Pre-commit¶
Instale os hooks do pre-commit (configuração única):
Depois de pre-commit install, os hooks rodam automaticamente em todo git commit —
eles verificam somente os arquivos staged, corrigem automaticamente o que puderem, e abortam
o commit se alguma alteração foi feita. Basta git add nas correções e commitar de novo.
Antes de Fazer Push¶
Sempre rode o check completo antes de fazer push (o mesmo comando que o CI roda):
Isso valida todos os arquivos (Python, Markdown, YAML, shell scripts) quanto a:
- Espaços em branco no final da linha e newlines ausentes no final do arquivo
- Erros de lint do Python (imports não usados, nomes indefinidos)
- Ordenação de imports
- Formatação de código
Comandos Rápidos¶
make check # run all checks (same as CI)
make lint # Python lint only (ruff check)
make lint-fix # Python lint + auto-fix (ruff check --fix)
make format # Python format only (ruff format)
Use make lint / make format durante o desenvolvimento para feedback rápido em arquivos
Python. Use make check antes de fazer push para garantir que o CI vai passar.
Processo de Pull Request¶
1. Faça um Fork e Clone¶
# Fork via GitHub UI, then:
git clone git@github.com:YOUR_USERNAME/nectar-sdk.git
cd nectar-sdk
git remote add upstream git@github.com:Black-Bee-Drones/nectar-sdk.git
2. Crie uma Branch de Feature¶
Nomenclatura de Branches:
feat/- Novas funcionalidadesfix/- Correções de bugsdocs/- Somente documentaçãorefactor/- Refatoração de códigotest/- Adições/correções de testes
3. Faça as Alterações¶
Siga o estilo de código do projeto:
- Python: siga o PEP 8
- Docstrings: use o estilo NumPy
- Type hints: inclua anotações de tipo para APIs públicas
4. Faça Commit com Conventional Commits¶
Seguimos o Conventional Commits:
# Format: type(scope): description
git commit -m "feat(control): add obstacle avoidance strategy"
git commit -m "fix(vision): resolve camera initialization race condition"
git commit -m "docs(readme): update installation instructions"
Tipos:
| Tipo | Descrição |
|---|---|
feat |
Nova funcionalidade |
fix |
Correção de bug |
docs |
Documentação |
style |
Formatação (sem alteração de código) |
refactor |
Reestruturação de código |
test |
Adições de teste |
chore |
Build/ferramental |
5. Faça Push e Crie o PR¶
Depois abra um Pull Request no GitHub usando nosso template de PR.
Diretrizes de Código¶
Documentação¶
- Atualize o README.md do módulo ao adicionar funcionalidades
- Adicione docstrings às classes e métodos públicos
- Inclua exemplos de uso para novas APIs
Convenções de README (padrão de excelência: control/mavros/README.md):
- Estrutura (progressive disclosure): comece com um propósito de uma linha, depois um snippet executável de visão rápida (o caso comum), depois Conceitos (prosa curta + um diagrama de nível médio), depois um Uso orientado a tarefas, depois uma seção de Referência para tabelas exaustivas (tópicos/serviços/parâmetros/tipos). O Uso vem antes do diagrama de classes detalhado, nunca depois de uma parede dele. Mantenha a prosa concisa e técnica — sem buzzwords, sem repetição.
- Fonte única de verdade: documente a lógica compartilhada uma única vez e aponte para ela; não repita a mesma coisa em dois READMEs. A lógica de voo compartilhada vive em Vehicle core (especificidades do ArduPilot em ArduPilot); o VSLAM indoor em Localization; a instalação no Guia de instalação; o Docker no Guia Docker; a simulação em Simulation. Os READMEs dos módulos concentram a profundidade técnica; o site de documentação os espelha por meio de
scripts/docs/sync_readmes.py. O README raiz é uma landing page curta do GitHub — aponte para o site, não duplique tabelas de instalação, diagramas de arquitetura ou índices de módulo ali. - Diagramas (Mermaid): mantenha um diagrama por README, em uma seção Conceitos depois do uso de visão rápida. O Zensical renderiza Mermaid no lado do cliente e adapta fontes/cores ao esquema claro/escuro ativo, e todo diagrama tem clique para zoom/pan (veja
website/javascripts/). Para os diagramas técnicos (classe, sequência, estado), confie no tema — não defina cores, para que fiquem legíveis nos dois esquemas. Reserve cores para o diagrama de alto nível de arquitetura/visão geral, onde uma paletaclassDefcurada (fill:em tom médio com umcolor:de texto explícito) ajuda a compreensão e funciona nos dois esquemas. Use o layout ELK (config: { layout: elk }no frontmatter do diagrama) somente para fluxogramas densos no site — o Mermaid do GitHub não tem ELK, então nunca coloquelayout: elkem um README que também deve renderizar no GitHub. Evite envolver o Mermaid em<details>/colapsáveis (a fence não renderiza dentro de blocos raw-HTML). Git graphs renderizam, mas não seguem o tema e são fracos no mobile — use com moderação. - Renderiza no parser estrito: o site de documentação usa Python-Markdown, que é mais estrito que o GitHub. Coloque uma linha em branco antes de toda lista, tabela e bloco de código com fence (regras
MD022/MD031/MD032/MD058, aplicadas pelomarkdownlint-cli2no pre-commit e no CI). Markdown que passa pelo linter renderiza tanto no site quanto no GitHub; o inverso não é garantido. Faça o preview localmente commake docs-serve. - Tabs e admonitions são exclusivos do site: tabs de conteúdo (
=== "...") e admonitions!!!/???renderizam somente no site de documentação, não no GitHub. Use-os nas páginas autorais dewebsite/e nos guias site-first sobdocs/(setup, Docker), onde eles carregam a escolha do leitor (backend, câmera, SO) do início ao fim. READMEs de módulo e de exemplo também são exibidos no GitHub, então ali apresente alternativas como blocos com fence e rótulo em negrito separados (não tabs) e use blockquotes simples (> **Warning:** ...) para notas. Uma fence que usa comentários#como cabeçalhos de caso/variante deve se tornar blocos com rótulo em negrito (ou uma tabela) em um README, e um grupo de tabs em uma página do site. - Texto de link legível: nunca use um caminho como o texto visível do link (
[vehicle/README.md](...),[control/px4/config](...)). Escreva um título legível ([Vehicle core](...),[PX4 config](...)). Apontadores inline para arquivo-fonte (([sequencer.py](sequencer.py))) são aceitáveis. Links de símbolo → API gerada pertencem somente às páginas dewebsite/(um link de README paraapi/*.mdnão é seguro para renderização dupla). - Mantenha-se fiel ao código: todo comando, flag, tópico e nome de classe deve corresponder ao código. Prefira flags reais de argparse / nomes de parâmetros reais em vez de inventados.
- Ao adicionar um módulo: atualize o README do módulo e seu índice pai; se for uma nova área de nível superior, adicione um bullet em Features no README raiz apontando para a página correspondente no site de documentação. Siga Adicionando um componente abaixo.
Exemplo de Docstring:
def move_to(
self,
x: float,
y: float,
z: float,
precision: float = 0.2,
) -> bool:
"""
Navigate to a position in the world frame.
Parameters
----------
x : float
Target X position in meters.
y : float
Target Y position in meters.
z : float
Target Z position (altitude) in meters.
precision : float, optional
Position tolerance in meters. Default is 0.2.
Returns
-------
bool
True if target reached within timeout, False otherwise.
Examples
--------
>>> drone.move_to(x=2.0, y=1.0, z=1.5, precision=0.3)
True
"""
Testes¶
A suíte funcional vive em
nectar/test/ (ela
não é instalada com o pacote) e é pytest puro:
nectar/test/functional/— umtest_<module>.pypor módulo do SDK. Cada teste realiza uma operação real sobre entradas sintéticas ou um loopback. Os testes se auto-pulam (nunca falham) quando uma dependência opcional está ausente, viapytest.importorskip(...).nectar/test/hardware/— checks condicionados a dispositivo (RealSense, OAK-D, TF-Luna), marcados comohardwaree desselecionados por padrão.nectar/test/conftest.py+nectar/test/helpers.py— fixtures (ros_node,fake_fcu,qt_app) e o FCU de loopback.
Rode:
make verify-functional # all modules (hardware/gpu deselected)
make verify-functional MODULE="vision control" # subset -> pytest -m "vision or control"
make test # colcon test: the suite + cmake/xml lint
make verify-hardware # opt in to device checks on the rig
# (equivalently, from the package dir: cd nectar && pytest test/hardware -m hardware)
Diretrizes:
- Adicione um teste para toda nova funcionalidade. Coloque-o no
test/functional/test_<module>.pycorrespondente, marque o módulo compytestmark = pytest.mark.<module>, e condicione qualquer coisa que precise de dispositivo/GPU/sim/rede com os markershardware/gpu/sim/network(registrados empyproject.toml). Faça assert comassertsimples; pule compytest.skip(...)/pytest.importorskip(...)— nunca falhe por uma dependência opcional ausente. - Faça lint/format com o ruff (a única fonte de verdade —
make lint/make format, também aplicado pelo pre-commit).make verify-functionalse auto-pula de forma limpa, então um run verde na sua máquina reflete o que sua instalação de fato suporta. - Teste com hardware real ao modificar código de controle de drone, e rode os markers
relevantes de
make verify-functionalantes de submeter. make doctorimprime um relatório somente leitura do ambiente e dispositivos atuais — útil quando um teste é pulado e você quer saber por quê.
CI local (cross-distro)¶
Os checks rodam em fidelidade crescente:
- Lint —
make check(ruff via pre-commit). - Suíte no host (a mais rica; todas as deps instaladas, incl. AI/Qt/CUDA) —
make verify,make verify-functional,make verify-hardwarena sua máquina de dev. - Cross-distro em Docker —
make ci-localbuilda a imagem do SDK a partir do código local para cada distro ROS e rodaverify+verify-functionalem cada uma, espelhando.github/workflows/_build-verify.yml. Ele imprime um resumo de pass/fail e grava um relatório JUnit por distro emci-local-results/. Somente amd64 (arm64 é coberto em um Jetson; Windows via WSL2 mais adiante). Builda uma imagem por vez e a remove depois, para limitar o uso de disco.
make ci-local # humble jazzy kilted (sdk stage)
make ci-local DISTROS=jazzy # one distro
make ci-local DISTROS="humble kilted" FULL=1 # include torch/AI (sdk-full; heavy)
make ci-local REALSENSE=1 # also build librealsense + realsense-verify
Disco: builds de imagem completos são grandes. O data-root do Docker precisa ter vários GB
livres (cada imagem sdk tem ~4-5 GB, sdk-full ~10 GB); o runner aborta um build abaixo
de MIN_FREE_GB (padrão 8). Recupere espaço com docker system prune / removendo imagens
antigas.
Voos SITL / integração (Tier 3)¶
A suíte em nectar/test/sitl/
voa uma missão smoke real em um simulador headless para cada firmware + protocolo
(connect → takeoff → move → land), validando o núcleo do veículo e o link
firmware/protocolo de ponta a ponta. A fixture sim_session possui o ciclo de vida da
simulação (reaproveitando make sim-start / sim-bridge / sim-stop), então um único
comando substitui a orquestração manual em dois terminais.
make verify-sitl # full matrix (needs the sim stack: make sim-install)
make verify-sitl FIRMWARE=px4 PROTOCOL=dds # one entry (FIRMWARE -> marker, PROTOCOL -> -k)
Matriz: ardupilot (mavros, mavlink), px4 (mavros, mavlink, dds), crazyflie (sim do
Crazyswarm2). O Bebop é hardware-only (sem simulador). O tier é marcado como sitl e
desselecionado por padrão.
Rode onde a stack de simulação estiver instalada (make sim-install): sua máquina de dev ou
o Jetson, e como um gate de pré-release. Não está no CI — buildar os simuladores
(ArduPilot + PX4 + Gazebo, todos a partir do código-fonte) leva ~45-70 min e não há binário
apt para atalhar isso, então não vale a pena como job por PR ou agendado. O mesmo comando
make verify-sitl roda sem alterações em amd64 e arm64. (O Crazyflie se auto-pula a menos
que o backend crazyflie_sim do Crazyswarm2 seja buildado a partir do código-fonte.)
Para adicionar um firmware/protocolo: adicione um SimSpec a
nectar/test/sitl/sim_helpers.py
(seus start_cmds, tipo de drone, preset de config e envelope de voo); o test_smoke_flight
parametrizado e os markers pegam isso automaticamente. A suíte de navegação mais profunda do
ArduPilot fica em
examples/simulation/sitl_test.py.
Adicionando um componente¶
Ao adicionar novos componentes:
- Módulo Control: estenda
BaseDroneou implemente o protocoloDrone; registre emDroneFactory. A lógica do ArduPilot agnóstica a transporte fica emcontrol/ardupilot/; a navegação externa indoor (VSLAM, ponte de vision-pose) emcontrol/localization/. - Módulo Vision:
- Câmeras: adicione em
vision/camera/drivers/, registre emCameraFactory - Algoritmos: adicione em
vision/algorithms/<category>/ - Módulo AI: estenda
BaseDetectionModel(ou o equivalente de segmentação), registre emDetector/Segmentor - Pontos de entrada ROS: os nós vão no diretório
nodes/correspondente e os arquivoslaunch/emnectar/launch/; instale os dois emnectar/CMakeLists.txt - Export: adicione os símbolos públicos ao
__init__.py(deps pesadas via_LAZY_ATTRS) - Documente: atualize o README.md do módulo; para um novo módulo de nível superior, adicione um bullet em Features no README raiz apontando para a página correspondente no site de documentação
Contrato de Tempo de Importação¶
Dependências pesadas de terceiros (torch, transformers, rfdetr, tensorflow, jax, matplotlib, mediapipe, pyrealsense2, depthai, supervision, ultralytics, pandas, geopy) não devem ser importadas no momento de carregamento do módulo, no caminho de from nectar.ai import Detector, from nectar.vision import ImageHandler, ou from nectar.control import MavrosConfig.
Dois padrões são usados para reforçar isso, ambos padrão de mercado (PEP 562, a mesma abordagem do scikit-learn / NumPy / SciPy):
- Superfície lazy do pacote em
__init__.py: re-exports pesados passam por um dict_LAZY_ATTRSe__getattr__. Tipos leves (dataclasses, enums, exceptions) permanecem eager. Vejanectar/ai/detection/__init__.pyenectar/vision/__init__.pypara o padrão canônico. - Imports locais em funções: quando um módulo precisa de
matplotlib/pandassomente dentro de um helper de plotagem, o import vive dentro do corpo da função, não no topo do arquivo. Vejanectar/vision/algorithms/distance/calibrator.py::ModelCalibrator.plot.
Verifique o contrato disparando um interpretador novo e afirmando que os módulos proibidos
estão ausentes de sys.modules depois de um import público:
python -c "import sys, nectar.control; assert 'torch' not in sys.modules and 'mediapipe' not in sys.modules"
Se você adicionar uma nova dependência pesada ou re-export, roteie-a por _LAZY_ATTRS (ou um
import local à função) para que o caminho de import público permaneça leve.
Processo de Revisão¶
Os mantenedores revisam o código e fornecem feedback; trate as alterações solicitadas e, uma vez aprovado, o PR é mesclado.
Dúvidas?¶
- GitHub Discussions para perguntas gerais
- GitHub Issues para bugs/funcionalidades