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}