use crate::subsystems::commander::Commander;
use crate::subsystems::high_level_commander::HighLevelCommander;
use crate::subsystems::console::Console;
use crate::subsystems::link_service::LinkService;
use crate::subsystems::localization::Localization;
use crate::subsystems::log::Log;
use crate::subsystems::memory::Memory;
use crate::subsystems::param::Param;
use crate::subsystems::supervisor::Supervisor;
use crate::crtp_utils::{CrtpDispatch, TocCache};
use crate::subsystems::platform::Platform;
use crate::{Error, Result};
use crate::{MIN_SUPPORTED_PROTOCOL_VERSION, MAX_SUPPORTED_PROTOCOL_VERSION};
use flume as channel;
use futures::lock::Mutex;
use tokio::task::JoinHandle;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::Arc;
use std::time::Duration;
struct ConnectGuard {
disconnect: Arc<AtomicBool>,
uplink_task: Option<JoinHandle<()>>,
dispatch_task: Option<JoinHandle<()>>,
}
impl ConnectGuard {
fn new(
disconnect: Arc<AtomicBool>,
uplink_task: JoinHandle<()>,
dispatch_task: JoinHandle<()>,
) -> Self {
Self {
disconnect,
uplink_task: Some(uplink_task),
dispatch_task: Some(dispatch_task),
}
}
fn disarm(mut self) -> (JoinHandle<()>, JoinHandle<()>) {
let ut = self.uplink_task.take().unwrap();
let dt = self.dispatch_task.take().unwrap();
(ut, dt)
}
}
impl Drop for ConnectGuard {
fn drop(&mut self) {
if self.uplink_task.is_some() || self.dispatch_task.is_some() {
self.disconnect.store(true, Relaxed);
if let Some(h) = self.uplink_task.take() {
h.abort();
}
if let Some(h) = self.dispatch_task.take() {
h.abort();
}
}
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
enum NrfCommand {
PowerOffAll = 0x01,
PowerOffStm32Domain = 0x02,
PowerOnStm32Domain = 0x03,
}
pub(crate) const CONSOLE_PORT: u8 = 0;
pub(crate) const PARAM_PORT: u8 = 2;
pub(crate) const COMMANDER_PORT: u8 = 3;
pub(crate) const MEMORY_PORT: u8 = 4;
pub(crate) const LOG_PORT: u8 = 5;
pub(crate) const LOCALIZATION_PORT: u8 = 6;
pub(crate) const GENERIC_SETPOINT_PORT: u8 = 7;
pub(crate) const HL_COMMANDER_PORT: u8 = 8;
pub(crate) const SUPERVISOR_PORT: u8 = 9;
pub(crate) const PLATFORM_PORT: u8 = 13;
pub(crate) const LINK_PORT: u8 = 15;
pub struct Crazyflie {
pub log: Log,
pub param: Param,
pub memory: Memory,
pub commander: Commander,
pub high_level_commander: HighLevelCommander,
pub console: Console,
pub localization: Localization,
pub platform: Platform,
pub link_service: LinkService,
pub supervisor: Supervisor,
uplink_task: Mutex<Option<JoinHandle<()>>>,
dispatch_task: Mutex<Option<JoinHandle<()>>>,
disconnect: Arc<AtomicBool>,
link: Arc<crazyflie_link::Connection>,
}
impl Crazyflie {
pub async fn connect_from_uri<T>(
link_context: &crazyflie_link::LinkContext,
uri: &str,
toc_cache: T,
) -> Result<Self>
where
T: TocCache + Send + Sync + 'static,
{
let link = link_context.open_link(uri).await?;
Self::connect_from_link(link, toc_cache).await
}
pub async fn connect_from_link<T>(
link: crazyflie_link::Connection,
toc_cache: T,
) -> Result<Self>
where
T: TocCache + Send + Sync + 'static,
{
let disconnect = Arc::new(AtomicBool::new(false));
let link = Arc::new(link);
let mut dispatcher = CrtpDispatch::new(link.clone(), disconnect.clone());
let disconnect_uplink = disconnect.clone();
let (uplink, rx) = channel::unbounded();
let link_uplink = link.clone();
let uplink_task = tokio::spawn(async move {
while !disconnect_uplink.load(Relaxed) {
match tokio::time::timeout(
Duration::from_millis(100), rx.recv_async()
).await
{
Ok(Ok(pk)) => {
if link_uplink.send_packet(pk).await.is_err() {
return;
}
}
Err(_) => (),
Ok(Err(flume::RecvError::Disconnected)) => return,
}
}
});
let platform_downlink = dispatcher.get_port_receiver(PLATFORM_PORT).unwrap();
let log_downlink = dispatcher.get_port_receiver(LOG_PORT).unwrap();
let param_downlink = dispatcher.get_port_receiver(PARAM_PORT).unwrap();
let console_downlink = dispatcher.get_port_receiver(CONSOLE_PORT).unwrap();
let localization_downlink = dispatcher.get_port_receiver(LOCALIZATION_PORT).unwrap();
let memory_downlink = dispatcher.get_port_receiver(MEMORY_PORT).unwrap();
let link_downlink = dispatcher.get_port_receiver(LINK_PORT).unwrap();
let supervisor_downlink = dispatcher.get_port_receiver(SUPERVISOR_PORT).unwrap();
let dispatch_task = dispatcher.run().await?;
let guard = ConnectGuard::new(disconnect.clone(), uplink_task, dispatch_task);
let platform = Platform::new(uplink.clone(), platform_downlink);
let protocol_version = platform.protocol_version().await?;
if !(MIN_SUPPORTED_PROTOCOL_VERSION..=MAX_SUPPORTED_PROTOCOL_VERSION)
.contains(&protocol_version)
{
return Err(Error::ProtocolVersionNotSupported {
min_supported: MIN_SUPPORTED_PROTOCOL_VERSION,
max_supported: MAX_SUPPORTED_PROTOCOL_VERSION,
found: protocol_version,
});
}
let log_future = Log::new(log_downlink, uplink.clone(), toc_cache.clone());
let param_future = Param::new(param_downlink, uplink.clone(), toc_cache.clone());
let memory_future = Memory::new(memory_downlink, uplink.clone());
let commander = Commander::new(uplink.clone());
let high_level_commander = HighLevelCommander::new(uplink.clone());
let console = Console::new(console_downlink).await?;
let localization = Localization::new(uplink.clone(), localization_downlink);
let link_service = LinkService::new(uplink.clone(), link_downlink, link.clone());
let supervisor = Supervisor::new(uplink.clone(), supervisor_downlink);
let (log, param, memory) = futures::join!(log_future, param_future, memory_future);
let log = log?;
let param = param?;
let memory = memory?;
let (uplink_task, dispatch_task) = guard.disarm();
Ok(Crazyflie {
log,
param,
memory,
commander,
high_level_commander,
console,
localization,
platform,
link_service,
supervisor,
uplink_task: Mutex::new(Some(uplink_task)),
dispatch_task: Mutex::new(Some(dispatch_task)),
disconnect,
link,
})
}
pub async fn disconnect(&self) {
self.disconnect.store(true, Relaxed);
if let Some(uplink_task) = self.uplink_task.lock().await.take() {
uplink_task.await.expect("Uplink task failed");
}
if let Some(dispatch_task) = self.dispatch_task.lock().await.take() {
dispatch_task.await.expect("Dispatcher task failed");
}
self.link.close().await;
}
pub async fn power_off_stm32_domain(link_context: &crazyflie_link::LinkContext, uri: &str) -> Result<()> {
Self::send_nrf_command(link_context, uri, NrfCommand::PowerOffStm32Domain).await
}
pub async fn power_on_stm32_domain(link_context: &crazyflie_link::LinkContext, uri: &str) -> Result<()> {
Self::send_nrf_command(link_context, uri, NrfCommand::PowerOnStm32Domain).await
}
pub async fn power_off_all(link_context: &crazyflie_link::LinkContext, uri: &str) -> Result<()> {
Self::send_nrf_command(link_context, uri, NrfCommand::PowerOffAll).await
}
async fn send_nrf_command(
link_context: &crazyflie_link::LinkContext,
uri: &str,
cmd: NrfCommand,
) -> Result<()> {
const TARGET_NRF51: u8 = 0xFE;
let link = link_context.open_link(uri).await?;
let packet: crazyflie_link::Packet = vec![0xFF, TARGET_NRF51, cmd as u8].into();
link.send_packet(packet).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let _ = link.close().await;
Ok(())
}
pub async fn wait_disconnect(&self) -> String {
let reason = self.link.wait_close().await;
self.disconnect().await;
reason
}
}
impl Drop for Crazyflie {
fn drop(&mut self) {
self.disconnect.store(true, Relaxed);
}
}