From 0b0b7aaee6d4da9a661dfbb98adf86761b43c095 Mon Sep 17 00:00:00 2001 From: Sameer Puri Date: Tue, 27 Oct 2026 16:08:08 -0700 Subject: [PATCH] Respect AOA_RESULT_REQ and normalize SESSION_INFO_NTF_CONFIG While PR #144 added DL-TDoA measurement generation alongside Two-Way Ranging, Pica populated local azimuth and elevation AoA fields across TWR and DL-TDoA measurements even when disabled by AOA_RESULT_REQ. Pica also evaluated AoA notification bounds on disabled axes and ignored FiRa Note 2 notification filtering rules under disabled AoA. Zero disabled local AoA angles and figures of merit while preserving aoa_destination_* fields. Normalize SESSION_INFO_NTF_CONFIG via effective_session_info_ntf_config() according to FiRa Table 28 / Table 38 Note 2, and restrict AOA_BOUND_CONFIG filtering to enabled AoA axes. Closes #147 --- py/pica/pica/packets/uci.py | 15 -- src/app_config.rs | 58 +++++- src/lib.rs | 133 +++++++----- src/uci_packets.pdl | 11 - tests/ranging.py | 402 ++++++++++++++++++++++++++++++++++++ 5 files changed, 533 insertions(+), 86 deletions(-) diff --git a/py/pica/pica/packets/uci.py b/py/pica/pica/packets/uci.py index 3de0d60..5457496 100644 --- a/py/pica/pica/packets/uci.py +++ b/py/pica/pica/packets/uci.py @@ -882,21 +882,6 @@ def from_int(v: int) -> Union[int, 'CapTlvType']: return v -class AoaResultReqType(enum.IntEnum): - AOA_DISABLE = 0x0 - AOA_ENABLE = 0x1 - AOA_ENABLE_AZIMUTH = 0x2 - AOA_ENABLE_ELEVATION = 0x3 - AOA_ENABLE_INTERLEAVED = 0xf0 - - @staticmethod - def from_int(v: int) -> Union[int, 'AoaResultReqType']: - try: - return AoaResultReqType(v) - except ValueError as exn: - raise exn - - class DeviceState(enum.IntEnum): DEVICE_STATE_READY = 0x1 DEVICE_STATE_ACTIVE = 0x2 diff --git a/src/app_config.rs b/src/app_config.rs index 63cdf81..e8392a9 100644 --- a/src/app_config.rs +++ b/src/app_config.rs @@ -127,6 +127,7 @@ const DEFAULT_DL_TDOA_ANCHOR_CFO: uci::DlTdoaAnchorCfo = uci::DlTdoaAnchorCfo::A const DEFAULT_DL_TDOA_TX_ACTIVE_RANGING_ROUNDS: bool = false; const DEFAULT_DL_TDOA_TIME_REFERENCE_ANCHOR: bool = false; const DEFAULT_DL_TDOA_RESPONDER_TOF: bool = false; +const DEFAULT_AOA_RESULT_REQ: uci::AoaResultReq = uci::AoaResultReq::AoaEnabled; const DEFAULT_AOA_BOUND_CONFIG: [u8; 8] = [0x00, 0xa6, 0x00, 0x5a, 0x00, 0xd3, 0x00, 0x2d]; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -138,9 +139,20 @@ struct AoaBounds { } impl AoaBounds { - fn contains(&self, azimuth: i16, elevation: i16) -> bool { - (self.min_azimuth..=self.max_azimuth).contains(&azimuth) - && (self.min_elevation..=self.max_elevation).contains(&elevation) + fn contains(&self, azimuth: i16, elevation: i16, req: uci::AoaResultReq) -> bool { + match req { + uci::AoaResultReq::AoaDisabled => false, + uci::AoaResultReq::AoaEnabled => { + (self.min_azimuth..=self.max_azimuth).contains(&azimuth) + && (self.min_elevation..=self.max_elevation).contains(&elevation) + } + uci::AoaResultReq::AoaEnabledAzimuthOnly => { + (self.min_azimuth..=self.max_azimuth).contains(&azimuth) + } + uci::AoaResultReq::AoaEnabledElevationOnly => { + (self.min_elevation..=self.max_elevation).contains(&elevation) + } + } } } const DEFAULT_CCC_TLVS: [(uci::AppConfigTlvType, Bytes); 5] = [ @@ -258,7 +270,7 @@ impl AppConfig { ), ( uci::AppConfigTlvType::AoaResultReq, - Bytes::from_static(&[uci::AoaResultReq::AoaEnabled as u8]), + Bytes::from_static(&[DEFAULT_AOA_RESULT_REQ as u8]), ), ( uci::AppConfigTlvType::RframeConfig, @@ -733,6 +745,11 @@ impl AppConfig { .unwrap_or(DEFAULT_SUSPEND_RANGING_ROUNDS) } + pub fn aoa_result_req(&self) -> uci::AoaResultReq { + self.get_enum(uci::AppConfigTlvType::AoaResultReq) + .unwrap_or(DEFAULT_AOA_RESULT_REQ) + } + pub fn set_number_of_controlees(&mut self, value: u8) { self.tlvs.insert( uci::AppConfigTlvType::NumberOfControlees, @@ -779,9 +796,27 @@ impl AppConfig { .unwrap_or(DEFAULT_SLOTS_PER_RR) } - pub fn session_info_ntf_config(&self) -> uci::SessionInfoNtfConfig { - self.get_enum(uci::AppConfigTlvType::SessionInfoNtfConfig) - .unwrap_or(DEFAULT_SESSION_INFO_NTF_CONFIG) + pub fn effective_session_info_ntf_config(&self) -> uci::SessionInfoNtfConfig { + let config = self + .get_enum(uci::AppConfigTlvType::SessionInfoNtfConfig) + .unwrap_or(DEFAULT_SESSION_INFO_NTF_CONFIG); + if self.aoa_result_req() == uci::AoaResultReq::AoaDisabled { + match config { + uci::SessionInfoNtfConfig::EnableAoaTrigger + | uci::SessionInfoNtfConfig::EnableAoaEdgeTrigger => { + uci::SessionInfoNtfConfig::Disable + } + uci::SessionInfoNtfConfig::EnableProximityAoaTrigger => { + uci::SessionInfoNtfConfig::EnableProximityTrigger + } + uci::SessionInfoNtfConfig::EnableProximityAoaEdgeTrigger => { + uci::SessionInfoNtfConfig::EnableProximityEdgeTrigger + } + _ => config, + } + } else { + config + } } pub fn in_band_termination_attempt_count(&self) -> u8 { @@ -925,8 +960,10 @@ impl AppConfig { pub fn is_within_bounds(&self, distance: u16, azimuth: i16, elevation: i16) -> bool { let within_proximity = (self.near_proximity_config()..=self.far_proximity_config()).contains(&distance); - let within_aoa = self.aoa_bound_config().contains(azimuth, elevation); - match self.session_info_ntf_config() { + let within_aoa = + self.aoa_bound_config() + .contains(azimuth, elevation, self.aoa_result_req()); + match self.effective_session_info_ntf_config() { uci::SessionInfoNtfConfig::EnableProximityTrigger | uci::SessionInfoNtfConfig::EnableProximityEdgeTrigger => within_proximity, uci::SessionInfoNtfConfig::EnableAoaTrigger @@ -935,7 +972,8 @@ impl AppConfig { | uci::SessionInfoNtfConfig::EnableProximityAoaEdgeTrigger => { within_proximity && within_aoa } - uci::SessionInfoNtfConfig::Enable | uci::SessionInfoNtfConfig::Disable => true, + uci::SessionInfoNtfConfig::Enable => true, + uci::SessionInfoNtfConfig::Disable => false, } } diff --git a/src/lib.rs b/src/lib.rs index 0d34eee..9a253d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,6 +147,17 @@ const fn degrees_to_q9_7(deg: i16) -> i16 { deg.saturating_mul(128) } +fn encode_aoa(measurement: RangingMeasurement, req: uci::AoaResultReq) -> (u16, u8, u16, u8) { + let azimuth = degrees_to_q9_7(measurement.azimuth) as u16; + let elevation = degrees_to_q9_7(i16::from(measurement.elevation)) as u16; + match req { + uci::AoaResultReq::AoaDisabled => (0, 0, 0, 0), + uci::AoaResultReq::AoaEnabled => (azimuth, 100, elevation, 100), + uci::AoaResultReq::AoaEnabledAzimuthOnly => (azimuth, 100, 0, 0), + uci::AoaResultReq::AoaEnabledElevationOnly => (0, 0, elevation, 100), + } +} + #[derive(Debug)] enum TwoWayRangingMeasurement { Short(ShortAddressTwoWayRangingMeasurement), @@ -155,10 +166,19 @@ enum TwoWayRangingMeasurement { impl TwoWayRangingMeasurement { fn new( + app_config: &AppConfig, mac_address: &MacAddress, local: RangingMeasurement, remote: RangingMeasurement, ) -> Self { + let (aoa_azimuth, aoa_azimuth_fom, aoa_elevation, aoa_elevation_fom) = + encode_aoa(local, app_config.aoa_result_req()); + let ( + aoa_destination_azimuth, + aoa_destination_azimuth_fom, + aoa_destination_elevation, + aoa_destination_elevation_fom, + ) = encode_aoa(remote, uci::AoaResultReq::AoaEnabled); match mac_address { MacAddress::Short(address) => { Self::Short(ShortAddressTwoWayRangingMeasurement { @@ -166,14 +186,14 @@ impl TwoWayRangingMeasurement { status: uci::Status::Ok, nlos: 0, // in Line Of Sight distance: local.range, - aoa_azimuth: degrees_to_q9_7(local.azimuth) as u16, - aoa_azimuth_fom: 100, // Yup, pretty sure about this - aoa_elevation: degrees_to_q9_7(i16::from(local.elevation)) as u16, - aoa_elevation_fom: 100, // Yup, pretty sure about this - aoa_destination_azimuth: degrees_to_q9_7(remote.azimuth) as u16, - aoa_destination_azimuth_fom: 100, - aoa_destination_elevation: degrees_to_q9_7(i16::from(remote.elevation)) as u16, - aoa_destination_elevation_fom: 100, + aoa_azimuth, + aoa_azimuth_fom, + aoa_elevation, + aoa_elevation_fom, + aoa_destination_azimuth, + aoa_destination_azimuth_fom, + aoa_destination_elevation, + aoa_destination_elevation_fom, slot_index: 0, rssi: u8::MAX, }) @@ -184,14 +204,14 @@ impl TwoWayRangingMeasurement { status: uci::Status::Ok, nlos: 0, distance: local.range, - aoa_azimuth: degrees_to_q9_7(local.azimuth) as u16, - aoa_azimuth_fom: 100, - aoa_elevation: degrees_to_q9_7(i16::from(local.elevation)) as u16, - aoa_elevation_fom: 100, - aoa_destination_azimuth: degrees_to_q9_7(remote.azimuth) as u16, - aoa_destination_azimuth_fom: 100, - aoa_destination_elevation: degrees_to_q9_7(i16::from(remote.elevation)) as u16, - aoa_destination_elevation_fom: 100, + aoa_azimuth, + aoa_azimuth_fom, + aoa_elevation, + aoa_elevation_fom, + aoa_destination_azimuth, + aoa_destination_azimuth_fom, + aoa_destination_elevation, + aoa_destination_elevation_fom, slot_index: 0, rssi: u8::MAX, }) @@ -347,6 +367,7 @@ impl DlTdoaMeasurement { } fn new( + app_config: &AppConfig, anchor: &device::DlTdoaAnchor, local: RangingMeasurement, message_type: uci::DlTdoaMessageType, @@ -354,6 +375,8 @@ impl DlTdoaMeasurement { tx_timestamp_length: uci::DlTdoaTimestampLength, tx_timestamp: u64, ) -> Self { + let (aoa_azimuth, aoa_azimuth_fom, aoa_elevation, aoa_elevation_fom) = + encode_aoa(local, app_config.aoa_result_req()); let rx_timestamp = tx_timestamp.wrapping_add(cm_to_tof_ticks(local.range)); let is_final = message_type == uci::DlTdoaMessageType::FinalDtm; let (wgs84_location, relative_location) = if is_final { @@ -386,10 +409,10 @@ impl DlTdoaMeasurement { block_index, round_index: anchor.round_index, nlos: 0, - aoa_azimuth: degrees_to_q9_7(local.azimuth) as u16, - aoa_azimuth_fom: 100, - aoa_elevation: degrees_to_q9_7(i16::from(local.elevation)) as u16, - aoa_elevation_fom: 100, + aoa_azimuth, + aoa_azimuth_fom, + aoa_elevation, + aoa_elevation_fom, rssi: u8::MAX, tx_timestamp_40, tx_timestamp_64, @@ -737,6 +760,7 @@ impl Pica { .then(|| (active_responders.len() as u64 + 1) * slot_ticks); let poll_dtm = DlTdoaMeasurement::new( + &session.app_config, init_anchor, init_local, uci::DlTdoaMessageType::PollDtm, @@ -759,6 +783,7 @@ impl Pica { let slot_offset_ticks = slot_idx * slot_ticks; Some( DlTdoaMeasurement::new( + &session.app_config, resp_anchor, local, uci::DlTdoaMessageType::ResponseDtm, @@ -781,6 +806,7 @@ impl Pica { .filter(|_| !response_dtms.is_empty()) .map(|final_offset| { DlTdoaMeasurement::new( + &session.app_config, init_anchor, init_local, uci::DlTdoaMessageType::FinalDtm, @@ -809,7 +835,8 @@ impl Pica { return; }; let send_ntf = session.app_config.dl_tdoa_role() == Some(uci::DeviceRole::DtTag) - && session.app_config.session_info_ntf_config() != uci::SessionInfoNtfConfig::Disable; + && session.app_config.effective_session_info_ntf_config() + != uci::SessionInfoNtfConfig::Disable; let ntf_packet = if send_ntf { let measurements = self.dl_tdoa_measurements(device_handle, session_id, session); session.encode_dl_tdoa_session_info_ntf(measurements) @@ -904,7 +931,12 @@ impl Pica { let remote = self .ranging_estimator .estimate(&peer_handle, &device.handle)?; - Some(TwoWayRangingMeasurement::new(&mac_address, local, remote)) + Some(TwoWayRangingMeasurement::new( + &session.app_config, + &mac_address, + local, + remote, + )) }) .unwrap_or_else(|| TwoWayRangingMeasurement::timeout(&mac_address)); measurements.push(measurement); @@ -1004,34 +1036,35 @@ impl Pica { } let effects = if session.session_type() != SessionType::FiraDataTransferSession { - let (send_ntf, measurements) = match session.app_config.session_info_ntf_config() { - uci::SessionInfoNtfConfig::Disable => (false, Vec::new()), - uci::SessionInfoNtfConfig::Enable => (true, measurements), - uci::SessionInfoNtfConfig::EnableProximityTrigger - | uci::SessionInfoNtfConfig::EnableAoaTrigger - | uci::SessionInfoNtfConfig::EnableProximityAoaTrigger => { - let filtered: Vec<_> = measurements - .into_iter() - .filter(|m| m.is_within_bounds(&session.app_config)) - .collect(); - (!filtered.is_empty(), filtered) - } - uci::SessionInfoNtfConfig::EnableProximityEdgeTrigger - | uci::SessionInfoNtfConfig::EnableAoaEdgeTrigger - | uci::SessionInfoNtfConfig::EnableProximityAoaEdgeTrigger => { - let emitted: Vec<_> = measurements - .into_iter() - .filter(|m| { - if m.is_within_bounds(&session.app_config) { - session.in_range_peers.insert(m.mac_address()) - } else { - session.in_range_peers.remove(&m.mac_address()) - } - }) - .collect(); - (!emitted.is_empty(), emitted) - } - }; + let (send_ntf, measurements) = + match session.app_config.effective_session_info_ntf_config() { + uci::SessionInfoNtfConfig::Disable => (false, Vec::new()), + uci::SessionInfoNtfConfig::Enable => (true, measurements), + uci::SessionInfoNtfConfig::EnableProximityTrigger + | uci::SessionInfoNtfConfig::EnableAoaTrigger + | uci::SessionInfoNtfConfig::EnableProximityAoaTrigger => { + let filtered: Vec<_> = measurements + .into_iter() + .filter(|m| m.is_within_bounds(&session.app_config)) + .collect(); + (!filtered.is_empty(), filtered) + } + uci::SessionInfoNtfConfig::EnableProximityEdgeTrigger + | uci::SessionInfoNtfConfig::EnableAoaEdgeTrigger + | uci::SessionInfoNtfConfig::EnableProximityAoaEdgeTrigger => { + let emitted: Vec<_> = measurements + .into_iter() + .filter(|m| { + if m.is_within_bounds(&session.app_config) { + session.in_range_peers.insert(m.mac_address()) + } else { + session.in_range_peers.remove(&m.mac_address()) + } + }) + .collect(); + (!emitted.is_empty(), emitted) + } + }; if send_ntf { let ntf_packet = match session.app_config.mac_address_mode() { diff --git a/src/uci_packets.pdl b/src/uci_packets.pdl index 6f452ab..29884ab 100644 --- a/src/uci_packets.pdl +++ b/src/uci_packets.pdl @@ -524,17 +524,6 @@ enum CapTlvType : 8 { }, } - -// AOA result request type. -// Values set for AOA_RESULT_REQ config ID. -enum AoaResultReqType : 8 { - AOA_DISABLE = 0x0, - AOA_ENABLE = 0x01, - AOA_ENABLE_AZIMUTH = 0x02, - AOA_ENABLE_ELEVATION = 0x03, - AOA_ENABLE_INTERLEAVED = 0xF0, -} - enum DeviceState : 8 { DEVICE_STATE_READY = 0x01, DEVICE_STATE_ACTIVE = 0x02, diff --git a/tests/ranging.py b/tests/ranging.py index f57b7c1..7104fb7 100755 --- a/tests/ranging.py +++ b/tests/ranging.py @@ -1000,6 +1000,7 @@ async def run(address: str, uci_port: int): await aliro_ranging_session(host0) await dl_tdoa_ranging_session(host0, host1, address, uci_port) await twr_block_stride_ranging(host0, host1) + await aoa_result_req_ranging(host0, host1) finally: host0.disconnect() host1.disconnect() @@ -1935,6 +1936,407 @@ async def stop_deinit_session(host: Host): await stop_deinit_session(host_controlee) +async def aoa_result_req_ranging(host0: Host, host1: Host): + def tlvs(cfg: dict) -> list[uci.AppConfigTlv]: + return [uci.AppConfigTlv(cfg_id=k, v=bytes(v)) for k, v in cfg.items()] + + async def init_session(host: Host, sid: int, cfg: dict): + host.send_control( + uci.SessionInitCmd( + session_id=sid, session_type=uci.SessionType.FIRA_RANGING_SESSION + ) + ) + await host.expect_control(uci.SessionInitRsp(status=uci.Status.OK)) + await host.expect_control( + uci.SessionStatusNtf( + session_token=sid, + session_state=uci.SessionState.SESSION_STATE_INIT, + reason_code=0, + ) + ) + host.send_control(uci.SessionSetAppConfigCmd(session_token=sid, tlvs=tlvs(cfg))) + await host.expect_control( + uci.SessionSetAppConfigRsp(status=uci.Status.OK, cfg_status=[]) + ) + await host.expect_control( + uci.SessionStatusNtf( + session_token=sid, + session_state=uci.SessionState.SESSION_STATE_IDLE, + reason_code=0, + ) + ) + + async def start_session(host: Host, sid: int): + host.send_control(uci.SessionStartCmd(session_id=sid)) + await host.expect_control(uci.SessionStartRsp(status=uci.Status.OK)) + await host.expect_control( + uci.SessionStatusNtf( + session_token=sid, + session_state=uci.SessionState.SESSION_STATE_ACTIVE, + reason_code=0, + ) + ) + await host.expect_control( + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_ACTIVE) + ) + + async def stop_session(host: Host, sid: int): + host.send_control(uci.SessionStopCmd(session_id=sid)) + await host.expect_control(uci.SessionStopRsp(status=uci.Status.OK)) + await host.expect_control( + uci.SessionStatusNtf( + session_token=sid, + session_state=uci.SessionState.SESSION_STATE_IDLE, + reason_code=0, + ) + ) + await host.expect_control( + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) + ) + + async def deinit_session(host: Host, sid: int): + host.send_control(uci.SessionDeinitCmd(session_token=sid)) + await host.expect_control(uci.SessionDeinitRsp(status=uci.Status.OK)) + await host.expect_control( + uci.SessionStatusNtf( + session_token=sid, + session_state=uci.SessionState.SESSION_STATE_DEINIT, + reason_code=0, + ) + ) + + twr_sid = 10 + await init_session( + host1, + twr_sid, + { + uci.AppConfigTlvType.DEVICE_ROLE: [uci.DeviceRole.RESPONDER], + uci.AppConfigTlvType.DEVICE_TYPE: [uci.DeviceType.CONTROLEE], + uci.AppConfigTlvType.DEVICE_MAC_ADDRESS: host1.mac_address, + uci.AppConfigTlvType.DST_MAC_ADDRESS: host0.mac_address, + uci.AppConfigTlvType.MAC_ADDRESS_MODE: [uci.MacAddressMode.MODE_0], + uci.AppConfigTlvType.MULTI_NODE_MODE: [uci.MultiNodeMode.ONE_TO_ONE], + uci.AppConfigTlvType.SCHEDULE_MODE: [uci.ScheduleMode.CONTENTION_BASED], + uci.AppConfigTlvType.RANGING_ROUND_USAGE: [0x06], + uci.AppConfigTlvType.RANGING_DURATION: int(100).to_bytes(4, "little"), + uci.AppConfigTlvType.SESSION_INFO_NTF_CONFIG: [ + uci.SessionInfoNtfConfig.DISABLE + ], + }, + ) + await start_session(host1, twr_sid) + + await init_session( + host0, + twr_sid, + { + uci.AppConfigTlvType.DEVICE_ROLE: [uci.DeviceRole.INITIATOR], + uci.AppConfigTlvType.DEVICE_TYPE: [uci.DeviceType.CONTROLLER], + uci.AppConfigTlvType.DEVICE_MAC_ADDRESS: host0.mac_address, + uci.AppConfigTlvType.DST_MAC_ADDRESS: host1.mac_address, + uci.AppConfigTlvType.MAC_ADDRESS_MODE: [uci.MacAddressMode.MODE_0], + uci.AppConfigTlvType.MULTI_NODE_MODE: [uci.MultiNodeMode.ONE_TO_ONE], + uci.AppConfigTlvType.SCHEDULE_MODE: [uci.ScheduleMode.CONTENTION_BASED], + uci.AppConfigTlvType.RANGING_ROUND_USAGE: [0x06], + uci.AppConfigTlvType.RANGING_DURATION: int(100).to_bytes(4, "little"), + uci.AppConfigTlvType.NUMBER_OF_CONTROLEES: [1], + uci.AppConfigTlvType.MAX_NUMBER_OF_MEASUREMENTS: int(1).to_bytes( + 2, "little" + ), + }, + ) + + host0.send_control( + uci.SessionSetAppConfigCmd( + session_token=twr_sid, + tlvs=[ + uci.AppConfigTlv( + cfg_id=uci.AppConfigTlvType.AOA_RESULT_REQ, v=bytes([0xFF]) + ), + ], + ) + ) + await host0.expect_control( + uci.SessionSetAppConfigRsp( + status=uci.Status.INVALID_PARAM, + cfg_status=[ + uci.AppConfigStatus( + cfg_id=uci.AppConfigTlvType.AOA_RESULT_REQ, + status=uci.Status.INVALID_PARAM, + ), + ], + ) + ) + + twr_modes = [ + (None, 100, 100), + (uci.AoaResultReq.AOA_DISABLED, 0, 0), + (uci.AoaResultReq.AOA_ENABLED_AZIMUTH_ONLY, 100, 0), + (uci.AoaResultReq.AOA_ENABLED_ELEVATION_ONLY, 0, 100), + ] + + for req, exp_az_fom, exp_el_fom in twr_modes: + if req is not None: + host0.send_control( + uci.SessionSetAppConfigCmd( + session_token=twr_sid, + tlvs=tlvs({uci.AppConfigTlvType.AOA_RESULT_REQ: [req]}), + ) + ) + await host0.expect_control( + uci.SessionSetAppConfigRsp(status=uci.Status.OK, cfg_status=[]) + ) + + await start_session(host0, twr_sid) + event = await host0.expect_control( + uci.ShortMacTwoWaySessionInfoNtf, timeout=2.0 + ) + assert len(event.two_way_ranging_measurements) == 1 + m = event.two_way_ranging_measurements[0] + assert m.status == uci.Status.OK + assert m.aoa_azimuth_fom == exp_az_fom and m.aoa_elevation_fom == exp_el_fom + if exp_az_fom == 0: + assert m.aoa_azimuth == 0 + if exp_el_fom == 0: + assert m.aoa_elevation == 0 + assert ( + m.aoa_destination_azimuth == 0 + and m.aoa_destination_elevation == 0 + and m.aoa_destination_azimuth_fom == 100 + and m.aoa_destination_elevation_fom == 100 + ) + await host0.expect_control( + uci.SessionStatusNtf( + session_token=twr_sid, + session_state=uci.SessionState.SESSION_STATE_IDLE, + reason_code=uci.ReasonCode.MAX_NUMBER_OF_MEASUREMENTS_REACHED, + ) + ) + await host0.expect_control( + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) + ) + + host0.send_control( + uci.SessionSetAppConfigCmd( + session_token=twr_sid, + tlvs=tlvs( + { + uci.AppConfigTlvType.AOA_RESULT_REQ: [ + uci.AoaResultReq.AOA_DISABLED + ], + uci.AppConfigTlvType.SESSION_INFO_NTF_CONFIG: [ + uci.SessionInfoNtfConfig.ENABLE_AOA_TRIGGER + ], + } + ), + ) + ) + await host0.expect_control( + uci.SessionSetAppConfigRsp(status=uci.Status.OK, cfg_status=[]) + ) + await start_session(host0, twr_sid) + await host0.expect_control( + uci.SessionStatusNtf( + session_token=twr_sid, + session_state=uci.SessionState.SESSION_STATE_IDLE, + reason_code=uci.ReasonCode.MAX_NUMBER_OF_MEASUREMENTS_REACHED, + ) + ) + await host0.expect_control( + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) + ) + + host0.send_control( + uci.SessionSetAppConfigCmd( + session_token=twr_sid, + tlvs=tlvs( + { + uci.AppConfigTlvType.AOA_RESULT_REQ: [ + uci.AoaResultReq.AOA_DISABLED + ], + uci.AppConfigTlvType.SESSION_INFO_NTF_CONFIG: [ + uci.SessionInfoNtfConfig.ENABLE_PROXIMITY_AOA_TRIGGER + ], + } + ), + ) + ) + await host0.expect_control( + uci.SessionSetAppConfigRsp(status=uci.Status.OK, cfg_status=[]) + ) + await start_session(host0, twr_sid) + event = await host0.expect_control(uci.ShortMacTwoWaySessionInfoNtf, timeout=2.0) + assert len(event.two_way_ranging_measurements) == 1 + m = event.two_way_ranging_measurements[0] + assert m.status == uci.Status.OK + assert m.aoa_azimuth_fom == 0 and m.aoa_elevation_fom == 0 + await host0.expect_control( + uci.SessionStatusNtf( + session_token=twr_sid, + session_state=uci.SessionState.SESSION_STATE_IDLE, + reason_code=uci.ReasonCode.MAX_NUMBER_OF_MEASUREMENTS_REACHED, + ) + ) + await host0.expect_control( + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) + ) + + host0.send_control( + uci.SessionSetAppConfigCmd( + session_token=twr_sid, + tlvs=tlvs( + { + uci.AppConfigTlvType.AOA_RESULT_REQ: [ + uci.AoaResultReq.AOA_ENABLED_AZIMUTH_ONLY + ], + uci.AppConfigTlvType.SESSION_INFO_NTF_CONFIG: [ + uci.SessionInfoNtfConfig.ENABLE_AOA_TRIGGER + ], + uci.AppConfigTlvType.AOA_BOUND_CONFIG: ( + int(-10 * 128).to_bytes(2, "little", signed=True) + + int(10 * 128).to_bytes(2, "little", signed=True) + + int(20 * 128).to_bytes(2, "little", signed=True) + + int(45 * 128).to_bytes(2, "little", signed=True) + ), + } + ), + ) + ) + await host0.expect_control( + uci.SessionSetAppConfigRsp(status=uci.Status.OK, cfg_status=[]) + ) + await start_session(host0, twr_sid) + event = await host0.expect_control(uci.ShortMacTwoWaySessionInfoNtf, timeout=2.0) + assert len(event.two_way_ranging_measurements) == 1 + m = event.two_way_ranging_measurements[0] + assert m.status == uci.Status.OK + assert m.aoa_azimuth_fom == 100 and m.aoa_elevation_fom == 0 + await host0.expect_control( + uci.SessionStatusNtf( + session_token=twr_sid, + session_state=uci.SessionState.SESSION_STATE_IDLE, + reason_code=uci.ReasonCode.MAX_NUMBER_OF_MEASUREMENTS_REACHED, + ) + ) + await host0.expect_control( + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) + ) + + await deinit_session(host0, twr_sid) + await stop_session(host1, twr_sid) + await deinit_session(host1, twr_sid) + + dl_sid = 11 + await init_session( + host1, + dl_sid, + { + uci.AppConfigTlvType.DEVICE_ROLE: [uci.DeviceRole.DT_ANCHOR], + uci.AppConfigTlvType.DEVICE_TYPE: [uci.DeviceType.CONTROLLER], + uci.AppConfigTlvType.DEVICE_MAC_ADDRESS: host1.mac_address, + uci.AppConfigTlvType.MAC_ADDRESS_MODE: [uci.MacAddressMode.MODE_0], + uci.AppConfigTlvType.MULTI_NODE_MODE: [uci.MultiNodeMode.ONE_TO_ONE], + uci.AppConfigTlvType.SCHEDULE_MODE: [uci.ScheduleMode.CONTENTION_BASED], + uci.AppConfigTlvType.RANGING_ROUND_USAGE: [ + uci.RangingRoundUsage.ONE_WAY_RANGING_DL_TDOA + ], + uci.AppConfigTlvType.RANGING_DURATION: int(100).to_bytes(4, "little"), + uci.AppConfigTlvType.DL_TDOA_ANCHOR_LOCATION: bytes([0x03]) + + bytes(uci.RelativeLocation(x=10, y=20, z=30).serialize()), + uci.AppConfigTlvType.DL_TDOA_TX_ACTIVE_RANGING_ROUNDS: [0x01], + }, + ) + host1.send_control( + uci.SessionUpdateDtAnchorRangingRoundsCmd( + session_token=dl_sid, + ranging_rounds=[ + uci.DtAnchorRangingRound( + round_index=0, role=uci.DtAnchorActingRole.INITIATOR + ) + ], + ) + ) + await host1.expect_control( + uci.SessionUpdateDtAnchorRangingRoundsRsp( + status=uci.Status.OK, ranging_round_indexes=[] + ) + ) + await start_session(host1, dl_sid) + + await init_session( + host0, + dl_sid, + { + uci.AppConfigTlvType.DEVICE_ROLE: [uci.DeviceRole.DT_TAG], + uci.AppConfigTlvType.DEVICE_TYPE: [uci.DeviceType.CONTROLEE], + uci.AppConfigTlvType.DEVICE_MAC_ADDRESS: host0.mac_address, + uci.AppConfigTlvType.MAC_ADDRESS_MODE: [uci.MacAddressMode.MODE_0], + uci.AppConfigTlvType.MULTI_NODE_MODE: [uci.MultiNodeMode.ONE_TO_ONE], + uci.AppConfigTlvType.SCHEDULE_MODE: [uci.ScheduleMode.CONTENTION_BASED], + uci.AppConfigTlvType.RANGING_ROUND_USAGE: [ + uci.RangingRoundUsage.ONE_WAY_RANGING_DL_TDOA + ], + uci.AppConfigTlvType.RANGING_DURATION: int(100).to_bytes(4, "little"), + uci.AppConfigTlvType.AOA_RESULT_REQ: [uci.AoaResultReq.AOA_DISABLED], + }, + ) + host0.send_control( + uci.SessionUpdateDtTagRangingRoundsCmd( + session_token=dl_sid, ranging_round_indexes=[0] + ) + ) + await host0.expect_control( + uci.SessionUpdateDtTagRangingRoundsRsp( + status=uci.Status.OK, ranging_round_indexes=[] + ) + ) + await start_session(host0, dl_sid) + + event = await host0.expect_control(uci.ShortMacDlTDoASessionInfoNtf, timeout=2.0) + assert len(event.dl_tdoa_measurements) == 1 + m = event.dl_tdoa_measurements[0].measurement + assert m.status == uci.Status.OK + assert m.relative_location == uci.RelativeLocation(x=10, y=20, z=30) + assert ( + m.aoa_azimuth == 0 + and m.aoa_azimuth_fom == 0 + and m.aoa_elevation == 0 + and m.aoa_elevation_fom == 0 + ) + + await stop_session(host0, dl_sid) + host0.send_control( + uci.SessionSetAppConfigCmd( + session_token=dl_sid, + tlvs=tlvs( + { + uci.AppConfigTlvType.AOA_RESULT_REQ: [ + uci.AoaResultReq.AOA_ENABLED_AZIMUTH_ONLY + ] + } + ), + ) + ) + await host0.expect_control( + uci.SessionSetAppConfigRsp(status=uci.Status.OK, cfg_status=[]) + ) + await start_session(host0, dl_sid) + + event = await host0.expect_control(uci.ShortMacDlTDoASessionInfoNtf, timeout=2.0) + assert len(event.dl_tdoa_measurements) == 1 + m = event.dl_tdoa_measurements[0].measurement + assert m.status == uci.Status.OK + assert m.relative_location == uci.RelativeLocation(x=10, y=20, z=30) + assert ( + m.aoa_azimuth_fom == 100 and m.aoa_elevation == 0 and m.aoa_elevation_fom == 0 + ) + + await stop_session(host0, dl_sid) + await deinit_session(host0, dl_sid) + await stop_session(host1, dl_sid) + await deinit_session(host1, dl_sid) + + def main(): """Start a Pica interactive console.""" parser = argparse.ArgumentParser(description=__doc__)