1 /*
2  * Copyright (c) 2024 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <math.h>
8 #include <zephyr/bluetooth/cs.h>
9 #include "distance_estimation.h"
10 
11 #define CS_FREQUENCY_MHZ(ch)    (2402u + 1u * (ch))
12 #define CS_FREQUENCY_HZ(ch)     (CS_FREQUENCY_MHZ(ch) * 1000000.0f)
13 #define SPEED_OF_LIGHT_M_PER_S  (299792458.0f)
14 #define SPEED_OF_LIGHT_NM_PER_S (SPEED_OF_LIGHT_M_PER_S / 1000000000.0f)
15 #define PI                      3.14159265358979323846f
16 #define MAX_NUM_SAMPLES         256
17 
18 struct iq_sample_and_channel {
19 	bool failed;
20 	uint8_t channel;
21 	uint8_t antenna_permutation;
22 	struct bt_le_cs_iq_sample local_iq_sample;
23 	struct bt_le_cs_iq_sample peer_iq_sample;
24 };
25 
26 struct rtt_timing {
27 	bool failed;
28 	int16_t toa_tod_initiator;
29 	int16_t tod_toa_reflector;
30 };
31 
32 static struct iq_sample_and_channel mode_2_data[MAX_NUM_SAMPLES];
33 static struct rtt_timing mode_1_data[MAX_NUM_SAMPLES];
34 
35 struct processing_context {
36 	bool local_steps;
37 	uint8_t mode_1_data_index;
38 	uint8_t mode_2_data_index;
39 	uint8_t n_ap;
40 	enum bt_conn_le_cs_role role;
41 };
42 
calc_complex_product(int32_t z_a_real,int32_t z_a_imag,int32_t z_b_real,int32_t z_b_imag,int32_t * z_out_real,int32_t * z_out_imag)43 static void calc_complex_product(int32_t z_a_real, int32_t z_a_imag, int32_t z_b_real,
44 				 int32_t z_b_imag, int32_t *z_out_real, int32_t *z_out_imag)
45 {
46 	*z_out_real = z_a_real * z_b_real - z_a_imag * z_b_imag;
47 	*z_out_imag = z_a_real * z_b_imag + z_a_imag * z_b_real;
48 }
49 
linear_regression(float * x_values,float * y_values,uint8_t n_samples)50 static float linear_regression(float *x_values, float *y_values, uint8_t n_samples)
51 {
52 	if (n_samples == 0) {
53 		return 0.0;
54 	}
55 
56 	/* Estimates b in y = a + b x */
57 
58 	float y_mean = 0.0;
59 	float x_mean = 0.0;
60 
61 	for (uint8_t i = 0; i < n_samples; i++) {
62 		y_mean += (y_values[i] - y_mean) / (i + 1);
63 		x_mean += (x_values[i] - x_mean) / (i + 1);
64 	}
65 
66 	float b_est_upper = 0.0;
67 	float b_est_lower = 0.0;
68 
69 	for (uint8_t i = 0; i < n_samples; i++) {
70 		b_est_upper += (x_values[i] - x_mean) * (y_values[i] - y_mean);
71 		b_est_lower += (x_values[i] - x_mean) * (x_values[i] - x_mean);
72 	}
73 
74 	return b_est_upper / b_est_lower;
75 }
76 
bubblesort_2(float * array1,float * array2,uint16_t len)77 static void bubblesort_2(float *array1, float *array2, uint16_t len)
78 {
79 	bool swapped;
80 	float temp;
81 
82 	for (uint16_t i = 0; i < len - 1; i++) {
83 		swapped = false;
84 		for (uint16_t j = 0; j < len - i - 1; j++) {
85 			if (array1[j] > array1[j + 1]) {
86 				temp = array1[j];
87 				array1[j] = array1[j + 1];
88 				array1[j + 1] = temp;
89 				temp = array2[j];
90 				array2[j] = array2[j + 1];
91 				array2[j + 1] = temp;
92 				swapped = true;
93 			}
94 		}
95 
96 		if (!swapped) {
97 			break;
98 		}
99 	}
100 }
101 
estimate_distance_using_phase_slope(struct iq_sample_and_channel * data,uint8_t len)102 static float estimate_distance_using_phase_slope(struct iq_sample_and_channel *data, uint8_t len)
103 {
104 	int32_t combined_i;
105 	int32_t combined_q;
106 	uint16_t num_angles = 0;
107 	static float theta[MAX_NUM_SAMPLES];
108 	static float frequencies[MAX_NUM_SAMPLES];
109 
110 	for (uint8_t i = 0; i < len; i++) {
111 		if (!data[i].failed) {
112 			calc_complex_product(data[i].local_iq_sample.i, data[i].local_iq_sample.q,
113 					     data[i].peer_iq_sample.i, data[i].peer_iq_sample.q,
114 					     &combined_i, &combined_q);
115 
116 			theta[num_angles] = atan2(1.0 * combined_q, 1.0 * combined_i);
117 			frequencies[num_angles] = 1.0 * CS_FREQUENCY_MHZ(data[i].channel);
118 			num_angles++;
119 		}
120 	}
121 
122 	if (num_angles < 2) {
123 		return 0.0;
124 	}
125 
126 	/* Sort phases by tone frequency */
127 	bubblesort_2(frequencies, theta, num_angles);
128 
129 	/* One-dimensional phase unwrapping */
130 	for (uint8_t i = 1; i < num_angles; i++) {
131 		float difference = theta[i] - theta[i - 1];
132 
133 		if (difference > PI) {
134 			for (uint8_t j = i; j < num_angles; j++) {
135 				theta[j] -= 2.0f * PI;
136 			}
137 		} else if (difference < -PI) {
138 			for (uint8_t j = i; j < num_angles; j++) {
139 				theta[j] += 2.0f * PI;
140 			}
141 		}
142 	}
143 
144 	float phase_slope = linear_regression(frequencies, theta, num_angles);
145 
146 	float distance = -phase_slope * (SPEED_OF_LIGHT_M_PER_S / (4 * PI));
147 
148 	return distance / 1000000.0f; /* Scale to meters. */
149 }
150 
estimate_distance_using_time_of_flight(uint8_t n_samples)151 static float estimate_distance_using_time_of_flight(uint8_t n_samples)
152 {
153 	float tof;
154 	float tof_mean = 0.0;
155 
156 	/* Cumulative Moving Average */
157 	for (uint8_t i = 0; i < n_samples; i++) {
158 		if (!mode_1_data[i].failed) {
159 			tof = (mode_1_data[i].toa_tod_initiator -
160 			       mode_1_data[i].tod_toa_reflector) /
161 			      2;
162 			tof_mean += (tof - tof_mean) / (i + 1);
163 		}
164 	}
165 
166 	float tof_mean_ns = tof_mean / 2.0f;
167 
168 	return tof_mean_ns * SPEED_OF_LIGHT_NM_PER_S;
169 }
170 
process_step_data(struct bt_le_cs_subevent_step * step,void * user_data)171 static bool process_step_data(struct bt_le_cs_subevent_step *step, void *user_data)
172 {
173 	struct processing_context *context = (struct processing_context *)user_data;
174 
175 	if (step->mode == BT_CONN_LE_CS_MAIN_MODE_2) {
176 		struct bt_hci_le_cs_step_data_mode_2 *step_data =
177 			(struct bt_hci_le_cs_step_data_mode_2 *)step->data;
178 
179 		if (context->local_steps) {
180 			for (uint8_t i = 0; i < (context->n_ap + 1); i++) {
181 				if (step_data->tone_info[i].extension_indicator !=
182 				    BT_HCI_LE_CS_NOT_TONE_EXT_SLOT) {
183 					continue;
184 				}
185 
186 				mode_2_data[context->mode_2_data_index].channel = step->channel;
187 				mode_2_data[context->mode_2_data_index].antenna_permutation =
188 					step_data->antenna_permutation_index;
189 				mode_2_data[context->mode_2_data_index].local_iq_sample =
190 					bt_le_cs_parse_pct(
191 						step_data->tone_info[i].phase_correction_term);
192 				if (step_data->tone_info[i].quality_indicator ==
193 					    BT_HCI_LE_CS_TONE_QUALITY_LOW ||
194 				    step_data->tone_info[i].quality_indicator ==
195 					    BT_HCI_LE_CS_TONE_QUALITY_UNAVAILABLE) {
196 					mode_2_data[context->mode_2_data_index].failed = true;
197 				}
198 
199 				context->mode_2_data_index++;
200 			}
201 		} else {
202 			for (uint8_t i = 0; i < (context->n_ap + 1); i++) {
203 				if (step_data->tone_info[i].extension_indicator !=
204 				    BT_HCI_LE_CS_NOT_TONE_EXT_SLOT) {
205 					continue;
206 				}
207 
208 				mode_2_data[context->mode_2_data_index].peer_iq_sample =
209 					bt_le_cs_parse_pct(
210 						step_data->tone_info[i].phase_correction_term);
211 				if (step_data->tone_info[i].quality_indicator ==
212 					    BT_HCI_LE_CS_TONE_QUALITY_LOW ||
213 				    step_data->tone_info[i].quality_indicator ==
214 					    BT_HCI_LE_CS_TONE_QUALITY_UNAVAILABLE) {
215 					mode_2_data[context->mode_2_data_index].failed = true;
216 				}
217 
218 				context->mode_2_data_index++;
219 			}
220 		}
221 	} else if (step->mode == BT_HCI_OP_LE_CS_MAIN_MODE_1) {
222 		struct bt_hci_le_cs_step_data_mode_1 *step_data =
223 			(struct bt_hci_le_cs_step_data_mode_1 *)step->data;
224 
225 		if (step_data->packet_quality_aa_check !=
226 			    BT_HCI_LE_CS_PACKET_QUALITY_AA_CHECK_SUCCESSFUL ||
227 		    step_data->packet_rssi == BT_HCI_LE_CS_PACKET_RSSI_NOT_AVAILABLE ||
228 		    step_data->tod_toa_reflector == BT_HCI_LE_CS_TIME_DIFFERENCE_NOT_AVAILABLE) {
229 			mode_1_data[context->mode_1_data_index].failed = true;
230 		}
231 
232 		if (context->local_steps) {
233 			if (context->role == BT_CONN_LE_CS_ROLE_INITIATOR) {
234 				mode_1_data[context->mode_1_data_index].toa_tod_initiator =
235 					step_data->toa_tod_initiator;
236 			} else if (context->role == BT_CONN_LE_CS_ROLE_REFLECTOR) {
237 				mode_1_data[context->mode_1_data_index].tod_toa_reflector =
238 					step_data->tod_toa_reflector;
239 			}
240 		} else {
241 			if (context->role == BT_CONN_LE_CS_ROLE_INITIATOR) {
242 				mode_1_data[context->mode_1_data_index].tod_toa_reflector =
243 					step_data->tod_toa_reflector;
244 			} else if (context->role == BT_CONN_LE_CS_ROLE_REFLECTOR) {
245 				mode_1_data[context->mode_1_data_index].toa_tod_initiator =
246 					step_data->toa_tod_initiator;
247 			}
248 		}
249 
250 		context->mode_1_data_index++;
251 	}
252 
253 	return true;
254 }
255 
estimate_distance(uint8_t * local_steps,uint16_t local_steps_len,uint8_t * peer_steps,uint16_t peer_steps_len,uint8_t n_ap,enum bt_conn_le_cs_role role)256 void estimate_distance(uint8_t *local_steps, uint16_t local_steps_len, uint8_t *peer_steps,
257 		       uint16_t peer_steps_len, uint8_t n_ap, enum bt_conn_le_cs_role role)
258 {
259 	struct net_buf_simple buf;
260 
261 	struct processing_context context = {
262 		.local_steps = true,
263 		.mode_1_data_index = 0,
264 		.mode_2_data_index = 0,
265 		.n_ap = n_ap,
266 		.role = role,
267 	};
268 
269 	memset(mode_1_data, 0, sizeof(mode_1_data));
270 	memset(mode_2_data, 0, sizeof(mode_2_data));
271 
272 	net_buf_simple_init_with_data(&buf, local_steps, local_steps_len);
273 
274 	bt_le_cs_step_data_parse(&buf, process_step_data, &context);
275 
276 	context.mode_1_data_index = 0;
277 	context.mode_2_data_index = 0;
278 	context.local_steps = false;
279 
280 	net_buf_simple_init_with_data(&buf, peer_steps, peer_steps_len);
281 
282 	bt_le_cs_step_data_parse(&buf, process_step_data, &context);
283 
284 	float phase_slope_based_distance =
285 		estimate_distance_using_phase_slope(mode_2_data, context.mode_2_data_index);
286 
287 	float rtt_based_distance =
288 		estimate_distance_using_time_of_flight(context.mode_1_data_index);
289 
290 	if (rtt_based_distance == 0.0f && phase_slope_based_distance == 0.0f) {
291 		printk("A reliable distance estimate could not be computed.\n");
292 	} else {
293 		printk("Estimated distance to reflector:\n");
294 	}
295 
296 	if (rtt_based_distance != 0.0f) {
297 		printk("- Round-Trip Timing method: %f meters (derived from %d samples)\n",
298 		       (double)rtt_based_distance, context.mode_1_data_index);
299 	}
300 	if (phase_slope_based_distance != 0.0f) {
301 		printk("- Phase-Based Ranging method: %f meters (derived from %d samples)\n",
302 		       (double)phase_slope_based_distance, context.mode_2_data_index);
303 	}
304 }
305