16. Ranging and synchronization


It is common to use underwater acoustic modems for range estimation, as the travel time of acoustic signals can easily be measured. In Section 2.3 , we saw that we can use the range command to estimate range between nodes. This command uses the RANGING service described below.

Ranging is closely related to time synchronization, since travel time measurement between two nodes requires some sort of synchronization between the nodes. If the nodes are synchronized, one-way travel time (OWTT) can be directly measured and used to estimate range. If the nodes are not synchronized, two-way travel time (TWTT) can be used to measure range and synchronize the nodes simultaneously. The RANGING service supports both modes of ranging, and manages synchronization information between nodes.

16.1. Overview

The RANGING service provides messages and parameters to support OWTT and TWTT ranging, and to manage synchronization information between the nodes.

16.1.1. Messages

The following messages are defined by the RANGING service:

  • RangeReq AGREE / REFUSE / FAILURE — measure range to peer node via OWTT or TWTT

  • BeaconReq AGREE / REFUSE / FAILURE — transmit beacon message for OWTT

  • SyncInfoReq SyncInfoRsp / REFUSE / FAILURE — get synchronization information for peer node

  • ClearSyncReq AGREE / REFUSE / FAILURE — clear synchronization information for peer node

  • RangeNtf — sent to the requestor or the agent’s topic when range information for peer node becomes available

  • BadRangeNtf — sent to the requestor or the agent’s topic when invalid range information for peer node indicates potentially outdated synchronization with that node

The use of these messages will become clearer through examples below.

16.1.2. Parameters

A few parameters control the behavior of the RANGING service provider:

  • channel — channel (DATA/CONTROL) to use for ranging

  • lifetime — lifetime or validity for synchronization information (seconds)

  • minRange — minimum valid range (m)

  • maxRange — maximum valid range (m)

  • maxBadRangeCnt — number of BadRangeNtf for peer node to discard synchronization information

The lifetime of syncrhonization information should be set based on the expected drift of modem clocks. Lifetime is defined as the time for the expected clock drift (scaled by speed of sound in water) to exceed the required range estimation accuracy. If a network uses modems with low-drift clocks (such as oven-controlled oscillators), the lifetime can be quite long (hours to days). Without low-drift clocks, reasonable lifetimes may only be in the order of several minutes to tens of minutes.

Why do clocks drift?

Most electronics use crystal oscillators for timekeeping. A crystal oscillator is an electronic circuit that uses the mechanical resonance of a vibrating piezoelectric crystal to create an electrical signal with a desired frequency. The resonant frequency depends on size, shape, elasticity, and the speed of sound in the material. Due to manufacturing tolerences, these properties are not exactly identical across manufactured crystals, and so different crystals designed for the same nominal frequency produce slightly different frequency signals. Furthermore, as the operating temperature of the crystal changes, its material properties change, and so does its resonant frequency. These differences in frequency are tiny, but over long periods of time, the differences accumulate and cause the clocks to drift.

For applications where drift is undesirable, temperature-compensated crystal oscillators (TCXO) or oven-controlled crystal oscillators (OCXO) may be used. TCXOs try to adjust their oscillation frequency electronically, to compensate for temperature changes. OCXOs, on the other hand, try to maintain a constant temperature with a mini-oven around the crystal. For very sensitive applications, atomic clocks may be used for even lower drift. But given long enough time, even the most precise of these oscillators will accumulate tiny errors and the clocks will eventually drift!

The minimum and maximum valid range settings control what the RANGING service considers to be a bad range — a range measurement that is unlikely to occur in reality (e.g. negative values, or ranges much further than the communication ability of the modems). Frequent bad ranges to a node are used to detect that the node synchronization information is invalid and should be discarded. The number of BadRangeNtf before discarding the synchronization information is specified as maxBadRangeCnt .

16.2. Examples

In order to understand how the RANGING service provides OWTT and TWTT ranging, it is instructive to try a few examples using the Netiquette 3-node network simulation ( bin/unet samples/netq-network.groovy ). Start the simulation, and connect to node A:

> agentsForService(org.arl.unet.Services.RANGING)  (1)
> ranging
<<< Ranging >>>

  channel = 1                                      (2)
  lifetime = 0                                     (3)
  maxBadRangeCnt = 10
  maxRange = 6500.0
  minRange = -10.0

> range host('B')                                  (4)
> ranging << new RangeReq(to: host('B'))           (5)
ranging >> RangeNtf:INFORM[from:31 to:232 range:371.0 offset:-256067128]
> ntf.range                                        (6)
1 We see that the ranging agent provides the RANGING service on the node.
2 The CONTROL channel (channel 1) is being used for ranging.
3 The lifetime is set to 0, indicating that the node clocks may have a large drift.
4 The range command provides us the range to node B of almost 1 km.
5 The range command is implemented by sending a RangeReq to the ranging agent. We directly send that message. As expected, it leads to a RangeNtf message that gives us the same range estimate as the range command. The RangeNtf also provides us time synchronization information between the nodes (as time difference between the nodes, or offset , in microseconds).
6 While the RangeNtf showed a range of 371.0 m, this was just due to rounding off for display. The actual value in the message is 370.98 m, as reported previously by the range command.

16.2.1. Two-way travel time ranging

The range measurement above used TWTT ranging. While node B participated in the range measurement by responding to node A’s request for a two-way frame exchange, this is all done quietly and we see nothing on node B’s shell. To see what is happening on both nodes, subscribe to the phy and ranging agent’s topics on both nodes. Then repeat the RangeReq on node A:

Node A:
> subscribe phy
> subscribe ranging
> ranging << new RangeReq(to: host('B'))
phy >> TxFrameStartNtf:INFORM[type:CONTROL txTime:2546485580 txDuration:950]
phy >> RxFrameStartNtf:INFORM[type:CONTROL rxTime:2548827417]
phy >> RxFrameNtf:INFORM[type:CONTROL from:31 to:232 protocol:1 rxTime:2548827417 txTime:2292518452 (7 bytes)]
ranging >> RangeNtf:INFORM[from:31 to:232 range:371.0 offset:-256067128]

We see that node A transmitted a CONTROL frame. It then received a timestamped CONTROL frame back from node B. The timing information in both frames was used to compute the range and time offset between the nodes. This was sent back to us as a RangeNtf . This is the frame exchange that implements TWTT ranging.

If we look at node B’s shell at the same time:

Node B:
> subscribe phy
> subscribe ranging
phy >> RxFrameStartNtf:INFORM[type:CONTROL rxTime:2290660289]
phy >> RxFrameNtf:INFORM[type:CONTROL from:232 to:31 protocol:1 rxTime:2290660289 (1 byte)]
phy >> TxFrameStartNtf:INFORM[type:CONTROL txTime:2292518452 txDuration:950]

We see that node B received a CONTROL frame and responded back with a CONTROL frame.

We can ask node A for the synchronization information it has gathered:

Node A:
> ranging << new SyncInfoReq(to: host('B'))
SyncInfoRsp:INFORM[to:31 offset:-256067128 validTill:1568557167512]

We see that it has stored the time offset to node B, along with a validity. However, you’ll find that the validity has already expired, since the lifetime parameter was set to 0. If you ask for synchronization information on node B, you’ll find that it does not have any:

Node B:
> ranging << new SyncInfoReq(to: host('A'))
REFUSE: Information unavailable

Without synchronization information, OWTT ranging cannot be performed.

16.2.2. Synchronization

If we have low-drift clocks on all our nodes, we can set the lifetime parameter of the ranging agent to a larger value. Let’s do that on all nodes. Also unsubscribe from phy to avoid too much clutter, but ensure that you’re subscribed to ranging on all 3 nodes (node A, node B and node C):

Nodes A, B and C:
> ranging.lifetime = 3600
> unsubscribe phy
> subscribe ranging

Now, initiate TWTT ranging to from node A to node B again:

Node A:
> ranging << new RangeReq(to: host('B'))
ranging >> RangeNtf:INFORM[from:31 to:232 range:371.0 offset:-256067128]

Not much of a difference here, but if you look at the shell for node B, you’ll see a notification:

Node B:
ranging >> RangeNtf:INFORM[from:232 to:31 range:371.0 offset:256067128]

The information in this RangeNtf is the same as the RangeNtf on node A, except that the to and from fields are exchanged, and the offset has the opposite sign. This makes sense, since the RangeNtf on node B is from node B’s perspective.

But why did node B receive this RangeNtf ? If we did a TWTT from node A, node A transmitted a frame, node B responded, and node A computed the two-way travel time. How did node B get that information to generate the RangeNtf ? Now that the lifetime is non-zero, node A transmits the range and time offset to node B to synchronize the nodes. We can verify this by asking node B for the synchronization information it has gleaned:

Node B:
> ranging << new SyncInfoReq(to: host('A'))
SyncInfoRsp:INFORM[to:232 offset:256067128 validTill:1568562001976]

In fact, if you look at the shell for node C, you’ll see that it hears this information as well, but it does not have any synchronization information to either node A or B:

Node C:
> ranging << new SyncInfoReq(to: host('A'))
REFUSE: Information unavailable
> ranging << new SyncInfoReq(to: host('B'))
REFUSE: Information unavailable

Let’s try TWTT ranging from node A to node C:

Node A:
> ranging << new RangeReq(to: host('C'))
ranging >> RangeNtf:INFORM[from:74 to:232 range:529.9 offset:630715082]

Now, if you check node C, you’ll see that it has not only gotten the RangeNtf , but also has stored the synchronization information:

Node C:
ranging >> RangeNtf:INFORM[from:232 to:74 range:529.9 offset:-630715082]
> ranging << new SyncInfoReq(to: host('A'))
SyncInfoRsp:INFORM[to:232 offset:-630715082 validTill:1568562266302]

Checking node B, we find that it has also heard the exchange between nodes A and C, and gotten a RangeNtf for it. More interestingly, it has synchronization information (time offset) for node C, although we did not ever do a TWTT exchange between nodes B and C! It has inferred the time offset to node C because it knew the time offset to node A, and overheard the time offset between node A and node C!

Node B:
ranging >> RangeNtf:INFORM[from:74 to:232 range:529.9]
> ranging << new SyncInfoReq(to: host('C'))
SyncInfoRsp:INFORM[to:74 offset:886782210 validTill:1568562266192]

Based on two TWTT exchanges, node A knows time offset to nodes B and C, node B knows time offset to nodes A and C, node C knows time offet to node A. Now that we have the nodes somewhat synchronized, we are in a position to try out OWTT now.

16.2.3. One-way travel time ranging

Let’s transmit a ranging beacon from node A:

Node A
> ranging << new BeaconReq()

On node B and C, we see RangeNtf from the OWTT ranging:

Node B
ranging >> RangeNtf:INFORM[from:232 to:31 range:371.0]
Node C
ranging >> RangeNtf:INFORM[from:232 to:74 range:529.9]
Any timestamped frame transmission from node A will generate RangeNtf on nodes B and C. This can be used to piggyback data (e.g. 42) along with the beacon: phy << new TxFrameReq(timestamped: true, data: [42]) . This will generate a RxFrameNtf on nodes B and C, if you subscribe to phy , in addition to the RangeNtf messages. This works with both CONTROL and DATA frames.

We can also get node A to request node C to transmit a beacon:

Node A
> ranging << new RangeReq(to: host('C'), reqBeacon: true)
ranging >> RangeNtf:INFORM[from:74 to:232 range:529.9]

This yeilds a RangeNtf back on node A, giving range from node C to node A. But since node B hears the beacon, and has synchronization information for node C, it also produces a RangeNtf with the range from node C to node B:

Node B
ranging >> RangeNtf:INFORM[from:74 to:31 range:615.9]

Once you have network time synchronization, you can have a lot of fun with OWTT ranging and beacons!

16.2.4. Expired synchronization information

What happens once synchronization information expires? Does the ranging agent no longer get the OWTT RangeNtf messages?

The RangeNtf messages are still produced, but the message attribute valid is set to false . This attribute can be used by client agents to initiate a TWTT exchange to renew synchronization information, if necessary. So, if you work with OWTT ranging, remember to check the valid attribute of RangeNtf messages that you receive, to ensure that they are based on unexpired synchronization information and therefore accurate.

<<< [Baseband service] [Node information] >>>