Skip to content

Commit 66b14f7

Browse files
committed
Add ras and cs manager module.
bug: v/70712 Signed-off-by: huangyulong3 <huangyulong3@xiaomi.com>
1 parent d800bf1 commit 66b14f7

File tree

13 files changed

+4660
-0
lines changed

13 files changed

+4660
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
#ifndef __SAL_LE_CS_INTERFACE_H__
18+
#define __SAL_LE_CS_INTERFACE_H__
19+
20+
#include "bluetooth.h"
21+
#include <cs_service.h>
22+
#include <stdint.h>
23+
bt_status_t bt_sal_cs_read_remote_supported_capabilities(bt_controller_id_t id, bt_address_t* addr);
24+
bt_status_t bt_sal_cs_set_default_settings(bt_controller_id_t id, bt_address_t* addr,
25+
bt_le_srv_cs_set_default_settings_param_t* params);
26+
bt_status_t bt_sal_cs_read_remote_fae_table(bt_controller_id_t id, bt_address_t* addr);
27+
bt_status_t bt_sal_cs_create_config(bt_controller_id_t id, bt_address_t* addr,
28+
bt_le_srv_cs_create_config_params_t* params,
29+
bt_le_srv_cs_create_config_context_t context);
30+
bt_status_t bt_sal_cs_security_enable(bt_controller_id_t id, bt_address_t* addr);
31+
bt_status_t bt_sal_cs_procedure_enable(bt_address_t* addr, const bt_le_srv_cs_procedure_enable_param_t* params);
32+
bt_status_t bt_sal_cs_remove_config(bt_controller_id_t id, bt_address_t* addr, uint8_t config_id);
33+
bt_status_t bt_sal_cs_set_procedure_parameters(bt_controller_id_t id, bt_address_t* addr,
34+
const bt_le_srv_cs_set_procedure_parameters_param_t* params);
35+
bt_status_t bt_sal_cs_set_channel_classification(uint8_t channel_classification[10], bt_address_t* addr);
36+
bt_status_t bt_sal_cs_read_local_supported_capabilities(bt_srv_conn_le_cs_capabilities_t* params, bt_address_t* addr);
37+
bt_status_t bt_sal_cs_write_cached_remote_supported_capabilities(bt_srv_conn_le_cs_capabilities_t* params, bt_address_t* addr);
38+
39+
#endif //__SAL_LE_CS_INTERFACE_H__
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
#ifndef __LE_CS_MANAGER_H__
18+
#define __LE_CS_MANAGER_H__
19+
20+
#include <stdint.h>
21+
#include <zephyr/bluetooth/cs.h>
22+
23+
void bt_le_cs_run_distance_estimation(uint8_t *local_step_data, uint16_t local_data_len,
24+
uint8_t *peer_step_data, uint16_t peer_data_len,
25+
uint8_t antenna_count, enum bt_conn_le_cs_role device_role);
26+
27+
28+
#endif //__LE_CS_MANAGER_H__
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
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

Comments
 (0)