1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use anyhow::Result;
16 use clap::Parser;
17 use env_logger::Env;
18 use pica::{Pica, PicaCommand};
19 use std::net::{Ipv4Addr, SocketAddrV4};
20 use std::path::PathBuf;
21 use tokio::net::TcpListener;
22 use tokio::sync::mpsc;
23 use tokio::try_join;
24 
25 const DEFAULT_UCI_PORT: u16 = 7000;
26 
accept_incoming(cmd_tx: mpsc::Sender<PicaCommand>, uci_port: u16) -> Result<()>27 async fn accept_incoming(cmd_tx: mpsc::Sender<PicaCommand>, uci_port: u16) -> Result<()> {
28     let uci_socket = SocketAddrV4::new(Ipv4Addr::LOCALHOST, uci_port);
29     let uci_listener = TcpListener::bind(uci_socket).await?;
30     log::info!("Pica: Listening on: {}", uci_port);
31 
32     loop {
33         let (socket, addr) = uci_listener.accept().await?;
34         log::info!("Uwb host addr: {}", addr);
35 
36         let (read_half, write_half) = socket.into_split();
37         let stream = Box::pin(futures::stream::unfold(read_half, pica::packets::uci::read));
38         let sink = Box::pin(futures::sink::unfold(write_half, pica::packets::uci::write));
39 
40         cmd_tx
41             .send(PicaCommand::Connect(stream, sink))
42             .await
43             .map_err(|_| anyhow::anyhow!("pica command stream closed"))?
44     }
45 }
46 
47 #[derive(Parser, Debug)]
48 #[command(name = "pica", about = "Virtual UWB subsystem")]
49 struct Args {
50     /// Output directory for storing .pcapng traces.
51     /// If provided, .pcapng traces of client connections are automatically
52     /// saved under the name `device-{handle}.pcapng`.
53     #[arg(short, long, value_name = "PCAPNG_DIR")]
54     pcapng_dir: Option<PathBuf>,
55     /// Configure the TCP port for the UCI server.
56     #[arg(short, long, value_name = "UCI_PORT", default_value_t = DEFAULT_UCI_PORT)]
57     uci_port: u16,
58 }
59 
60 struct MockRangingEstimator();
61 
62 /// The position cannot be communicated to the pica environment when
63 /// using the default binary (HTTP interface not available).
64 /// Thus the ranging estimator cannot produce any result.
65 impl pica::RangingEstimator for MockRangingEstimator {
estimate( &self, _left: &pica::Handle, _right: &pica::Handle, ) -> Option<pica::RangingMeasurement>66     fn estimate(
67         &self,
68         _left: &pica::Handle,
69         _right: &pica::Handle,
70     ) -> Option<pica::RangingMeasurement> {
71         Some(Default::default())
72     }
73 }
74 
75 #[tokio::main]
main() -> Result<()>76 async fn main() -> Result<()> {
77     env_logger::Builder::from_env(Env::default().default_filter_or("debug")).init();
78 
79     let args = Args::parse();
80 
81     let pica = Pica::new(Box::new(MockRangingEstimator()), args.pcapng_dir);
82     let commands = pica.commands();
83 
84     try_join!(accept_incoming(commands.clone(), args.uci_port), pica.run(),)?;
85 
86     Ok(())
87 }
88