dw1000/
ranging.rs

1//! Implementation of double-sided two-way ranging
2//!
3//! This ranging technique is described in the DW1000 user manual, section 12.3.
4//! This module uses three messages for a range measurement, as described in
5//! section 12.3.2.
6//!
7//! This module defines the messages required, and provides code for sending and
8//! decoding them. It is left to the user to tie all that together, by sending
9//! out the messages at the right time.
10//!
11//! There can be some variation in the use of this module, depending on the use
12//! case. Here is one example of how this module can be used:
13//! 1. Nodes are divided into anchors and tags. Tags are those nodes whose
14//!    position interests us. Anchors are placed in known locations to enable
15//!    range measurements.
16//! 2. Anchors regularly send out pings ([`Ping`]).
17//! 3. Tags listen for these pings, and reply with a ranging request
18//!    ([`Request`]) for each ping they receive.
19//! 4. When an anchor receives a ranging request, it replies with a ranging
20//!    response ([`Response`]).
21//! 5. Once the tag receives the ranging response, it has all the information it
22//!    needs to compute the distance.
23//!
24//! Please refer to the [examples] in the DWM1001 Board Support Crate for an
25//! implementation of this scheme.
26//!
27//! In this scheme, anchors initiate the exchange, which results in the tag
28//! having the distance information. Possible variations include the tag
29//! initiating the request and the anchor calculating the distance, or a
30//! peer-to-peer scheme without dedicated tags and anchors.
31//!
32//! Please note that using the code in this module without further processing of
33//! the result will yield imprecise measurements. To improve the precision of
34//! those measurements, a range bias needs to be applied. Please refer to the
35//! user manual, and [this DWM1001 issue] for more information.
36//!
37//! [`Ping`]: struct.Ping.html
38//! [`Request`]: struct.Request.html
39//! [`Response`]: struct.Response.html
40//! [examples]: https://github.com/braun-robotics/rust-dwm1001/tree/master/examples
41//! [this DWM1001 issue]: https://github.com/braun-robotics/rust-dwm1001/issues/55
42
43use core::mem::size_of;
44
45use embedded_hal::{blocking::spi, digital::v2::OutputPin};
46use serde::{Deserialize, Serialize};
47use ssmarshal;
48
49use crate::hl::SendTime;
50use crate::{
51    hl, mac,
52    time::{Duration, Instant},
53    Error, Ready, Sending, TxConfig, DW1000,
54};
55
56/// The transmission delay
57///
58/// This defines the transmission delay as 10 ms. This should be enough to
59/// finish the rest of the preparation and send the message, even if we're
60/// running with unoptimized code.
61const TX_DELAY: u32 = 10_000_000;
62
63/// Implemented by all ranging messages
64pub trait Message: Sized + for<'de> Deserialize<'de> + Serialize {
65    /// A prelude that identifies the message
66    const PRELUDE: Prelude;
67
68    /// The length of the message's prelude
69    ///
70    /// This is a bit of a hack that we need until `slice::<impl [T]>::len` is
71    /// stable as a const fn.
72    const PRELUDE_LEN: usize;
73
74    /// The length of the whole message, including prelude and data
75    const LEN: usize = Self::PRELUDE_LEN + size_of::<Self>();
76
77    /// Decodes a received message of this type
78    ///
79    /// The user is responsible for receiving a message using
80    /// [`DW1000::receive`]. Once a message has been received, this method can
81    /// be used to check what type of message this is.
82    ///
83    /// Returns `Ok(None)`, if the message is not of the right type. Otherwise,
84    /// returns `Ok(Some(RxMessage<Self>)), if the message is of the right type,
85    /// and no error occured.
86    fn decode<SPI, CS>(message: &hl::Message) -> Result<Option<RxMessage<Self>>, Error<SPI, CS>>
87    where
88        SPI: spi::Transfer<u8> + spi::Write<u8>,
89        CS: OutputPin,
90    {
91        if !message.frame.payload.starts_with(Self::PRELUDE.0) {
92            // Not a message of this type
93            return Ok(None);
94        }
95
96        if message.frame.payload.len() != Self::LEN {
97            // Invalid message
98            return Err(Error::BufferTooSmall {
99                required_len: Self::LEN,
100            });
101        }
102
103        // The message passes muster. Let's decode it.
104        let (payload, _) =
105            ssmarshal::deserialize::<Self>(&message.frame.payload[Self::PRELUDE.0.len()..])?;
106
107        Ok(Some(RxMessage {
108            rx_time: message.rx_time,
109            source: message.frame.header.source,
110            payload,
111        }))
112    }
113}
114
115/// An incoming ranging message
116///
117/// Contains the received payload, as well as some metadata that's required to
118/// create a reply to the message.
119#[derive(Debug)]
120pub struct RxMessage<T: Message> {
121    /// The time the message was received
122    pub rx_time: Instant,
123
124    /// The source of the message
125    pub source: Option<mac::Address>,
126
127    /// The message data
128    pub payload: T,
129}
130
131/// An outgoing ranging message
132///
133/// Contains the payload to be sent, as well as some metadata.
134#[derive(Debug)]
135pub struct TxMessage<T: Message> {
136    /// The recipient of the message
137    ///
138    /// This is an IEEE 802.15.4 MAC address. This could be a broadcast address,
139    /// for messages that are sent to all other nodes in range.
140    pub recipient: Option<mac::Address>,
141
142    /// The time this message is going to be sent
143    ///
144    /// When creating this struct, this is going to be an instant in the near
145    /// future. When sending the message, the sending is delayed to make sure it
146    /// it sent at exactly this instant.
147    pub tx_time: Instant,
148
149    /// The actual message payload
150    pub payload: T,
151}
152
153impl<T> TxMessage<T>
154where
155    T: Message,
156{
157    /// Send this message via the DW1000
158    ///
159    /// Serializes the message payload and uses [`DW1000::send`] internally to
160    /// send it.
161    pub fn send<'r, SPI, CS>(
162        &self,
163        dw1000: DW1000<SPI, CS, Ready>,
164    ) -> Result<DW1000<SPI, CS, Sending>, Error<SPI, CS>>
165    where
166        SPI: spi::Transfer<u8> + spi::Write<u8>,
167        CS: OutputPin,
168    {
169        // Create a buffer that fits the biggest message currently implemented.
170        // This is a really ugly hack. The size of the buffer should just be
171        // `T::LEN`. Unfortunately that's not possible. See:
172        // https://github.com/rust-lang/rust/issues/42863
173        const LEN: usize = 48;
174        assert!(T::LEN <= LEN);
175        let mut buf = [0; LEN];
176
177        buf[..T::PRELUDE.0.len()].copy_from_slice(T::PRELUDE.0);
178        ssmarshal::serialize(&mut buf[T::PRELUDE.0.len()..], &self.payload)?;
179
180        let future = dw1000.send(
181            &buf[..T::LEN],
182            self.recipient,
183            SendTime::Delayed(self.tx_time),
184            TxConfig::default(),
185        )?;
186
187        Ok(future)
188    }
189}
190
191/// Sent before a message's data to identify the message
192#[derive(Debug, Deserialize, Serialize)]
193#[repr(C)]
194pub struct Prelude(pub &'static [u8]);
195
196/// Ranging ping message
197///
198/// This message is typically sent to initiate a range measurement transaction.
199/// See [module documentation] for more info.
200///
201/// [module documentation]: index.html
202#[derive(Debug, Deserialize, Serialize)]
203#[repr(C)]
204pub struct Ping {
205    /// When the ping was sent, in local sender time
206    pub ping_tx_time: Instant,
207}
208
209impl Ping {
210    /// Creates a new ping message
211    ///
212    /// Only creates the message, but doesn't yet send it. Sets the transmission
213    /// time to 10 milliseconds in the future. Make sure to send the message
214    /// within that time frame, or the distance measurement will be negatively
215    /// affected.
216    pub fn new<SPI, CS>(
217        dw1000: &mut DW1000<SPI, CS, Ready>,
218    ) -> Result<TxMessage<Self>, Error<SPI, CS>>
219    where
220        SPI: spi::Transfer<u8> + spi::Write<u8>,
221        CS: OutputPin,
222    {
223        let tx_time = dw1000.sys_time()? + Duration::from_nanos(TX_DELAY);
224        let ping_tx_time = tx_time + dw1000.get_tx_antenna_delay()?;
225
226        let payload = Ping { ping_tx_time };
227
228        Ok(TxMessage {
229            recipient: mac::Address::broadcast(&mac::AddressMode::Short),
230            tx_time,
231            payload,
232        })
233    }
234}
235
236impl Message for Ping {
237    const PRELUDE: Prelude = Prelude(b"RANGING PING");
238    const PRELUDE_LEN: usize = 12;
239}
240
241/// Ranging request message
242///
243/// This message is typically sent in response to a ranging ping, to request a
244/// ranging response. See [module documentation] for more info.
245///
246/// [module documentation]: index.html
247#[derive(Debug, Deserialize, Serialize)]
248#[repr(C)]
249pub struct Request {
250    /// When the original ping was sent, in local time on the anchor
251    pub ping_tx_time: Instant,
252
253    /// The time between the ping being received and the reply being sent
254    pub ping_reply_time: Duration,
255
256    /// When the ranging request was sent, in local sender time
257    pub request_tx_time: Instant,
258}
259
260impl Request {
261    /// Creates a new ranging request message
262    ///
263    /// Only creates the message, but doesn't yet send it. Sets the transmission
264    /// time to 10 milliseconds in the future. Make sure to send the message
265    /// within that time frame, or the distance measurement will be negatively
266    /// affected.
267    pub fn new<SPI, CS>(
268        dw1000: &mut DW1000<SPI, CS, Ready>,
269        ping: &RxMessage<Ping>,
270    ) -> Result<TxMessage<Self>, Error<SPI, CS>>
271    where
272        SPI: spi::Transfer<u8> + spi::Write<u8>,
273        CS: OutputPin,
274    {
275        let tx_time = dw1000.sys_time()? + Duration::from_nanos(TX_DELAY);
276        let request_tx_time = tx_time + dw1000.get_tx_antenna_delay()?;
277
278        let ping_reply_time = request_tx_time.duration_since(ping.rx_time);
279
280        let payload = Request {
281            ping_tx_time: ping.payload.ping_tx_time,
282            ping_reply_time,
283            request_tx_time,
284        };
285
286        Ok(TxMessage {
287            recipient: ping.source,
288            tx_time,
289            payload,
290        })
291    }
292}
293
294impl Message for Request {
295    const PRELUDE: Prelude = Prelude(b"RANGING REQUEST");
296    const PRELUDE_LEN: usize = 15;
297}
298
299/// Ranging response message
300///
301/// This message is typically sent in response to a ranging request, to wrap up
302/// the range measurement transaction.. See [module documentation] for more
303/// info.
304///
305/// [module documentation]: index.html
306#[derive(Debug, Deserialize, Serialize)]
307#[repr(C)]
308pub struct Response {
309    /// The time between the ping being received and the reply being sent
310    pub ping_reply_time: Duration,
311
312    /// The time between the ping being sent and the reply being received
313    pub ping_round_trip_time: Duration,
314
315    /// The time the ranging request was sent, in local sender time
316    pub request_tx_time: Instant,
317
318    /// The time between the request being received and a reply being sent
319    pub request_reply_time: Duration,
320}
321
322impl Response {
323    /// Creates a new ranging response message
324    ///
325    /// Only creates the message, but doesn't yet send it. Sets the transmission
326    /// time to 10 milliseconds in the future. Make sure to send the message
327    /// within that time frame, or the distance measurement will be negatively
328    /// affected.
329    pub fn new<SPI, CS>(
330        dw1000: &mut DW1000<SPI, CS, Ready>,
331        request: &RxMessage<Request>,
332    ) -> Result<TxMessage<Self>, Error<SPI, CS>>
333    where
334        SPI: spi::Transfer<u8> + spi::Write<u8>,
335        CS: OutputPin,
336    {
337        let tx_time = dw1000.sys_time()? + Duration::from_nanos(TX_DELAY);
338        let response_tx_time = tx_time + dw1000.get_tx_antenna_delay()?;
339
340        let ping_round_trip_time = request.rx_time.duration_since(request.payload.ping_tx_time);
341        let request_reply_time = response_tx_time.duration_since(request.rx_time);
342
343        let payload = Response {
344            ping_reply_time: request.payload.ping_reply_time,
345            ping_round_trip_time,
346            request_tx_time: request.payload.request_tx_time,
347            request_reply_time,
348        };
349
350        Ok(TxMessage {
351            recipient: request.source,
352            tx_time,
353            payload,
354        })
355    }
356}
357
358impl Message for Response {
359    const PRELUDE: Prelude = Prelude(b"RANGING RESPONSE");
360    const PRELUDE_LEN: usize = 16;
361}
362
363/// Computes the distance to another node from a ranging response
364pub fn compute_distance_mm(response: &RxMessage<Response>) -> Result<u64, ComputeDistanceError> {
365    // To keep variable names to a reasonable length, this function uses `rt` as
366    // a short-hand for "reply time" and `rtt` and a short-hand for "round-trip
367    // time".
368
369    let ping_rt = response.payload.ping_reply_time.value();
370    let ping_rtt = response.payload.ping_round_trip_time.value();
371    let request_rt = response.payload.request_reply_time.value();
372    let request_rtt = response
373        .rx_time
374        .duration_since(response.payload.request_tx_time)
375        .value();
376
377    // Compute time of flight according to the formula given in the DW1000 user
378    // manual, section 12.3.2.
379    let rtt_product = ping_rtt
380        .checked_mul(request_rtt)
381        .ok_or(ComputeDistanceError::RoundTripTimesTooLarge)?;
382    let rt_product = ping_rt
383        .checked_mul(request_rt)
384        .ok_or(ComputeDistanceError::ReplyTimesTooLarge)?;
385    let rt_sum = ping_rt
386        .checked_add(request_rt)
387        .ok_or(ComputeDistanceError::SumTooLarge)?;
388    let rtt_sum = ping_rtt
389        .checked_add(request_rtt)
390        .ok_or(ComputeDistanceError::SumTooLarge)?;
391    let sum = rt_sum
392        .checked_add(rtt_sum)
393        .ok_or(ComputeDistanceError::SumTooLarge)?;
394
395    let time_diff = rtt_product
396        .checked_sub(rt_product)
397        .ok_or(ComputeDistanceError::RtGreaterThanRtt)?;
398
399    let time_of_flight = time_diff / sum;
400
401    // Nominally, all time units are based on a 64 Ghz clock, meaning each time
402    // unit is 1/64 ns.
403
404    const SPEED_OF_LIGHT: u64 = 299_792_458; // m/s or nm/ns
405
406    let distance_nm_times_64 = SPEED_OF_LIGHT
407        .checked_mul(time_of_flight)
408        .ok_or(ComputeDistanceError::TimeOfFlightTooLarge)?;
409    let distance_mm = distance_nm_times_64 / 64 / 1_000_000;
410
411    Ok(distance_mm)
412}
413
414/// Returned from [`compute_distance_mm`] in case of an error
415#[derive(Debug)]
416pub enum ComputeDistanceError {
417    /// Reply times are too large to be multiplied
418    ReplyTimesTooLarge,
419
420    /// Round-trip times are too large to be multiplied
421    RoundTripTimesTooLarge,
422
423    /// The sum computed as part of the algorithm is too large
424    SumTooLarge,
425
426    /// The time of flight is so large, the distance calculation would overflow
427    TimeOfFlightTooLarge,
428
429    /// Round trip product is greater than round trip time due to low power
430    // Not exactly sure what causes this but it's a potential problem and occurs when VCC is low
431    RtGreaterThanRtt,
432}