blob: fb4a91de9ef3f4c8141f8ecae9699496d2bb1f83 [file]
// Copyright 2024 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 log::{debug, error};
use netsim_proto::model::Device as ProtoDevice;
use pica::{Handle, RangingEstimator, RangingMeasurement};
use crate::devices::{chip::ChipIdentifier, devices_handler::get_device};
use crate::ranging::{compute_range_azimuth_elevation, Pose};
use crate::uwb::ranging_data::RangingDataSet;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, MutexGuard};
// Initially a single HashMap might grow into a Struct to allow
// for more fields.
type State = HashMap<Handle, ChipIdentifier>;
#[derive(Clone)]
pub struct SharedState(Arc<Mutex<State>>);
impl SharedState {
pub fn new() -> Self {
SharedState(Arc::new(Mutex::new(State::new())))
}
fn lock(&self) -> MutexGuard<State> {
self.0.lock().expect("Poisoned SharedState lock")
}
pub fn get_chip_id(&self, pica_id: &Handle) -> anyhow::Result<ChipIdentifier> {
self.lock().get(pica_id).ok_or(anyhow::anyhow!("pica_id: {pica_id} not in State")).cloned()
}
pub fn insert(&self, pica_id: Handle, chip_id: ChipIdentifier) {
self.lock().insert(pica_id, chip_id);
}
pub fn remove(&self, pica_id: &Handle) {
self.lock().remove(pica_id);
}
}
// Netsim's UwbRangingEstimator
pub struct UwbRangingEstimator {
shared_state: SharedState,
data_set: RangingDataSet,
}
impl UwbRangingEstimator {
pub fn new(shared_state: SharedState) -> Self {
UwbRangingEstimator { shared_state, data_set: RangingDataSet::new(None) }
}
// Utility to convert the UWB Chip handle into the device and chip_id.
fn handle_to_netsim_model(&self, handle: &Handle) -> Option<(ChipIdentifier, ProtoDevice)> {
let chip_id = self.shared_state.get_chip_id(handle).map_err(|e| debug!("{e:?}")).ok()?;
let device = get_device(&chip_id).map_err(|e| debug!("{e:?}")).ok()?;
Some((chip_id, device))
}
}
impl RangingEstimator for UwbRangingEstimator {
fn estimate(&self, a: &Handle, b: &Handle) -> Option<RangingMeasurement> {
// Use the Handle to obtain the positions and orientation information in netsim
// and perform compute_range_azimuth_elevation
let (a_chip_id, a_device) = self.handle_to_netsim_model(a)?;
let (b_chip_id, b_device) = self.handle_to_netsim_model(b)?;
// Chips are invisible at the PHY layer when uwb is disabled so test here.
// Note, chips must always process Host-Controller messages even when
// state-off to avoid protocol stack timeouts and other glitches.
if !is_uwb_state_on(&a_device, &a_chip_id) || !is_uwb_state_on(&b_device, &b_chip_id) {
return None;
}
let (a_p, a_o) = (a_device.position, a_device.orientation);
let (b_p, b_o) = (b_device.position, b_device.orientation);
let a_pose = Pose::new(a_p.x, a_p.y, a_p.z, a_o.yaw, a_o.pitch, a_o.roll);
let b_pose = Pose::new(b_p.x, b_p.y, b_p.z, b_o.yaw, b_o.pitch, b_o.roll);
compute_range_azimuth_elevation(&a_pose, &b_pose)
.map(|(range, azimuth, elevation)| RangingMeasurement {
range: self.data_set.sample(range, None).round() as u16,
azimuth,
elevation,
})
.map_err(|e| error!("{e:?}"))
.ok()
}
}
/// With given ProtoDevice and ChipIdentifier, return true if state of a UWB chip
/// with ChipIdentifier inside ProtoDevice is ON. Otherwise, return false.
fn is_uwb_state_on(device: &ProtoDevice, chip_id: &ChipIdentifier) -> bool {
for chip in &device.chips {
if chip.has_uwb() && chip.id == chip_id.0 {
return chip.uwb().state.unwrap_or(false);
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use netsim_proto::model::chip::Radio as ProtoRadio;
use netsim_proto::model::Chip as ProtoChip;
fn create_proto_device_with_uwb_chip(uwb_state: bool) -> (ProtoDevice, ChipIdentifier) {
// Setting Radio State to true
let mut proto_radio = ProtoRadio::new();
proto_radio.state = Some(uwb_state);
// Setting UWB ProtoChip
let mut proto_chip = ProtoChip::new();
proto_chip.set_uwb(proto_radio);
let chip_id = proto_chip.id;
// Adding chip to proto_device
let mut proto_device = ProtoDevice::new();
proto_device.chips.push(proto_chip);
// Return proto_device
(proto_device, ChipIdentifier(chip_id))
}
#[test]
fn test_is_uwb_state_on() {
// False when no uwb chip is present in ProtoDevice
assert!(!is_uwb_state_on(&ProtoDevice::new(), &ChipIdentifier(0)));
// Check if UWB State is on
let (proto_device, chip_id) = create_proto_device_with_uwb_chip(true);
assert!(is_uwb_state_on(&proto_device, &chip_id));
// Check if UWB State is off
let (proto_device, chip_id) = create_proto_device_with_uwb_chip(false);
assert!(!is_uwb_state_on(&proto_device, &chip_id));
}
}