Módulo de Controle de Drones¶
Papel¶
Controle de drones baseado em protocolo (protocol) para ROS 2. DroneFactory constrói um drone por chave por meio do protocolo comum Drone; cada plataforma implementa a mesma interface (takeoff, land, move_to, move_to_gps, move_velocity, rtl, gerenciamento de obstáculos).
Índice da documentação¶
As páginas abaixo detalham cada submódulo:
| README | Escopo |
|---|---|
| Vehicle core | Núcleo do veículo agnóstico a firmware: navegação, frames, takeoff/land, GPS/EGM96, PID, hooks de firmware |
| ArduPilot | Especialização ArduPilot: arming em GUIDED, GUID_OPTIONS/WPNAV, RTL nativo, parâmetros |
| PX4 | Especialização PX4: streaming de setpoint OFFBOARD, AUTO.LAND/RTL; backends MAVROS / MAVLink direto / uXRCE-DDS |
| MAVROS transport | Transporte MAVROS (MavrosDrone, Px4MavrosDrone) |
| MAVLink transport | Transporte pymavlink neutro em relação a firmware (MavlinkDrone, Px4MavlinkDrone) |
| Localization | Navegação externa (external-nav) por VSLAM indoor: arquitetura, Run, FCU, SOP indoor |
| Localization concepts | Teoria de SLAM / VIO / V-SLAM e fusão com o FCU |
| Legacy T265 | Histórico do T265 + vision_to_mavros |
| Obstacles | Detecção de obstáculos + estratégias de desvio de obstáculos |
| PID | Controlador PID e ajuste (tuning) |
| Bebop | Parrot Bebop 2 (BebopDrone) |
| Crazyflie | Bitcraze Crazyflie (CrazyflieDrone) |
Resumo rápido¶
import nectar
from nectar.control import DroneFactory, MavrosConfig, PoseSource
nectar.init()
drone = DroneFactory.create("mavros", MavrosConfig(pose_source=PoseSource.GPS))
drone.takeoff(altitude=2.0)
drone.move_to(x=5.0, y=0.0, z=0.0, precision=0.3) # same calls on every backend
drone.land()
nectar.shutdown()
Escolha um backend pela chave — mavros, mavlink, px4, px4_mavlink, px4_dds, bebop,
crazyflie — com o config correspondente; as chamadas de voo permanecem as mesmas.
Lado a lado com MAVROS / pymavlink puro: Com e sem Nectar (with-without.md).
Conceitos¶
ArduPilot e PX4 compartilham um único núcleo agnóstico a firmware (VehicleDrone) e alcançam o
FCU por meio de transportes intercambiáveis (MAVROS, MAVLink direto, PX4 uXRCE-DDS); Bebop e
Crazyflie implementam o protocolo Drone diretamente.
classDiagram
class DroneFactory {
<<singleton>>
-_builders Dict~str,BuilderFunc~
+create(type, config, executor) Drone
+register(type, factory_func)
+available_types() list~str~
+is_registered(type) bool
}
class Drone {
<<protocol>>
+is_ready bool
+connect() bool
+disconnect()
+arm() bool
+disarm() bool
+takeoff(altitude) bool
+land(timeout) bool
+move_velocity(vx, vy, vz, vyaw, duration, reference)
+move_to(x, y, z, yaw, reference, timeout, precision, method) bool
+move_to_gps(lat, lon, alt, heading, timeout, precision, method) bool
+emergency_stop()
+set_home() bool
+rtl(altitude, precision, method, land) bool
}
class BaseDrone {
<<abstract>>
-_config DroneConfig
-_node Node
-_connected bool
-_driver_running bool
-_obstacle_manager ObstacleManager
-_subscribers List~Subscription~
-_publishers List~Publisher~
-_clients List~Client~
-_callback_group ReentrantCallbackGroup
+config DroneConfig
+node Node
+is_ready bool
+driver_running bool
+is_armed Optional~bool~
+flight_mode Optional~str~
+is_fcu_connected Optional~bool~
+driver_session_name str
+obstacle_manager ObstacleManager
+add_obstacle_detector(name, detector, strategy, config)
+remove_obstacle_detector(name)
+enable_obstacle_detector(name)
+disable_obstacle_detector(name)
+enable_all_obstacle_detectors()
+disable_all_obstacle_detectors()
+check_driver_status() bool
+start_driver_process() bool
+stop_driver_process() bool
+delay(seconds)
+cleanup()
#_init_driver()
#_wait_for_driver(timeout) bool
#_create_subscriber(msg_type, topic, callback, qos) Subscription
#_create_publisher(msg_type, topic, qos) Publisher
#_create_client(srv_type, service_name) Client
#_get_driver_name()* str
#_start_driver()* bool
#_get_driver_command()* str
}
class VehicleDrone {
<<abstract>>
-_transport VehicleTransport
-_navigator VehicleNavigator
-_sequencer FlightSequencer
-_pid_config Optional~PositionPIDConfig~
-_setpoint_config Optional~SetpointConfig~
-_takeoff_position Optional
-_pose_source PoseSource
+is_indoor bool
+is_armed flight_mode is_fcu_connected
+gps heading rel_alt
+local_pose vision_pose Optional~LocalPose~
+lidar_available bool
+position position_as_target
+get_altitude(source) Optional~float~
+distance_sensors get_distance(orientation)
+takeoff() land() move_to() move_to_gps() move_velocity() rtl()
+set_mode() set_param() set_speed() set_home()
+set_actuator() set_gripper()
+set_takeoff_position() set_pid_config() set_setpoint_config()
+arm()* _rtl_native()* _change_speed()* capabilities*
}
class ArduPilotDrone {
GUIDED arm, GUID_OPTIONS/WPNAV
native RTL, do_servo, DO_CHANGE_SPEED
}
class Px4Drone {
OFFBOARD + setpoint pump
AUTO.LAND/RTL, MPC_* speed
}
class MavrosDrone {
+from_config(config, executor)$ MavrosDrone
}
class MavlinkDrone {
+connection MavlinkConnection
+from_config(config, executor)$ MavlinkDrone
}
class Px4MavrosDrone {
+from_config(config, executor)$ Px4MavrosDrone
}
class Px4MavlinkDrone {
+connection MavlinkConnection
+from_config(config, executor)$ Px4MavlinkDrone
}
class Px4DdsDrone {
+from_config(config, executor)$ Px4DdsDrone
}
class CrazyflieDrone {
+from_config(config, executor)$ CrazyflieDrone
}
class VehicleTransport {
<<abstract>>
+state local_pose vision_pose gps heading rel_alt rangefinder distance_sensors
+arm() set_mode() command_takeoff() command_land() set_param()
+send_velocity_target() send_local_target() send_global_target()
}
class MavrosTransport
class PymavlinkTransport
class Px4DdsTransport
class BebopDrone {
+from_config(config, executor)$ BebopDrone
+flip(direction)
+camera_control(tilt, pan)
+snapshot()
-_setup_publishers()
}
class DroneConfig {
<<dataclass>>
+name str
+start_driver bool
}
class MavrosConfig {
<<dataclass>>
+pose_source PoseSource
+expect_lidar bool
+sensor_timeout float
+connection_string str
+pid_config_file setpoint_config_file Optional~str~
+apply_setpoint_params bool
+state_topic gps_topic vision_topic str
+heading_topic rel_alt_topic lidar_topic str
+local_position_topic str
}
class MavlinkConfig {
<<dataclass>>
+pose_source PoseSource
+expect_lidar bool
+connection_string str
+baud source_system source_component int
+rx_rate_hz heartbeat_hz vision_rate_hz float
+stream_rates Optional~Dict~
+vision_pose_topic str
+pid_config_file setpoint_config_file Optional~str~
+apply_setpoint_params bool
}
class BebopConfig {
<<dataclass>>
+name str
+start_driver bool
+ip str
+namespace str
}
class Px4MavrosConfig {
<<dataclass>>
+pose_source PoseSource
+offboard_rate_hz float
+mavros_launch str
+connection_string str
+shares MavrosConfig telemetry topics
}
class Px4MavlinkConfig {
<<dataclass>>
+pose_source PoseSource
+offboard_rate_hz float
+connection_string str
+shares MavlinkConfig link settings
}
class Px4DdsConfig {
<<dataclass>>
+pose_source PoseSource
+offboard_rate_hz float
+px4_namespace str
+agent_port int
+local_position_topic status_topic global_position_topic str
}
class ObstacleManager {
-_handlers dict~str,ObstacleHandler~
+add(name, handler) remove(name) get(name)
+enable(name) disable(name) enable_all() disable_all()
+should_continue_navigation(drone) bool
+get_axis_control() tuple~bool,bool,bool~
+reset_all() cleanup()
}
DroneFactory --> BaseDrone : creates
Drone <|.. BaseDrone : implements
BaseDrone <|-- VehicleDrone
BaseDrone <|-- BebopDrone
BaseDrone <|-- CrazyflieDrone
VehicleDrone <|-- ArduPilotDrone
VehicleDrone <|-- Px4Drone
VehicleDrone o-- VehicleTransport
ArduPilotDrone <|-- MavrosDrone
ArduPilotDrone <|-- MavlinkDrone
Px4Drone <|-- Px4MavrosDrone
Px4Drone <|-- Px4MavlinkDrone
Px4Drone <|-- Px4DdsDrone
VehicleTransport <|.. MavrosTransport
VehicleTransport <|.. PymavlinkTransport
VehicleTransport <|.. Px4DdsTransport
MavrosDrone ..> MavrosTransport : builds
MavlinkDrone ..> PymavlinkTransport : builds
Px4MavrosDrone ..> MavrosTransport : builds
Px4MavlinkDrone ..> PymavlinkTransport : builds
Px4DdsDrone ..> Px4DdsTransport : builds
BaseDrone *-- ObstacleManager
BaseDrone o-- DroneConfig
MavrosDrone o-- MavrosConfig
MavlinkDrone o-- MavlinkConfig
Px4MavrosDrone o-- Px4MavrosConfig
Px4MavlinkDrone o-- Px4MavlinkConfig
Px4DdsDrone o-- Px4DdsConfig
BebopDrone o-- BebopConfig
DroneConfig <|-- MavrosConfig
DroneConfig <|-- MavlinkConfig
DroneConfig <|-- Px4MavrosConfig
DroneConfig <|-- Px4MavlinkConfig
DroneConfig <|-- Px4DdsConfig
DroneConfig <|-- BebopConfig
Modelo de runtime¶
Cada drone possui seu próprio Node do ROS 2 (criado internamente com um nome sufixado por UUID). Todos os nós de subsistema do SDK são adicionados a um MultiThreadedExecutor compartilhado, gerenciado por nectar.runtime, que gira (spin) em uma thread de fundo. Chamadas bloqueantes (takeoff, land, move_to) dormem na thread do usuário; o executor continua disparando callbacks (state, pose, GPS, lidar, IMU) sem contenção.
Três padrões de uso compartilham as mesmas primitivas:
- Script standalone:
nectar.init()cria o executor compartilhado de forma lazy e inicia a thread de spin.DroneFactory.create("mavros", config)registra o nó do drone nele. Chamenectar.shutdown()ao sair. - Missão Yasmin: chame
nectar.use_executor(YasminNode.get_instance()._executor)uma vez no início. Os subsistemas do SDK criados depois se registram no executor do Yasmin em vez de criar uma segunda thread de spin. - GUI:
ROSExecutor.start()registra seuMultiThreadedExecutoremnectar.runtime. Drones/handlers criados dentro das abas compartilham esse executor automaticamente.
Arquitetura de transporte¶
Os dois firmwares são alcançados por transportes intercambiáveis por meio de um único núcleo. Toda a lógica de voo/navegação vive uma única vez no VehicleDrone, agnóstico a transporte; as especializações de firmware (ArduPilotDrone, Px4Drone) acrescentam somente a semântica do firmware, lendo telemetria e emitindo comandos/setpoints por meio de um VehicleTransport plugável:
MavrosTransport— subscriptions → telemetria, service clients → comandos, publishers → setpoints (exige ummavros_nodeem execução). Compartilhado porMavrosDrone(ArduPilot) ePx4MavrosDrone.PymavlinkTransport— possui o link do FCU diretamente (um timer do ROS drena o RX; comandos/setpoints saem viamav.*_send). Neutro em relação a firmware: umMavlinkModeCodecinjetado isola a única diferença de firmware — encode/decode do modo de voo.ArduPilotModeCodec(padrão,SET_MODE) apoia oMavlinkDrone;Px4ModeCodec(MAV_CMD_DO_SET_MODE) apoia oPx4MavlinkDrone. Indoor (PoseSource.VISION): a pose do companion vem do tópico VSLAM; o feed do FCU é feito porvision_poseexterno por padrão (auto_vision_feedé opt-in). Veja MAVLink transport.Px4DdsTransport— uORB nativo do PX4 pela ponte uXRCE-DDS (px4_msgs), para oPx4DdsDrone. Veja PX4.
Assim o PX4 oferece três backends (px4 = MAVROS, px4_mavlink = MAVLink direto, px4_dds = uXRCE-DDS) e o ArduPilot dois (mavros, mavlink) — todos compartilhando a mesma lógica de voo do Px4Drone / ArduPilotDrone, então as missões são agnósticas ao backend.
O núcleo opera sobre tipos simples, sem ROS; cada transporte converte seus tipos on-wire (mavros_msgs/geometry_msgs, MAVLink puro, ou px4_msgs) de/para esses tipos. ENU/FLU e radianos em todo lugar; os transportes tratam a conversão NED/FRD.
Capacidades¶
Cada drone declara um frozenset[Capability] (veja capabilities.py); consulte com drone.supports(Capability.GPS_NAV). Novos drones declaram o que suportam sobrescrevendo a propriedade capabilities. Operações não suportadas levantam CapabilityNotSupportedError.
Conjuntos declarados por drone (Sim = suportado, — = não suportado):
| Capacidade | ArduPilot (mavros, mavlink) |
PX4 (px4, px4_mavlink, px4_dds) |
Crazyflie | Bebop |
|---|---|---|---|---|
PID_NAV |
Sim | Sim | — | — |
LOCAL_SETPOINT |
Sim | Sim | Sim | — |
VELOCITY_BODY |
Sim | Sim | Sim | Sim |
VELOCITY_WORLD |
Sim | Sim | Sim | — |
VELOCITY_TAKEOFF |
Sim | Sim | Sim | — |
SERVO |
Sim | — | — | — |
ACTUATOR |
Sim | Sim | — | — |
GRIPPER |
Sim | Sim | — | — |
PARAMS |
Sim | Sim | Sim | — |
NATIVE_RTL |
Sim | Sim | — | Sim |
OBSTACLE_AVOIDANCE |
Sim | Sim | — | — |
RANGEFINDER |
Sim | Sim | — | — |
DISTANCE_SENSORS |
Sim | Sim | — | — |
GPS_NAV |
outdoor | outdoor | — | — |
GLOBAL_SETPOINT |
outdoor | outdoor | — | — |
VISION_POSE |
indoor | indoor | — | — |
GPS_NAV/GLOBAL_SETPOINT (outdoor) ou VISION_POSE (indoor) são selecionados a partir de pose_source. O PX4 não tem SERVO (sem PWM por canal via do_servo) mas mantém ACTUATOR (DO_SET_ACTUATOR) e GRIPPER (DO_GRIPPER) para payloads; suas capacidades são idênticas nos três backends PX4.
Componentes principais¶
DroneFactory¶
Instanciação centralizada de drones com registro de tipos.
API:
DroneFactory.create(drone_type: str, config: DroneConfig,
executor: Optional[Executor] = None) -> BaseDrone
DroneFactory.register(drone_type: str, factory_func: Callable)
Tipos suportados:
| Chave | Firmware / plataforma | Transporte | Classe de config |
|---|---|---|---|
mavros |
ArduPilot | MAVROS | MavrosConfig |
mavlink |
ArduPilot | pymavlink direto (sem MAVROS) | MavlinkConfig |
px4 |
PX4 | MAVROS (streaming de setpoint OFFBOARD) | Px4MavrosConfig |
px4_mavlink |
PX4 | pymavlink direto (sem MAVROS) | Px4MavlinkConfig |
px4_dds |
PX4 | uXRCE-DDS nativo (px4_msgs) |
Px4DdsConfig |
bebop |
Parrot Bebop 2 | bebop_driver (ROS) |
BebopConfig |
crazyflie |
Bitcraze Crazyflie | Crazyswarm2 | CrazyflieConfig |
Exemplo:
import nectar
from nectar.control import DroneFactory, MavrosConfig, PoseSource
nectar.init()
config = MavrosConfig(pose_source=PoseSource.VISION)
drone = DroneFactory.create("mavros", config) # optional: executor=<your Executor>
Protocolo Drone¶
Interface com tipagem estrutural (duck-typed) que define o contrato do drone. Todos os drones devem implementar:
Operações principais:
connect(),disconnect(): Gerenciamento de conexãoarm(),disarm(): Controle dos motorestakeoff(),land(): Manobras verticaisemergency_stop(): Parada forçada
Movimento:
move_velocity(): Controle direto de velocidademove_to(): Navegação por posiçãomove_to_gps(): Navegação por waypoint GPSrtl(): Retorno ao ponto de lançamento (return-to-launch)
Estado:
is_ready: status de conexão e do driver (todos os drones)- Drones com FCU (ArduPilot/PX4) também expõem
is_armed,flight_mode,is_fcu_connected(veja Vehicle core); as demais plataformas expõem seus próprios campos de prontidão
BaseDrone¶
Base abstrata que fornece funcionalidade comum.
Responsabilidades:
- Ciclo de vida do driver (start, monitor)
- Gerenciamento de recursos do ROS2 (subscribers, publishers, clients)
- Integração com o obstacle manager
- Utilitário de delay com spin do ROS
Métodos protegidos:
_create_subscriber(),_create_publisher(),_create_client()_init_driver(),check_driver_status(),_wait_for_driver()delay(seconds): Delay não bloqueante
Sistema de configuração¶
Hierarquia de dataclasses com tipagem segura.
MavrosConfig:
MavrosConfig(
pose_source: PoseSource = PoseSource.GPS, # GPS or VISION
expect_lidar: bool = True,
connection_string: str = "serial:///dev/ttyUSB0:921600",
pid_config_file: Optional[str] = None,
local_position_topic: str = "/mavros/local_position/pose",
# ... topic configurations with sensible defaults
)
BebopConfig:
Movimento, navegação, RTL¶
MoveReference seleciona o frame: BODY (relativo ao heading atual), WORLD (frame mundo ENU), TAKEOFF (relativo à pose de takeoff). A API pública de movimento — move_velocity, move_to, move_to_gps, rtl — além dos métodos de navegação (POSITION, POSITION_GLOBAL, PID, PID_EKF), das fontes de altitude, do tratamento de GPS/EGM96 e dos modos de RTL, é definida uma única vez no núcleo compartilhado: veja Vehicle core. Bebop e Crazyflie suportam um subconjunto (veja seus READMEs e a matriz de capacidades acima).
Detecção de obstáculos¶
Detector + estratégia + handler/manager, integrados à navegação via drone.add_obstacle_detector(...). O design completo, os detectores e as estratégias estão documentados em Obstacles.
from nectar.control import DepthObstacleDetector, strategies
drone.add_obstacle_detector("depth", DepthObstacleDetector(), strategies.PauseStrategy())
drone.enable_all_obstacle_detectors()
Controle PID¶
PID de posição por eixo (x/y/z/yaw), carregado do config/*.yaml de cada firmware conforme is_indoor e sobrescritível em runtime via drone.set_pid_config(...). O ciclo de carregamento vive em Vehicle core; o ajuste (tuning) e o schema de config estão em PID.
Exceções¶
DroneError é a exceção base; todo erro de controle levantado pelo SDK a subclassa:
DriverNotFoundError— o executável do driver/bridge não foi encontradoTakeoffPositionNotSetError— um movimento relativo ao takeoff foi solicitado antes da decolagemSensorNotAvailableError— um sensor obrigatório (GPS, vision, rangefinder) está faltandoCapabilityNotSupportedError— o drone não declara aCapabilitysolicitada
Exemplos de uso¶
Missão de waypoints GPS¶
config = MavrosConfig(pose_source=PoseSource.GPS)
drone = DroneFactory.create("mavros", config)
waypoints = [
(-27.1234, -48.4567, 15.0),
(-27.1245, -48.4578, 15.0),
(-27.1256, -48.4589, 15.0)
]
drone.takeoff(altitude=15.0)
for lat, lon, alt in waypoints:
drone.move_to_gps(lat, lon, alt, precision=1.0)
drone.land()
Múltiplos frames de referência¶
Relativo ao corpo (body) — 1m para frente e 0,5m para a esquerda a partir da posição atual:
from nectar.control.types import MoveReference
drone.takeoff(1.5)
drone.move_to(x=1.0, y=0.5, z=0.0, reference=MoveReference.BODY)
Relativo ao takeoff — 2m para frente do ponto de takeoff, depois de volta a ele:
drone.move_to(x=2.0, y=0.0, z=0.0, reference=MoveReference.TAKEOFF)
drone.move_to(x=0.0, y=0.0, z=0.0, reference=MoveReference.TAKEOFF)
Velocidade em frame mundo:
Navegação com desvio de obstáculos¶
from nectar.control import DepthObstacleDetector, strategies
detector = DepthObstacleDetector()
drone.add_obstacle_detector("depth", detector, strategies.PauseStrategy())
drone.enable_obstacle_detector("depth")
drone.takeoff(1.5)
drone.move_to(x=10.0, y=0.0, z=0.0) # Pauses when obstacles detected
drone.land()
Cada submódulo está documentado em seu próprio README, indexado na tabela do Índice da documentação no topo desta página.
Sistema de tipos¶
Enums:
PoseSource: GPS, VISIONMoveReference: BODY, WORLD, TAKEOFFNavigationMethod: POSITION, POSITION_GLOBAL, PID, PID_EKFRTLMethod: NAVIGATE, NATIVEAltitudeSource: AUTO, LIDAR, VISION, REL_ALTObstacleDirection: FRONT, BACK, LEFT, RIGHT, UP, DOWN
Dataclasses:
ObstacleInfo: Resultado da detecção de obstáculo (direção, distância, zona)