README.md
1# Clock Synchronization
2
3How it works
4
5## Step 1 - rough sync
6
7 T0 = current_time()
8 Tell the remote to zero clock.
9 Wait for confirmation from remote
10 maxE = current_time() - T0
11 All further local time is measured from T0
12
13
14After this step we are sure that the remote clock lags behind the local clock by
15some value E. And we know that E >= 0 because remote was zeroed *after* we
16zeroed the local time (recored T0). And also E<= maxE. So 0 = minE < E < maxE.
17
18
19## Step 2 - find better lower bound - `minE`
20
21Send some messages from local to remote, note the time right before sending the
22message (denote it as `t_local`) and have the remote reply with his timestamps
23of when it received the messages according to his clock that lags by unknown
24positive value `E` behind the local clock, denote it by `t_remote`.
25
26
27 t_remote = t_local - E + travel_time
28 E = t_local - t_remote + travel_time > t_local - t_remote
29 since travel_time > 0
30 E > t_local - t_remote
31
32 set minE to max(minE, t_local - t_remote)
33 Repeat
34
35We need to first send a bunch of messages with some random small delays, and
36only after that get the remote timestamps for all of them. This helps deal with
37unwanted buffering and delay added by the kernel of hardware in the outgoing
38direction.
39
40## Step 3 - find better upper bound `maxE`
41
42Same idea, but in the opposite direction. Remote device sends us messages and
43then the timestamps according to his clock of when they were sent. We record the
44local timestamps when we receive them.
45
46 t_local = t_remote + E + travel_time
47 E = t_local - t_remote - travel time < t_local - t_remote
48 set maxE = min(maxE, t_local - t_remote)
49 Repeat
50
51## Comparison with NTP
52
53NTP measures the mean travel_time (latency) and assumes it to be symmetric - the
54same in both directions. If the symmetry assumption is broken, there is no way
55to detect this. Both, systematic asymmetry in latency and clock difference would
56result in exactly the same observations -
57[explanation here](http://cs.stackexchange.com/questions/103/clock-synchronization-in-a-network-with-asymmetric-delays).
58
59In our case the latency can be relatively small compared to network, but is
60likely to be asymmetric due to the asymmetric nature of USB. The algorithm
61described above guarantees that the clock difference is within the measured
62bounds `minE < E < maxE`, though the resulting interval `deltaE = maxE - minE`
63can be fairly large compared to synchronization accuracy of NTP on a network
64with good latency symmetry.
65
66Observed values for `deltaE`
67 - Linux desktop machine (HP Z420), USB2 port: ~100us
68 - Same Linux machine, USB3 port: ~800us
69 - Nexus 5 ~100us
70 - Nexus 7 (both the old and the newer model) ~300us
71 - Samsung Galaxy S3 ~150us
72
73
74
75## Implementation notes
76
77General
78 - All times in this C code are recored in microseconds, unless otherwise
79 specified.
80 - The timestamped messages are all single byte.
81
82USB communication
83 - USB hierarchy recap: USB device has several interfaces. Each interface has
84 several endpoints. Endpoints are directional IN = data going into the host,
85 OUT = data going out of the host. To get data from the device via an IN
86 endpoint, we must query it.
87 - There are two types of endpoints - BULK and INTERRUPT. For our case it's not
88 really important. Currently all the comms are done via a BULK interface
89 exposed when you compile Teensy code in "Serial".
90 - All the comms are done using the Linux API declared in linux/usbdevice_fs.h
91 - The C code can be compiled for both Android JNI and Linux.
92 - The C code is partially based on the code of libusbhost from the Android OS
93 core code, but does not use that library because it's an overkill for our
94 needs.
95
96## There are two ways of communicating via usbdevice_fs
97
98 // Async way
99 ioctl(fd, USBDEVFS_SUBMITURB, urb);
100 // followed by
101 ioctl(fd, USBDEVFS_REAPURB, &urb); // Blocks until there is a URB to read.
102
103 // Sync way
104 struct usbdevfs_bulktransfer ctrl;
105 ctrl.ep = endpoint;
106 ctrl.len = length;
107 ctrl.data = buffer;
108 ctrl.timeout = timeout; // [milliseconds] Will timeout if there is nothing to read
109 int ret = ioctl(fd, USBDEVFS_BULK, &ctrl);
110
111
112
113