diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index c224c5e..9fbf876 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,14 +4,16 @@ use std::path::Path; use std::sync::Arc; use std::sync::Mutex; +use std::time::Duration; use std::{thread, time}; use tokio::runtime::Runtime; use crazyflie_lib::Crazyflie; use futures::StreamExt; use tauri::Manager; -use tokio::sync::mpsc; - +//use tokio::sync::mpsc; +use flume as mpsc; +use futures_util::task::SpawnExt; use ts_rs::TS; #[tauri::command] @@ -31,7 +33,7 @@ async fn connect(uri: &str, state: tauri::State<'_, BackendState>) -> Result<(), state .cmd .send(CrazyflieBackendCommand::Connect(uri.to_string())) - .await + .map_err(|e| format!("{:?}", e))?; Ok(()) } @@ -41,7 +43,7 @@ async fn disconnect(state: tauri::State<'_, BackendState>) -> Result<(), String> state .cmd .send(CrazyflieBackendCommand::Disconnect) - .await + .map_err(|e| format!("{:?}", e))?; Ok(()) } @@ -52,7 +54,7 @@ async fn start_scan(address: &str, state: tauri::State<'_, BackendState>) -> Res state .cmd .send(CrazyflieBackendCommand::StartScan(address.to_string())) - .await + .map_err(|e| format!("{:?}", e))?; Ok(()) } @@ -63,7 +65,7 @@ async fn stop_scan(state: tauri::State<'_, BackendState>) -> Result<(), String> state .cmd .send(CrazyflieBackendCommand::StopScan) - .await + .map_err(|e| format!("{:?}", e))?; Ok(()) } @@ -94,20 +96,26 @@ enum CrazyflieBackendState { } #[derive(Debug, serde::Serialize, TS)] -#[ts(export, export_to = "../src/backend-interface.ts")] +#[ts(export, export_to = "../src/interface/scan_response.ts")] struct ScanResponse { uris: Vec, err: Option, } #[derive(Debug, serde::Serialize, TS)] -#[ts(export, export_to = "../src/backend-interface.ts")] +#[ts(export, export_to = "../src/interface/connected_event.ts")] struct ConnectedEvent { connected: bool, uri: String, err: Option, } +#[derive(Debug, serde::Serialize, TS)] +#[ts(export, export_to = "../src/interface/console_event.ts")] +struct ConsoleEvent { + message: String, +} + async fn crazyflie_backend_scan( mut ui_to_cf_rx: mpsc::Receiver, address: String, @@ -117,33 +125,34 @@ async fn crazyflie_backend_scan( //let mut ui_to_cf_rx_inner = ui_to_cf_rx.unwrap(); loop { println!("Looping around to scan again!"); - tokio::select! { - found = link_context.scan([0xE7; 5]) => { - println!("Scanning for Crazyflies on address {}", address); - match found { - Ok(uris) => { - let response = ScanResponse { uris, err: None }; - t.emit_all("scan", &response).unwrap(); - } - Err(e) => { - println!("Error scanning for Crazyflies: {:?}", e); - let response = ScanResponse { uris: vec![], err: Some(e.to_string()) }; - t.emit_all("scan", &response).unwrap(); - break; - } - } + println!("Scanning for Crazyflies on address {}", address); + match link_context.scan([0xE7; 5]).await { + Ok(uris) => { + let response = ScanResponse { uris, err: None }; + t.emit_all("scan", &response).unwrap(); } - Some(input) = ui_to_cf_rx.recv() => { - match input { - CrazyflieBackendCommand::StopScan => { - break; - } - _ => { - println!("Error: Received CF command: {:?} in scan state!", input); - } - } + Err(e) => { + println!("Error scanning for Crazyflies: {:?}", e); + let response = ScanResponse { uris: vec![], err: Some(e.to_string()) }; + t.emit_all("scan", &response).unwrap(); + break; } + } + + match ui_to_cf_rx.try_recv() { + Ok(input) => { + match input { + CrazyflieBackendCommand::StopScan => { + println!("Will be exiting scan mode!"); + break; + } + _ => { + println!("Error: Received CF command: {:?} in scan state!", input); + } + } } + _ => {} + } } println!("Exiting scan mode!"); @@ -157,44 +166,69 @@ async fn crazyflie_backend_connected( ) -> mpsc::Receiver { let link_context = crazyflie_link::LinkContext::new(async_executors::AsyncStd); - let cf = crazyflie_lib::Crazyflie::connect_from_uri( - async_executors::AsyncStd, - &link_context, - uri.as_str(), - ) - .await.unwrap(); - - // match cf { - // Ok(cf) => { - // println!("Connected to Crazyflie on {}", uri); - // let response = ConnectedEvent { - // connected: true, - // uri, - // err: None, - // }; - // t.emit_all("connected", &response).unwrap(); - - // let mut console_stream = cf.console.line_stream().await; + println!("Calling connect now on {}!", uri); + + let cf = async_executors::AsyncStd::block_on(async { + crazyflie_lib::Crazyflie::connect_from_uri(async_executors::AsyncStd, &link_context, uri.as_str()) + .await + }); + + match cf { + Ok(cf) => { + println!("Connected to Crazyflie on {}", uri); + let response = ConnectedEvent { + connected: true, + uri, + err: None, + }; + t.emit_all("connected", &response).unwrap(); + + let (tx, rx) = flume::unbounded::(); + + std::thread::spawn( move || { + while let Ok(line) = rx.recv() { + let response = ConsoleEvent { message: line.clone()}; + t.emit_all("console", &response).unwrap(); + println!("Received: {}", line); + } + }); + + println!("Starting console"); + + //async_executors::AsyncStd::block_on(async { + let mut stream = cf.console.line_stream().await; + + async_executors::AsyncStd.spawn(async move { + while let Some(line) = stream.next().await { + tx.send_async(line).await.expect("Failed to send line"); + } + println!("Exiting console stream loop"); + }).unwrap(); + + //}).unwrap(); + + println!("Starting to sleep a bit"); + + async_std::task::sleep(Duration::from_secs(10)).await; + + println!("Have Slept, waiting for disconnect"); + + cf.wait_disconnect().await; + + println!("Exiting main connected loop"); + - // } - // Err(e) => { - // println!("Error connecting to Crazyflie on {}: {:?}", uri, e); - // let response = ConnectedEvent { - // connected: false, - // uri, - // err: Some(e.to_string()), - // }; - // t.emit_all("connected", &response).unwrap(); - // } - // } - - //let mut console_stream = cf.console.line_stream().await; - - // tokio::select! { - // Some(line) = console_stream.next() => { - // println!("Console line: {:?}", line); - // } - // } + } + Err(e) => { + println!("Error connecting to Crazyflie on {}: {:?}", uri, e); + let response = ConnectedEvent { + connected: false, + uri, + err: Some(e.to_string()), + }; + t.emit_all("connected", &response).unwrap(); + } + } ui_to_cf_rx } @@ -212,17 +246,17 @@ async fn crazyflie_backend( // Connected -> Idle // if state - let cmd = ui_to_cf_rx.recv().await; + let cmd = ui_to_cf_rx.recv(); match cmd { - Some(output) => match output { + Ok(output) => match output { CrazyflieBackendCommand::StartScan(address) => { println!("Start scanning for Crazyflies on address {}", address); ui_to_cf_rx = crazyflie_backend_scan(ui_to_cf_rx, address.to_string(), t.clone()).await; } CrazyflieBackendCommand::Connect(uri) => { - println!("Con to Crazyflie on {}", uri); + println!("Connect to Crazyflie on {}", uri); ui_to_cf_rx = crazyflie_backend_connected(ui_to_cf_rx, uri.to_string(), t.clone()).await; } @@ -230,8 +264,8 @@ async fn crazyflie_backend( println!("Error: Received CF command: {:?} in main state!", output); } }, - None => { - println!("Error: Received None from ui_to_cf_rx in main state!"); + Err(e) => { + println!("Error receiving CF command: {:?}", e); } } // tokio::select! { @@ -246,7 +280,7 @@ async fn crazyflie_backend( } fn main() { - let (ui_to_cf_tx, ui_to_cf_rx) = mpsc::channel::(1); + let (ui_to_cf_tx, ui_to_cf_rx) = mpsc::unbounded(); tauri::Builder::default() .manage(BackendState::new(ui_to_cf_tx)) diff --git a/src/App.tsx b/src/App.tsx index abe2041..a10b031 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,6 +18,8 @@ import { Sidebar, Menu, MenuItem, SubMenu } from 'react-pro-sidebar'; import 'bootstrap/dist/css/bootstrap.min.css'; import ConnectionModal from "./components/connection_modal"; +import ConsoleTab from "./components/console_tab"; +import BackEnd from './backend'; function App() { const [greetMsg, setGreetMsg] = useState(""); @@ -40,7 +42,7 @@ function App() { }> } /> } /> - } /> + } /> {/* Using path="*"" means "match anything", so this route acts like a catch-all for URLs that we don't have explicit @@ -48,6 +50,7 @@ function App() { } /> + ); } @@ -132,14 +135,6 @@ function Fly() { ); } -function Console() { - return ( -
-
Console output right here
-
- ); -} - function NoMatch() { return (
diff --git a/src/backend.tsx b/src/backend.tsx new file mode 100644 index 0000000..51130f8 --- /dev/null +++ b/src/backend.tsx @@ -0,0 +1,47 @@ +import { useEffect, useContext } from 'react'; +import { listen } from "@tauri-apps/api/event"; +import Button from 'react-bootstrap/Button'; +import Modal from 'react-bootstrap/Modal'; +import Table from 'react-bootstrap/Table'; + +import { toast } from 'react-toastify'; + +import Container from 'react-bootstrap/Container'; +import Row from 'react-bootstrap/Row'; +import Col from 'react-bootstrap/Col'; + +import { store } from './store.js'; + +import { ConsoleEvent } from './interface/console_event'; + +interface ConsoleEventMessage { + payload: ConsoleEvent; +} + +function BackEnd() { + const { state, dispatch } = useContext(store); + + const changeColor = () => { + dispatch({ type: "CHANGE_COLOR", payload: "blue" }); + }; + + useEffect(() => { + console.log("Backend loaded"); + const unListen = listen("console", (e: ConsoleEventMessage) => { + //setConsoleText(consoleText + e.payload.message + "\n"); + console.log("Got console stuff off backend") + dispatch({ type: "CONSOLE", payload: e.payload.message }); + + }); + + return () => { + console.log("Backend unloaded"); + unListen.then((f) => f()); + }; + }, []); + + + return null; +} + +export default BackEnd; \ No newline at end of file diff --git a/src/components/connection_modal.tsx b/src/components/connection_modal.tsx index bbef8cf..e735160 100644 --- a/src/components/connection_modal.tsx +++ b/src/components/connection_modal.tsx @@ -14,7 +14,7 @@ import 'bootstrap/dist/css/bootstrap.min.css'; import { invoke } from "@tauri-apps/api/tauri"; -import { ScanResponse } from '../backend-interface'; +import { ScanResponse } from '../interface/scan_response'; // interface ProgressEventPayload { diff --git a/src/components/console_tab.tsx b/src/components/console_tab.tsx new file mode 100644 index 0000000..7569dc3 --- /dev/null +++ b/src/components/console_tab.tsx @@ -0,0 +1,33 @@ +import { useContext, useState } from 'react'; +import { listen } from "@tauri-apps/api/event"; +import Button from 'react-bootstrap/Button'; +import Modal from 'react-bootstrap/Modal'; +import Table from 'react-bootstrap/Table'; + +import { toast } from 'react-toastify'; + +import Container from 'react-bootstrap/Container'; +import Row from 'react-bootstrap/Row'; +import Col from 'react-bootstrap/Col'; + +import { store } from './../store.js'; + +import { ConsoleEvent } from '../interface/console_event'; + +interface ConsoleEventMessage { + payload: ConsoleEvent; +} + +function ConsoleTab() { + const [consoleText, setConsoleText] = useState(""); + + const { state, dispatch } = useContext(store); + + return ( +
+
{state.console}
+
+ ); +} + +export default ConsoleTab; \ No newline at end of file diff --git a/src/interface/connected_event.ts b/src/interface/connected_event.ts new file mode 100644 index 0000000..51f007d --- /dev/null +++ b/src/interface/connected_event.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface ConnectedEvent { connected: boolean, uri: string, err: string | null, } \ No newline at end of file diff --git a/src/interface/console_event.ts b/src/interface/console_event.ts new file mode 100644 index 0000000..8750d99 --- /dev/null +++ b/src/interface/console_event.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface ConsoleEvent { message: string, } \ No newline at end of file diff --git a/src/backend-interface.ts b/src/interface/scan_response.ts similarity index 100% rename from src/backend-interface.ts rename to src/interface/scan_response.ts diff --git a/src/main.tsx b/src/main.tsx index cdbf648..9eef99b 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,11 +3,14 @@ import ReactDOM from "react-dom/client"; import App from "./App"; import "./styles.css"; import { BrowserRouter } from "react-router-dom"; +import { StateProvider } from "./store"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + + + ); diff --git a/src/store.tsx b/src/store.tsx new file mode 100644 index 0000000..6caac54 --- /dev/null +++ b/src/store.tsx @@ -0,0 +1,24 @@ +// store.js +import { createContext, useReducer } from "react"; +const initialState = { + color: "red", + console: "", +}; + +const store = createContext(initialState); +const { Provider } = store; + +const StateProvider = ({ children }) => { + const [state, dispatch] = useReducer((state, action) => { + switch (action.type) { + case "CHANGE_COLOR": + return { ...state, color: action.payload }; + case "CONSOLE": + return { ...state, console: state.console + action.payload + "\n" }; + default: + throw new Error(); + } + }, initialState); + return {children}; +}; +export { store, StateProvider }; \ No newline at end of file