1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 #include <assert.h>
21 #include <string.h>
22 #include <stdio.h>
23 #include <errno.h>
24
25 #include <rtthread.h>
26
27 #include "config/config.h"
28 #include "nimble/ble.h"
29 #include "host/ble_hs.h"
30 #include "services/gap/ble_svc_gap.h"
31 #include "blecsc_sens.h"
32
33 /* Wheel size for simulation calculations */
34 #define CSC_SIM_WHEEL_CIRCUMFERENCE_MM 2000
35 /* Simulated cadence lower limit */
36 #define CSC_SIM_CRANK_RPM_MIN 20
37 /* Simulated cadence upper limit */
38 #define CSC_SIM_CRANK_RPM_MAX 100
39 /* Simulated speed lower limit */
40 #define CSC_SIM_SPEED_KPH_MIN 0
41 /* Simulated speed upper limit */
42 #define CSC_SIM_SPEED_KPH_MAX 35
43
44 /* Noticication status */
45 static bool notify_state = false;
46
47 /* Connection handle */
48 static uint16_t conn_handle;
49
50 static uint8_t blecsc_addr_type;
51
52 /* Advertised device name */
53 static const char *device_name = "blecsc_sensor";
54
55 /* Measurement and notification timer */
56 static struct ble_npl_callout blecsc_measure_timer;
57
58 /* Variable holds current CSC measurement state */
59 static struct ble_csc_measurement_state csc_measurement_state;
60
61 /* Variable holds simulted speed (kilometers per hour) */
62 static uint16_t csc_sim_speed_kph = CSC_SIM_SPEED_KPH_MIN;
63
64 /* Variable holds simulated cadence (RPM) */
65 static uint8_t csc_sim_crank_rpm = CSC_SIM_CRANK_RPM_MIN;
66
67 static int blecsc_gap_event(struct ble_gap_event *event, void *arg);
68
69
70 /*
71 * Enables advertising with parameters:
72 * o General discoverable mode
73 * o Undirected connectable mode
74 */
75 static void
blecsc_advertise(void)76 blecsc_advertise(void)
77 {
78 struct ble_gap_adv_params adv_params;
79 struct ble_hs_adv_fields fields;
80 int rc;
81
82 /*
83 * Set the advertisement data included in our advertisements:
84 * o Flags (indicates advertisement type and other general info)
85 * o Advertising tx power
86 * o Device name
87 */
88 memset(&fields, 0, sizeof(fields));
89
90 /*
91 * Advertise two flags:
92 * o Discoverability in forthcoming advertisement (general)
93 * o BLE-only (BR/EDR unsupported)
94 */
95 fields.flags = BLE_HS_ADV_F_DISC_GEN |
96 BLE_HS_ADV_F_BREDR_UNSUP;
97
98 /*
99 * Indicate that the TX power level field should be included; have the
100 * stack fill this value automatically. This is done by assigning the
101 * special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
102 */
103 fields.tx_pwr_lvl_is_present = 1;
104 fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
105
106 fields.name = (uint8_t *)device_name;
107 fields.name_len = strlen(device_name);
108 fields.name_is_complete = 1;
109
110 /*
111 * Set appearance.
112 */
113 fields.appearance = ble_svc_gap_device_appearance();
114 fields.appearance_is_present = 1;
115
116 rc = ble_gap_adv_set_fields(&fields);
117 if (rc != 0) {
118 MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc);
119 return;
120 }
121
122 /* Begin advertising */
123 memset(&adv_params, 0, sizeof(adv_params));
124 adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
125 adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
126 rc = ble_gap_adv_start(blecsc_addr_type, NULL, BLE_HS_FOREVER,
127 &adv_params, blecsc_gap_event, NULL);
128 if (rc != 0) {
129 MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc);
130 return;
131 }
132 }
133
134
135 /* Update simulated CSC measurements.
136 * Each call increments wheel and crank revolution counters by one and
137 * computes last event time in order to match simulated candence and speed.
138 * Last event time is expressedd in 1/1024th of second units.
139 *
140 * 60 * 1024
141 * crank_dt = --------------
142 * cadence[RPM]
143 *
144 *
145 * circumference[mm] * 1024 * 60 * 60
146 * wheel_dt = -------------------------------------
147 * 10^6 * speed [kph]
148 */
149 static void
blecsc_simulate_speed_and_cadence()150 blecsc_simulate_speed_and_cadence()
151 {
152 uint16_t wheel_rev_period;
153 uint16_t crank_rev_period;
154
155 /* Update simulated crank and wheel rotation speed */
156 csc_sim_speed_kph++;
157 if (csc_sim_speed_kph >= CSC_SIM_SPEED_KPH_MAX) {
158 csc_sim_speed_kph = CSC_SIM_SPEED_KPH_MIN;
159 }
160
161 csc_sim_crank_rpm++;
162 if (csc_sim_crank_rpm >= CSC_SIM_CRANK_RPM_MAX) {
163 csc_sim_crank_rpm = CSC_SIM_CRANK_RPM_MIN;
164 }
165
166 /* Calculate simulated measurement values */
167 if (csc_sim_speed_kph > 0){
168 wheel_rev_period = (36*64*CSC_SIM_WHEEL_CIRCUMFERENCE_MM) /
169 (625*csc_sim_speed_kph);
170 csc_measurement_state.cumulative_wheel_rev++;
171 csc_measurement_state.last_wheel_evt_time += wheel_rev_period;
172 }
173
174 if (csc_sim_crank_rpm > 0){
175 crank_rev_period = (60*1024) / csc_sim_crank_rpm;
176 csc_measurement_state.cumulative_crank_rev++;
177 csc_measurement_state.last_crank_evt_time += crank_rev_period;
178 }
179
180 MODLOG_DFLT(INFO, "CSC simulated values: speed = %d kph, cadence = %d \n",
181 csc_sim_speed_kph, csc_sim_crank_rpm);
182 }
183
184 /* Run CSC measurement simulation and notify it to the client */
185 static void
blecsc_measurement(struct ble_npl_event * ev)186 blecsc_measurement(struct ble_npl_event *ev)
187 {
188 int rc;
189
190 rc = ble_npl_callout_reset(&blecsc_measure_timer, RT_TICK_PER_SECOND);
191 assert(rc == 0);
192
193 blecsc_simulate_speed_and_cadence();
194
195 if (notify_state) {
196 rc = gatt_svr_chr_notify_csc_measurement(conn_handle);
197 assert(rc == 0);
198 }
199 }
200
201 static int
blecsc_gap_event(struct ble_gap_event * event,void * arg)202 blecsc_gap_event(struct ble_gap_event *event, void *arg)
203 {
204 switch (event->type) {
205 case BLE_GAP_EVENT_CONNECT:
206 /* A new connection was established or a connection attempt failed */
207 MODLOG_DFLT(INFO, "connection %s; status=%d\n",
208 event->connect.status == 0 ? "established" : "failed",
209 event->connect.status);
210
211 if (event->connect.status != 0) {
212 /* Connection failed; resume advertising */
213 blecsc_advertise();
214 conn_handle = 0;
215 }
216 else {
217 conn_handle = event->connect.conn_handle;
218 }
219 break;
220
221 case BLE_GAP_EVENT_DISCONNECT:
222 MODLOG_DFLT(INFO, "disconnect; reason=%d\n", event->disconnect.reason);
223 conn_handle = 0;
224 /* Connection terminated; resume advertising */
225 blecsc_advertise();
226 break;
227
228 case BLE_GAP_EVENT_ADV_COMPLETE:
229 MODLOG_DFLT(INFO, "adv complete\n");
230 break;
231
232 case BLE_GAP_EVENT_SUBSCRIBE:
233 MODLOG_DFLT(INFO, "subscribe event attr_handle=%d\n",
234 event->subscribe.attr_handle);
235
236 if (event->subscribe.attr_handle == csc_measurement_handle) {
237 notify_state = event->subscribe.cur_notify;
238 MODLOG_DFLT(INFO, "csc measurement notify state = %d\n",
239 notify_state);
240 }
241 else if (event->subscribe.attr_handle == csc_control_point_handle) {
242 gatt_svr_set_cp_indicate(event->subscribe.cur_indicate);
243 MODLOG_DFLT(INFO, "csc control point indicate state = %d\n",
244 event->subscribe.cur_indicate);
245 }
246 break;
247
248 case BLE_GAP_EVENT_MTU:
249 MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d mtu=%d\n",
250 event->mtu.conn_handle,
251 event->mtu.value);
252 break;
253
254 }
255
256 return 0;
257 }
258
259 static void
blecsc_on_sync(void)260 blecsc_on_sync(void)
261 {
262 int rc;
263
264 /* Figure out address to use while advertising (no privacy) */
265 rc = ble_hs_id_infer_auto(0, &blecsc_addr_type);
266 assert(rc == 0);
267
268 /* Begin advertising */
269 blecsc_advertise();
270 }
271
272 extern struct ble_npl_eventq *nimble_port_get_dflt_eventq(void);
273 /*
274 * main
275 *
276 * The main task for the project. This function initializes the packages,
277 * then starts serving events from default event queue.
278 *
279 * @return int NOTE: this function should never return!
280 */
blecsc_sens_entry(void)281 int blecsc_sens_entry(void)
282 {
283 int rc;
284
285
286 /* Initialize the NimBLE host configuration */
287 ble_hs_cfg.sync_cb = blecsc_on_sync;
288
289 /* Initialize measurement and notification timer */
290 ble_npl_callout_init(&blecsc_measure_timer, nimble_port_get_dflt_eventq(),
291 blecsc_measurement, NULL);
292 rc = ble_npl_callout_reset(&blecsc_measure_timer, RT_TICK_PER_SECOND);
293 assert(rc == 0);
294
295 rc = gatt_svr_init(&csc_measurement_state);
296 assert(rc == 0);
297
298 /* Set the default device name */
299 rc = ble_svc_gap_device_name_set(device_name);
300 assert(rc == 0);
301
302 /* startup bluetooth host stack*/
303 ble_hs_thread_startup();
304
305 return 0;
306 }
307
308 MSH_CMD_EXPORT_ALIAS(blecsc_sens_entry, blecsc_sens, "bluetooth cycle sensor sample");
309