blob: 426dbb704167834fece7a1d9225bdbb0859b8549 [file]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use bytes::Bytes;
use futures::{channel::mpsc::UnboundedSender, sink::SinkExt, StreamExt};
use lazy_static::lazy_static;
use pica::{Handle, Pica};
use netsim_proto::model::chip::Radio as ProtoRadio;
use netsim_proto::model::Chip as ProtoChip;
use netsim_proto::stats::{netsim_radio_stats, NetsimRadioStats as ProtoRadioStats};
use crate::devices::chip::ChipIdentifier;
use crate::uwb::ranging_estimator::{SharedState, UwbRangingEstimator};
use crate::wireless::packet::handle_response;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;
use super::{WirelessAdaptor, WirelessAdaptorImpl};
// TODO(b/331267949): Construct Manager struct for each wireless_adaptor module
lazy_static! {
static ref PICA_HANDLE_TO_STATE: SharedState = SharedState::new();
static ref PICA: Arc<Mutex<Pica>> = Arc::new(Mutex::new(Pica::new(
Box::new(UwbRangingEstimator::new(PICA_HANDLE_TO_STATE.clone())),
None
)));
static ref PICA_RUNTIME: Arc<tokio::runtime::Runtime> =
Arc::new(tokio::runtime::Runtime::new().unwrap());
}
/// Parameters for creating UWB chips
pub struct CreateParams {
pub address: String,
}
/// UWB struct will keep track of pica_id
pub struct Uwb {
pica_id: Handle,
uci_stream_writer: UnboundedSender<Vec<u8>>,
state: AtomicBool,
tx_count: AtomicI32,
rx_count: Arc<AtomicI32>,
}
impl Drop for Uwb {
fn drop(&mut self) {
PICA_HANDLE_TO_STATE.remove(&self.pica_id);
}
}
impl WirelessAdaptor for Uwb {
fn handle_request(&self, packet: &Bytes) {
// TODO(b/330788870): Increment tx_count
self.uci_stream_writer
.unbounded_send(packet.clone().into())
.expect("UciStream Receiver Disconnected");
let _ = self.tx_count.fetch_add(1, Ordering::SeqCst);
}
fn reset(&self) {
self.state.store(true, Ordering::SeqCst);
self.tx_count.store(0, Ordering::SeqCst);
self.rx_count.store(0, Ordering::SeqCst);
}
fn get(&self) -> ProtoChip {
let mut chip_proto = ProtoChip::new();
let uwb_proto = ProtoRadio {
state: self.state.load(Ordering::SeqCst).into(),
tx_count: self.tx_count.load(Ordering::SeqCst),
rx_count: self.rx_count.load(Ordering::SeqCst),
..Default::default()
};
chip_proto.mut_uwb().clone_from(&uwb_proto);
chip_proto
}
fn patch(&self, chip: &ProtoChip) {
if !chip.has_uwb() {
return;
}
if let Some(patch_state) = chip.uwb().state {
self.state.store(patch_state, Ordering::SeqCst);
}
}
fn get_stats(&self, duration_secs: u64) -> Vec<ProtoRadioStats> {
let mut stats_proto = ProtoRadioStats::new();
stats_proto.set_duration_secs(duration_secs);
stats_proto.set_kind(netsim_radio_stats::Kind::UWB);
let chip_proto = self.get();
if chip_proto.has_uwb() {
stats_proto.set_tx_count(chip_proto.uwb().tx_count);
stats_proto.set_rx_count(chip_proto.uwb().rx_count);
}
vec![stats_proto]
}
}
pub fn uwb_start() {
// TODO: Provide TcpStream as UWB connector
let _ = thread::Builder::new().name("pica_service".to_string()).spawn(move || {
log::info!("PICA STARTED");
let _guard = PICA_RUNTIME.enter();
futures::executor::block_on(pica::run(&PICA))
});
}
pub fn new(_create_params: &CreateParams, chip_id: ChipIdentifier) -> WirelessAdaptorImpl {
let (uci_stream_sender, uci_stream_receiver) = futures::channel::mpsc::unbounded();
let (uci_sink_sender, uci_sink_receiver) = futures::channel::mpsc::unbounded();
let _guard = PICA_RUNTIME.enter();
let pica_id = PICA
.lock()
.unwrap()
.add_device(Box::pin(uci_stream_receiver), Box::pin(uci_sink_sender.sink_err_into()))
.unwrap();
PICA_HANDLE_TO_STATE.insert(pica_id, chip_id);
let rx_count = Arc::new(AtomicI32::new(0));
let uwb = Uwb {
pica_id,
uci_stream_writer: uci_stream_sender,
state: AtomicBool::new(true),
tx_count: AtomicI32::new(0),
rx_count: rx_count.clone(),
};
// Spawn a future for obtaining packet from pica and invoking handle_response_rust
PICA_RUNTIME.spawn(async move {
let mut uci_sink_receiver = uci_sink_receiver;
while let Some(packet) = uci_sink_receiver.next().await {
handle_response(chip_id, &Bytes::from(packet));
rx_count.fetch_add(1, Ordering::SeqCst);
}
});
Box::new(uwb)
}
#[cfg(test)]
mod tests {
use super::*;
fn new_uwb_wireless_adaptor() -> WirelessAdaptorImpl {
new(&CreateParams { address: "test".to_string() }, ChipIdentifier(0))
}
fn patch_chip_proto() -> ProtoChip {
let mut chip_proto = ProtoChip::new();
let uwb_proto = ProtoRadio { state: false.into(), ..Default::default() };
chip_proto.mut_uwb().clone_from(&uwb_proto);
chip_proto
}
#[test]
fn test_uwb_get() {
let wireless_adaptor = new_uwb_wireless_adaptor();
assert!(wireless_adaptor.get().has_uwb());
}
#[test]
fn test_uwb_patch_and_reset() {
let wireless_adaptor = new_uwb_wireless_adaptor();
wireless_adaptor.patch(&patch_chip_proto());
let binding = wireless_adaptor.get();
let radio = binding.uwb();
assert_eq!(radio.state, Some(false));
wireless_adaptor.reset();
let binding = wireless_adaptor.get();
let radio = binding.uwb();
assert_eq!(radio.rx_count, 0);
assert_eq!(radio.tx_count, 0);
assert_eq!(radio.state, Some(true));
}
#[test]
fn test_get_stats() {
let wireless_adaptor = new_uwb_wireless_adaptor();
let radio_stat_vec = wireless_adaptor.get_stats(0);
let radio_stat = radio_stat_vec.first().unwrap();
assert_eq!(radio_stat.kind(), netsim_radio_stats::Kind::UWB);
assert_eq!(radio_stat.duration_secs(), 0);
}
}