|
| 1 | +/**************************************************************************** |
| 2 | + * Copyright (C) 2025 Xiaomi Corporation |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + ***************************************************************************/ |
| 16 | + |
| 17 | + #include <math.h> |
| 18 | + #include <zephyr/bluetooth/cs.h> |
| 19 | + #include "le_cs_manager.h" |
| 20 | + |
| 21 | + #ifdef CONFIG_BLUETOOTH_LE_CS |
| 22 | + |
| 23 | +#define LE_CS_FREQUENCY_MHZ(ch) (2402u + 1u * (ch)) |
| 24 | +#define LE_CS_FREQUENCY_HZ(ch) (LE_CS_FREQUENCY_MHZ(ch) * 1000000.0f) |
| 25 | +#define LE_CS_SPEED_OF_LIGHT_M_PER_S (299792458.0f) |
| 26 | +#define LE_CS_SPEED_OF_LIGHT_NM_PER_S (LE_CS_SPEED_OF_LIGHT_M_PER_S / 1000000000.0f) |
| 27 | +#define PI 3.14159265358979323846f |
| 28 | +#define LE_CS_MAX_NUM_SAMPLES 256 |
| 29 | + |
| 30 | +struct iq_sample_and_channel { |
| 31 | + bool failed; |
| 32 | + uint8_t channel; |
| 33 | + uint8_t antenna_permutation; |
| 34 | + struct bt_le_cs_iq_sample local_iq_sample; |
| 35 | + struct bt_le_cs_iq_sample peer_iq_sample; |
| 36 | +}; |
| 37 | + |
| 38 | +struct rtt_timing { |
| 39 | + bool failed; |
| 40 | + int16_t initiator_toa_tod; |
| 41 | + int16_t reflector_tod_toa; |
| 42 | +}; |
| 43 | + |
| 44 | +static struct iq_sample_and_channel mode_2_data[LE_CS_MAX_NUM_SAMPLES]; |
| 45 | +static struct rtt_timing tof_data_records[LE_CS_MAX_NUM_SAMPLES]; |
| 46 | + |
| 47 | +struct cs_processing_state { |
| 48 | + bool processing_local_data; |
| 49 | + uint8_t mode_1_data_index; |
| 50 | + uint8_t mode_2_data_index; |
| 51 | + uint8_t n_ap; |
| 52 | + enum bt_conn_le_cs_role role; |
| 53 | +}; |
| 54 | + |
| 55 | +static void le_cs_compute_complex_mul(int32_t real1, int32_t imag1, |
| 56 | + int32_t real2, int32_t imag2, |
| 57 | + int32_t *result_real, int32_t *result_imag) |
| 58 | +{ |
| 59 | + *result_real = real1 * real2 - imag1 * imag2; |
| 60 | + *result_imag = real1 * imag2 + imag1 * real2; |
| 61 | +} |
| 62 | + |
| 63 | +static float le_cs_perform_linear_fit(float *x, float *y, uint8_t count) |
| 64 | +{ |
| 65 | + if (count == 0) { |
| 66 | + return 0.0f; |
| 67 | + } |
| 68 | + |
| 69 | + float mean_x = 0.0f, mean_y = 0.0f; |
| 70 | + |
| 71 | + for (uint8_t i = 0; i < count; i++) { |
| 72 | + mean_x += (x[i] - mean_x) / (i + 1); |
| 73 | + mean_y += (y[i] - mean_y) / (i + 1); |
| 74 | + } |
| 75 | + |
| 76 | + float numerator = 0.0f, denominator = 0.0f; |
| 77 | + |
| 78 | + for (uint8_t i = 0; i < count; i++) { |
| 79 | + float dx = x[i] - mean_x; |
| 80 | + float dy = y[i] - mean_y; |
| 81 | + numerator += dx * dy; |
| 82 | + denominator += dx * dx; |
| 83 | + } |
| 84 | + |
| 85 | + return numerator / denominator; |
| 86 | +} |
| 87 | + |
| 88 | +static void le_cs_sort_by_reference_array(float *keys, float *values, uint16_t size) |
| 89 | +{ |
| 90 | + for (uint16_t i = 0; i < size - 1; i++) { |
| 91 | + bool swapped = false; |
| 92 | + for (uint16_t j = 0; j < size - i - 1; j++) { |
| 93 | + if (keys[j] > keys[j + 1]) { |
| 94 | + float tmp_key = keys[j]; |
| 95 | + float tmp_val = values[j]; |
| 96 | + keys[j] = keys[j + 1]; |
| 97 | + values[j] = values[j + 1]; |
| 98 | + keys[j + 1] = tmp_key; |
| 99 | + values[j + 1] = tmp_val; |
| 100 | + swapped = true; |
| 101 | + } |
| 102 | + } |
| 103 | + if (!swapped) { |
| 104 | + break; |
| 105 | + } |
| 106 | + } |
| 107 | +} |
| 108 | + |
| 109 | +static float le_cs_compute_distance_from_phase_gradient(struct iq_sample_and_channel *samples, |
| 110 | + uint8_t sample_count) |
| 111 | +{ |
| 112 | + int32_t i_combined, q_combined; |
| 113 | + uint16_t valid_count = 0; |
| 114 | + static float phase_array[LE_CS_MAX_NUM_SAMPLES]; |
| 115 | + static float freq_array[LE_CS_MAX_NUM_SAMPLES]; |
| 116 | + |
| 117 | + for (uint8_t idx = 0; idx < sample_count; idx++) { |
| 118 | + if (!samples[idx].failed) { |
| 119 | + le_cs_compute_complex_mul(samples[idx].local_iq_sample.i, samples[idx].local_iq_sample.q, |
| 120 | + samples[idx].peer_iq_sample.i, samples[idx].peer_iq_sample.q, |
| 121 | + &i_combined, &q_combined); |
| 122 | + |
| 123 | + phase_array[valid_count] = atan2f((float)q_combined, (float)i_combined); |
| 124 | + freq_array[valid_count] = (float)LE_CS_FREQUENCY_MHZ(samples[idx].channel); |
| 125 | + valid_count++; |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + if (valid_count < 2) { |
| 130 | + return 0.0f; |
| 131 | + } |
| 132 | + |
| 133 | + sort_by_reference_array(freq_array, phase_array, valid_count); |
| 134 | + |
| 135 | + for (uint8_t i = 1; i < valid_count; i++) { |
| 136 | + float delta = phase_array[i] - phase_array[i - 1]; |
| 137 | + if (delta > PI) { |
| 138 | + for (uint8_t j = i; j < valid_count; j++) { |
| 139 | + phase_array[j] -= 2.0f * PI; |
| 140 | + } |
| 141 | + } else if (delta < -PI) { |
| 142 | + for (uint8_t j = i; j < valid_count; j++) { |
| 143 | + phase_array[j] += 2.0f * PI; |
| 144 | + } |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + float slope = le_cs_perform_linear_fit(freq_array, phase_array, valid_count); |
| 149 | + float estimated_distance = -slope * (LE_CS_SPEED_OF_LIGHT_NM_PER_S / (4.0f * PI)); |
| 150 | + |
| 151 | + return estimated_distance / 1000000.0f; // Convert to meters |
| 152 | +} |
| 153 | + |
| 154 | +static float calculate_distance_via_tof(uint8_t sample_count) |
| 155 | +{ |
| 156 | + float time_diff; |
| 157 | + float avg_tof = 0.0f; |
| 158 | + |
| 159 | + // Cumulative Moving Average (CMA) |
| 160 | + for (uint8_t index = 0; index < sample_count; index++) { |
| 161 | + if (!tof_data_records[index].is_invalid) { |
| 162 | + time_diff = (tof_data_records[index].initiator_toa_tod - |
| 163 | + tof_data_records[index].reflector_tod_toa) / 2.0f; |
| 164 | + |
| 165 | + avg_tof += (time_diff - avg_tof) / (index + 1); |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + float avg_tof_ns = avg_tof / 2.0f; |
| 170 | + |
| 171 | + return avg_tof_ns * LE_CS_SPEED_OF_LIGHT_NM_PER_S; |
| 172 | +} |
| 173 | + |
| 174 | +static bool handle_subevent_step_data(struct bt_le_cs_subevent_step *subevent_step, void *arg) |
| 175 | +{ |
| 176 | + struct cs_processing_state *state = (struct cs_processing_state *)arg; |
| 177 | + |
| 178 | + if (subevent_step->mode == BT_CONN_LE_CS_MAIN_MODE_2) { |
| 179 | + struct bt_hci_le_cs_step_data_mode_2 *mode2_raw = |
| 180 | + (struct bt_hci_le_cs_step_data_mode_2 *)subevent_step->data; |
| 181 | + |
| 182 | + if (state->processing_local_data) { |
| 183 | + for (uint8_t idx = 0; idx < (state->antenna_count + 1); idx++) { |
| 184 | + if (mode2_raw->tone_info[idx].extension_indicator != |
| 185 | + BT_HCI_LE_CS_NOT_TONE_EXT_SLOT) { |
| 186 | + continue; |
| 187 | + } |
| 188 | + |
| 189 | + phase_data_mode2[state->index_mode2].channel = subevent_step->channel; |
| 190 | + phase_data_mode2[state->index_mode2].antenna_permutation = |
| 191 | + mode2_raw->antenna_permutation_index; |
| 192 | + phase_data_mode2[state->index_mode2].local_iq = |
| 193 | + bt_le_cs_parse_pct(mode2_raw->tone_info[idx].phase_correction_term); |
| 194 | + |
| 195 | + if (mode2_raw->tone_info[idx].quality_indicator == |
| 196 | + BT_HCI_LE_CS_TONE_QUALITY_LOW || |
| 197 | + mode2_raw->tone_info[idx].quality_indicator == |
| 198 | + BT_HCI_LE_CS_TONE_QUALITY_UNAVAILABLE) { |
| 199 | + phase_data_mode2[state->index_mode2].invalid = true; |
| 200 | + } |
| 201 | + |
| 202 | + state->index_mode2++; |
| 203 | + } |
| 204 | + } else { |
| 205 | + for (uint8_t idx = 0; idx < (state->antenna_count + 1); idx++) { |
| 206 | + if (mode2_raw->tone_info[idx].extension_indicator != |
| 207 | + BT_HCI_LE_CS_NOT_TONE_EXT_SLOT) { |
| 208 | + continue; |
| 209 | + } |
| 210 | + |
| 211 | + phase_data_mode2[state->index_mode2].peer_iq = |
| 212 | + bt_le_cs_parse_pct(mode2_raw->tone_info[idx].phase_correction_term); |
| 213 | + |
| 214 | + if (mode2_raw->tone_info[idx].quality_indicator == |
| 215 | + BT_HCI_LE_CS_TONE_QUALITY_LOW || |
| 216 | + mode2_raw->tone_info[idx].quality_indicator == |
| 217 | + BT_HCI_LE_CS_TONE_QUALITY_UNAVAILABLE) { |
| 218 | + phase_data_mode2[state->index_mode2].invalid = true; |
| 219 | + } |
| 220 | + |
| 221 | + state->index_mode2++; |
| 222 | + } |
| 223 | + } |
| 224 | + } else if (subevent_step->mode == BT_HCI_OP_LE_CS_MAIN_MODE_1) { |
| 225 | + struct bt_hci_le_cs_step_data_mode_1 *mode1_raw = |
| 226 | + (struct bt_hci_le_cs_step_data_mode_1 *)subevent_step->data; |
| 227 | + |
| 228 | + if (mode1_raw->packet_quality_aa_check != |
| 229 | + BT_HCI_LE_CS_PACKET_QUALITY_AA_CHECK_SUCCESSFUL || |
| 230 | + mode1_raw->packet_rssi == BT_HCI_LE_CS_PACKET_RSSI_NOT_AVAILABLE || |
| 231 | + mode1_raw->tod_toa_reflector == BT_HCI_LE_CS_TIME_DIFFERENCE_NOT_AVAILABLE) { |
| 232 | + tof_data_mode1[state->index_mode1].invalid = true; |
| 233 | + } |
| 234 | + |
| 235 | + if (state->processing_local_data) { |
| 236 | + if (state->role == BT_CONN_LE_CS_ROLE_INITIATOR) { |
| 237 | + tof_data_mode1[state->index_mode1].initiator_toa_tod = |
| 238 | + mode1_raw->toa_tod_initiator; |
| 239 | + } else if (state->role == BT_CONN_LE_CS_ROLE_REFLECTOR) { |
| 240 | + tof_data_mode1[state->index_mode1].reflector_tod_toa = |
| 241 | + mode1_raw->tod_toa_reflector; |
| 242 | + } |
| 243 | + } else { |
| 244 | + if (state->role == BT_CONN_LE_CS_ROLE_INITIATOR) { |
| 245 | + tof_data_mode1[state->index_mode1].reflector_tod_toa = |
| 246 | + mode1_raw->tod_toa_reflector; |
| 247 | + } else if (state->role == BT_CONN_LE_CS_ROLE_REFLECTOR) { |
| 248 | + tof_data_mode1[state->index_mode1].initiator_toa_tod = |
| 249 | + mode1_raw->toa_tod_initiator; |
| 250 | + } |
| 251 | + } |
| 252 | + |
| 253 | + state->index_mode1++; |
| 254 | + } |
| 255 | + |
| 256 | + return true; |
| 257 | +} |
| 258 | + |
| 259 | +void bt_le_cs_run_distance_estimation(uint8_t *local_step_data, uint16_t local_data_len, |
| 260 | + uint8_t *peer_step_data, uint16_t peer_data_len, |
| 261 | + uint8_t antenna_count, enum bt_conn_le_cs_role device_role) |
| 262 | +{ |
| 263 | + struct net_buf_simple buffer; |
| 264 | + |
| 265 | + struct cs_processing_state state = { |
| 266 | + .processing_local_data = true, |
| 267 | + .index_mode_1 = 0, |
| 268 | + .index_mode_2 = 0, |
| 269 | + .antenna_count = antenna_count, |
| 270 | + .role = device_role, |
| 271 | + }; |
| 272 | + |
| 273 | + memset(tof_data_mode1, 0, sizeof(tof_data_mode1)); |
| 274 | + memset(phase_data_mode2, 0, sizeof(phase_data_mode2)); |
| 275 | + |
| 276 | + net_buf_simple_init_with_data(&buffer, local_step_data, local_data_len); |
| 277 | + bt_le_cs_step_data_parse(&buffer, handle_subevent_step_data, &state); |
| 278 | + |
| 279 | + state.index_mode_1 = 0; |
| 280 | + state.index_mode_2 = 0; |
| 281 | + state.processing_local_data = false; |
| 282 | + |
| 283 | + net_buf_simple_init_with_data(&buffer, peer_step_data, peer_data_len); |
| 284 | + bt_le_cs_step_data_parse(&buffer, handle_subevent_step_data, &state); |
| 285 | + |
| 286 | + float distance_by_phase = compute_distance_from_phase_gradient( |
| 287 | + phase_data_mode2, state.index_mode_2); |
| 288 | + |
| 289 | + float distance_by_tof = calculate_distance_via_tof(state.index_mode_1); |
| 290 | + |
| 291 | + if (distance_by_tof == 0.0f && distance_by_phase == 0.0f) { |
| 292 | + BT_LOGI("A reliable distance estimate could not be computed."); |
| 293 | + } else { |
| 294 | + BT_LOGI("Estimated distance to reflector:"); |
| 295 | + } |
| 296 | + |
| 297 | + if (distance_by_tof != 0.0f) { |
| 298 | + BT_LOGI("- Round-Trip Timing method: %f meters (derived from %d samples)\n", |
| 299 | + (double)distance_by_tof, state.index_mode_1); |
| 300 | + } |
| 301 | + |
| 302 | + if (distance_by_phase != 0.0f) { |
| 303 | + BT_LOGI("- Phase-Based Ranging method: %f meters (derived from %d samples)\n", |
| 304 | + (double)distance_by_phase, state.index_mode_2); |
| 305 | + } |
| 306 | + |
| 307 | + return; |
| 308 | +} |
| 309 | + |
| 310 | +#endif /* CONFIG_BLUETOOTH_LE_CS */ |
0 commit comments