From f4f763d5dbe0919ce72d13fb8576282789b787a0 Mon Sep 17 00:00:00 2001 From: Szymon Janc Date: Wed, 30 May 2018 15:40:44 +0200 Subject: [PATCH] Add sample applications from mynewt-core repository --- apps/blecent/pkg.yml | 36 + apps/blecent/src/blecent.h | 120 + apps/blecent/src/main.c | 530 ++++ apps/blecent/src/misc.c | 209 ++ apps/blecent/src/peer.c | 807 ++++++ apps/blecent/syscfg.yml | 26 + apps/blecsc/README.md | 9 + apps/blecsc/pkg.yml | 40 + apps/blecsc/src/blecsc_sens.h | 114 + apps/blecsc/src/gatt_svr.c | 385 +++ apps/blecsc/src/main.c | 319 +++ apps/blecsc/syscfg.yml | 38 + apps/blehci/pkg.yml | 30 + apps/blehci/src/main.c | 33 + apps/blehci/syscfg.yml | 23 + apps/blehr/README.md | 9 + apps/blehr/pkg.yml | 40 + apps/blehr/src/blehr_sens.h | 55 + apps/blehr/src/gatt_svr.c | 177 ++ apps/blehr/src/main.c | 262 ++ apps/blehr/syscfg.yml | 35 + apps/blemesh/pkg.yml | 36 + apps/blemesh/src/main.c | 436 +++ apps/blemesh/syscfg.yml | 48 + apps/blemesh_light/pkg.yml | 36 + apps/blemesh_light/src/light_model.c | 242 ++ apps/blemesh_light/src/light_model.h | 37 + apps/blemesh_light/src/main.c | 124 + apps/blemesh_light/src/ws2812.c | 133 + apps/blemesh_light/src/ws2812.h | 42 + apps/blemesh_light/syscfg.yml | 60 + apps/blemesh_shell/pkg.yml | 36 + apps/blemesh_shell/src/main.c | 114 + apps/blemesh_shell/syscfg.yml | 65 + apps/bleprph/pkg.yml | 43 + apps/bleprph/src/bleprph.h | 70 + apps/bleprph/src/gatt_svr.c | 204 ++ apps/bleprph/src/main.c | 356 +++ apps/bleprph/src/misc.c | 43 + apps/bleprph/src/phy.c | 128 + apps/bleprph/syscfg.yml | 65 + apps/bletest/pkg.yml | 37 + apps/bletest/src/bletest_hci.c | 782 ++++++ apps/bletest/src/bletest_priv.h | 86 + apps/bletest/src/main.c | 1326 +++++++++ apps/bletest/syscfg.yml | 34 + apps/btshell/pkg.yml | 38 + apps/btshell/src/btshell.h | 200 ++ apps/btshell/src/cmd.c | 3764 ++++++++++++++++++++++++++ apps/btshell/src/cmd.h | 64 + apps/btshell/src/cmd_gatt.c | 587 ++++ apps/btshell/src/cmd_gatt.h | 39 + apps/btshell/src/cmd_l2cap.c | 261 ++ apps/btshell/src/cmd_l2cap.h | 32 + apps/btshell/src/gatt_svr.c | 595 ++++ apps/btshell/src/main.c | 2116 +++++++++++++++ apps/btshell/src/misc.c | 162 ++ apps/btshell/src/parse.c | 587 ++++ apps/btshell/syscfg.yml | 44 + 59 files changed, 16369 insertions(+) create mode 100644 apps/blecent/pkg.yml create mode 100644 apps/blecent/src/blecent.h create mode 100644 apps/blecent/src/main.c create mode 100644 apps/blecent/src/misc.c create mode 100644 apps/blecent/src/peer.c create mode 100644 apps/blecent/syscfg.yml create mode 100644 apps/blecsc/README.md create mode 100644 apps/blecsc/pkg.yml create mode 100644 apps/blecsc/src/blecsc_sens.h create mode 100644 apps/blecsc/src/gatt_svr.c create mode 100644 apps/blecsc/src/main.c create mode 100644 apps/blecsc/syscfg.yml create mode 100644 apps/blehci/pkg.yml create mode 100644 apps/blehci/src/main.c create mode 100644 apps/blehci/syscfg.yml create mode 100644 apps/blehr/README.md create mode 100644 apps/blehr/pkg.yml create mode 100644 apps/blehr/src/blehr_sens.h create mode 100644 apps/blehr/src/gatt_svr.c create mode 100644 apps/blehr/src/main.c create mode 100644 apps/blehr/syscfg.yml create mode 100644 apps/blemesh/pkg.yml create mode 100644 apps/blemesh/src/main.c create mode 100644 apps/blemesh/syscfg.yml create mode 100644 apps/blemesh_light/pkg.yml create mode 100644 apps/blemesh_light/src/light_model.c create mode 100644 apps/blemesh_light/src/light_model.h create mode 100755 apps/blemesh_light/src/main.c create mode 100755 apps/blemesh_light/src/ws2812.c create mode 100755 apps/blemesh_light/src/ws2812.h create mode 100644 apps/blemesh_light/syscfg.yml create mode 100644 apps/blemesh_shell/pkg.yml create mode 100644 apps/blemesh_shell/src/main.c create mode 100644 apps/blemesh_shell/syscfg.yml create mode 100644 apps/bleprph/pkg.yml create mode 100644 apps/bleprph/src/bleprph.h create mode 100644 apps/bleprph/src/gatt_svr.c create mode 100644 apps/bleprph/src/main.c create mode 100644 apps/bleprph/src/misc.c create mode 100644 apps/bleprph/src/phy.c create mode 100644 apps/bleprph/syscfg.yml create mode 100644 apps/bletest/pkg.yml create mode 100644 apps/bletest/src/bletest_hci.c create mode 100644 apps/bletest/src/bletest_priv.h create mode 100644 apps/bletest/src/main.c create mode 100644 apps/bletest/syscfg.yml create mode 100644 apps/btshell/pkg.yml create mode 100644 apps/btshell/src/btshell.h create mode 100644 apps/btshell/src/cmd.c create mode 100644 apps/btshell/src/cmd.h create mode 100644 apps/btshell/src/cmd_gatt.c create mode 100644 apps/btshell/src/cmd_gatt.h create mode 100644 apps/btshell/src/cmd_l2cap.c create mode 100644 apps/btshell/src/cmd_l2cap.h create mode 100644 apps/btshell/src/gatt_svr.c create mode 100644 apps/btshell/src/main.c create mode 100644 apps/btshell/src/misc.c create mode 100644 apps/btshell/src/parse.c create mode 100644 apps/btshell/syscfg.yml diff --git a/apps/blecent/pkg.yml b/apps/blecent/pkg.yml new file mode 100644 index 000000000..2674f5f8d --- /dev/null +++ b/apps/blecent/pkg.yml @@ -0,0 +1,36 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +pkg.name: apps/blecent +pkg.type: app +pkg.description: Simple BLE central application. +pkg.author: "Apache Mynewt " +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + +pkg.deps: + - "@apache-mynewt-core/kernel/os" + - "@apache-mynewt-core/sys/console/full" + - "@apache-mynewt-core/sys/log/full" + - "@apache-mynewt-core/sys/stats/full" + - nimble/controller + - nimble/host + - nimble/host/util + - nimble/host/services/gap + - nimble/host/services/gatt + - nimble/host/store/ram + - nimble/transport/ram diff --git a/apps/blecent/src/blecent.h b/apps/blecent/src/blecent.h new file mode 100644 index 000000000..882d85686 --- /dev/null +++ b/apps/blecent/src/blecent.h @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef H_BLECENT_ +#define H_BLECENT_ + +#include "os/mynewt.h" +#include "log/log.h" +#ifdef __cplusplus +extern "C" { +#endif + +struct ble_hs_adv_fields; +struct ble_gap_conn_desc; +struct ble_hs_cfg; +union ble_store_value; +union ble_store_key; + +extern struct log blecent_log; + +/* blecent uses the first "peruser" log module. */ +#define BLECENT_LOG_MODULE (LOG_MODULE_PERUSER + 0) + +/* Convenience macro for logging to the blecent module. */ +#define BLECENT_LOG(lvl, ...) \ + LOG_ ## lvl(&blecent_log, BLECENT_LOG_MODULE, __VA_ARGS__) + +#define BLECENT_SVC_ALERT_UUID 0x1811 +#define BLECENT_CHR_SUP_NEW_ALERT_CAT_UUID 0x2A47 +#define BLECENT_CHR_NEW_ALERT 0x2A46 +#define BLECENT_CHR_SUP_UNR_ALERT_CAT_UUID 0x2A48 +#define BLECENT_CHR_UNR_ALERT_STAT_UUID 0x2A45 +#define BLECENT_CHR_ALERT_NOT_CTRL_PT 0x2A44 + +/** Misc. */ +void print_bytes(const uint8_t *bytes, int len); +void print_mbuf(const struct os_mbuf *om); +char *addr_str(const void *addr); +void print_uuid(const ble_uuid_t *uuid); +void print_conn_desc(const struct ble_gap_conn_desc *desc); +void print_adv_fields(const struct ble_hs_adv_fields *fields); + +/** Peer. */ +struct peer_dsc { + SLIST_ENTRY(peer_dsc) next; + struct ble_gatt_dsc dsc; +}; +SLIST_HEAD(peer_dsc_list, peer_dsc); + +struct peer_chr { + SLIST_ENTRY(peer_chr) next; + struct ble_gatt_chr chr; + + struct peer_dsc_list dscs; +}; +SLIST_HEAD(peer_chr_list, peer_chr); + +struct peer_svc { + SLIST_ENTRY(peer_svc) next; + struct ble_gatt_svc svc; + + struct peer_chr_list chrs; +}; +SLIST_HEAD(peer_svc_list, peer_svc); + +struct peer; +typedef void peer_disc_fn(const struct peer *peer, int status, void *arg); + +struct peer { + SLIST_ENTRY(peer) next; + + uint16_t conn_handle; + + /** List of discovered GATT services. */ + struct peer_svc_list svcs; + + /** Keeps track of where we are in the service discovery process. */ + uint16_t disc_prev_chr_val; + struct peer_svc *cur_svc; + + /** Callback that gets executed when service discovery completes. */ + peer_disc_fn *disc_cb; + void *disc_cb_arg; +}; + +int peer_disc_all(uint16_t conn_handle, peer_disc_fn *disc_cb, + void *disc_cb_arg); +const struct peer_dsc * +peer_dsc_find_uuid(const struct peer *peer, const ble_uuid_t *svc_uuid, + const ble_uuid_t *chr_uuid, const ble_uuid_t *dsc_uuid); +const struct peer_chr * +peer_chr_find_uuid(const struct peer *peer, const ble_uuid_t *svc_uuid, + const ble_uuid_t *chr_uuid); +const struct peer_svc * +peer_svc_find_uuid(const struct peer *peer, const ble_uuid_t *uuid); +int peer_delete(uint16_t conn_handle); +int peer_add(uint16_t conn_handle); +int peer_init(int max_peers, int max_svcs, int max_chrs, int max_dscs); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/apps/blecent/src/main.c b/apps/blecent/src/main.c new file mode 100644 index 000000000..ff44fbf95 --- /dev/null +++ b/apps/blecent/src/main.c @@ -0,0 +1,530 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include "os/mynewt.h" +#include "bsp/bsp.h" + +/* BLE */ +#include "nimble/ble.h" +#include "controller/ble_ll.h" +#include "host/ble_hs.h" +#include "host/util/util.h" + +/* RAM HCI transport. */ +#include "transport/ram/ble_hci_ram.h" + +/* Mandatory services. */ +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" + +/* Application-specified header. */ +#include "blecent.h" + +/** Log data. */ +struct log blecent_log; + +static int blecent_gap_event(struct ble_gap_event *event, void *arg); + +/** + * Application callback. Called when the read of the ANS Supported New Alert + * Category characteristic has completed. + */ +static int +blecent_on_read(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, + void *arg) +{ + BLECENT_LOG(INFO, "Read complete; status=%d conn_handle=%d", error->status, + conn_handle); + if (error->status == 0) { + BLECENT_LOG(INFO, " attr_handle=%d value=", attr->handle); + print_mbuf(attr->om); + } + BLECENT_LOG(INFO, "\n"); + + return 0; +} + +/** + * Application callback. Called when the write to the ANS Alert Notification + * Control Point characteristic has completed. + */ +static int +blecent_on_write(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, + void *arg) +{ + BLECENT_LOG(INFO, "Write complete; status=%d conn_handle=%d " + "attr_handle=%d\n", + error->status, conn_handle, attr->handle); + + return 0; +} + +/** + * Application callback. Called when the attempt to subscribe to notifications + * for the ANS Unread Alert Status characteristic has completed. + */ +static int +blecent_on_subscribe(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, + void *arg) +{ + BLECENT_LOG(INFO, "Subscribe complete; status=%d conn_handle=%d " + "attr_handle=%d\n", + error->status, conn_handle, attr->handle); + + return 0; +} + +/** + * Performs three concurrent GATT operations against the specified peer: + * 1. Reads the ANS Supported New Alert Category characteristic. + * 2. Writes the ANS Alert Notification Control Point characteristic. + * 3. Subscribes to notifications for the ANS Unread Alert Status + * characteristic. + * + * If the peer does not support a required service, characteristic, or + * descriptor, then the peer lied when it claimed support for the alert + * notification service! When this happens, or if a GATT procedure fails, + * this function immediately terminates the connection. + */ +static void +blecent_read_write_subscribe(const struct peer *peer) +{ + const struct peer_chr *chr; + const struct peer_dsc *dsc; + uint8_t value[2]; + int rc; + + /* Read the supported-new-alert-category characteristic. */ + chr = peer_chr_find_uuid(peer, + BLE_UUID16_DECLARE(BLECENT_SVC_ALERT_UUID), + BLE_UUID16_DECLARE(BLECENT_CHR_SUP_NEW_ALERT_CAT_UUID)); + if (chr == NULL) { + BLECENT_LOG(ERROR, "Error: Peer doesn't support the Supported New " + "Alert Category characteristic\n"); + goto err; + } + + rc = ble_gattc_read(peer->conn_handle, chr->chr.val_handle, + blecent_on_read, NULL); + if (rc != 0) { + BLECENT_LOG(ERROR, "Error: Failed to read characteristic; rc=%d\n", + rc); + goto err; + } + + /* Write two bytes (99, 100) to the alert-notification-control-point + * characteristic. + */ + chr = peer_chr_find_uuid(peer, + BLE_UUID16_DECLARE(BLECENT_SVC_ALERT_UUID), + BLE_UUID16_DECLARE(BLECENT_CHR_ALERT_NOT_CTRL_PT)); + if (chr == NULL) { + BLECENT_LOG(ERROR, "Error: Peer doesn't support the Alert " + "Notification Control Point characteristic\n"); + goto err; + } + + value[0] = 99; + value[1] = 100; + rc = ble_gattc_write_flat(peer->conn_handle, chr->chr.val_handle, + value, sizeof value, blecent_on_write, NULL); + if (rc != 0) { + BLECENT_LOG(ERROR, "Error: Failed to write characteristic; rc=%d\n", + rc); + } + + /* Subscribe to notifications for the Unread Alert Status characteristic. + * A central enables notifications by writing two bytes (1, 0) to the + * characteristic's client-characteristic-configuration-descriptor (CCCD). + */ + dsc = peer_dsc_find_uuid(peer, + BLE_UUID16_DECLARE(BLECENT_SVC_ALERT_UUID), + BLE_UUID16_DECLARE(BLECENT_CHR_UNR_ALERT_STAT_UUID), + BLE_UUID16_DECLARE(BLE_GATT_DSC_CLT_CFG_UUID16)); + if (dsc == NULL) { + BLECENT_LOG(ERROR, "Error: Peer lacks a CCCD for the Unread Alert " + "Status characteristic\n"); + goto err; + } + + value[0] = 1; + value[1] = 0; + rc = ble_gattc_write_flat(peer->conn_handle, dsc->dsc.handle, + value, sizeof value, blecent_on_subscribe, NULL); + if (rc != 0) { + BLECENT_LOG(ERROR, "Error: Failed to subscribe to characteristic; " + "rc=%d\n", rc); + goto err; + } + + return; + +err: + /* Terminate the connection. */ + ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM); +} + +/** + * Called when service discovery of the specified peer has completed. + */ +static void +blecent_on_disc_complete(const struct peer *peer, int status, void *arg) +{ + + if (status != 0) { + /* Service discovery failed. Terminate the connection. */ + BLECENT_LOG(ERROR, "Error: Service discovery failed; status=%d " + "conn_handle=%d\n", status, peer->conn_handle); + ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM); + return; + } + + /* Service discovery has completed successfully. Now we have a complete + * list of services, characteristics, and descriptors that the peer + * supports. + */ + BLECENT_LOG(ERROR, "Service discovery complete; status=%d " + "conn_handle=%d\n", status, peer->conn_handle); + + /* Now perform three concurrent GATT procedures against the peer: read, + * write, and subscribe to notifications. + */ + blecent_read_write_subscribe(peer); +} + +/** + * Initiates the GAP general discovery procedure. + */ +static void +blecent_scan(void) +{ + uint8_t own_addr_type; + struct ble_gap_disc_params disc_params; + int rc; + + /* Figure out address to use while advertising (no privacy for now) */ + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + BLECENT_LOG(ERROR, "error determining address type; rc=%d\n", rc); + return; + } + + /* Tell the controller to filter duplicates; we don't want to process + * repeated advertisements from the same device. + */ + disc_params.filter_duplicates = 1; + + /** + * Perform a passive scan. I.e., don't send follow-up scan requests to + * each advertiser. + */ + disc_params.passive = 1; + + /* Use defaults for the rest of the parameters. */ + disc_params.itvl = 0; + disc_params.window = 0; + disc_params.filter_policy = 0; + disc_params.limited = 0; + + rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params, + blecent_gap_event, NULL); + if (rc != 0) { + BLECENT_LOG(ERROR, "Error initiating GAP discovery procedure; rc=%d\n", + rc); + } +} + +/** + * Indicates whether we should tre to connect to the sender of the specified + * advertisement. The function returns a positive result if the device + * advertises connectability and support for the Alert Notification service. + */ +static int +blecent_should_connect(const struct ble_gap_disc_desc *disc) +{ + struct ble_hs_adv_fields fields; + int rc; + int i; + + /* The device has to be advertising connectability. */ + if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && + disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { + + return 0; + } + + rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data); + if (rc != 0) { + return rc; + } + + /* The device has to advertise support for the Alert Notification + * service (0x1811). + */ + for (i = 0; i < fields.num_uuids16; i++) { + if (ble_uuid_u16(&fields.uuids16[i].u) == BLECENT_SVC_ALERT_UUID) { + return 1; + } + } + + return 0; +} + +/** + * Connects to the sender of the specified advertisement of it looks + * interesting. A device is "interesting" if it advertises connectability and + * support for the Alert Notification service. + */ +static void +blecent_connect_if_interesting(const struct ble_gap_disc_desc *disc) +{ + int rc; + + /* Don't do anything if we don't care about this advertiser. */ + if (!blecent_should_connect(disc)) { + return; + } + + /* Scanning must be stopped before a connection can be initiated. */ + rc = ble_gap_disc_cancel(); + if (rc != 0) { + BLECENT_LOG(DEBUG, "Failed to cancel scan; rc=%d\n", rc); + return; + } + + /* Try to connect the the advertiser. Allow 30 seconds (30000 ms) for + * timeout. + */ + rc = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &disc->addr, 30000, NULL, + blecent_gap_event, NULL); + if (rc != 0) { + BLECENT_LOG(ERROR, "Error: Failed to connect to device; addr_type=%d " + "addr=%s\n", disc->addr.type, + addr_str(disc->addr.val)); + return; + } +} + +/** + * The nimble host executes this callback when a GAP event occurs. The + * application associates a GAP event callback with each connection that is + * established. blecent uses the same callback for all connections. + * + * @param event The event being signalled. + * @param arg Application-specified argument; unused by + * blecent. + * + * @return 0 if the application successfully handled the + * event; nonzero on failure. The semantics + * of the return code is specific to the + * particular GAP event being signalled. + */ +static int +blecent_gap_event(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + struct ble_hs_adv_fields fields; + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_DISC: + rc = ble_hs_adv_parse_fields(&fields, event->disc.data, + event->disc.length_data); + if (rc != 0) { + return 0; + } + + /* An advertisment report was received during GAP discovery. */ + print_adv_fields(&fields); + + /* Try to connect to the advertiser if it looks interesting. */ + blecent_connect_if_interesting(&event->disc); + return 0; + + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + if (event->connect.status == 0) { + /* Connection successfully established. */ + BLECENT_LOG(INFO, "Connection established "); + + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + print_conn_desc(&desc); + BLECENT_LOG(INFO, "\n"); + + /* Remember peer. */ + rc = peer_add(event->connect.conn_handle); + if (rc != 0) { + BLECENT_LOG(ERROR, "Failed to add peer; rc=%d\n", rc); + return 0; + } + + /* Perform service discovery. */ + rc = peer_disc_all(event->connect.conn_handle, + blecent_on_disc_complete, NULL); + if (rc != 0) { + BLECENT_LOG(ERROR, "Failed to discover services; rc=%d\n", rc); + return 0; + } + } else { + /* Connection attempt failed; resume scanning. */ + BLECENT_LOG(ERROR, "Error: Connection failed; status=%d\n", + event->connect.status); + blecent_scan(); + } + + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + /* Connection terminated. */ + BLECENT_LOG(INFO, "disconnect; reason=%d ", event->disconnect.reason); + print_conn_desc(&event->disconnect.conn); + BLECENT_LOG(INFO, "\n"); + + /* Forget about peer. */ + peer_delete(event->disconnect.conn.conn_handle); + + /* Resume scanning. */ + blecent_scan(); + return 0; + + case BLE_GAP_EVENT_DISC_COMPLETE: + BLECENT_LOG(INFO, "discovery complete; reason=%d\n", + event->disc_complete.reason); + return 0; + + case BLE_GAP_EVENT_ENC_CHANGE: + /* Encryption has been enabled or disabled for this connection. */ + BLECENT_LOG(INFO, "encryption change event; status=%d ", + event->enc_change.status); + rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); + assert(rc == 0); + print_conn_desc(&desc); + return 0; + + case BLE_GAP_EVENT_NOTIFY_RX: + /* Peer sent us a notification or indication. */ + BLECENT_LOG(INFO, "received %s; conn_handle=%d attr_handle=%d " + "attr_len=%d\n", + event->notify_rx.indication ? + "indication" : + "notification", + event->notify_rx.conn_handle, + event->notify_rx.attr_handle, + OS_MBUF_PKTLEN(event->notify_rx.om)); + + /* Attribute data is contained in event->notify_rx.attr_data. */ + return 0; + + case BLE_GAP_EVENT_MTU: + BLECENT_LOG(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n", + event->mtu.conn_handle, + event->mtu.channel_id, + event->mtu.value); + return 0; + + case BLE_GAP_EVENT_REPEAT_PAIRING: + /* We already have a bond with the peer, but it is attempting to + * establish a new secure link. This app sacrifices security for + * convenience: just throw away the old bond and accept the new link. + */ + + /* Delete the old bond. */ + rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); + assert(rc == 0); + ble_store_util_delete_peer(&desc.peer_id_addr); + + /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should + * continue with the pairing operation. + */ + return BLE_GAP_REPEAT_PAIRING_RETRY; + + default: + return 0; + } +} + +static void +blecent_on_reset(int reason) +{ + BLECENT_LOG(ERROR, "Resetting state; reason=%d\n", reason); +} + +static void +blecent_on_sync(void) +{ + int rc; + + /* Make sure we have proper identity address set (public preferred) */ + rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + /* Begin scanning for a peripheral to connect to. */ + blecent_scan(); +} + +/** + * main + * + * All application logic and NimBLE host work is performed in default task. + * + * @return int NOTE: this function should never return! + */ +int +main(void) +{ + int rc; + + /* Initialize OS */ + sysinit(); + + /* Initialize the blecent log. */ + log_register("blecent", &blecent_log, &log_console_handler, NULL, + LOG_SYSLEVEL); + + /* Configure the host. */ + log_register("ble_hs", &ble_hs_log, &log_console_handler, NULL, + LOG_SYSLEVEL); + ble_hs_cfg.reset_cb = blecent_on_reset; + ble_hs_cfg.sync_cb = blecent_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + /* Initialize data structures to track connected peers. */ + rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64); + assert(rc == 0); + + /* Set the default device name. */ + rc = ble_svc_gap_device_name_set("nimble-blecent"); + assert(rc == 0); + + /* os start should never return. If it does, this should be an error */ + while (1) { + os_eventq_run(os_eventq_dflt_get()); + } + + return 0; +} diff --git a/apps/blecent/src/misc.c b/apps/blecent/src/misc.c new file mode 100644 index 000000000..eb4af0cc1 --- /dev/null +++ b/apps/blecent/src/misc.c @@ -0,0 +1,209 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include "host/ble_hs.h" +#include "host/ble_uuid.h" +#include "blecent.h" + +/** + * Utility function to log an array of bytes. + */ +void +print_bytes(const uint8_t *bytes, int len) +{ + int i; + + for (i = 0; i < len; i++) { + BLECENT_LOG(DEBUG, "%s0x%02x", i != 0 ? ":" : "", bytes[i]); + } +} + +void +print_mbuf(const struct os_mbuf *om) +{ + int colon; + + colon = 0; + while (om != NULL) { + if (colon) { + BLECENT_LOG(DEBUG, ":"); + } else { + colon = 1; + } + print_bytes(om->om_data, om->om_len); + om = SLIST_NEXT(om, om_next); + } +} + +char * +addr_str(const void *addr) +{ + static char buf[6 * 2 + 5 + 1]; + const uint8_t *u8p; + + u8p = addr; + sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", + u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]); + + return buf; +} + +void +print_uuid(const ble_uuid_t *uuid) +{ + char buf[BLE_UUID_STR_LEN]; + + BLECENT_LOG(DEBUG, "%s", ble_uuid_to_str(uuid, buf)); +} + +/** + * Logs information about a connection to the console. + */ +void +print_conn_desc(const struct ble_gap_conn_desc *desc) +{ + BLECENT_LOG(DEBUG, "handle=%d our_ota_addr_type=%d our_ota_addr=%s ", + desc->conn_handle, desc->our_ota_addr.type, + addr_str(desc->our_ota_addr.val)); + BLECENT_LOG(DEBUG, "our_id_addr_type=%d our_id_addr=%s ", + desc->our_id_addr.type, addr_str(desc->our_id_addr.val)); + BLECENT_LOG(DEBUG, "peer_ota_addr_type=%d peer_ota_addr=%s ", + desc->peer_ota_addr.type, addr_str(desc->peer_ota_addr.val)); + BLECENT_LOG(DEBUG, "peer_id_addr_type=%d peer_id_addr=%s ", + desc->peer_id_addr.type, addr_str(desc->peer_id_addr.val)); + BLECENT_LOG(DEBUG, "conn_itvl=%d conn_latency=%d supervision_timeout=%d " + "encrypted=%d authenticated=%d bonded=%d", + desc->conn_itvl, desc->conn_latency, + desc->supervision_timeout, + desc->sec_state.encrypted, + desc->sec_state.authenticated, + desc->sec_state.bonded); +} + + +void +print_adv_fields(const struct ble_hs_adv_fields *fields) +{ + char s[BLE_HS_ADV_MAX_SZ]; + const uint8_t *u8p; + int i; + + if (fields->flags != 0) { + BLECENT_LOG(DEBUG, " flags=0x%02x\n", fields->flags); + } + + if (fields->uuids16 != NULL) { + BLECENT_LOG(DEBUG, " uuids16(%scomplete)=", + fields->uuids16_is_complete ? "" : "in"); + for (i = 0; i < fields->num_uuids16; i++) { + print_uuid(&fields->uuids16[i].u); + BLECENT_LOG(DEBUG, " "); + } + BLECENT_LOG(DEBUG, "\n"); + } + + if (fields->uuids32 != NULL) { + BLECENT_LOG(DEBUG, " uuids32(%scomplete)=", + fields->uuids32_is_complete ? "" : "in"); + for (i = 0; i < fields->num_uuids32; i++) { + print_uuid(&fields->uuids32[i].u); + BLECENT_LOG(DEBUG, " "); + } + BLECENT_LOG(DEBUG, "\n"); + } + + if (fields->uuids128 != NULL) { + BLECENT_LOG(DEBUG, " uuids128(%scomplete)=", + fields->uuids128_is_complete ? "" : "in"); + for (i = 0; i < fields->num_uuids128; i++) { + print_uuid(&fields->uuids128[i].u); + BLECENT_LOG(DEBUG, " "); + } + BLECENT_LOG(DEBUG, "\n"); + } + + if (fields->name != NULL) { + assert(fields->name_len < sizeof s - 1); + memcpy(s, fields->name, fields->name_len); + s[fields->name_len] = '\0'; + BLECENT_LOG(DEBUG, " name(%scomplete)=%s\n", + fields->name_is_complete ? "" : "in", s); + } + + if (fields->tx_pwr_lvl_is_present) { + BLECENT_LOG(DEBUG, " tx_pwr_lvl=%d\n", fields->tx_pwr_lvl); + } + + if (fields->slave_itvl_range != NULL) { + BLECENT_LOG(DEBUG, " slave_itvl_range="); + print_bytes(fields->slave_itvl_range, BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN); + BLECENT_LOG(DEBUG, "\n"); + } + + if (fields->svc_data_uuid16 != NULL) { + BLECENT_LOG(DEBUG, " svc_data_uuid16="); + print_bytes(fields->svc_data_uuid16, fields->svc_data_uuid16_len); + BLECENT_LOG(DEBUG, "\n"); + } + + if (fields->public_tgt_addr != NULL) { + BLECENT_LOG(DEBUG, " public_tgt_addr="); + u8p = fields->public_tgt_addr; + for (i = 0; i < fields->num_public_tgt_addrs; i++) { + BLECENT_LOG(DEBUG, "public_tgt_addr=%s ", addr_str(u8p)); + u8p += BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN; + } + BLECENT_LOG(DEBUG, "\n"); + } + + if (fields->appearance_is_present) { + BLECENT_LOG(DEBUG, " appearance=0x%04x\n", fields->appearance); + } + + if (fields->adv_itvl_is_present) { + BLECENT_LOG(DEBUG, " adv_itvl=0x%04x\n", fields->adv_itvl); + } + + if (fields->svc_data_uuid32 != NULL) { + BLECENT_LOG(DEBUG, " svc_data_uuid32="); + print_bytes(fields->svc_data_uuid32, fields->svc_data_uuid32_len); + BLECENT_LOG(DEBUG, "\n"); + } + + if (fields->svc_data_uuid128 != NULL) { + BLECENT_LOG(DEBUG, " svc_data_uuid128="); + print_bytes(fields->svc_data_uuid128, fields->svc_data_uuid128_len); + BLECENT_LOG(DEBUG, "\n"); + } + + if (fields->uri != NULL) { + BLECENT_LOG(DEBUG, " uri="); + print_bytes(fields->uri, fields->uri_len); + BLECENT_LOG(DEBUG, "\n"); + } + + if (fields->mfg_data != NULL) { + BLECENT_LOG(DEBUG, " mfg_data="); + print_bytes(fields->mfg_data, fields->mfg_data_len); + BLECENT_LOG(DEBUG, "\n"); + } +} diff --git a/apps/blecent/src/peer.c b/apps/blecent/src/peer.c new file mode 100644 index 000000000..f18dcbcc0 --- /dev/null +++ b/apps/blecent/src/peer.c @@ -0,0 +1,807 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include "host/ble_hs.h" +#include "blecent.h" + +static void *peer_svc_mem; +static struct os_mempool peer_svc_pool; + +static void *peer_chr_mem; +static struct os_mempool peer_chr_pool; + +static void *peer_dsc_mem; +static struct os_mempool peer_dsc_pool; + +static void *peer_mem; +static struct os_mempool peer_pool; +static SLIST_HEAD(, peer) peers; + +static struct peer_svc * +peer_svc_find_range(struct peer *peer, uint16_t attr_handle); +static struct peer_svc * +peer_svc_find(struct peer *peer, uint16_t svc_start_handle, + struct peer_svc **out_prev); +int +peer_svc_is_empty(const struct peer_svc *svc); + +uint16_t +chr_end_handle(const struct peer_svc *svc, const struct peer_chr *chr); +int +chr_is_empty(const struct peer_svc *svc, const struct peer_chr *chr); +static struct peer_chr * +peer_chr_find(const struct peer_svc *svc, uint16_t chr_def_handle, + struct peer_chr **out_prev); +static void +peer_disc_chrs(struct peer *peer); + +static int +peer_dsc_disced(uint16_t conn_handle, const struct ble_gatt_error *error, + uint16_t chr_def_handle, const struct ble_gatt_dsc *dsc, + void *arg); + +static struct peer * +peer_find(uint16_t conn_handle) +{ + struct peer *peer; + + SLIST_FOREACH(peer, &peers, next) { + if (peer->conn_handle == conn_handle) { + return peer; + } + } + + return NULL; +} + +static void +peer_disc_complete(struct peer *peer, int rc) +{ + peer->disc_prev_chr_val = 0; + + /* Notify caller that discovery has completed. */ + if (peer->disc_cb != NULL) { + peer->disc_cb(peer, rc, peer->disc_cb_arg); + } +} + +static struct peer_dsc * +peer_dsc_find_prev(const struct peer_chr *chr, uint16_t dsc_handle) +{ + struct peer_dsc *prev; + struct peer_dsc *dsc; + + prev = NULL; + SLIST_FOREACH(dsc, &chr->dscs, next) { + if (dsc->dsc.handle >= dsc_handle) { + break; + } + + prev = dsc; + } + + return prev; +} + +static struct peer_dsc * +peer_dsc_find(const struct peer_chr *chr, uint16_t dsc_handle, + struct peer_dsc **out_prev) +{ + struct peer_dsc *prev; + struct peer_dsc *dsc; + + prev = peer_dsc_find_prev(chr, dsc_handle); + if (prev == NULL) { + dsc = SLIST_FIRST(&chr->dscs); + } else { + dsc = SLIST_NEXT(prev, next); + } + + if (dsc != NULL && dsc->dsc.handle != dsc_handle) { + dsc = NULL; + } + + if (out_prev != NULL) { + *out_prev = prev; + } + return dsc; +} + +static int +peer_dsc_add(struct peer *peer, uint16_t chr_val_handle, + const struct ble_gatt_dsc *gatt_dsc) +{ + struct peer_dsc *prev; + struct peer_dsc *dsc; + struct peer_svc *svc; + struct peer_chr *chr; + + svc = peer_svc_find_range(peer, chr_val_handle); + if (svc == NULL) { + /* Can't find service for discovered descriptor; this shouldn't + * happen. + */ + assert(0); + return BLE_HS_EUNKNOWN; + } + + chr = peer_chr_find(svc, chr_val_handle, NULL); + if (chr == NULL) { + /* Can't find characteristic for discovered descriptor; this shouldn't + * happen. + */ + assert(0); + return BLE_HS_EUNKNOWN; + } + + dsc = peer_dsc_find(chr, gatt_dsc->handle, &prev); + if (dsc != NULL) { + /* Descriptor already discovered. */ + return 0; + } + + dsc = os_memblock_get(&peer_dsc_pool); + if (dsc == NULL) { + /* Out of memory. */ + return BLE_HS_ENOMEM; + } + memset(dsc, 0, sizeof *dsc); + + dsc->dsc = *gatt_dsc; + + if (prev == NULL) { + SLIST_INSERT_HEAD(&chr->dscs, dsc, next); + } else { + SLIST_NEXT(prev, next) = dsc; + } + + return 0; +} + +static void +peer_disc_dscs(struct peer *peer) +{ + struct peer_chr *chr; + struct peer_svc *svc; + int rc; + + /* Search through the list of discovered characteristics for the first + * characteristic that contains undiscovered descriptors. Then, discover + * all descriptors belonging to that characteristic. + */ + SLIST_FOREACH(svc, &peer->svcs, next) { + SLIST_FOREACH(chr, &svc->chrs, next) { + if (!chr_is_empty(svc, chr) && + SLIST_EMPTY(&chr->dscs) && + peer->disc_prev_chr_val <= chr->chr.def_handle) { + + rc = ble_gattc_disc_all_dscs(peer->conn_handle, + chr->chr.val_handle, + chr_end_handle(svc, chr), + peer_dsc_disced, peer); + if (rc != 0) { + peer_disc_complete(peer, rc); + } + + peer->disc_prev_chr_val = chr->chr.val_handle; + return; + } + } + } + + /* All descriptors discovered. */ + peer_disc_complete(peer, 0); +} + +static int +peer_dsc_disced(uint16_t conn_handle, const struct ble_gatt_error *error, + uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc, + void *arg) +{ + struct peer *peer; + int rc; + + peer = arg; + assert(peer->conn_handle == conn_handle); + + switch (error->status) { + case 0: + rc = peer_dsc_add(peer, chr_val_handle, dsc); + break; + + case BLE_HS_EDONE: + /* All descriptors in this characteristic discovered; start discovering + * descriptors in the next characteristic. + */ + if (peer->disc_prev_chr_val > 0) { + peer_disc_dscs(peer); + } + rc = 0; + break; + + default: + /* Error; abort discovery. */ + rc = error->status; + break; + } + + if (rc != 0) { + /* Error; abort discovery. */ + peer_disc_complete(peer, rc); + } + + return rc; +} + +uint16_t +chr_end_handle(const struct peer_svc *svc, const struct peer_chr *chr) +{ + const struct peer_chr *next_chr; + + next_chr = SLIST_NEXT(chr, next); + if (next_chr != NULL) { + return next_chr->chr.def_handle - 1; + } else { + return svc->svc.end_handle; + } +} + +int +chr_is_empty(const struct peer_svc *svc, const struct peer_chr *chr) +{ + return chr_end_handle(svc, chr) <= chr->chr.val_handle; +} + +static struct peer_chr * +peer_chr_find_prev(const struct peer_svc *svc, uint16_t chr_val_handle) +{ + struct peer_chr *prev; + struct peer_chr *chr; + + prev = NULL; + SLIST_FOREACH(chr, &svc->chrs, next) { + if (chr->chr.val_handle >= chr_val_handle) { + break; + } + + prev = chr; + } + + return prev; +} + +static struct peer_chr * +peer_chr_find(const struct peer_svc *svc, uint16_t chr_val_handle, + struct peer_chr **out_prev) +{ + struct peer_chr *prev; + struct peer_chr *chr; + + prev = peer_chr_find_prev(svc, chr_val_handle); + if (prev == NULL) { + chr = SLIST_FIRST(&svc->chrs); + } else { + chr = SLIST_NEXT(prev, next); + } + + if (chr != NULL && chr->chr.val_handle != chr_val_handle) { + chr = NULL; + } + + if (out_prev != NULL) { + *out_prev = prev; + } + return chr; +} + +static void +peer_chr_delete(struct peer_chr *chr) +{ + struct peer_dsc *dsc; + + while ((dsc = SLIST_FIRST(&chr->dscs)) != NULL) { + SLIST_REMOVE_HEAD(&chr->dscs, next); + os_memblock_put(&peer_dsc_pool, dsc); + } + + os_memblock_put(&peer_chr_pool, chr); +} + +static int +peer_chr_add(struct peer *peer, uint16_t svc_start_handle, + const struct ble_gatt_chr *gatt_chr) +{ + struct peer_chr *prev; + struct peer_chr *chr; + struct peer_svc *svc; + + svc = peer_svc_find(peer, svc_start_handle, NULL); + if (svc == NULL) { + /* Can't find service for discovered characteristic; this shouldn't + * happen. + */ + assert(0); + return BLE_HS_EUNKNOWN; + } + + chr = peer_chr_find(svc, gatt_chr->def_handle, &prev); + if (chr != NULL) { + /* Characteristic already discovered. */ + return 0; + } + + chr = os_memblock_get(&peer_chr_pool); + if (chr == NULL) { + /* Out of memory. */ + return BLE_HS_ENOMEM; + } + memset(chr, 0, sizeof *chr); + + chr->chr = *gatt_chr; + + if (prev == NULL) { + SLIST_INSERT_HEAD(&svc->chrs, chr, next); + } else { + SLIST_NEXT(prev, next) = chr; + } + + return 0; +} + +static int +peer_chr_disced(uint16_t conn_handle, const struct ble_gatt_error *error, + const struct ble_gatt_chr *chr, void *arg) +{ + struct peer *peer; + int rc; + + peer = arg; + assert(peer->conn_handle == conn_handle); + + switch (error->status) { + case 0: + rc = peer_chr_add(peer, peer->cur_svc->svc.start_handle, chr); + break; + + case BLE_HS_EDONE: + /* All characteristics in this service discovered; start discovering + * characteristics in the next service. + */ + if (peer->disc_prev_chr_val > 0) { + peer_disc_chrs(peer); + } + rc = 0; + break; + + default: + rc = error->status; + break; + } + + if (rc != 0) { + /* Error; abort discovery. */ + peer_disc_complete(peer, rc); + } + + return rc; +} + +static void +peer_disc_chrs(struct peer *peer) +{ + struct peer_svc *svc; + int rc; + + /* Search through the list of discovered service for the first service that + * contains undiscovered characteristics. Then, discover all + * characteristics belonging to that service. + */ + SLIST_FOREACH(svc, &peer->svcs, next) { + if (!peer_svc_is_empty(svc) && SLIST_EMPTY(&svc->chrs)) { + peer->cur_svc = svc; + rc = ble_gattc_disc_all_chrs(peer->conn_handle, + svc->svc.start_handle, + svc->svc.end_handle, + peer_chr_disced, peer); + if (rc != 0) { + peer_disc_complete(peer, rc); + } + return; + } + } + + /* All characteristics discovered. */ + peer_disc_dscs(peer); +} + +int +peer_svc_is_empty(const struct peer_svc *svc) +{ + return svc->svc.end_handle <= svc->svc.start_handle; +} + +static struct peer_svc * +peer_svc_find_prev(struct peer *peer, uint16_t svc_start_handle) +{ + struct peer_svc *prev; + struct peer_svc *svc; + + prev = NULL; + SLIST_FOREACH(svc, &peer->svcs, next) { + if (svc->svc.start_handle >= svc_start_handle) { + break; + } + + prev = svc; + } + + return prev; +} + +static struct peer_svc * +peer_svc_find(struct peer *peer, uint16_t svc_start_handle, + struct peer_svc **out_prev) +{ + struct peer_svc *prev; + struct peer_svc *svc; + + prev = peer_svc_find_prev(peer, svc_start_handle); + if (prev == NULL) { + svc = SLIST_FIRST(&peer->svcs); + } else { + svc = SLIST_NEXT(prev, next); + } + + if (svc != NULL && svc->svc.start_handle != svc_start_handle) { + svc = NULL; + } + + if (out_prev != NULL) { + *out_prev = prev; + } + return svc; +} + +static struct peer_svc * +peer_svc_find_range(struct peer *peer, uint16_t attr_handle) +{ + struct peer_svc *svc; + + SLIST_FOREACH(svc, &peer->svcs, next) { + if (svc->svc.start_handle <= attr_handle && + svc->svc.end_handle >= attr_handle) { + + return svc; + } + } + + return NULL; +} + +const struct peer_svc * +peer_svc_find_uuid(const struct peer *peer, const ble_uuid_t *uuid) +{ + const struct peer_svc *svc; + + SLIST_FOREACH(svc, &peer->svcs, next) { + if (ble_uuid_cmp(&svc->svc.uuid.u, uuid) == 0) { + return svc; + } + } + + return NULL; +} + +const struct peer_chr * +peer_chr_find_uuid(const struct peer *peer, const ble_uuid_t *svc_uuid, + const ble_uuid_t *chr_uuid) +{ + const struct peer_svc *svc; + const struct peer_chr *chr; + + svc = peer_svc_find_uuid(peer, svc_uuid); + if (svc == NULL) { + return NULL; + } + + SLIST_FOREACH(chr, &svc->chrs, next) { + if (ble_uuid_cmp(&chr->chr.uuid.u, chr_uuid) == 0) { + return chr; + } + } + + return NULL; +} + +const struct peer_dsc * +peer_dsc_find_uuid(const struct peer *peer, const ble_uuid_t *svc_uuid, + const ble_uuid_t *chr_uuid, const ble_uuid_t *dsc_uuid) +{ + const struct peer_chr *chr; + const struct peer_dsc *dsc; + + chr = peer_chr_find_uuid(peer, svc_uuid, chr_uuid); + if (chr == NULL) { + return NULL; + } + + SLIST_FOREACH(dsc, &chr->dscs, next) { + if (ble_uuid_cmp(&dsc->dsc.uuid.u, dsc_uuid) == 0) { + return dsc; + } + } + + return NULL; +} + +static int +peer_svc_add(struct peer *peer, const struct ble_gatt_svc *gatt_svc) +{ + struct peer_svc *prev; + struct peer_svc *svc; + + svc = peer_svc_find(peer, gatt_svc->start_handle, &prev); + if (svc != NULL) { + /* Service already discovered. */ + return 0; + } + + svc = os_memblock_get(&peer_svc_pool); + if (svc == NULL) { + /* Out of memory. */ + return BLE_HS_ENOMEM; + } + memset(svc, 0, sizeof *svc); + + svc->svc = *gatt_svc; + SLIST_INIT(&svc->chrs); + + if (prev == NULL) { + SLIST_INSERT_HEAD(&peer->svcs, svc, next); + } else { + SLIST_INSERT_AFTER(prev, svc, next); + } + + return 0; +} + +static void +peer_svc_delete(struct peer_svc *svc) +{ + struct peer_chr *chr; + + while ((chr = SLIST_FIRST(&svc->chrs)) != NULL) { + SLIST_REMOVE_HEAD(&svc->chrs, next); + peer_chr_delete(chr); + } + + os_memblock_put(&peer_svc_pool, svc); +} + +static int +peer_svc_disced(uint16_t conn_handle, const struct ble_gatt_error *error, + const struct ble_gatt_svc *service, void *arg) +{ + struct peer *peer; + int rc; + + peer = arg; + assert(peer->conn_handle == conn_handle); + + switch (error->status) { + case 0: + rc = peer_svc_add(peer, service); + break; + + case BLE_HS_EDONE: + /* All services discovered; start discovering characteristics. */ + if (peer->disc_prev_chr_val > 0) { + peer_disc_chrs(peer); + } + rc = 0; + break; + + default: + rc = error->status; + break; + } + + if (rc != 0) { + /* Error; abort discovery. */ + peer_disc_complete(peer, rc); + } + + return rc; +} + + +int +peer_disc_all(uint16_t conn_handle, peer_disc_fn *disc_cb, void *disc_cb_arg) +{ + struct peer_svc *svc; + struct peer *peer; + int rc; + + peer = peer_find(conn_handle); + if (peer == NULL) { + return BLE_HS_ENOTCONN; + } + + /* Undiscover everything first. */ + while ((svc = SLIST_FIRST(&peer->svcs)) != NULL) { + SLIST_REMOVE_HEAD(&peer->svcs, next); + peer_svc_delete(svc); + } + + peer->disc_prev_chr_val = 1; + peer->disc_cb = disc_cb; + peer->disc_cb_arg = disc_cb_arg; + + rc = ble_gattc_disc_all_svcs(conn_handle, peer_svc_disced, peer); + if (rc != 0) { + return rc; + } + + return 0; +} + +int +peer_delete(uint16_t conn_handle) +{ + struct peer_svc *svc; + struct peer *peer; + int rc; + + peer = peer_find(conn_handle); + if (peer == NULL) { + return BLE_HS_ENOTCONN; + } + + SLIST_REMOVE(&peers, peer, peer, next); + + while ((svc = SLIST_FIRST(&peer->svcs)) != NULL) { + SLIST_REMOVE_HEAD(&peer->svcs, next); + peer_svc_delete(svc); + } + + rc = os_memblock_put(&peer_pool, peer); + if (rc != 0) { + return BLE_HS_EOS; + } + + return 0; +} + +int +peer_add(uint16_t conn_handle) +{ + struct peer *peer; + + /* Make sure the connection handle is unique. */ + peer = peer_find(conn_handle); + if (peer != NULL) { + return BLE_HS_EALREADY; + } + + peer = os_memblock_get(&peer_pool); + if (peer == NULL) { + /* Out of memory. */ + return BLE_HS_ENOMEM; + } + + memset(peer, 0, sizeof *peer); + peer->conn_handle = conn_handle; + + SLIST_INSERT_HEAD(&peers, peer, next); + + return 0; +} + +static void +peer_free_mem(void) +{ + free(peer_mem); + peer_mem = NULL; + + free(peer_svc_mem); + peer_svc_mem = NULL; + + free(peer_chr_mem); + peer_chr_mem = NULL; + + free(peer_dsc_mem); + peer_dsc_mem = NULL; +} + +int +peer_init(int max_peers, int max_svcs, int max_chrs, int max_dscs) +{ + int rc; + + /* Free memory first in case this function gets called more than once. */ + peer_free_mem(); + + peer_mem = malloc( + OS_MEMPOOL_BYTES(max_peers, sizeof (struct peer))); + if (peer_mem == NULL) { + rc = BLE_HS_ENOMEM; + goto err; + } + + rc = os_mempool_init(&peer_pool, max_peers, + sizeof (struct peer), peer_mem, + "peer_pool"); + if (rc != 0) { + rc = BLE_HS_EOS; + goto err; + } + + peer_svc_mem = malloc( + OS_MEMPOOL_BYTES(max_svcs, sizeof (struct peer_svc))); + if (peer_svc_mem == NULL) { + rc = BLE_HS_ENOMEM; + goto err; + } + + rc = os_mempool_init(&peer_svc_pool, max_svcs, + sizeof (struct peer_svc), peer_svc_mem, + "peer_svc_pool"); + if (rc != 0) { + rc = BLE_HS_EOS; + goto err; + } + + peer_chr_mem = malloc( + OS_MEMPOOL_BYTES(max_chrs, sizeof (struct peer_chr))); + if (peer_chr_mem == NULL) { + rc = BLE_HS_ENOMEM; + goto err; + } + + rc = os_mempool_init(&peer_chr_pool, max_chrs, + sizeof (struct peer_chr), peer_chr_mem, + "peer_chr_pool"); + if (rc != 0) { + rc = BLE_HS_EOS; + goto err; + } + + peer_dsc_mem = malloc( + OS_MEMPOOL_BYTES(max_dscs, sizeof (struct peer_dsc))); + if (peer_dsc_mem == NULL) { + rc = BLE_HS_ENOMEM; + goto err; + } + + rc = os_mempool_init(&peer_dsc_pool, max_dscs, + sizeof (struct peer_dsc), peer_dsc_mem, + "peer_dsc_pool"); + if (rc != 0) { + rc = BLE_HS_EOS; + goto err; + } + + return 0; + +err: + peer_free_mem(); + return rc; +} diff --git a/apps/blecent/syscfg.yml b/apps/blecent/syscfg.yml new file mode 100644 index 000000000..f20064551 --- /dev/null +++ b/apps/blecent/syscfg.yml @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Package: apps/blecent + +syscfg.vals: + # DEBUG logging is a bit noisy; use INFO. + LOG_LEVEL: 1 + + # Default task settings + OS_MAIN_STACK_SIZE: 336 diff --git a/apps/blecsc/README.md b/apps/blecsc/README.md new file mode 100644 index 000000000..bccf176a1 --- /dev/null +++ b/apps/blecsc/README.md @@ -0,0 +1,9 @@ +# BLE Cycling Speed and Cadence peripheral app. + +The source files are located in the src/ directory. + +pkg.yml contains the base definition of the app. + +syscfg.yml contains setting definitions and overrides. + + diff --git a/apps/blecsc/pkg.yml b/apps/blecsc/pkg.yml new file mode 100644 index 000000000..606a0b40a --- /dev/null +++ b/apps/blecsc/pkg.yml @@ -0,0 +1,40 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +pkg.name: apps/blecsc +pkg.type: app +pkg.description: BLE peripheral cycling speed and cadence sensor. +pkg.author: "Maciej Jurczak" +pkg.email: "mjurczak@gmail.com" +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + +pkg.deps: + - "@apache-mynewt-core/boot/bootutil" + - "@apache-mynewt-core/kernel/os" + - "@apache-mynewt-core/sys/console/full" + - "@apache-mynewt-core/sys/log/full" + - "@apache-mynewt-core/sys/stats/full" + - "@apache-mynewt-core/sys/sysinit" + - "@apache-mynewt-core/sys/id" + - nimble/controller + - nimble/host + - nimble/host/services/gap + - nimble/host/services/gatt + - nimble/host/store/config + - nimble/transport diff --git a/apps/blecsc/src/blecsc_sens.h b/apps/blecsc/src/blecsc_sens.h new file mode 100644 index 000000000..b9a5cd817 --- /dev/null +++ b/apps/blecsc/src/blecsc_sens.h @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef H_BLECSC_SENSOR_ +#define H_BLECSC_SENSOR_ + +#include "log/log.h" +#include "nimble/ble.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct log blecsc_log; + +/* blecsc uses the first "peruser" log module */ +#define BLECSC_LOG_MODULE (LOG_MODULE_PERUSER + 0) + +/* Convenience macro for logging to the blerh module */ +#define BLECSC_LOG(lvl, ...) \ + LOG_ ## lvl(&blecsc_log, BLECSC_LOG_MODULE, __VA_ARGS__) + +/* Cycling Speed and Cadence configuration */ +#define GATT_CSC_UUID 0x1816 +#define GATT_CSC_MEASUREMENT_UUID 0x2A5B +#define GATT_CSC_FEATURE_UUID 0x2A5C +#define GATT_SENSOR_LOCATION_UUID 0x2A5D +#define GATT_SC_CONTROL_POINT_UUID 0x2A55 +/* Device Information configuration */ +#define GATT_DEVICE_INFO_UUID 0x180A +#define GATT_MANUFACTURER_NAME_UUID 0x2A29 +#define GATT_MODEL_NUMBER_UUID 0x2A24 + +/*CSC Measurement flags*/ +#define CSC_MEASUREMENT_WHEEL_REV_PRESENT 0x01 +#define CSC_MEASUREMENT_CRANK_REV_PRESENT 0x02 + +/* CSC feature flags */ +#define CSC_FEATURE_WHEEL_REV_DATA 0x01 +#define CSC_FEATURE_CRANK_REV_DATA 0x02 +#define CSC_FEATURE_MULTIPLE_SENSOR_LOC 0x04 + +/* Sensor location enum */ +#define SENSOR_LOCATION_OTHER 0 +#define SENSOR_LOCATION_TOP_OF_SHOE 1 +#define SENSOR_LOCATION_IN_SHOE 2 +#define SENSOR_LOCATION_HIP 3 +#define SENSOR_LOCATION_FRONT_WHEEL 4 +#define SENSOR_LOCATION_LEFT_CRANK 5 +#define SENSOR_LOCATION_RIGHT_CRANK 6 +#define SENSOR_LOCATION_LEFT_PEDAL 7 +#define SENSOR_LOCATION_RIGHT_PEDAL 8 +#define SENSOR_LOCATION_FROT_HUB 9 +#define SENSOR_LOCATION_REAR_DROPOUT 10 +#define SENSOR_LOCATION_CHAINSTAY 11 +#define SENSOR_LOCATION_REAR_WHEEL 12 +#define SENSOR_LOCATION_REAR_HUB 13 +#define SENSOR_LOCATION_CHEST 14 +#define SENSOR_LOCATION_SPIDER 15 +#define SENSOR_LOCATION_CHAIN_RING 16 + +/* SC Control Point op codes */ +#define SC_CP_OP_SET_CUMULATIVE_VALUE 1 +#define SC_CP_OP_START_SENSOR_CALIBRATION 2 +#define SC_CP_OP_UPDATE_SENSOR_LOCATION 3 +#define SC_CP_OP_REQ_SUPPORTED_SENSOR_LOCATIONS 4 +#define SC_CP_OP_RESPONSE 16 + +/*SC Control Point response values */ +#define SC_CP_RESPONSE_SUCCESS 1 +#define SC_CP_RESPONSE_OP_NOT_SUPPORTED 2 +#define SC_CP_RESPONSE_INVALID_PARAM 3 +#define SC_CP_RESPONSE_OP_FAILED 4 + +/* CSC simulation configuration */ +#define CSC_FEATURES (CSC_FEATURE_WHEEL_REV_DATA | \ + CSC_FEATURE_CRANK_REV_DATA |\ + CSC_FEATURE_MULTIPLE_SENSOR_LOC) + +struct ble_csc_measurement_state { + uint32_t cumulative_wheel_rev; + uint16_t last_wheel_evt_time; + uint16_t cumulative_crank_rev; + uint16_t last_crank_evt_time; +}; + +extern uint16_t csc_measurement_handle; +extern uint16_t csc_control_point_handle; + +int gatt_svr_init(struct ble_csc_measurement_state * csc_measurement_state); +int gatt_svr_chr_notify_csc_measurement(uint16_t conn_handle); +void gatt_svr_set_cp_indicate(uint8_t indication_status); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/apps/blecsc/src/gatt_svr.c b/apps/blecsc/src/gatt_svr.c new file mode 100644 index 000000000..6958c56bb --- /dev/null +++ b/apps/blecsc/src/gatt_svr.c @@ -0,0 +1,385 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include "os/mynewt.h" +#include "host/ble_hs.h" +#include "host/ble_uuid.h" +#include "blecsc_sens.h" + +#define CSC_ERR_CCC_DESC_IMPROPERLY_CONFIGURED 0x81 + +static const char *manuf_name = "Apache Mynewt"; +static const char *model_num = "Mynewt CSC Sensor"; + +static const uint8_t csc_supported_sensor_locations[] = { + SENSOR_LOCATION_FRONT_WHEEL, + SENSOR_LOCATION_REAR_DROPOUT, + SENSOR_LOCATION_CHAINSTAY, + SENSOR_LOCATION_REAR_WHEEL +}; + +static uint8_t sensor_location = SENSOR_LOCATION_REAR_DROPOUT; +static struct ble_csc_measurement_state * measurement_state; +uint16_t csc_measurement_handle; +uint16_t csc_control_point_handle; +uint8_t csc_cp_indication_status; + +static int +gatt_svr_chr_access_csc_measurement(uint16_t conn_handle, + uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg); + +static int +gatt_svr_chr_access_csc_feature(uint16_t conn_handle, + uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg); + +static int +gatt_svr_chr_access_sensor_location(uint16_t conn_handle, + uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg); + +static int +gatt_svr_chr_access_sc_control_point(uint16_t conn_handle, + uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg); + +static int +gatt_svr_chr_access_device_info(uint16_t conn_handle, + uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg); + +static const struct ble_gatt_svc_def gatt_svr_svcs[] = { + { + /* Service: Cycling Speed and Cadence */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = BLE_UUID16_DECLARE(GATT_CSC_UUID), + .characteristics = (struct ble_gatt_chr_def[]) { { + /* Characteristic: Cycling Speed and Cadence Measurement */ + .uuid = BLE_UUID16_DECLARE(GATT_CSC_MEASUREMENT_UUID), + .access_cb = gatt_svr_chr_access_csc_measurement, + .val_handle = &csc_measurement_handle, + .flags = BLE_GATT_CHR_F_NOTIFY, + }, { + /* Characteristic: Cycling Speed and Cadence features */ + .uuid = BLE_UUID16_DECLARE(GATT_CSC_FEATURE_UUID), + .access_cb = gatt_svr_chr_access_csc_feature, + .flags = BLE_GATT_CHR_F_READ, + }, { + /* Characteristic: Sensor Location */ + .uuid = BLE_UUID16_DECLARE(GATT_SENSOR_LOCATION_UUID), + .access_cb = gatt_svr_chr_access_sensor_location, + .flags = BLE_GATT_CHR_F_READ, + }, { + /* Characteristic: SC Control Point*/ + .uuid = BLE_UUID16_DECLARE(GATT_SC_CONTROL_POINT_UUID), + .access_cb = gatt_svr_chr_access_sc_control_point, + .val_handle = &csc_control_point_handle, + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE, + }, { + 0, /* No more characteristics in this service */ + }, } + }, + + { + /* Service: Device Information */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = BLE_UUID16_DECLARE(GATT_DEVICE_INFO_UUID), + .characteristics = (struct ble_gatt_chr_def[]) { { + /* Characteristic: * Manufacturer name */ + .uuid = BLE_UUID16_DECLARE(GATT_MANUFACTURER_NAME_UUID), + .access_cb = gatt_svr_chr_access_device_info, + .flags = BLE_GATT_CHR_F_READ, + }, { + /* Characteristic: Model number string */ + .uuid = BLE_UUID16_DECLARE(GATT_MODEL_NUMBER_UUID), + .access_cb = gatt_svr_chr_access_device_info, + .flags = BLE_GATT_CHR_F_READ, + }, { + 0, /* No more characteristics in this service */ + }, } + }, + + { + 0, /* No more services */ + }, +}; + +static int +gatt_svr_chr_access_csc_measurement(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + return BLE_ATT_ERR_READ_NOT_PERMITTED; +} + +static int +gatt_svr_chr_access_csc_feature(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + static const uint16_t csc_feature = CSC_FEATURES; + int rc; + + assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR); + rc = os_mbuf_append(ctxt->om, &csc_feature, sizeof(csc_feature)); + + return (rc == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; +} + +static int +gatt_svr_chr_access_sensor_location(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + int rc; + + assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR); + rc = os_mbuf_append(ctxt->om, &sensor_location, sizeof(sensor_location)); + + return (rc == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; +} + +static int +gatt_svr_chr_access_sc_control_point(uint16_t conn_handle, + uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg) +{ + uint8_t op_code; + uint8_t new_sensor_location; + uint8_t new_cumulative_wheel_rev_arr[4]; + struct os_mbuf *om_indication; + uint8_t response = SC_CP_RESPONSE_OP_NOT_SUPPORTED; + int ii; + int rc; + + assert(ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR); + + if (!csc_cp_indication_status) { + BLECSC_LOG(INFO, "SC Control Point; CCC descriptor " + "improperly configured"); + return CSC_ERR_CCC_DESC_IMPROPERLY_CONFIGURED; + } + + /* Read control point op code*/ + rc = os_mbuf_copydata(ctxt->om, 0, sizeof(op_code), &op_code); + if (rc != 0){ + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + BLECSC_LOG(INFO, "SC Control Point; opcode=%d\n", op_code); + + /* Allocate response buffer */ + om_indication = ble_hs_mbuf_att_pkt(); + + switch(op_code){ +#if (CSC_FEATURES & CSC_FEATURE_WHEEL_REV_DATA) + case SC_CP_OP_SET_CUMULATIVE_VALUE: + /* Read new cumulative wheel revolutions value*/ + rc = os_mbuf_copydata(ctxt->om, 1, + sizeof(new_cumulative_wheel_rev_arr), + new_cumulative_wheel_rev_arr); + if (rc != 0){ + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + + measurement_state->cumulative_wheel_rev = + get_le32(new_cumulative_wheel_rev_arr); + + BLECSC_LOG(INFO, "SC Control Point; Set cumulative value = %d\n", + measurement_state->cumulative_wheel_rev); + + response = SC_CP_RESPONSE_SUCCESS; + break; +#endif + +#if (CSC_FEATURES & CSC_FEATURE_MULTIPLE_SENSOR_LOC) + case SC_CP_OP_UPDATE_SENSOR_LOCATION: + /* Read new sensor location value*/ + rc = os_mbuf_copydata(ctxt->om, 1, 1, &new_sensor_location); + if (rc != 0){ + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + + BLECSC_LOG(INFO, "SC Control Point; Sensor location update = %d\n", + new_sensor_location); + + /* Verify if requested new location is on supported locations list */ + response = SC_CP_RESPONSE_INVALID_PARAM; + for (ii = 0; ii < sizeof(csc_supported_sensor_locations); ii++){ + if (new_sensor_location == csc_supported_sensor_locations[ii]){ + sensor_location = new_sensor_location; + response = SC_CP_RESPONSE_SUCCESS; + break; + } + } + break; + + case SC_CP_OP_REQ_SUPPORTED_SENSOR_LOCATIONS: + response = SC_CP_RESPONSE_SUCCESS; + break; +#endif + + default: + break; + } + + /* Append response value */ + rc = os_mbuf_append(om_indication, &response, sizeof(response)); + + if (rc != 0){ + return BLE_ATT_ERR_INSUFFICIENT_RES; + } + +#if (CSC_FEATURES & CSC_FEATURE_MULTIPLE_SENSOR_LOC) + /* In case of supported locations request append locations list */ + if (op_code == SC_CP_OP_REQ_SUPPORTED_SENSOR_LOCATIONS){ + rc = os_mbuf_append(om_indication, &csc_supported_sensor_locations, + sizeof(csc_supported_sensor_locations)); + } + + if (rc != 0){ + return BLE_ATT_ERR_INSUFFICIENT_RES; + } +#endif + + rc = ble_gattc_indicate_custom(conn_handle, csc_control_point_handle, + om_indication); + + return rc; +} + +static int +gatt_svr_chr_access_device_info(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + uint16_t uuid; + int rc; + + uuid = ble_uuid_u16(ctxt->chr->uuid); + + if (uuid == GATT_MODEL_NUMBER_UUID) { + rc = os_mbuf_append(ctxt->om, model_num, strlen(model_num)); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + if (uuid == GATT_MANUFACTURER_NAME_UUID) { + rc = os_mbuf_append(ctxt->om, manuf_name, strlen(manuf_name)); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + assert(0); + return BLE_ATT_ERR_UNLIKELY; +} + +int +gatt_svr_chr_notify_csc_measurement(uint16_t conn_handle) +{ + int rc; + struct os_mbuf *om; + uint8_t data_buf[11]; + uint8_t data_offset = 1; + + memset(data_buf, 0, sizeof(data_buf)); + +#if (CSC_FEATURES & CSC_FEATURE_WHEEL_REV_DATA) + data_buf[0] |= CSC_MEASUREMENT_WHEEL_REV_PRESENT; + put_le16(&(data_buf[5]), measurement_state->last_wheel_evt_time); + put_le32(&(data_buf[1]), measurement_state->cumulative_wheel_rev); + data_offset += 6; +#endif + +#if (CSC_FEATURES & CSC_FEATURE_CRANK_REV_DATA) + data_buf[0] |= CSC_MEASUREMENT_CRANK_REV_PRESENT; + put_le16(&(data_buf[data_offset]), + measurement_state->cumulative_crank_rev); + put_le16(&(data_buf[data_offset + 2]), + measurement_state->last_crank_evt_time); + data_offset += 4; +#endif + + om = ble_hs_mbuf_from_flat(data_buf, data_offset); + + rc = ble_gattc_notify_custom(conn_handle, csc_measurement_handle, om); + return rc; +} + +void +gatt_svr_set_cp_indicate(uint8_t indication_status) +{ + csc_cp_indication_status = indication_status; +} + +void +gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) +{ + char buf[BLE_UUID_STR_LEN]; + + switch (ctxt->op) { + case BLE_GATT_REGISTER_OP_SVC: + BLECSC_LOG(DEBUG, "registered service %s with handle=%d\n", + ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf), + ctxt->svc.handle); + break; + + case BLE_GATT_REGISTER_OP_CHR: + BLECSC_LOG(DEBUG, "registering characteristic %s with " + "def_handle=%d val_handle=%d\n", + ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), + ctxt->chr.def_handle, + ctxt->chr.val_handle); + break; + + case BLE_GATT_REGISTER_OP_DSC: + BLECSC_LOG(DEBUG, "registering descriptor %s with handle=%d\n", + ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf), + ctxt->dsc.handle); + break; + + default: + assert(0); + break; + } +} + +int +gatt_svr_init(struct ble_csc_measurement_state * csc_measurement_state) +{ + int rc; + + rc = ble_gatts_count_cfg(gatt_svr_svcs); + if (rc != 0) { + return rc; + } + + rc = ble_gatts_add_svcs(gatt_svr_svcs); + if (rc != 0) { + return rc; + } + + measurement_state = csc_measurement_state; + + return 0; +} + diff --git a/apps/blecsc/src/main.c b/apps/blecsc/src/main.c new file mode 100644 index 000000000..fb809dbff --- /dev/null +++ b/apps/blecsc/src/main.c @@ -0,0 +1,319 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include + +#include "os/mynewt.h" +#include "console/console.h" +#include "config/config.h" +#include "nimble/ble.h" +#include "host/ble_hs.h" +#include "services/gap/ble_svc_gap.h" +#include "blecsc_sens.h" + +/* Wheel size for simulation calculations */ +#define CSC_SIM_WHEEL_CIRCUMFERENCE_MM 2000 +/* Simulated cadence lower limit */ +#define CSC_SIM_CRANK_RPM_MIN 20 +/* Simulated cadence upper limit */ +#define CSC_SIM_CRANK_RPM_MAX 100 +/* Simulated speed lower limit */ +#define CSC_SIM_SPEED_KPH_MIN 0 +/* Simulated speed upper limit */ +#define CSC_SIM_SPEED_KPH_MAX 35 + +/* Log data */ +struct log blecsc_log; + +/* Noticication status */ +static bool notify_state = false; + +/* Connection handle */ +static uint16_t conn_handle; + +static uint8_t blecsc_addr_type; + +/* Advertised device name */ +static const char *device_name = "blecsc_sensor"; + +/* Measurement and notification timer */ +static struct os_callout blecsc_measure_timer; + +/* Variable holds current CSC measurement state */ +static struct ble_csc_measurement_state csc_measurement_state; + +/* Variable holds simulted speed (kilometers per hour) */ +static uint16_t csc_sim_speed_kph = CSC_SIM_SPEED_KPH_MIN; + +/* Variable holds simulated cadence (RPM) */ +static uint8_t csc_sim_crank_rpm = CSC_SIM_CRANK_RPM_MIN; + +static int blecsc_gap_event(struct ble_gap_event *event, void *arg); + + +/* + * Enables advertising with parameters: + * o General discoverable mode + * o Undirected connectable mode + */ +static void +blecsc_advertise(void) +{ + struct ble_gap_adv_params adv_params; + struct ble_hs_adv_fields fields; + int rc; + + /* + * Set the advertisement data included in our advertisements: + * o Flags (indicates advertisement type and other general info) + * o Advertising tx power + * o Device name + */ + memset(&fields, 0, sizeof(fields)); + + /* + * Advertise two flags: + * o Discoverability in forthcoming advertisement (general) + * o BLE-only (BR/EDR unsupported) + */ + fields.flags = BLE_HS_ADV_F_DISC_GEN | + BLE_HS_ADV_F_BREDR_UNSUP; + + /* + * Indicate that the TX power level field should be included; have the + * stack fill this value automatically. This is done by assigning the + * special value BLE_HS_ADV_TX_PWR_LVL_AUTO. + */ + fields.tx_pwr_lvl_is_present = 1; + fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; + + fields.name = (uint8_t *)device_name; + fields.name_len = strlen(device_name); + fields.name_is_complete = 1; + + /* + * Set appearance. + */ + fields.appearance = ble_svc_gap_device_appearance(); + fields.appearance_is_present = 1; + + rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + BLECSC_LOG(ERROR, "error setting advertisement data; rc=%d\n", rc); + return; + } + + /* Begin advertising */ + memset(&adv_params, 0, sizeof(adv_params)); + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + rc = ble_gap_adv_start(blecsc_addr_type, NULL, BLE_HS_FOREVER, + &adv_params, blecsc_gap_event, NULL); + if (rc != 0) { + BLECSC_LOG(ERROR, "error enabling advertisement; rc=%d\n", rc); + return; + } +} + + +/* Update simulated CSC measurements. + * Each call increments wheel and crank revolution counters by one and + * computes last event time in order to match simulated candence and speed. + * Last event time is expressedd in 1/1024th of second units. + * + * 60 * 1024 + * crank_dt = -------------- + * cadence[RPM] + * + * + * circumference[mm] * 1024 * 60 * 60 + * wheel_dt = ------------------------------------- + * 10^6 * speed [kph] + */ +static void +blecsc_simulate_speed_and_cadence() +{ + uint16_t wheel_rev_period; + uint16_t crank_rev_period; + + /* Update simulated crank and wheel rotation speed */ + csc_sim_speed_kph++; + if (csc_sim_speed_kph >= CSC_SIM_SPEED_KPH_MAX) { + csc_sim_speed_kph = CSC_SIM_SPEED_KPH_MIN; + } + + csc_sim_crank_rpm++; + if (csc_sim_crank_rpm >= CSC_SIM_CRANK_RPM_MAX) { + csc_sim_crank_rpm = CSC_SIM_CRANK_RPM_MIN; + } + + /* Calculate simulated measurement values */ + if (csc_sim_speed_kph > 0){ + wheel_rev_period = (36*64*CSC_SIM_WHEEL_CIRCUMFERENCE_MM) / + (625*csc_sim_speed_kph); + csc_measurement_state.cumulative_wheel_rev++; + csc_measurement_state.last_wheel_evt_time += wheel_rev_period; + } + + if (csc_sim_crank_rpm > 0){ + crank_rev_period = (60*1024) / csc_sim_crank_rpm; + csc_measurement_state.cumulative_crank_rev++; + csc_measurement_state.last_crank_evt_time += crank_rev_period; + } + + BLECSC_LOG(INFO, "CSC simulated values: speed = %d kph, cadence = %d \n", + csc_sim_speed_kph, csc_sim_crank_rpm); +} + +/* Run CSC measurement simulation and notify it to the client */ +static void +blecsc_measurement(struct os_event *ev) +{ + int rc; + + rc = os_callout_reset(&blecsc_measure_timer, OS_TICKS_PER_SEC); + assert(rc == 0); + + blecsc_simulate_speed_and_cadence(); + + if (notify_state) { + rc = gatt_svr_chr_notify_csc_measurement(conn_handle); + assert(rc == 0); + } +} + +static int +blecsc_gap_event(struct ble_gap_event *event, void *arg) +{ + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed */ + BLECSC_LOG(INFO, "connection %s; status=%d\n", + event->connect.status == 0 ? "established" : "failed", + event->connect.status); + + if (event->connect.status != 0) { + /* Connection failed; resume advertising */ + blecsc_advertise(); + conn_handle = 0; + } + else { + conn_handle = event->connect.conn_handle; + } + break; + + case BLE_GAP_EVENT_DISCONNECT: + BLECSC_LOG(INFO, "disconnect; reason=%d\n", event->disconnect.reason); + conn_handle = 0; + /* Connection terminated; resume advertising */ + blecsc_advertise(); + break; + + case BLE_GAP_EVENT_ADV_COMPLETE: + BLECSC_LOG(INFO, "adv complete\n"); + break; + + case BLE_GAP_EVENT_SUBSCRIBE: + BLECSC_LOG(INFO, "subscribe event attr_handle=%d\n", + event->subscribe.attr_handle); + + if (event->subscribe.attr_handle == csc_measurement_handle) { + notify_state = event->subscribe.cur_notify; + BLECSC_LOG(INFO, "csc measurement notify state = %d\n", + notify_state); + } + else if (event->subscribe.attr_handle == csc_control_point_handle) { + gatt_svr_set_cp_indicate(event->subscribe.cur_indicate); + BLECSC_LOG(INFO, "csc control point indicate state = %d\n", + event->subscribe.cur_indicate); + } + break; + + case BLE_GAP_EVENT_MTU: + BLECSC_LOG(INFO, "mtu update event; conn_handle=%d mtu=%d\n", + event->mtu.conn_handle, + event->mtu.value); + break; + + } + + return 0; +} + +static void +blecsc_on_sync(void) +{ + int rc; + + /* Figure out address to use while advertising (no privacy) */ + rc = ble_hs_id_infer_auto(0, &blecsc_addr_type); + assert(rc == 0); + + /* Begin advertising */ + blecsc_advertise(); +} + +/* + * main + * + * The main task for the project. This function initializes the packages, + * then starts serving events from default event queue. + * + * @return int NOTE: this function should never return! + */ +int +main(void) +{ + int rc; + + /* Initialize OS */ + sysinit(); + + /* Initialize the blecsc log */ + log_register("blecsc_sens_log", &blecsc_log, &log_console_handler, NULL, + LOG_SYSLEVEL); + + /* Initialize the NimBLE host configuration */ + log_register("blecsc_sens", &ble_hs_log, &log_console_handler, NULL, + LOG_SYSLEVEL); + ble_hs_cfg.sync_cb = blecsc_on_sync; + + /* Initialize measurement and notification timer */ + os_callout_init(&blecsc_measure_timer, os_eventq_dflt_get(), + blecsc_measurement, NULL); + rc = os_callout_reset(&blecsc_measure_timer, OS_TICKS_PER_SEC); + assert(rc == 0); + + rc = gatt_svr_init(&csc_measurement_state); + assert(rc == 0); + + /* Set the default device name */ + rc = ble_svc_gap_device_name_set(device_name); + assert(rc == 0); + + /* As the last thing, process events from default event queue */ + while (1) { + os_eventq_run(os_eventq_dflt_get()); + } + return 0; +} + diff --git a/apps/blecsc/syscfg.yml b/apps/blecsc/syscfg.yml new file mode 100644 index 000000000..2f41785ba --- /dev/null +++ b/apps/blecsc/syscfg.yml @@ -0,0 +1,38 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + + +syscfg.vals: + # Disable central and observer roles. + BLE_ROLE_BROADCASTER: 1 + BLE_ROLE_CENTRAL: 0 + BLE_ROLE_OBSERVER: 0 + BLE_ROLE_PERIPHERAL: 1 + + # Disable unused eddystone feature. + BLE_EDDYSTONE: 0 + + # Log reboot messages to a flash circular buffer. + REBOOT_LOG_FCB: 1 + LOG_FCB: 1 + CONFIG_FCB: 1 + + # Set public device address. + BLE_PUBLIC_DEV_ADDR: ((uint8_t[6]){0xcc, 0xbb, 0xaa, 0x33, 0x22, 0x11}) + + # Set device appearance to Cycling Speed and Cadence Sensor + BLE_SVC_GAP_APPEARANCE: BLE_SVC_GAP_APPEARANCE_CYC_SPEED_AND_CADENCE_SENSOR diff --git a/apps/blehci/pkg.yml b/apps/blehci/pkg.yml new file mode 100644 index 000000000..712a5feb7 --- /dev/null +++ b/apps/blehci/pkg.yml @@ -0,0 +1,30 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +pkg.name: apps/blehci +pkg.type: app +pkg.description: BLE controller application exposing HCI over UART +pkg.author: "Johan Hedberg " +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + +pkg.deps: + - "@apache-mynewt-core/sys/console/stub" + - "@apache-mynewt-core/sys/stats/full" + - "@apache-mynewt-core/kernel/os" + - nimble/controller + - nimble/transport/uart diff --git a/apps/blehci/src/main.c b/apps/blehci/src/main.c new file mode 100644 index 000000000..040c1572c --- /dev/null +++ b/apps/blehci/src/main.c @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include "os/mynewt.h" + +int +main(void) +{ + /* Initialize OS */ + sysinit(); + + while (1) { + os_eventq_run(os_eventq_dflt_get()); + } + return 0; +} diff --git a/apps/blehci/syscfg.yml b/apps/blehci/syscfg.yml new file mode 100644 index 000000000..89f101380 --- /dev/null +++ b/apps/blehci/syscfg.yml @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Package: apps/blehci + +syscfg.vals: + # Default task settings + OS_MAIN_STACK_SIZE: 64 diff --git a/apps/blehr/README.md b/apps/blehr/README.md new file mode 100644 index 000000000..d06d95ded --- /dev/null +++ b/apps/blehr/README.md @@ -0,0 +1,9 @@ +# BLE Heart Rate peripheral app. + +The source files are located in the src/ directory. + +pkg.yml contains the base definition of the app. + +syscfg.yml contains setting definitions and overrides. + + diff --git a/apps/blehr/pkg.yml b/apps/blehr/pkg.yml new file mode 100644 index 000000000..f043a5bfb --- /dev/null +++ b/apps/blehr/pkg.yml @@ -0,0 +1,40 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +pkg.name: apps/blehr +pkg.type: app +pkg.description: BLE peripheral heartrate sensor. +pkg.author: "Szymon Czapracki" +pkg.email: "szymon.czapracki@codecoup.pl" +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + +pkg.deps: + - "@apache-mynewt-core/boot/bootutil" + - "@apache-mynewt-core/kernel/os" + - "@apache-mynewt-core/sys/console/full" + - "@apache-mynewt-core/sys/log/full" + - "@apache-mynewt-core/sys/stats/full" + - "@apache-mynewt-core/sys/sysinit" + - "@apache-mynewt-core/sys/id" + - nimble/controller + - nimble/host + - nimble/host/services/gap + - nimble/host/services/gatt + - nimble/host/store/config + - nimble/transport/ram diff --git a/apps/blehr/src/blehr_sens.h b/apps/blehr/src/blehr_sens.h new file mode 100644 index 000000000..2eec9ddf4 --- /dev/null +++ b/apps/blehr/src/blehr_sens.h @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef H_BLEHR_SENSOR_ +#define H_BLEHR_SENSOR_ + +#include "log/log.h" +#include "nimble/ble.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct log blehr_log; + +/* blehr uses the first "peruser" log module */ +#define BLEHR_LOG_MODULE (LOG_MODULE_PERUSER + 0) + +/* Convenience macro for logging to the blerh module */ +#define BLEHR_LOG(lvl, ...) \ + LOG_ ## lvl(&blehr_log, BLEHR_LOG_MODULE, __VA_ARGS__) + +/* Heart-rate configuration */ +#define GATT_HRS_UUID 0x180D +#define GATT_HRS_MEASUREMENT_UUID 0x2A37 +#define GATT_HRS_BODY_SENSOR_LOC_UUID 0x2A38 +#define GATT_DEVICE_INFO_UUID 0x180A +#define GATT_MANUFACTURER_NAME_UUID 0x2A29 +#define GATT_MODEL_NUMBER_UUID 0x2A24 + +extern uint16_t hrs_hrm_handle; + +int gatt_svr_init(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/apps/blehr/src/gatt_svr.c b/apps/blehr/src/gatt_svr.c new file mode 100644 index 000000000..eac843e8a --- /dev/null +++ b/apps/blehr/src/gatt_svr.c @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include "host/ble_hs.h" +#include "host/ble_uuid.h" +#include "blehr_sens.h" + +static const char *manuf_name = "Apache Mynewt"; +static const char *model_num = "Mynewt HR Sensor"; +uint16_t hrs_hrm_handle; + +static int +gatt_svr_chr_access_heart_rate(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg); + +static int +gatt_svr_chr_access_device_info(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg); + +static const struct ble_gatt_svc_def gatt_svr_svcs[] = { + { + /* Service: Heart-rate */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = BLE_UUID16_DECLARE(GATT_HRS_UUID), + .characteristics = (struct ble_gatt_chr_def[]) { { + /* Characteristic: Heart-rate measurement */ + .uuid = BLE_UUID16_DECLARE(GATT_HRS_MEASUREMENT_UUID), + .access_cb = gatt_svr_chr_access_heart_rate, + .val_handle = &hrs_hrm_handle, + .flags = BLE_GATT_CHR_F_NOTIFY, + }, { + /* Characteristic: Body sensor location */ + .uuid = BLE_UUID16_DECLARE(GATT_HRS_BODY_SENSOR_LOC_UUID), + .access_cb = gatt_svr_chr_access_heart_rate, + .flags = BLE_GATT_CHR_F_READ, + }, { + 0, /* No more characteristics in this service */ + }, } + }, + + { + /* Service: Device Information */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = BLE_UUID16_DECLARE(GATT_DEVICE_INFO_UUID), + .characteristics = (struct ble_gatt_chr_def[]) { { + /* Characteristic: * Manufacturer name */ + .uuid = BLE_UUID16_DECLARE(GATT_MANUFACTURER_NAME_UUID), + .access_cb = gatt_svr_chr_access_device_info, + .flags = BLE_GATT_CHR_F_READ, + }, { + /* Characteristic: Model number string */ + .uuid = BLE_UUID16_DECLARE(GATT_MODEL_NUMBER_UUID), + .access_cb = gatt_svr_chr_access_device_info, + .flags = BLE_GATT_CHR_F_READ, + }, { + 0, /* No more characteristics in this service */ + }, } + }, + + { + 0, /* No more services */ + }, +}; + +static int +gatt_svr_chr_access_heart_rate(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + /* Sensor location, set to "Chest" */ + static uint8_t body_sens_loc = 0x01; + uint16_t uuid; + int rc; + + uuid = ble_uuid_u16(ctxt->chr->uuid); + + if (uuid == GATT_HRS_BODY_SENSOR_LOC_UUID) { + rc = os_mbuf_append(ctxt->om, &body_sens_loc, sizeof(body_sens_loc)); + + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + assert(0); + return BLE_ATT_ERR_UNLIKELY; +} + +static int +gatt_svr_chr_access_device_info(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + uint16_t uuid; + int rc; + + uuid = ble_uuid_u16(ctxt->chr->uuid); + + if (uuid == GATT_MODEL_NUMBER_UUID) { + rc = os_mbuf_append(ctxt->om, model_num, strlen(model_num)); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + if (uuid == GATT_MANUFACTURER_NAME_UUID) { + rc = os_mbuf_append(ctxt->om, manuf_name, strlen(manuf_name)); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + assert(0); + return BLE_ATT_ERR_UNLIKELY; +} + +void +gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) +{ + char buf[BLE_UUID_STR_LEN]; + + switch (ctxt->op) { + case BLE_GATT_REGISTER_OP_SVC: + BLEHR_LOG(DEBUG, "registered service %s with handle=%d\n", + ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf), + ctxt->svc.handle); + break; + + case BLE_GATT_REGISTER_OP_CHR: + BLEHR_LOG(DEBUG, "registering characteristic %s with " + "def_handle=%d val_handle=%d\n", + ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), + ctxt->chr.def_handle, + ctxt->chr.val_handle); + break; + + case BLE_GATT_REGISTER_OP_DSC: + BLEHR_LOG(DEBUG, "registering descriptor %s with handle=%d\n", + ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf), + ctxt->dsc.handle); + break; + + default: + assert(0); + break; + } +} + +int +gatt_svr_init(void) +{ + int rc; + + rc = ble_gatts_count_cfg(gatt_svr_svcs); + if (rc != 0) { + return rc; + } + + rc = ble_gatts_add_svcs(gatt_svr_svcs); + if (rc != 0) { + return rc; + } + + return 0; +} + diff --git a/apps/blehr/src/main.c b/apps/blehr/src/main.c new file mode 100644 index 000000000..e538beeb7 --- /dev/null +++ b/apps/blehr/src/main.c @@ -0,0 +1,262 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include + +#include "os/mynewt.h" +#include "console/console.h" +#include "config/config.h" +#include "nimble/ble.h" +#include "host/ble_hs.h" +#include "services/gap/ble_svc_gap.h" +#include "blehr_sens.h" + +/* Log data */ +struct log blehr_log; + +static bool notify_state; + +static const char *device_name = "blehr_sensor"; + +static int blehr_gap_event(struct ble_gap_event *event, void *arg); + +static uint8_t blehr_addr_type; + +/* Sending notify data timer */ +static struct os_callout blehr_tx_timer; + +/* Variable to simulate heart beats */ +static uint8_t heartrate = 90; + +/* + * Enables advertising with parameters: + * o General discoverable mode + * o Undirected connectable mode + */ +static void +blehr_advertise(void) +{ + struct ble_gap_adv_params adv_params; + struct ble_hs_adv_fields fields; + int rc; + + /* + * Set the advertisement data included in our advertisements: + * o Flags (indicates advertisement type and other general info) + * o Advertising tx power + * o Device name + */ + memset(&fields, 0, sizeof(fields)); + + /* + * Advertise two flags: + * o Discoverability in forthcoming advertisement (general) + * o BLE-only (BR/EDR unsupported) + */ + fields.flags = BLE_HS_ADV_F_DISC_GEN | + BLE_HS_ADV_F_BREDR_UNSUP; + + /* + * Indicate that the TX power level field should be included; have the + * stack fill this value automatically. This is done by assigning the + * special value BLE_HS_ADV_TX_PWR_LVL_AUTO. + */ + fields.tx_pwr_lvl_is_present = 1; + fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; + + fields.name = (uint8_t *)device_name; + fields.name_len = strlen(device_name); + fields.name_is_complete = 1; + + rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + BLEHR_LOG(ERROR, "error setting advertisement data; rc=%d\n", rc); + return; + } + + /* Begin advertising */ + memset(&adv_params, 0, sizeof(adv_params)); + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + rc = ble_gap_adv_start(blehr_addr_type, NULL, BLE_HS_FOREVER, + &adv_params, blehr_gap_event, NULL); + if (rc != 0) { + BLEHR_LOG(ERROR, "error enabling advertisement; rc=%d\n", rc); + return; + } +} + +static void +blehr_tx_hrate_stop(void) +{ + os_callout_stop(&blehr_tx_timer); +} + +/* Reset heartrate measurment */ +static void +blehr_tx_hrate_reset(void) +{ + int rc; + + rc = os_callout_reset(&blehr_tx_timer, OS_TICKS_PER_SEC); + assert(rc == 0); +} + +/* This functions simulates heart beat and notifies it to the client */ +static void +blehr_tx_hrate(struct os_event *ev) +{ + static uint8_t hrm[2]; + int rc; + struct os_mbuf *om; + + if (!notify_state) { + blehr_tx_hrate_stop(); + heartrate = 90; + return; + } + + hrm[0] = 0x06; /* contact of a sensor */ + hrm[1] = heartrate; /* storing dummy data */ + + /* Simulation of heart beats */ + heartrate++; + if (heartrate == 160) { + heartrate = 90; + } + + om = ble_hs_mbuf_from_flat(hrm, sizeof(hrm)); + + rc = ble_gattc_notify_custom(notify_state, hrs_hrm_handle, om); + + assert(rc == 0); + blehr_tx_hrate_reset(); +} + +static int +blehr_gap_event(struct ble_gap_event *event, void *arg) +{ + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed */ + BLEHR_LOG(INFO, "connection %s; status=%d\n", + event->connect.status == 0 ? "established" : "failed", + event->connect.status); + + if (event->connect.status != 0) { + /* Connection failed; resume advertising */ + blehr_advertise(); + } + break; + + case BLE_GAP_EVENT_DISCONNECT: + BLEHR_LOG(INFO, "disconnect; reason=%d\n", event->disconnect.reason); + + /* Connection terminated; resume advertising */ + blehr_advertise(); + break; + + case BLE_GAP_EVENT_ADV_COMPLETE: + BLEHR_LOG(INFO, "adv complete\n"); + blehr_advertise(); + break; + + case BLE_GAP_EVENT_SUBSCRIBE: + BLEHR_LOG(INFO, "subscribe event; cur_notify=%d\n value handle; " + "val_handle=%d\n", + event->subscribe.cur_notify, + hrs_hrm_handle); + if (event->subscribe.attr_handle == hrs_hrm_handle) { + notify_state = event->subscribe.cur_notify; + blehr_tx_hrate_reset(); + } else if (event->subscribe.attr_handle != hrs_hrm_handle) { + notify_state = event->subscribe.cur_notify; + blehr_tx_hrate_stop(); + } + break; + + case BLE_GAP_EVENT_MTU: + BLEHR_LOG(INFO, "mtu update event; conn_handle=%d mtu=%d\n", + event->mtu.conn_handle, + event->mtu.value); + break; + + } + + return 0; +} + +static void +blehr_on_sync(void) +{ + int rc; + + /* Use privacy */ + rc = ble_hs_id_infer_auto(0, &blehr_addr_type); + assert(rc == 0); + + /* Begin advertising */ + blehr_advertise(); +} + +/* + * main + * + * The main task for the project. This function initializes the packages, + * then starts serving events from default event queue. + * + * @return int NOTE: this function should never return! + */ +int +main(void) +{ + int rc; + + /* Initialize OS */ + sysinit(); + + /* Initialize the blehr log */ + log_register("blehr_sens_log", &blehr_log, &log_console_handler, NULL, + LOG_SYSLEVEL); + + /* Initialize the NimBLE host configuration */ + log_register("blehr_sens", &ble_hs_log, &log_console_handler, NULL, + LOG_SYSLEVEL); + ble_hs_cfg.sync_cb = blehr_on_sync; + + os_callout_init(&blehr_tx_timer, os_eventq_dflt_get(), + blehr_tx_hrate, NULL); + + rc = gatt_svr_init(); + assert(rc == 0); + + /* Set the default device name */ + rc = ble_svc_gap_device_name_set(device_name); + assert(rc == 0); + + /* As the last thing, process events from default event queue */ + while (1) { + os_eventq_run(os_eventq_dflt_get()); + } + return 0; +} + diff --git a/apps/blehr/syscfg.yml b/apps/blehr/syscfg.yml new file mode 100644 index 000000000..a6be2e29d --- /dev/null +++ b/apps/blehr/syscfg.yml @@ -0,0 +1,35 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + + +syscfg.vals: + # Disable central and observer roles. + BLE_ROLE_BROADCASTER: 1 + BLE_ROLE_CENTRAL: 0 + BLE_ROLE_OBSERVER: 0 + BLE_ROLE_PERIPHERAL: 1 + + # Disable unused eddystone feature. + BLE_EDDYSTONE: 0 + + # Log reboot messages to a flash circular buffer. + REBOOT_LOG_FCB: 1 + LOG_FCB: 1 + CONFIG_FCB: 1 + + # Set public device address. + BLE_PUBLIC_DEV_ADDR: ((uint8_t[6]){0xcc, 0xbb, 0xaa, 0x33, 0x22, 0x11}) diff --git a/apps/blemesh/pkg.yml b/apps/blemesh/pkg.yml new file mode 100644 index 000000000..a6b15be13 --- /dev/null +++ b/apps/blemesh/pkg.yml @@ -0,0 +1,36 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +pkg.name: apps/blemesh +pkg.type: app +pkg.description: Sample application for BLE Mesh node with on/off model +pkg.author: "Łukasz Rymanowski " +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + +pkg.deps: + - "@apache-mynewt-core/kernel/os" + - "@apache-mynewt-core/sys/console/full" + - "@apache-mynewt-core/sys/log/full" + - "@apache-mynewt-core/sys/stats/full" + - "@apache-mynewt-core/sys/shell" + - nimble/controller + - nimble/host + - nimble/host/services/gap + - nimble/host/services/gatt + - nimble/host/store/ram + - nimble/transport/ram diff --git a/apps/blemesh/src/main.c b/apps/blemesh/src/main.c new file mode 100644 index 000000000..105ec6f9b --- /dev/null +++ b/apps/blemesh/src/main.c @@ -0,0 +1,436 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include "os/mynewt.h" +#include "mesh/mesh.h" +#include "console/console.h" +#include "hal/hal_system.h" +#include "hal/hal_gpio.h" +#include "bsp/bsp.h" +#include "shell/shell.h" + +/* BLE */ +#include "nimble/ble.h" +#include "host/ble_hs.h" +#include "services/gap/ble_svc_gap.h" +#include "mesh/glue.h" + +#define BT_DBG_ENABLED (MYNEWT_VAL(BLE_MESH_DEBUG)) + +/* Company ID */ +#define CID_VENDOR 0x05C3 +#define STANDARD_TEST_ID 0x00 +#define TEST_ID 0x01 +static int recent_test_id = STANDARD_TEST_ID; + +#define FAULT_ARR_SIZE 2 + +static bool has_reg_fault = true; + +static struct bt_mesh_cfg_srv cfg_srv = { + .relay = BT_MESH_RELAY_DISABLED, + .beacon = BT_MESH_BEACON_ENABLED, +#if MYNEWT_VAL(BLE_MESH_FRIEND) + .frnd = BT_MESH_FRIEND_ENABLED, +#else + .gatt_proxy = BT_MESH_GATT_PROXY_NOT_SUPPORTED, +#endif +#if MYNEWT_VAL(BLE_MESH_GATT_PROXY) + .gatt_proxy = BT_MESH_GATT_PROXY_ENABLED, +#else + .gatt_proxy = BT_MESH_GATT_PROXY_NOT_SUPPORTED, +#endif + .default_ttl = 7, + + /* 3 transmissions with 20ms interval */ + .net_transmit = BT_MESH_TRANSMIT(2, 20), + .relay_retransmit = BT_MESH_TRANSMIT(2, 20), +}; + +static int +fault_get_cur(struct bt_mesh_model *model, + uint8_t *test_id, + uint16_t *company_id, + uint8_t *faults, + uint8_t *fault_count) +{ + uint8_t reg_faults[FAULT_ARR_SIZE] = { [0 ... FAULT_ARR_SIZE-1] = 0xff }; + + console_printf("fault_get_cur() has_reg_fault %u\n", has_reg_fault); + + *test_id = recent_test_id; + *company_id = CID_VENDOR; + + *fault_count = min(*fault_count, sizeof(reg_faults)); + memcpy(faults, reg_faults, *fault_count); + + return 0; +} + +static int +fault_get_reg(struct bt_mesh_model *model, + uint16_t company_id, + uint8_t *test_id, + uint8_t *faults, + uint8_t *fault_count) +{ + if (company_id != CID_VENDOR) { + return -BLE_HS_EINVAL; + } + + console_printf("fault_get_reg() has_reg_fault %u\n", has_reg_fault); + + *test_id = recent_test_id; + + if (has_reg_fault) { + uint8_t reg_faults[FAULT_ARR_SIZE] = { [0 ... FAULT_ARR_SIZE-1] = 0xff }; + + *fault_count = min(*fault_count, sizeof(reg_faults)); + memcpy(faults, reg_faults, *fault_count); + } else { + *fault_count = 0; + } + + return 0; +} + +static int +fault_clear(struct bt_mesh_model *model, uint16_t company_id) +{ + if (company_id != CID_VENDOR) { + return -BLE_HS_EINVAL; + } + + has_reg_fault = false; + + return 0; +} + +static int +fault_test(struct bt_mesh_model *model, uint8_t test_id, uint16_t company_id) +{ + if (company_id != CID_VENDOR) { + return -BLE_HS_EINVAL; + } + + if (test_id != STANDARD_TEST_ID && test_id != TEST_ID) { + return -BLE_HS_EINVAL; + } + + recent_test_id = test_id; + has_reg_fault = true; + bt_mesh_fault_update(model->elem); + + return 0; +} + +static const struct bt_mesh_health_srv_cb health_srv_cb = { + .fault_get_cur = &fault_get_cur, + .fault_get_reg = &fault_get_reg, + .fault_clear = &fault_clear, + .fault_test = &fault_test, +}; + +static struct bt_mesh_health_srv health_srv = { + .cb = &health_srv_cb, +}; + +static struct bt_mesh_model_pub health_pub; + +static void +health_pub_init(void) +{ + health_pub.msg = BT_MESH_HEALTH_FAULT_MSG(0); +} + +static struct bt_mesh_model_pub gen_level_pub; +static struct bt_mesh_model_pub gen_onoff_pub; + +static uint8_t gen_on_off_state; +static int16_t gen_level_state; + +static void gen_onoff_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + struct os_mbuf *msg = NET_BUF_SIMPLE(3); + uint8_t *status; + + console_printf("#mesh-onoff STATUS\n"); + + bt_mesh_model_msg_init(msg, BT_MESH_MODEL_OP_2(0x82, 0x04)); + status = net_buf_simple_add(msg, 1); + *status = gen_on_off_state; + + if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) { + console_printf("#mesh-onoff STATUS: send status failed\n"); + } + + os_mbuf_free_chain(msg); +} + +static void gen_onoff_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct os_mbuf *buf) +{ + console_printf("#mesh-onoff GET\n"); + + gen_onoff_status(model, ctx); +} + +static void gen_onoff_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct os_mbuf *buf) +{ + console_printf("#mesh-onoff SET\n"); + + gen_on_off_state = buf->om_data[0]; + hal_gpio_write(LED_2, !gen_on_off_state); + + gen_onoff_status(model, ctx); +} + +static void gen_onoff_set_unack(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct os_mbuf *buf) +{ + console_printf("#mesh-onoff SET-UNACK\n"); + + gen_on_off_state = buf->om_data[0]; + hal_gpio_write(LED_2, !gen_on_off_state); +} + +static const struct bt_mesh_model_op gen_onoff_op[] = { + { BT_MESH_MODEL_OP_2(0x82, 0x01), 0, gen_onoff_get }, + { BT_MESH_MODEL_OP_2(0x82, 0x02), 2, gen_onoff_set }, + { BT_MESH_MODEL_OP_2(0x82, 0x03), 2, gen_onoff_set_unack }, + BT_MESH_MODEL_OP_END, +}; + +static void gen_level_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + struct os_mbuf *msg = NET_BUF_SIMPLE(4); + + console_printf("#mesh-level STATUS\n"); + + bt_mesh_model_msg_init(msg, BT_MESH_MODEL_OP_2(0x82, 0x08)); + net_buf_simple_add_le16(msg, gen_level_state); + + if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) { + console_printf("#mesh-level STATUS: send status failed\n"); + } + + os_mbuf_free_chain(msg); +} + +static void gen_level_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct os_mbuf *buf) +{ + console_printf("#mesh-level GET\n"); + + gen_level_status(model, ctx); +} + +static void gen_level_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct os_mbuf *buf) +{ + int16_t level; + + level = (int16_t) net_buf_simple_pull_le16(buf); + console_printf("#mesh-level SET: level=%d\n", level); + + gen_level_status(model, ctx); + + gen_level_state = level; + console_printf("#mesh-level: level=%d\n", gen_level_state); +} + +static void gen_level_set_unack(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct os_mbuf *buf) +{ + int16_t level; + + level = (int16_t) net_buf_simple_pull_le16(buf); + console_printf("#mesh-level SET-UNACK: level=%d\n", level); + + gen_level_state = level; + console_printf("#mesh-level: level=%d\n", gen_level_state); +} + +static void gen_delta_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct os_mbuf *buf) +{ + int16_t delta_level; + + delta_level = (int16_t) net_buf_simple_pull_le16(buf); + console_printf("#mesh-level DELTA-SET: delta_level=%d\n", delta_level); + + gen_level_status(model, ctx); + + gen_level_state += delta_level; + console_printf("#mesh-level: level=%d\n", gen_level_state); +} + +static void gen_delta_set_unack(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct os_mbuf *buf) +{ + int16_t delta_level; + + delta_level = (int16_t) net_buf_simple_pull_le16(buf); + console_printf("#mesh-level DELTA-SET: delta_level=%d\n", delta_level); + + gen_level_state += delta_level; + console_printf("#mesh-level: level=%d\n", gen_level_state); +} + +static void gen_move_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct os_mbuf *buf) +{ +} + +static void gen_move_set_unack(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct os_mbuf *buf) +{ +} + +static const struct bt_mesh_model_op gen_level_op[] = { + { BT_MESH_MODEL_OP_2(0x82, 0x05), 0, gen_level_get }, + { BT_MESH_MODEL_OP_2(0x82, 0x06), 3, gen_level_set }, + { BT_MESH_MODEL_OP_2(0x82, 0x07), 3, gen_level_set_unack }, + { BT_MESH_MODEL_OP_2(0x82, 0x09), 5, gen_delta_set }, + { BT_MESH_MODEL_OP_2(0x82, 0x0a), 5, gen_delta_set_unack }, + { BT_MESH_MODEL_OP_2(0x82, 0x0b), 3, gen_move_set }, + { BT_MESH_MODEL_OP_2(0x82, 0x0c), 3, gen_move_set_unack }, + BT_MESH_MODEL_OP_END, +}; + +static struct bt_mesh_model root_models[] = { + BT_MESH_MODEL_CFG_SRV(&cfg_srv), + BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub), + BT_MESH_MODEL(BT_MESH_MODEL_ID_GEN_ONOFF_SRV, gen_onoff_op, + &gen_onoff_pub, NULL), + BT_MESH_MODEL(BT_MESH_MODEL_ID_GEN_LEVEL_SRV, gen_level_op, + &gen_level_pub, NULL), +}; + +static struct bt_mesh_model vnd_models[] = { + BT_MESH_MODEL_VND(CID_VENDOR, BT_MESH_MODEL_ID_GEN_ONOFF_SRV, gen_onoff_op, + &gen_onoff_pub, NULL), +}; + +static struct bt_mesh_elem elements[] = { + BT_MESH_ELEM(0, root_models, vnd_models), +}; + +static const struct bt_mesh_comp comp = { + .cid = CID_VENDOR, + .elem = elements, + .elem_count = ARRAY_SIZE(elements), +}; + +static int output_number(bt_mesh_output_action_t action, uint32_t number) +{ + console_printf("OOB Number: %lu\n", number); + + return 0; +} + +static void prov_complete(u16_t net_idx, u16_t addr) +{ + console_printf("Local node provisioned, primary address 0x%04x\n", addr); +} + +static const uint8_t dev_uuid[16] = MYNEWT_VAL(BLE_MESH_DEV_UUID); + +static const struct bt_mesh_prov prov = { + .uuid = dev_uuid, + .output_size = 4, + .output_actions = BT_MESH_DISPLAY_NUMBER | BT_MESH_BEEP | BT_MESH_VIBRATE | BT_MESH_BLINK, + .output_number = output_number, + .complete = prov_complete, +}; + +static void +blemesh_on_reset(int reason) +{ + BLE_HS_LOG(ERROR, "Resetting state; reason=%d\n", reason); +} + +static void +blemesh_on_sync(void) +{ + int err; + ble_addr_t addr; + + console_printf("Bluetooth initialized\n"); + + /* Use NRPA */ + err = ble_hs_id_gen_rnd(1, &addr); + assert(err == 0); + err = ble_hs_id_set_rnd(addr.val); + assert(err == 0); + + err = bt_mesh_init(addr.type, &prov, &comp); + if (err) { + console_printf("Initializing mesh failed (err %d)\n", err); + return; + } + +#if (MYNEWT_VAL(BLE_MESH_SHELL)) + shell_register_default_module("mesh"); +#endif + + console_printf("Mesh initialized\n"); +} + +int +main(int argc, char **argv) +{ + +#ifdef ARCH_sim + mcu_sim_parse_args(argc, argv); +#endif + + /* Initialize OS */ + sysinit(); + + /* Initialize the NimBLE host configuration. */ + log_register("ble_hs", &ble_hs_log, &log_console_handler, NULL, + LOG_SYSLEVEL); + ble_hs_cfg.reset_cb = blemesh_on_reset; + ble_hs_cfg.sync_cb = blemesh_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + hal_gpio_init_out(LED_2, 0); + + health_pub_init(); + + while (1) { + os_eventq_run(os_eventq_dflt_get()); + } + return 0; +} diff --git a/apps/blemesh/syscfg.yml b/apps/blemesh/syscfg.yml new file mode 100644 index 000000000..c109e6ea6 --- /dev/null +++ b/apps/blemesh/syscfg.yml @@ -0,0 +1,48 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Package: apps/blemesh + +syscfg.vals: + # Enable the shell task. + SHELL_TASK: 1 + + # Set log level to info (disable debug logging). + LOG_LEVEL: 1 + + # Default task settings + OS_MAIN_STACK_SIZE: 768 + + # Newtmgr is not supported in this app, so disable newtmgr-over-shell. + SHELL_NEWTMGR: 0 + + BLE_MESH: 1 + MSYS_1_BLOCK_COUNT: 48 + + BLE_MESH_DEBUG: 1 + BLE_MESH_DEBUG_NET: 1 + BLE_MESH_DEBUG_TRANS: 1 + BLE_MESH_DEBUG_BEACON: 1 + BLE_MESH_DEBUG_CRYPTO: 1 + BLE_MESH_DEBUG_PROV: 1 + BLE_MESH_DEBUG_ACCESS: 1 + BLE_MESH_DEBUG_MODEL: 1 + BLE_MESH_DEBUG_ADV: 1 + BLE_MESH_DEBUG_LOW_POWER: 1 + BLE_MESH_DEBUG_FRIEND: 1 + BLE_MESH_DEBUG_PROXY: 1 diff --git a/apps/blemesh_light/pkg.yml b/apps/blemesh_light/pkg.yml new file mode 100644 index 000000000..da5ce4553 --- /dev/null +++ b/apps/blemesh_light/pkg.yml @@ -0,0 +1,36 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +pkg.name: apps/blemesh_light +pkg.type: app +pkg.description: Sample application for BLE Mesh node with Light model +pkg.author: "Michał Narajowski " +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + +pkg.deps: + - "@apache-mynewt-core/kernel/os" + - "@apache-mynewt-core/sys/console/full" + - "@apache-mynewt-core/sys/log/full" + - "@apache-mynewt-core/sys/stats/full" + - "@apache-mynewt-core/sys/shell" + - nimble/controller + - nimble/host + - nimble/host/services/gap + - nimble/host/services/gatt + - nimble/host/store/ram + - nimble/transport/ram diff --git a/apps/blemesh_light/src/light_model.c b/apps/blemesh_light/src/light_model.c new file mode 100644 index 000000000..8b00e2c04 --- /dev/null +++ b/apps/blemesh_light/src/light_model.c @@ -0,0 +1,242 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "syscfg/syscfg.h" + +#if MYNEWT_VAL(BLE_MESH_SHELL_MODELS) + +#include "mesh/mesh.h" +#include "bsp.h" +#include "pwm/pwm.h" +#include "light_model.h" +#include "ws2812.h" + +#if (!MYNEWT_VAL(USE_NEOPIXEL)) +#if MYNEWT_VAL(PWM_0) +struct pwm_dev *pwm0; +#endif +#if MYNEWT_VAL(PWM_1) +struct pwm_dev *pwm1; +#endif +#if MYNEWT_VAL(PWM_2) +struct pwm_dev *pwm2; +#endif +#if MYNEWT_VAL(PWM_3) +struct pwm_dev *pwm3; +#endif + +static uint16_t top_val; +#endif + +#if (MYNEWT_VAL(USE_NEOPIXEL)) +static uint32_t neopixel[WS2812_NUM_LED]; +#endif + +static u8_t gen_onoff_state; +static s16_t gen_level_state; + +static void light_set_lightness(u8_t percentage) +{ +#if (!MYNEWT_VAL(USE_NEOPIXEL)) + int rc; + + uint16_t pwm_val = (uint16_t) (percentage * top_val / 100); + +#if MYNEWT_VAL(PWM_0) + rc = pwm_set_duty_cycle(pwm0, 0, pwm_val); + assert(rc == 0); +#endif +#if MYNEWT_VAL(PWM_1) + rc = pwm_set_duty_cycle(pwm1, 0, pwm_val); + assert(rc == 0); +#endif +#if MYNEWT_VAL(PWM_2) + rc = pwm_set_duty_cycle(pwm2, 0, pwm_val); + assert(rc == 0); +#endif +#if MYNEWT_VAL(PWM_3) + rc = pwm_set_duty_cycle(pwm3, 0, pwm_val); + assert(rc == 0); +#endif +#else + int i; + u32_t lightness; + u8_t max_lightness = 0x1f; + + lightness = (u8_t) (percentage * max_lightness / 100); + + for (i = 0; i < WS2812_NUM_LED; i++) { + neopixel[i] = (lightness | lightness << 8 | lightness << 16); + } + ws2812_write(neopixel); +#endif +} + +static void update_light_state(void) +{ + u16_t level = (u16_t)gen_level_state; + int percent = 100 * level / 0xffff; + + if (gen_onoff_state == 0) { + percent = 0; + } + light_set_lightness((uint8_t) percent); +} + +int light_model_gen_onoff_get(struct bt_mesh_model *model, u8_t *state) +{ + *state = gen_onoff_state; + return 0; +} + +int light_model_gen_onoff_set(struct bt_mesh_model *model, u8_t state) +{ + gen_onoff_state = state; + update_light_state(); + return 0; +} + +int light_model_gen_level_get(struct bt_mesh_model *model, s16_t *level) +{ + *level = gen_level_state; + return 0; +} + +int light_model_gen_level_set(struct bt_mesh_model *model, s16_t level) +{ + gen_level_state = level; + if ((u16_t)gen_level_state > 0x0000) { + gen_onoff_state = 1; + } + if ((u16_t)gen_level_state == 0x0000) { + gen_onoff_state = 0; + } + update_light_state(); + return 0; +} + +int light_model_light_lightness_get(struct bt_mesh_model *model, s16_t *lightness) +{ + return light_model_gen_level_get(model, lightness); +} + +int light_model_light_lightness_set(struct bt_mesh_model *model, s16_t lightness) +{ + return light_model_gen_level_set(model, lightness); +} + +#if (!MYNEWT_VAL(USE_NEOPIXEL)) +struct pwm_dev_cfg dev_conf = { + .n_cycles = 0, + .int_prio = 3, +}; + +#if MYNEWT_VAL(PWM_0) +static struct pwm_chan_cfg led1_conf = { + .pin = LED_1, + .inverted = true, +}; +#endif + +#if MYNEWT_VAL(PWM_1) +static struct pwm_chan_cfg led2_conf = { + .pin = LED_2, + .inverted = true, +}; +#endif + +#if MYNEWT_VAL(PWM_2) +static struct pwm_chan_cfg led3_conf = { + .pin = LED_3, + .inverted = true, +}; +#endif +#endif + +#if MYNEWT_VAL(PWM_3) +static struct pwm_chan_cfg led4_conf = { + .pin = LED_4, + .inverted = true, +}; +#endif + +#if (!MYNEWT_VAL(USE_NEOPIXEL)) +void init_pwm_dev(struct pwm_dev **pwm, char *dev_name, struct pwm_chan_cfg *chan_cfg) +{ + int rc = 0; + + *pwm = (struct pwm_dev *) os_dev_open(dev_name, 0, NULL); + assert(pwm); + rc = pwm_configure_device(*pwm, &dev_conf); + assert(rc == 0); + rc = pwm_configure_channel(*pwm, 0, chan_cfg); + assert(rc == 0); + rc = pwm_enable(*pwm); + assert(rc == 0); +} + +int pwm_init(void) +{ + +#if MYNEWT_VAL(PWM_0) + init_pwm_dev(&pwm0, "pwm0", &led1_conf); +#endif + +#if MYNEWT_VAL(PWM_1) + init_pwm_dev(&pwm1, "pwm1", &led2_conf); +#endif + +#if MYNEWT_VAL(PWM_2) + init_pwm_dev(&pwm2, "pwm2", &led3_conf); +#endif + +#if MYNEWT_VAL(PWM_3) + init_pwm_dev(&pwm3, "pwm3", &led4_conf); +#endif + + if (!pwm0) { + return 0; + } + + top_val = (uint16_t) pwm_get_top_value(pwm0); + update_light_state(); + + return 0; +} +#endif +#endif + +int light_model_init(void) +{ +#if MYNEWT_VAL(BLE_MESH_SHELL_MODELS) + int rc; +#if (!MYNEWT_VAL(USE_NEOPIXEL)) + rc = pwm_init(); + assert(rc == 0); +#else + rc = ws2812_init(); + assert(rc == 0); + update_light_state(); +#endif + return rc; +#else + return 0; +#endif +} + diff --git a/apps/blemesh_light/src/light_model.h b/apps/blemesh_light/src/light_model.h new file mode 100644 index 000000000..7fcdd0c32 --- /dev/null +++ b/apps/blemesh_light/src/light_model.h @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Copyright (c) 2017 Intel Corporation + * + */ + +#ifndef __BT_MESH_LIGHT_MODEL_H +#define __BT_MESH_LIGHT_MODEL_H + +#include "syscfg/syscfg.h" +#include "mesh/mesh.h" + +int light_model_gen_onoff_get(struct bt_mesh_model *model, u8_t *state); +int light_model_gen_onoff_set(struct bt_mesh_model *model, u8_t state); +int light_model_gen_level_get(struct bt_mesh_model *model, s16_t *level); +int light_model_gen_level_set(struct bt_mesh_model *model, s16_t level); +int light_model_light_lightness_get(struct bt_mesh_model *model, s16_t *lightness); +int light_model_light_lightness_set(struct bt_mesh_model *model, s16_t lightness); +int light_model_init(void); + +#endif diff --git a/apps/blemesh_light/src/main.c b/apps/blemesh_light/src/main.c new file mode 100755 index 000000000..63be77b0d --- /dev/null +++ b/apps/blemesh_light/src/main.c @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include +#include "sysinit/sysinit.h" +#include "os/os.h" +#include "mesh/mesh.h" +#include "console/console.h" +#include "hal/hal_system.h" +#include "hal/hal_gpio.h" +#include "bsp/bsp.h" +#include "shell/shell.h" + +/* BLE */ +#include "nimble/ble.h" +#include "host/ble_hs.h" +#include "services/gap/ble_svc_gap.h" +#include "mesh/glue.h" +#include "mesh/testing.h" +#include "mesh/model_srv.h" +#include "light_model.h" + + +static void model_bound_cb(u16_t addr, struct bt_mesh_model *model, + u16_t key_idx) +{ + int rc; + + console_printf("Model bound: remote addr 0x%04x key_idx 0x%04x model %p\n", + addr, key_idx, model); + + if (model->id != BT_MESH_MODEL_ID_GEN_LEVEL_SRV) { + return; + } + + /* Hack for demo purposes */ + rc = bt_test_bind_app_key_to_model(model, key_idx, + BT_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV); + + if (rc) { + console_printf("Failed to bind light lightness srv model to app_key"); + } else { + console_printf("Successfuly bound light lightness srv model to app_key"); + } +} + +static struct bt_test_cb bt_test_cb = { + .mesh_model_bound = model_bound_cb, +}; + +static void +blemesh_on_reset(int reason) +{ + BLE_HS_LOG(ERROR, "Resetting state; reason=%d\n", reason); +} + +static struct bt_mesh_gen_onoff_srv_cb gen_onoff_srv_cb = { + .get = light_model_gen_onoff_get, + .set = light_model_gen_onoff_set, +}; +static struct bt_mesh_gen_level_srv_cb gen_level_srv_cb = { + .get = light_model_gen_level_get, + .set = light_model_gen_level_set, +}; +static struct bt_mesh_light_lightness_srv_cb light_lightness_srv_cb = { + .get = light_model_light_lightness_get, + .set = light_model_light_lightness_set, +}; + +static void +blemesh_on_sync(void) +{ + console_printf("Bluetooth initialized\n"); + + shell_register_default_module("mesh"); + + bt_test_cb_register(&bt_test_cb); + + light_model_init(); + bt_mesh_set_gen_onoff_srv_cb(&gen_onoff_srv_cb); + bt_mesh_set_gen_level_srv_cb(&gen_level_srv_cb); + bt_mesh_set_light_lightness_srv_cb(&light_lightness_srv_cb); + + console_printf("Mesh initialized\n"); + + /* Hack for demo purposes */ + bt_test_shell_init(); +} + +int +main(void) +{ + /* Initialize OS */ + sysinit(); + + /* Initialize the NimBLE host configuration. */ + log_register("ble_hs", &ble_hs_log, &log_console_handler, NULL, + LOG_SYSLEVEL); + ble_hs_cfg.reset_cb = blemesh_on_reset; + ble_hs_cfg.sync_cb = blemesh_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + bt_mesh_register_gatt(); + + while (1) { + os_eventq_run(os_eventq_dflt_get()); + } + return 0; +} diff --git a/apps/blemesh_light/src/ws2812.c b/apps/blemesh_light/src/ws2812.c new file mode 100755 index 000000000..ddc6ed397 --- /dev/null +++ b/apps/blemesh_light/src/ws2812.c @@ -0,0 +1,133 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include "sysinit/sysinit.h" +#include "os/os.h" +#include "bsp/bsp.h" +#include "pwm/pwm.h" +#include "nrfx.h" +#include "nrfx_pwm.h" +#include "ws2812.h" + +#define BITS_PER_SEQ (24) +#define BIT0 (0x8000 | 6) +#define BIT1 (0x8000 | 11) + +static const nrfx_pwm_t pwm = NRFX_PWM_INSTANCE(WS2812_PWM); + +static const nrfx_pwm_config_t pwm_config = { + .output_pins = { WS2812_GPIO, NRFX_PWM_PIN_NOT_USED, NRFX_PWM_PIN_NOT_USED, NRFX_PWM_PIN_NOT_USED }, + .irq_priority = 3, + .base_clock = NRF_PWM_CLK_16MHz, + .count_mode = NRF_PWM_MODE_UP, + .top_value = 20, + .load_mode = NRF_PWM_LOAD_COMMON, + .step_mode = NRF_PWM_STEP_AUTO, +}; + +static uint16_t pwm_seq_values[2][BITS_PER_SEQ]; + +static const nrf_pwm_sequence_t pwm_seq[2] = { + { + .values.p_raw = pwm_seq_values[0], + .length = BITS_PER_SEQ, + .repeats = 0, + .end_delay = 0, + }, { + .values.p_raw = pwm_seq_values[1], + .length = BITS_PER_SEQ, + .repeats = 0, + .end_delay = 0, + }, +}; + +static uint32_t led_color[WS2812_NUM_LED]; +static int led_idx; + +static void +load_pixel(void) +{ + uint16_t *seq_values; + uint32_t grb; + int i; + + seq_values = pwm_seq_values[led_idx & 1]; + grb = led_color[led_idx]; + + for (i = 0; i < BITS_PER_SEQ; i++) { + *seq_values = grb & 0x800000 ? BIT1 : BIT0; + grb <<= 1; + seq_values++; + } + + led_idx++; +} + +static void +pwm_handler_func(nrfx_pwm_evt_type_t event_type) +{ + switch (event_type) { + case NRFX_PWM_EVT_END_SEQ0: + case NRFX_PWM_EVT_END_SEQ1: + load_pixel(); + break; + default: + break; + } +} + +int +ws2812_init(void) +{ + nrfx_err_t err; + + err = nrfx_pwm_init(&pwm, &pwm_config, pwm_handler_func); + + return err != NRFX_SUCCESS; +} + +int +ws2812_write(const uint32_t *rgb) +{ + uint32_t grb; + int i; + + for (i = 0; i < WS2812_NUM_LED; i++) { + grb = 0; + grb |= (rgb[i] & 0x00FF00) << 8; + grb |= (rgb[i] & 0xFF0000) >> 8; + grb |= (rgb[i] & 0x0000FF); + + led_color[i] = grb; + } + + led_idx = 0; + + load_pixel(); + load_pixel(); + nrfx_pwm_complex_playback(&pwm, &pwm_seq[0], &pwm_seq[1], WS2812_NUM_LED, + NRFX_PWM_FLAG_SIGNAL_END_SEQ0 | + NRFX_PWM_FLAG_SIGNAL_END_SEQ1 | + NRFX_PWM_FLAG_STOP); + + return 0; +} diff --git a/apps/blemesh_light/src/ws2812.h b/apps/blemesh_light/src/ws2812.h new file mode 100755 index 000000000..93b8faf8d --- /dev/null +++ b/apps/blemesh_light/src/ws2812.h @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef __WS2812_H__ +#define __WS2812_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define WS2812_PWM 0 +#define WS2812_GPIO 30 +#define WS2812_NUM_LED 32 + +int ws2812_init(void); + +int ws2812_write(const uint32_t *rgb); + +#ifdef __cplusplus +} +#endif + +#endif /* __WS2812_H__ */ diff --git a/apps/blemesh_light/syscfg.yml b/apps/blemesh_light/syscfg.yml new file mode 100644 index 000000000..e40f09930 --- /dev/null +++ b/apps/blemesh_light/syscfg.yml @@ -0,0 +1,60 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Package: apps/blemesh_light + +syscfg.defs: + USE_NEOPIXEL: + value: 0 + +syscfg.vals: + # Enable the shell task. + SHELL_TASK: 1 + + # Set log level to info (disable debug logging). + LOG_LEVEL: 1 + + # Default task settings + OS_MAIN_STACK_SIZE: 768 + + # Newtmgr is not supported in this app, so disable newtmgr-over-shell. + SHELL_NEWTMGR: 0 + + MSYS_1_BLOCK_COUNT: 80 + + BLE_MESH: 1 + BLE_MESH_SHELL: 1 + BLE_MESH_PROV: 1 + BLE_MESH_PB_ADV: 1 + BLE_MESH_PB_GATT: 1 + BLE_MESH_GATT_PROXY: 1 + BLE_MESH_TESTING: 1 + BLE_MESH_FRIEND: 0 + BLE_MESH_CFG_CLI: 1 + BLE_MESH_HEALTH_CLI: 0 + BLE_MESH_SHELL_MODELS: 1 + BLE_MESH_TESTING: 1 + BLE_MESH_OOB_OUTPUT_ACTIONS: 0 + USE_NEOPIXEL: 0 + +syscfg.vals.BLE_MESH_SHELL_MODELS: + PWM_0: 1 + PWM_1: 1 + PWM_2: 1 + PWM_3: 1 + diff --git a/apps/blemesh_shell/pkg.yml b/apps/blemesh_shell/pkg.yml new file mode 100644 index 000000000..3b5377701 --- /dev/null +++ b/apps/blemesh_shell/pkg.yml @@ -0,0 +1,36 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +pkg.name: apps/blemesh_shell +pkg.type: app +pkg.description: Sample application for BLE Mesh node with shell support +pkg.author: "Michał Narajowski " +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + +pkg.deps: + - "@apache-mynewt-core/kernel/os" + - "@apache-mynewt-core/sys/console/full" + - "@apache-mynewt-core/sys/log/full" + - "@apache-mynewt-core/sys/stats/full" + - "@apache-mynewt-core/sys/shell" + - nimble/controller + - nimble/host + - nimble/host/services/gap + - nimble/host/services/gatt + - nimble/host/store/ram + - nimble/transport/ram diff --git a/apps/blemesh_shell/src/main.c b/apps/blemesh_shell/src/main.c new file mode 100644 index 000000000..32aa28ad7 --- /dev/null +++ b/apps/blemesh_shell/src/main.c @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include "os/mynewt.h" +#include "mesh/mesh.h" +#include "console/console.h" +#include "hal/hal_system.h" +#include "hal/hal_gpio.h" +#include "bsp/bsp.h" +#include "shell/shell.h" + +/* BLE */ +#include "nimble/ble.h" +#include "host/ble_hs.h" +#include "services/gap/ble_svc_gap.h" +#include "mesh/glue.h" +#include "mesh/testing.h" + + +void net_recv_ev(uint8_t ttl, uint8_t ctl, uint16_t src, uint16_t dst, + const void *payload, size_t payload_len) +{ + console_printf("Received net packet: ttl 0x%02x ctl 0x%02x src 0x%04x " + "dst 0x%04x " "payload_len %d\n", ttl, ctl, src, dst, + payload_len); +} + +static void model_bound_cb(u16_t addr, struct bt_mesh_model *model, + u16_t key_idx) +{ + console_printf("Model bound: remote addr 0x%04x key_idx 0x%04x model %p\n", + addr, key_idx, model); +} + +static void model_unbound_cb(u16_t addr, struct bt_mesh_model *model, + u16_t key_idx) +{ + console_printf("Model unbound: remote addr 0x%04x key_idx 0x%04x " + "model %p\n", addr, key_idx, model); +} + +static void invalid_bearer_cb(u8_t opcode) +{ + console_printf("Invalid bearer: opcode 0x%02x\n", opcode); +} + +static void incomp_timer_exp_cb(void) +{ + console_printf("Incomplete timer expired\n"); +} + +static struct bt_test_cb bt_test_cb = { + .mesh_net_recv = net_recv_ev, + .mesh_model_bound = model_bound_cb, + .mesh_model_unbound = model_unbound_cb, + .mesh_prov_invalid_bearer = invalid_bearer_cb, + .mesh_trans_incomp_timer_exp = incomp_timer_exp_cb, +}; + +static void +blemesh_on_reset(int reason) +{ + BLE_HS_LOG(ERROR, "Resetting state; reason=%d\n", reason); +} + +static void +blemesh_on_sync(void) +{ + console_printf("Bluetooth initialized\n"); + + shell_register_default_module("mesh"); + + if (IS_ENABLED(CONFIG_BT_TESTING)) { + bt_test_cb_register(&bt_test_cb); + } +} + +int +main(void) +{ + /* Initialize OS */ + sysinit(); + + /* Initialize the NimBLE host configuration. */ + log_register("ble_hs", &ble_hs_log, &log_console_handler, NULL, + LOG_SYSLEVEL); + ble_hs_cfg.reset_cb = blemesh_on_reset; + ble_hs_cfg.sync_cb = blemesh_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + hal_gpio_init_out(LED_2, 0); + + while (1) { + os_eventq_run(os_eventq_dflt_get()); + } + return 0; +} diff --git a/apps/blemesh_shell/syscfg.yml b/apps/blemesh_shell/syscfg.yml new file mode 100644 index 000000000..07cde3b41 --- /dev/null +++ b/apps/blemesh_shell/syscfg.yml @@ -0,0 +1,65 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Package: apps/blemesh_shell + +syscfg.vals: + # Enable the shell task. + SHELL_TASK: 1 + + # Set log level to info (disable debug logging). + LOG_LEVEL: 0 + + # Default task settings + OS_MAIN_STACK_SIZE: 768 + + # Newtmgr is not supported in this app, so disable newtmgr-over-shell. + SHELL_NEWTMGR: 0 + + MSYS_1_BLOCK_COUNT: 80 + + BLE_MESH: 1 + BLE_MESH_SHELL: 1 + BLE_MESH_PROV: 1 + BLE_MESH_RELAY: 1 + BLE_MESH_PB_ADV: 1 + BLE_MESH_PB_GATT: 1 + BLE_MESH_LOW_POWER: 1 + BLE_MESH_LPN_AUTO: 0 + BLE_MESH_GATT_PROXY: 1 + BLE_MESH_LABEL_COUNT: 2 + BLE_MESH_SUBNET_COUNT: 2 + BLE_MESH_MODEL_GROUP_COUNT: 2 + BLE_MESH_APP_KEY_COUNT: 4 + BLE_MESH_IV_UPDATE_TEST: 1 + BLE_MESH_TESTING: 1 + BLE_MESH_FRIEND: 1 + BLE_MESH_CFG_CLI: 1 + + BLE_MESH_DEBUG: 0 + BLE_MESH_DEBUG_NET: 0 + BLE_MESH_DEBUG_TRANS: 0 + BLE_MESH_DEBUG_BEACON: 0 + BLE_MESH_DEBUG_CRYPTO: 0 + BLE_MESH_DEBUG_PROV: 0 + BLE_MESH_DEBUG_ACCESS: 0 + BLE_MESH_DEBUG_MODEL: 0 + BLE_MESH_DEBUG_ADV: 0 + BLE_MESH_DEBUG_LOW_POWER: 0 + BLE_MESH_DEBUG_FRIEND: 0 + BLE_MESH_DEBUG_PROXY: 0 diff --git a/apps/bleprph/pkg.yml b/apps/bleprph/pkg.yml new file mode 100644 index 000000000..0b8e7dc60 --- /dev/null +++ b/apps/bleprph/pkg.yml @@ -0,0 +1,43 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +pkg.name: apps/bleprph +pkg.type: app +pkg.description: Simple BLE peripheral application. +pkg.author: "Apache Mynewt " +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + +pkg.deps: + - "@apache-mynewt-core/boot/split" + - "@apache-mynewt-core/boot/bootutil" + - "@apache-mynewt-core/kernel/os" + - "@apache-mynewt-core/mgmt/imgmgr" + - "@apache-mynewt-core/mgmt/newtmgr" + - "@apache-mynewt-core/mgmt/newtmgr/transport/ble" + - "@apache-mynewt-core/sys/console/full" + - "@apache-mynewt-core/sys/log/full" + - "@apache-mynewt-core/sys/stats/full" + - "@apache-mynewt-core/sys/sysinit" + - "@apache-mynewt-core/sys/id" + - net/nimble/host + - net/nimble/host/services/ans + - net/nimble/host/services/gap + - net/nimble/host/services/gatt + - net/nimble/host/store/config + - net/nimble/host/util + - net/nimble/transport diff --git a/apps/bleprph/src/bleprph.h b/apps/bleprph/src/bleprph.h new file mode 100644 index 000000000..a604936fd --- /dev/null +++ b/apps/bleprph/src/bleprph.h @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef H_BLEPRPH_ +#define H_BLEPRPH_ + +#include +#include "log/log.h" +#include "nimble/ble.h" +#ifdef __cplusplus +extern "C" { +#endif + +struct ble_hs_cfg; +struct ble_gatt_register_ctxt; + +extern struct log bleprph_log; + +/* bleprph uses the first "peruser" log module. */ +#define BLEPRPH_LOG_MODULE (LOG_MODULE_PERUSER + 0) + +/* Convenience macro for logging to the bleprph module. */ +#define BLEPRPH_LOG(lvl, ...) \ + LOG_ ## lvl(&bleprph_log, BLEPRPH_LOG_MODULE, __VA_ARGS__) + +/** GATT server. */ +#define GATT_SVR_SVC_ALERT_UUID 0x1811 +#define GATT_SVR_CHR_SUP_NEW_ALERT_CAT_UUID 0x2A47 +#define GATT_SVR_CHR_NEW_ALERT 0x2A46 +#define GATT_SVR_CHR_SUP_UNR_ALERT_CAT_UUID 0x2A48 +#define GATT_SVR_CHR_UNR_ALERT_STAT_UUID 0x2A45 +#define GATT_SVR_CHR_ALERT_NOT_CTRL_PT 0x2A44 + +void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg); +int gatt_svr_init(void); + +/* PHY support */ +#if MYNEWT_VAL(BLEPRPH_LE_PHY_SUPPORT) +#define CONN_HANDLE_INVALID 0xffff + +void phy_init(void); +void phy_conn_changed(uint16_t handle); +void phy_update(uint8_t phy); +#endif + +/** Misc. */ +void print_bytes(const uint8_t *bytes, int len); +void print_addr(const void *addr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/apps/bleprph/src/gatt_svr.c b/apps/bleprph/src/gatt_svr.c new file mode 100644 index 000000000..7025aaf35 --- /dev/null +++ b/apps/bleprph/src/gatt_svr.c @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include "bsp/bsp.h" +#include "host/ble_hs.h" +#include "host/ble_uuid.h" +#include "bleprph.h" + +/** + * The vendor specific security test service consists of two characteristics: + * o random-number-generator: generates a random 32-bit number each time + * it is read. This characteristic can only be read over an encrypted + * connection. + * o static-value: a single-byte characteristic that can always be read, + * but can only be written over an encrypted connection. + */ + +/* 59462f12-9543-9999-12c8-58b459a2712d */ +static const ble_uuid128_t gatt_svr_svc_sec_test_uuid = + BLE_UUID128_INIT(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12, + 0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59); + +/* 5c3a659e-897e-45e1-b016-007107c96df6 */ +static const ble_uuid128_t gatt_svr_chr_sec_test_rand_uuid = + BLE_UUID128_INIT(0xf6, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0, + 0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c); + +/* 5c3a659e-897e-45e1-b016-007107c96df7 */ +static const ble_uuid128_t gatt_svr_chr_sec_test_static_uuid = + BLE_UUID128_INIT(0xf7, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0, + 0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c); + +static uint8_t gatt_svr_sec_test_static_val; + +static int +gatt_svr_chr_access_sec_test(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg); + +static const struct ble_gatt_svc_def gatt_svr_svcs[] = { + { + /*** Service: Security test. */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = &gatt_svr_svc_sec_test_uuid.u, + .characteristics = (struct ble_gatt_chr_def[]) { { + /*** Characteristic: Random number generator. */ + .uuid = &gatt_svr_chr_sec_test_rand_uuid.u, + .access_cb = gatt_svr_chr_access_sec_test, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC, + }, { + /*** Characteristic: Static value. */ + .uuid = &gatt_svr_chr_sec_test_static_uuid.u, + .access_cb = gatt_svr_chr_access_sec_test, + .flags = BLE_GATT_CHR_F_READ | + BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC, + }, { + 0, /* No more characteristics in this service. */ + } }, + }, + + { + 0, /* No more services. */ + }, +}; + +static int +gatt_svr_chr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len, + void *dst, uint16_t *len) +{ + uint16_t om_len; + int rc; + + om_len = OS_MBUF_PKTLEN(om); + if (om_len < min_len || om_len > max_len) { + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + + rc = ble_hs_mbuf_to_flat(om, dst, max_len, len); + if (rc != 0) { + return BLE_ATT_ERR_UNLIKELY; + } + + return 0; +} + +static int +gatt_svr_chr_access_sec_test(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg) +{ + const ble_uuid_t *uuid; + int rand_num; + int rc; + + uuid = ctxt->chr->uuid; + + /* Determine which characteristic is being accessed by examining its + * 128-bit UUID. + */ + + if (ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_rand_uuid.u) == 0) { + assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR); + + /* Respond with a 32-bit random number. */ + rand_num = rand(); + rc = os_mbuf_append(ctxt->om, &rand_num, sizeof rand_num); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + if (ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_static_uuid.u) == 0) { + switch (ctxt->op) { + case BLE_GATT_ACCESS_OP_READ_CHR: + rc = os_mbuf_append(ctxt->om, &gatt_svr_sec_test_static_val, + sizeof gatt_svr_sec_test_static_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + + case BLE_GATT_ACCESS_OP_WRITE_CHR: + rc = gatt_svr_chr_write(ctxt->om, + sizeof gatt_svr_sec_test_static_val, + sizeof gatt_svr_sec_test_static_val, + &gatt_svr_sec_test_static_val, NULL); + return rc; + + default: + assert(0); + return BLE_ATT_ERR_UNLIKELY; + } + } + + /* Unknown characteristic; the nimble stack should not have called this + * function. + */ + assert(0); + return BLE_ATT_ERR_UNLIKELY; +} + +void +gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) +{ + char buf[BLE_UUID_STR_LEN]; + + switch (ctxt->op) { + case BLE_GATT_REGISTER_OP_SVC: + BLEPRPH_LOG(DEBUG, "registered service %s with handle=%d\n", + ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf), + ctxt->svc.handle); + break; + + case BLE_GATT_REGISTER_OP_CHR: + BLEPRPH_LOG(DEBUG, "registering characteristic %s with " + "def_handle=%d val_handle=%d\n", + ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), + ctxt->chr.def_handle, + ctxt->chr.val_handle); + break; + + case BLE_GATT_REGISTER_OP_DSC: + BLEPRPH_LOG(DEBUG, "registering descriptor %s with handle=%d\n", + ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf), + ctxt->dsc.handle); + break; + + default: + assert(0); + break; + } +} + +int +gatt_svr_init(void) +{ + int rc; + + rc = ble_gatts_count_cfg(gatt_svr_svcs); + if (rc != 0) { + return rc; + } + + rc = ble_gatts_add_svcs(gatt_svr_svcs); + if (rc != 0) { + return rc; + } + + return 0; +} diff --git a/apps/bleprph/src/main.c b/apps/bleprph/src/main.c new file mode 100644 index 000000000..61547ad79 --- /dev/null +++ b/apps/bleprph/src/main.c @@ -0,0 +1,356 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include "os/mynewt.h" +#include "bsp/bsp.h" +#include "hal/hal_gpio.h" +#include "console/console.h" +#include "hal/hal_system.h" +#include "config/config.h" +#include "split/split.h" + +/* BLE */ +#include "nimble/ble.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "services/gap/ble_svc_gap.h" + +/* Application-specified header. */ +#include "bleprph.h" + +/** Log data. */ +struct log bleprph_log; + +static int bleprph_gap_event(struct ble_gap_event *event, void *arg); + +/** + * Logs information about a connection to the console. + */ +static void +bleprph_print_conn_desc(struct ble_gap_conn_desc *desc) +{ + BLEPRPH_LOG(INFO, "handle=%d our_ota_addr_type=%d our_ota_addr=", + desc->conn_handle, desc->our_ota_addr.type); + print_addr(desc->our_ota_addr.val); + BLEPRPH_LOG(INFO, " our_id_addr_type=%d our_id_addr=", + desc->our_id_addr.type); + print_addr(desc->our_id_addr.val); + BLEPRPH_LOG(INFO, " peer_ota_addr_type=%d peer_ota_addr=", + desc->peer_ota_addr.type); + print_addr(desc->peer_ota_addr.val); + BLEPRPH_LOG(INFO, " peer_id_addr_type=%d peer_id_addr=", + desc->peer_id_addr.type); + print_addr(desc->peer_id_addr.val); + BLEPRPH_LOG(INFO, " conn_itvl=%d conn_latency=%d supervision_timeout=%d " + "encrypted=%d authenticated=%d bonded=%d\n", + desc->conn_itvl, desc->conn_latency, + desc->supervision_timeout, + desc->sec_state.encrypted, + desc->sec_state.authenticated, + desc->sec_state.bonded); +} + +/** + * Enables advertising with the following parameters: + * o General discoverable mode. + * o Undirected connectable mode. + */ +static void +bleprph_advertise(void) +{ + uint8_t own_addr_type; + struct ble_gap_adv_params adv_params; + struct ble_hs_adv_fields fields; + const char *name; + int rc; + + /* Figure out address to use while advertising (no privacy for now) */ + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + BLEPRPH_LOG(ERROR, "error determining address type; rc=%d\n", rc); + return; + } + + /** + * Set the advertisement data included in our advertisements: + * o Flags (indicates advertisement type and other general info). + * o Advertising tx power. + * o Device name. + * o 16-bit service UUIDs (alert notifications). + */ + + memset(&fields, 0, sizeof fields); + + /* Advertise two flags: + * o Discoverability in forthcoming advertisement (general) + * o BLE-only (BR/EDR unsupported). + */ + fields.flags = BLE_HS_ADV_F_DISC_GEN | + BLE_HS_ADV_F_BREDR_UNSUP; + + /* Indicate that the TX power level field should be included; have the + * stack fill this value automatically. This is done by assiging the + * special value BLE_HS_ADV_TX_PWR_LVL_AUTO. + */ + fields.tx_pwr_lvl_is_present = 1; + fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; + + name = ble_svc_gap_device_name(); + fields.name = (uint8_t *)name; + fields.name_len = strlen(name); + fields.name_is_complete = 1; + + fields.uuids16 = (ble_uuid16_t[]){ + BLE_UUID16_INIT(GATT_SVR_SVC_ALERT_UUID) + }; + fields.num_uuids16 = 1; + fields.uuids16_is_complete = 1; + + rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + BLEPRPH_LOG(ERROR, "error setting advertisement data; rc=%d\n", rc); + return; + } + + /* Begin advertising. */ + memset(&adv_params, 0, sizeof adv_params); + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, + &adv_params, bleprph_gap_event, NULL); + if (rc != 0) { + BLEPRPH_LOG(ERROR, "error enabling advertisement; rc=%d\n", rc); + return; + } +} + +/** + * The nimble host executes this callback when a GAP event occurs. The + * application associates a GAP event callback with each connection that forms. + * bleprph uses the same callback for all connections. + * + * @param event The type of event being signalled. + * @param ctxt Various information pertaining to the event. + * @param arg Application-specified argument; unuesd by + * bleprph. + * + * @return 0 if the application successfully handled the + * event; nonzero on failure. The semantics + * of the return code is specific to the + * particular GAP event being signalled. + */ +static int +bleprph_gap_event(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + BLEPRPH_LOG(INFO, "connection %s; status=%d ", + event->connect.status == 0 ? "established" : "failed", + event->connect.status); + if (event->connect.status == 0) { + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + bleprph_print_conn_desc(&desc); + +#if MYNEWT_VAL(BLEPRPH_LE_PHY_SUPPORT) + phy_conn_changed(event->connect.conn_handle); +#endif + } + BLEPRPH_LOG(INFO, "\n"); + + if (event->connect.status != 0) { + /* Connection failed; resume advertising. */ + bleprph_advertise(); + } + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + BLEPRPH_LOG(INFO, "disconnect; reason=%d ", event->disconnect.reason); + bleprph_print_conn_desc(&event->disconnect.conn); + BLEPRPH_LOG(INFO, "\n"); + +#if MYNEWT_VAL(BLEPRPH_LE_PHY_SUPPORT) + phy_conn_changed(CONN_HANDLE_INVALID); +#endif + + /* Connection terminated; resume advertising. */ + bleprph_advertise(); + return 0; + + case BLE_GAP_EVENT_CONN_UPDATE: + /* The central has updated the connection parameters. */ + BLEPRPH_LOG(INFO, "connection updated; status=%d ", + event->conn_update.status); + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + bleprph_print_conn_desc(&desc); + BLEPRPH_LOG(INFO, "\n"); + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: + BLEPRPH_LOG(INFO, "advertise complete; reason=%d", + event->adv_complete.reason); + bleprph_advertise(); + return 0; + + case BLE_GAP_EVENT_ENC_CHANGE: + /* Encryption has been enabled or disabled for this connection. */ + BLEPRPH_LOG(INFO, "encryption change event; status=%d ", + event->enc_change.status); + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + bleprph_print_conn_desc(&desc); + BLEPRPH_LOG(INFO, "\n"); + return 0; + + case BLE_GAP_EVENT_SUBSCRIBE: + BLEPRPH_LOG(INFO, "subscribe event; conn_handle=%d attr_handle=%d " + "reason=%d prevn=%d curn=%d previ=%d curi=%d\n", + event->subscribe.conn_handle, + event->subscribe.attr_handle, + event->subscribe.reason, + event->subscribe.prev_notify, + event->subscribe.cur_notify, + event->subscribe.prev_indicate, + event->subscribe.cur_indicate); + return 0; + + case BLE_GAP_EVENT_MTU: + BLEPRPH_LOG(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n", + event->mtu.conn_handle, + event->mtu.channel_id, + event->mtu.value); + return 0; + + case BLE_GAP_EVENT_REPEAT_PAIRING: + /* We already have a bond with the peer, but it is attempting to + * establish a new secure link. This app sacrifices security for + * convenience: just throw away the old bond and accept the new link. + */ + + /* Delete the old bond. */ + rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); + assert(rc == 0); + ble_store_util_delete_peer(&desc.peer_id_addr); + + /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should + * continue with the pairing operation. + */ + return BLE_GAP_REPEAT_PAIRING_RETRY; + +#if MYNEWT_VAL(BLEPRPH_LE_PHY_SUPPORT) + case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: + /* XXX: assume symmetric phy for now */ + phy_update(event->phy_updated.tx_phy); + return 0; +#endif + } + + return 0; +} + +static void +bleprph_on_reset(int reason) +{ + BLEPRPH_LOG(ERROR, "Resetting state; reason=%d\n", reason); +} + +static void +bleprph_on_sync(void) +{ + int rc; + + /* Make sure we have proper identity address set (public preferred) */ + rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + /* Begin advertising. */ + bleprph_advertise(); +} + +/** + * main + * + * The main task for the project. This function initializes the packages, + * then starts serving events from default event queue. + * + * @return int NOTE: this function should never return! + */ +int +main(void) +{ + int rc; + + /* Initialize OS */ + sysinit(); + + /* Initialize the bleprph log. */ + log_register("bleprph", &bleprph_log, &log_console_handler, NULL, + LOG_SYSLEVEL); + + /* Initialize the NimBLE host configuration. */ + log_register("ble_hs", &ble_hs_log, &log_console_handler, NULL, + LOG_SYSLEVEL); + ble_hs_cfg.reset_cb = bleprph_on_reset; + ble_hs_cfg.sync_cb = bleprph_on_sync; + ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + rc = gatt_svr_init(); + assert(rc == 0); + + /* Set the default device name. */ + rc = ble_svc_gap_device_name_set("nimble-bleprph"); + assert(rc == 0); + +#if MYNEWT_VAL(BLEPRPH_LE_PHY_SUPPORT) + phy_init(); +#endif + + conf_load(); + + /* If this app is acting as the loader in a split image setup, jump into + * the second stage application instead of starting the OS. + */ +#if MYNEWT_VAL(SPLIT_LOADER) + { + void *entry; + rc = split_app_go(&entry, true); + if (rc == 0) { + hal_system_start(entry); + } + } +#endif + + /* + * As the last thing, process events from default event queue. + */ + while (1) { + os_eventq_run(os_eventq_dflt_get()); + } + return 0; +} diff --git a/apps/bleprph/src/misc.c b/apps/bleprph/src/misc.c new file mode 100644 index 000000000..8ec785e51 --- /dev/null +++ b/apps/bleprph/src/misc.c @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "bleprph.h" + +/** + * Utility function to log an array of bytes. + */ +void +print_bytes(const uint8_t *bytes, int len) +{ + int i; + + for (i = 0; i < len; i++) { + BLEPRPH_LOG(INFO, "%s0x%02x", i != 0 ? ":" : "", bytes[i]); + } +} + +void +print_addr(const void *addr) +{ + const uint8_t *u8p; + + u8p = addr; + BLEPRPH_LOG(INFO, "%02x:%02x:%02x:%02x:%02x:%02x", + u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]); +} diff --git a/apps/bleprph/src/phy.c b/apps/bleprph/src/phy.c new file mode 100644 index 000000000..c6fb2b351 --- /dev/null +++ b/apps/bleprph/src/phy.c @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "os/mynewt.h" +#include "bsp/bsp.h" +#include "hal/hal_gpio.h" +#include "host/ble_gap.h" +#include "bleprph.h" + +#if MYNEWT_VAL(BLEPRPH_LE_PHY_SUPPORT) + +static const int button_gpio[4] = MYNEWT_VAL(BLEPRPH_LE_PHY_BUTTON_GPIO); +static const int led_gpio[3] = MYNEWT_VAL(BLEPRPH_LE_PHY_LED_GPIO); + +#define PHY_TO_PTR(_mask, _opts) (void *)(((_opts) << 16) | ((_mask))) +#define PTR_TO_PHY_MASK(_ptr) (uint8_t)(((int)_ptr) & 0x0ff) +#define PTR_TO_PHY_OPTS(_ptr) (uint8_t)(((int)_ptr) >> 16) + +static struct os_event gpio_event; + +static uint16_t conn_handle = CONN_HANDLE_INVALID; + +static void +gpio_irq_handler(void *arg) +{ + gpio_event.ev_arg = arg; + os_eventq_put(os_eventq_dflt_get(), &gpio_event); +} + +static void +gpio_event_handler(struct os_event *ev) +{ + uint8_t phy_mask; + uint8_t phy_opts; + int sr; + + OS_ENTER_CRITICAL(sr); + phy_mask = PTR_TO_PHY_MASK(ev->ev_arg); + phy_opts = PTR_TO_PHY_OPTS(ev->ev_arg); + OS_EXIT_CRITICAL(sr); + + if (conn_handle != CONN_HANDLE_INVALID) { + ble_gap_set_prefered_le_phy(conn_handle, phy_mask, phy_mask, phy_opts); + } +} + +static void +setup_button_gpio(int button, uint8_t phy_mask, uint8_t phy_opts) +{ + if (button < 0) { + return; + } + + hal_gpio_irq_init(button, gpio_irq_handler, PHY_TO_PTR(phy_mask, phy_opts), + HAL_GPIO_TRIG_FALLING, HAL_GPIO_PULL_UP); + hal_gpio_irq_enable(button); +} + +void +phy_init(void) +{ + gpio_event.ev_cb = gpio_event_handler; + + /* + * XXX: we could make this configurable, but for now assume all pins are + * valid, buttons gpio pins are pulled-up and LEDs are active-low - this + * is valid for nRF52840 PDK. + */ + setup_button_gpio(button_gpio[0], BLE_GAP_LE_PHY_1M_MASK, + BLE_GAP_LE_PHY_CODED_ANY); + setup_button_gpio(button_gpio[1], BLE_GAP_LE_PHY_2M_MASK, + BLE_GAP_LE_PHY_CODED_ANY); + setup_button_gpio(button_gpio[2], BLE_GAP_LE_PHY_CODED_MASK, + BLE_GAP_LE_PHY_CODED_S2); + setup_button_gpio(button_gpio[3], BLE_GAP_LE_PHY_CODED_MASK, + BLE_GAP_LE_PHY_CODED_S8); + + hal_gpio_init_out(led_gpio[0], 1); + hal_gpio_init_out(led_gpio[1], 1); + hal_gpio_init_out(led_gpio[2], 1); +} + +void +phy_conn_changed(uint16_t handle) +{ + uint8_t phy = 0; + + conn_handle = handle; + + if (handle != CONN_HANDLE_INVALID) { + /* XXX: assume symmetric phy for now */ + ble_gap_read_le_phy(handle, &phy, &phy); + } + + phy_update(phy); +} + +void +phy_update(uint8_t phy) +{ + if (conn_handle == CONN_HANDLE_INVALID) { + hal_gpio_write(led_gpio[0], 1); + hal_gpio_write(led_gpio[1], 1); + hal_gpio_write(led_gpio[2], 1); + } else { + hal_gpio_write(led_gpio[0], !(phy == BLE_GAP_LE_PHY_1M)); + hal_gpio_write(led_gpio[1], !(phy == BLE_GAP_LE_PHY_2M)); + hal_gpio_write(led_gpio[2], !(phy == BLE_GAP_LE_PHY_CODED)); + } +} + +#endif diff --git a/apps/bleprph/syscfg.yml b/apps/bleprph/syscfg.yml new file mode 100644 index 000000000..ec1ee64eb --- /dev/null +++ b/apps/bleprph/syscfg.yml @@ -0,0 +1,65 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Package: apps/bleprph + +syscfg.defs: + BLEPRPH_LE_PHY_SUPPORT: + description: > + Enable support for changing PHY preference on active connection. + PHY preference change is triggered by configured GPIO pins. + Current PHY is indicated using LEDs connected to configured + GPIO pins. + value: 0 + BLEPRPH_LE_PHY_BUTTON_GPIO: + description: > + GPIO pins for changing PHY preference on active connection. This + is an array of 4 GPIO pin numbers for 1M, 2M, LE Coded S=2 and + LE Coded S=8 respectively. + value: "(int[]){ BUTTON_1, BUTTON_2, BUTTON_3, BUTTON_4 }" + BLEPRPH_LE_PHY_LED_GPIO: + description: > + GPIO pins for indicating current PHY on active connection. This + is an array of 3 GPIO pin numbers for 1M, 2M and LE Coded + respectively. + value: "(int[]){ LED_1, LED_2, LED_3 }" + +syscfg.vals: + # Disable central and observer roles. + BLE_ROLE_BROADCASTER: 1 + BLE_ROLE_CENTRAL: 0 + BLE_ROLE_OBSERVER: 0 + BLE_ROLE_PERIPHERAL: 1 + + # Log reboot messages to a flash circular buffer. + REBOOT_LOG_FCB: 1 + LOG_FCB: 1 + CONFIG_FCB: 1 + + # Enable newtmgr commands. + STATS_NEWTMGR: 1 + LOG_NEWTMGR: 1 + CONFIG_NEWTMGR: 1 + + # OS main/default task + OS_MAIN_STACK_SIZE: 468 + + # Lots of smaller mbufs are required for newtmgr using typical BLE ATT MTU + # values. + MSYS_1_BLOCK_COUNT: 22 + MSYS_1_BLOCK_SIZE: 110 diff --git a/apps/bletest/pkg.yml b/apps/bletest/pkg.yml new file mode 100644 index 000000000..54d87985f --- /dev/null +++ b/apps/bletest/pkg.yml @@ -0,0 +1,37 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +pkg.name: apps/bletest +pkg.type: app +pkg.description: Test project for the nimble BLE controller. +pkg.author: "Apache Mynewt " +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + +pkg.deps: + - net/nimble/controller + - net/nimble/host + - net/nimble/transport/ram + - kernel/os + - sys/console/full + - sys/shell + - sys/config + - sys/log/full + - sys/stats/full +pkg.cflags: -DBLETEST diff --git a/apps/bletest/src/bletest_hci.c b/apps/bletest/src/bletest_hci.c new file mode 100644 index 000000000..047290bd5 --- /dev/null +++ b/apps/bletest/src/bletest_hci.c @@ -0,0 +1,782 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include "os/mynewt.h" +#include "bsp/bsp.h" + +/* BLE */ +#include "nimble/ble.h" +#include "nimble/ble_hci_trans.h" +#include "nimble/hci_common.h" +#include "host/ble_hs.h" +#include "controller/ble_ll.h" +#include "controller/ble_ll_hci.h" +#include "controller/ble_ll_conn.h" +#include "controller/ble_ll_scan.h" +#include "controller/ble_ll_adv.h" + +/* XXX: An app should not include private headers from a library. The bletest + * app uses some of nimble's internal details for logging. + */ +#include "../src/ble_hs_priv.h" +#include "bletest_priv.h" + +extern uint16_t g_bletest_ltk_reply_handle; + +void +bletest_send_conn_update(uint16_t handle) +{ + int rc; + struct hci_conn_update hcu; + + hcu.conn_latency = 4; + hcu.supervision_timeout = 2000; + hcu.conn_itvl_min = 1000; + hcu.conn_itvl_max = 1000; + hcu.handle = handle; + hcu.min_ce_len = 4; + hcu.max_ce_len = 4; + + rc = ble_hs_hci_cmd_le_conn_update(&hcu); + assert(rc == 0); +} + +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION) == 1) +void +bletest_ltk_req_reply(uint16_t handle) +{ + g_bletest_ltk_reply_handle |= (1 << (handle-1)); +} + +int +bletest_send_ltk_req_neg_reply(uint16_t handle) +{ + int rc; + uint8_t *dst; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + sizeof(uint16_t)]; + uint16_t ack_conn_handle; + uint8_t rsplen; + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_LT_KEY_REQ_NEG_REPLY, + sizeof(uint16_t), dst); + dst += BLE_HCI_CMD_HDR_LEN; + + put_le16(dst, handle); + rc = ble_hs_hci_cmd_tx(buf, &ack_conn_handle, 2, &rsplen); + if (rc == 0) { + if (rsplen != 2) { + rc = -1; + } + } + + return rc; +} + +int +bletest_send_ltk_req_reply(uint16_t handle) +{ + struct hci_lt_key_req_reply hkr; + uint16_t ack_conn_handle; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_LT_KEY_REQ_REPLY_LEN]; + uint8_t ack_params_len; + int rc; + + hkr.conn_handle = handle; + swap_buf(hkr.long_term_key, (uint8_t *)g_bletest_LTK, 16); + + ble_hs_hci_cmd_build_le_lt_key_req_reply(&hkr, buf, sizeof buf); + rc = ble_hs_hci_cmd_tx(buf, &ack_conn_handle, sizeof ack_conn_handle, + &ack_params_len); + if (rc != 0) { + return rc; + } + if (ack_params_len != BLE_HCI_LT_KEY_REQ_REPLY_ACK_PARAM_LEN - 1) { + return -1; + } + + if (le16toh(ack_conn_handle) != handle) { + return -1; + } + return 0; +} +#endif + +int +bletest_hci_reset_ctlr(void) +{ + uint8_t buf[BLE_HCI_CMD_HDR_LEN]; + + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_CTLR_BASEBAND, BLE_HCI_OCF_CB_RESET, + 0, buf); + return ble_hs_hci_cmd_tx(buf, NULL, 0, NULL); +} + +int +bletest_hci_rd_bd_addr(void) +{ + int rc; + uint8_t buf[BLE_HCI_CMD_HDR_LEN]; + uint8_t rspbuf[BLE_DEV_ADDR_LEN]; + uint8_t rsplen; + + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_INFO_PARAMS, BLE_HCI_OCF_IP_RD_BD_ADDR, 0, + buf); + rc = ble_hs_hci_cmd_tx(buf, rspbuf, BLE_DEV_ADDR_LEN, &rsplen); + if (rc != 0) { + return rc; + } + + if (rsplen != BLE_DEV_ADDR_LEN) { + return BLE_HS_ECONTROLLER; + } + + return rc; +} + +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION) == 1) +int +bletest_hci_le_encrypt(uint8_t *key, uint8_t *pt) +{ + int rc; + uint8_t *dst; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_LE_ENCRYPT_LEN]; + uint8_t rspbuf[16]; + uint8_t rsplen; + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_ENCRYPT, + BLE_HCI_LE_ENCRYPT_LEN, dst); + dst += BLE_HCI_CMD_HDR_LEN; + + swap_buf(dst, key, BLE_ENC_BLOCK_SIZE); + swap_buf(dst + BLE_ENC_BLOCK_SIZE, pt, BLE_ENC_BLOCK_SIZE); + rc = ble_hs_hci_cmd_tx(buf, rspbuf, 16, &rsplen); + if (rc != 0) { + return rc; + } + + if (rsplen != 16) { + return BLE_HS_ECONTROLLER; + } + return rc; +} +#endif + +int +bletest_hci_le_set_datalen(uint16_t handle, uint16_t txoctets, uint16_t txtime) +{ + int rc; + uint8_t *dst; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_SET_DATALEN_LEN]; + uint8_t rspbuf[2]; + uint8_t rsplen; + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_DATA_LEN, + BLE_HCI_SET_DATALEN_LEN, dst); + dst += BLE_HCI_CMD_HDR_LEN; + + put_le16(dst, handle); + put_le16(dst + 2, txoctets); + put_le16(dst + 4, txtime); + rc = ble_hs_hci_cmd_tx(buf, rspbuf, 2, &rsplen); + if (rc != 0) { + return rc; + } + + if (rsplen != 2) { + return BLE_HS_ECONTROLLER; + } + + return rc; +} + +int +bletest_hci_le_write_sugg_datalen(uint16_t txoctets, uint16_t txtime) +{ + uint8_t *dst; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_WR_SUGG_DATALEN_LEN]; + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_WR_SUGG_DEF_DATA_LEN, + BLE_HCI_WR_SUGG_DATALEN_LEN, dst); + dst += BLE_HCI_CMD_HDR_LEN; + + put_le16(dst, txoctets); + put_le16(dst + 2, txtime); + return ble_hs_hci_cmd_tx(buf, NULL, 0, NULL); +} + +int +bletest_hci_le_rd_sugg_datalen(void) +{ + int rc; + uint8_t buf[BLE_HCI_CMD_HDR_LEN]; + uint8_t rspbuf[BLE_HCI_RD_SUGG_DATALEN_RSPLEN]; + uint8_t rsplen; + + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_RD_SUGG_DEF_DATA_LEN, 0, + buf); + + rc = ble_hs_hci_cmd_tx(buf, rspbuf, BLE_HCI_RD_SUGG_DATALEN_RSPLEN, &rsplen); + if (rc != 0) { + return rc; + } + + if (rsplen != BLE_HCI_RD_SUGG_DATALEN_RSPLEN) { + return BLE_HS_ECONTROLLER; + } + + return 0; +} + +int +bletest_hci_rd_local_version(void) +{ + int rc; + uint8_t buf[BLE_HCI_CMD_HDR_LEN]; + uint8_t rspbuf[BLE_HCI_RD_LOC_VER_INFO_RSPLEN]; + uint8_t rsplen; + + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_INFO_PARAMS, BLE_HCI_OCF_IP_RD_LOCAL_VER, 0, + buf); + rc = ble_hs_hci_cmd_tx(buf, rspbuf, BLE_HCI_RD_LOC_VER_INFO_RSPLEN, &rsplen); + if (rc != 0) { + return rc; + } + + if (rsplen != BLE_HCI_RD_LOC_VER_INFO_RSPLEN) { + return BLE_HS_ECONTROLLER; + } + return rc; +} + +int +bletest_hci_rd_local_feat(void) +{ + int rc; + uint8_t buf[BLE_HCI_CMD_HDR_LEN]; + uint8_t rspbuf[BLE_HCI_RD_LOC_SUPP_FEAT_RSPLEN]; + uint8_t rsplen; + + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_INFO_PARAMS, BLE_HCI_OCF_IP_RD_LOC_SUPP_FEAT, + 0, buf); + rc = ble_hs_hci_cmd_tx(buf, rspbuf, BLE_HCI_RD_LOC_SUPP_FEAT_RSPLEN, &rsplen); + if (rc != 0) { + return rc; + } + + if (rsplen != BLE_HCI_RD_LOC_SUPP_FEAT_RSPLEN) { + return BLE_HS_ECONTROLLER; + } + return rc; +} + +int +bletest_hci_rd_local_supp_cmd(void) +{ + int rc; + uint8_t buf[BLE_HCI_CMD_HDR_LEN]; + uint8_t rspbuf[BLE_HCI_RD_LOC_SUPP_CMD_RSPLEN]; + uint8_t rsplen; + + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_INFO_PARAMS, BLE_HCI_OCF_IP_RD_LOC_SUPP_CMD, + 0, buf); + rc = ble_hs_hci_cmd_tx(buf, rspbuf, BLE_HCI_RD_LOC_SUPP_CMD_RSPLEN, &rsplen); + if (rc != 0) { + return rc; + } + + if (rsplen != BLE_HCI_RD_LOC_SUPP_CMD_RSPLEN) { + return BLE_HS_ECONTROLLER; + } + return rc; +} + +/** + * Read supported states + * + * OGF = 0x08 (LE) + * OCF = 0x001C + * + * @return int + */ +int +bletest_hci_le_read_supp_states(void) +{ + int rc; + uint8_t buf[BLE_HCI_CMD_HDR_LEN]; + uint8_t rspbuf[BLE_HCI_RD_SUPP_STATES_RSPLEN]; + uint8_t rsplen; + + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_RD_SUPP_STATES, 0, buf); + rc = ble_hs_hci_cmd_tx(buf, rspbuf, BLE_HCI_RD_SUPP_STATES_RSPLEN, &rsplen); + if (rc != 0) { + return rc; + } + + if (rsplen != BLE_HCI_RD_SUPP_STATES_RSPLEN) { + return BLE_HS_ECONTROLLER; + } + return rc; +} + +int +bletest_hci_le_rd_max_datalen(void) +{ + int rc; + uint8_t buf[BLE_HCI_CMD_HDR_LEN]; + uint8_t rspbuf[BLE_HCI_RD_MAX_DATALEN_RSPLEN]; + uint8_t rsplen; + + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_RD_MAX_DATA_LEN, 0, buf); + rc = ble_hs_hci_cmd_tx(buf, rspbuf, BLE_HCI_RD_MAX_DATALEN_RSPLEN, &rsplen); + if (rc != 0) { + return rc; + } + + if (rsplen != BLE_HCI_RD_MAX_DATALEN_RSPLEN) { + return BLE_HS_ECONTROLLER; + } + return rc; +} + +#if MYNEWT_VAL(BLE_ANDROID_MULTI_ADV_SUPPORT) +int +bletest_hci_le_set_multi_adv_data(uint8_t *data, uint8_t len, uint8_t instance) +{ + uint8_t *dst; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_MULTI_ADV_DATA_LEN]; + + if (instance >= BLE_LL_ADV_INSTANCES) { + return -1; + } + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_VENDOR, BLE_HCI_OCF_MULTI_ADV, + BLE_HCI_MULTI_ADV_DATA_LEN, dst); + dst += BLE_HCI_CMD_HDR_LEN; + + if (((data == NULL) && (len != 0)) || (len > BLE_HCI_MAX_ADV_DATA_LEN)) { + return BLE_ERR_INV_HCI_CMD_PARMS; + } + + memset(dst, 0, BLE_HCI_MULTI_ADV_DATA_LEN); + dst[0] = BLE_HCI_MULTI_ADV_DATA; + dst[1] = len; + memcpy(dst + 2, data, len); + dst[33] = instance; + + return ble_hs_hci_cmd_tx_empty_ack(buf); +} +#else +int +bletest_hci_le_set_adv_data(uint8_t *data, uint8_t len) +{ + int rc; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_SET_ADV_DATA_LEN]; + + rc = ble_hs_hci_cmd_build_le_set_adv_data(data, len, buf, sizeof buf); + assert(rc == 0); + return ble_hs_hci_cmd_tx_empty_ack(buf); +} +#endif + +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION) == 1) +int +bletest_hci_le_start_encrypt(struct hci_start_encrypt *cmd) +{ + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_LE_START_ENCRYPT_LEN]; + + ble_hs_hci_cmd_build_le_start_encrypt(cmd, buf, sizeof buf); + return ble_hs_hci_cmd_tx_empty_ack(buf); +} +#endif + +int +bletest_hci_le_read_rem_used_feat(uint16_t handle) +{ + uint8_t *dst; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_CONN_RD_REM_FEAT_LEN]; + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_RD_REM_FEAT, + BLE_HCI_CONN_RD_REM_FEAT_LEN, dst); + dst += BLE_HCI_CMD_HDR_LEN; + + put_le16(dst, handle); + return ble_hs_hci_cmd_tx(buf, NULL, 0, NULL); +} + +#if MYNEWT_VAL(BLE_ANDROID_MULTI_ADV_SUPPORT) +int +bletest_hci_le_set_multi_adv_params(struct hci_multi_adv_params *adv, + uint8_t instance) +{ + uint8_t *dst; + uint16_t itvl; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_MULTI_ADV_PARAMS_LEN]; + + if (instance >= BLE_LL_ADV_INSTANCES) { + return -1; + } + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_VENDOR, BLE_HCI_OCF_MULTI_ADV, + BLE_HCI_MULTI_ADV_PARAMS_LEN, dst); + dst += BLE_HCI_CMD_HDR_LEN; + + /* Make sure parameters are valid */ + if ((adv->adv_itvl_min > adv->adv_itvl_max) || + (adv->own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM) || + (adv->peer_addr_type > BLE_HCI_ADV_PEER_ADDR_MAX) || + (adv->adv_filter_policy > BLE_HCI_ADV_FILT_MAX) || + (adv->adv_type > BLE_HCI_ADV_TYPE_MAX) || + (adv->adv_channel_map == 0) || + ((adv->adv_channel_map & 0xF8) != 0)) { + /* These parameters are not valid */ + return -1; + } + + /* Make sure interval is valid for advertising type. */ + if ((adv->adv_type == BLE_HCI_ADV_TYPE_ADV_NONCONN_IND) || + (adv->adv_type == BLE_HCI_ADV_TYPE_ADV_SCAN_IND)) { + itvl = BLE_HCI_ADV_ITVL_NONCONN_MIN; + } else { + itvl = BLE_HCI_ADV_ITVL_MIN; + } + + /* Do not check if high duty-cycle directed */ + if (adv->adv_type != BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD) { + if ((adv->adv_itvl_min < itvl) || + (adv->adv_itvl_min > BLE_HCI_ADV_ITVL_MAX)) { + return -1; + } + } + + dst[0] = BLE_HCI_MULTI_ADV_PARAMS; + put_le16(dst + 1, adv->adv_itvl_min); + put_le16(dst + 3, adv->adv_itvl_max); + dst[5] = adv->adv_type; + dst[6] = adv->own_addr_type; + memcpy(dst + 7, adv->own_addr, BLE_DEV_ADDR_LEN); + dst[13] = adv->peer_addr_type; + memcpy(dst + 14, adv->peer_addr, BLE_DEV_ADDR_LEN); + dst[20] = adv->adv_channel_map; + dst[21] = adv->adv_filter_policy; + dst[22] = instance; + dst[23] = adv->adv_tx_pwr; + + return ble_hs_hci_cmd_tx_empty_ack(buf); +} +#else +int +bletest_hci_le_set_adv_params(struct hci_adv_params *adv) +{ + int rc; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_SET_ADV_PARAM_LEN]; + + rc = ble_hs_hci_cmd_build_le_set_adv_params(adv, buf, sizeof buf); + if (!rc) { + rc = ble_hs_hci_cmd_tx_empty_ack(buf); + } + return rc; +} +#endif + +int +bletest_hci_le_set_rand_addr(uint8_t *addr) +{ + uint8_t *dst; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_SET_RAND_ADDR_LEN]; + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_RAND_ADDR, + BLE_DEV_ADDR_LEN, dst); + dst += BLE_HCI_CMD_HDR_LEN; + + memcpy(dst, addr, BLE_DEV_ADDR_LEN); + return ble_hs_hci_cmd_tx(buf, NULL, 0, NULL); +} + +#if MYNEWT_VAL(BLE_ANDROID_MULTI_ADV_SUPPORT) +int +bletest_hci_le_set_multi_rand_addr(uint8_t *addr, uint8_t instance) +{ + uint8_t *dst; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_MULTI_ADV_SET_RAND_ADDR_LEN]; + + if (instance >= BLE_LL_ADV_INSTANCES) { + return -1; + } + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_VENDOR, BLE_HCI_OCF_MULTI_ADV, + BLE_HCI_MULTI_ADV_SET_RAND_ADDR_LEN, dst); + dst += BLE_HCI_CMD_HDR_LEN; + + dst[0] = BLE_HCI_MULTI_ADV_SET_RAND_ADDR; + memcpy(dst + 1, addr, BLE_DEV_ADDR_LEN); + dst[7] = instance; + return ble_hs_hci_cmd_tx(buf, NULL, 0, NULL); +} +#endif + +int +bletest_hci_rd_rem_version(uint16_t handle) +{ + uint8_t *dst; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + sizeof(uint16_t)]; + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_LINK_CTRL, BLE_HCI_OCF_RD_REM_VER_INFO, + sizeof(uint16_t), dst); + dst += BLE_HCI_CMD_HDR_LEN; + + put_le16(dst, handle); + return ble_hs_hci_cmd_tx(buf, NULL, 0, NULL); +} + +int +bletest_hci_le_set_host_chan_class(uint8_t *chanmap) +{ + uint8_t *dst; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_SET_HOST_CHAN_CLASS_LEN]; + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_HOST_CHAN_CLASS, + BLE_HCI_SET_HOST_CHAN_CLASS_LEN, dst); + dst += BLE_HCI_CMD_HDR_LEN; + + memcpy(dst, chanmap, BLE_HCI_SET_HOST_CHAN_CLASS_LEN); + return ble_hs_hci_cmd_tx(buf, NULL, 0, NULL); +} + +int +bletest_hci_le_rd_chanmap(uint16_t handle) +{ + int rc; + uint8_t *dst; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_RD_CHANMAP_LEN]; + uint8_t rspbuf[BLE_HCI_RD_CHANMAP_RSP_LEN]; + uint8_t rsplen; + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_RD_CHAN_MAP, + BLE_HCI_RD_CHANMAP_LEN, dst); + dst += BLE_HCI_CMD_HDR_LEN; + + put_le16(dst, handle); + rc = ble_hs_hci_cmd_tx(buf, rspbuf, BLE_HCI_RD_CHANMAP_RSP_LEN, &rsplen); + if (rc != 0) { + return rc; + } + + if (rsplen != BLE_HCI_RD_CHANMAP_RSP_LEN) { + return BLE_HS_ECONTROLLER; + } + + return rc; +} + +#if MYNEWT_VAL(BLE_ANDROID_MULTI_ADV_SUPPORT) +int +bletest_hci_le_set_multi_adv_enable(uint8_t enable, uint8_t instance) +{ + uint8_t *dst; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_MULTI_ADV_ENABLE_LEN]; + + if (instance >= BLE_LL_ADV_INSTANCES) { + return -1; + } + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_VENDOR, BLE_HCI_OCF_MULTI_ADV, + BLE_HCI_MULTI_ADV_ENABLE_LEN, dst); + dst += BLE_HCI_CMD_HDR_LEN; + + dst[0] = BLE_HCI_MULTI_ADV_ENABLE; + dst[1] = enable; + dst[2] = instance; + return ble_hs_hci_cmd_tx(buf, NULL, 0, NULL); +} +#else +int +bletest_hci_le_set_adv_enable(uint8_t enable) +{ + uint8_t *dst; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_SET_ADV_ENABLE_LEN]; + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_ADV_ENABLE, + BLE_HCI_SET_ADV_ENABLE_LEN, dst); + dst += BLE_HCI_CMD_HDR_LEN; + + dst[0] = enable; + return ble_hs_hci_cmd_tx(buf, NULL, 0, NULL); +} +#endif + +int +bletest_hci_le_set_event_mask(uint64_t event_mask) +{ + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_SET_LE_EVENT_MASK_LEN]; + + ble_hs_hci_cmd_build_le_set_event_mask(event_mask, buf, sizeof buf); + return ble_hs_hci_cmd_tx_empty_ack(buf); +} + +int +bletest_hci_set_event_mask(uint64_t event_mask) +{ + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_SET_EVENT_MASK_LEN]; + + ble_hs_hci_cmd_build_set_event_mask(event_mask, buf, sizeof buf); + return ble_hs_hci_cmd_tx_empty_ack(buf); +} + +#if MYNEWT_VAL(BLE_ANDROID_MULTI_ADV_SUPPORT) +int +bletest_hci_le_set_multi_scan_rsp_data(uint8_t *data, uint8_t len, + uint8_t instance) +{ + uint8_t *dst; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_MULTI_ADV_SCAN_RSP_DATA_LEN]; + + if (instance >= BLE_LL_ADV_INSTANCES) { + return -1; + } + + dst = buf; + ble_hs_hci_cmd_write_hdr(BLE_HCI_OGF_VENDOR, BLE_HCI_OCF_MULTI_ADV, + BLE_HCI_MULTI_ADV_SCAN_RSP_DATA_LEN, dst); + dst += BLE_HCI_CMD_HDR_LEN; + + if (((data == NULL) && (len != 0)) || (len>BLE_HCI_MAX_SCAN_RSP_DATA_LEN)) { + return BLE_ERR_INV_HCI_CMD_PARMS; + } + + memset(dst, 0, BLE_HCI_MULTI_ADV_SCAN_RSP_DATA_LEN); + dst[0] = BLE_HCI_MULTI_ADV_SCAN_RSP_DATA; + dst[1] = len; + memcpy(dst + 2, data, len); + dst[33] = instance; + return ble_hs_hci_cmd_tx_empty_ack(buf); +} +#else +int +bletest_hci_le_set_scan_rsp_data(uint8_t *data, uint8_t len) +{ + int rc; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_SET_SCAN_RSP_DATA_LEN]; + + rc = ble_hs_hci_cmd_build_le_set_scan_rsp_data(data, len, buf, sizeof buf); + assert(rc == 0); + return ble_hs_hci_cmd_tx_empty_ack(buf); +} +#endif + +int +bletest_hci_cmd_le_set_scan_params(uint8_t scan_type, uint16_t scan_itvl, + uint16_t scan_window, uint8_t own_addr_type, + uint8_t filter_policy) { + int rc; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_SET_SCAN_PARAM_LEN]; + + rc = ble_hs_hci_cmd_build_le_set_scan_params(scan_type, scan_itvl, + scan_window, own_addr_type, + filter_policy, buf, sizeof buf); + if (!rc) { + rc = ble_hs_hci_cmd_tx_empty_ack(buf); + } + return rc; +} + +int +bletest_hci_le_add_to_whitelist(uint8_t *addr, uint8_t addr_type) +{ + int rc; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_SET_SCAN_PARAM_LEN]; + + rc = ble_hs_hci_cmd_build_le_add_to_whitelist(addr, addr_type, buf, + sizeof buf); + if (!rc) { + rc = ble_hs_hci_cmd_tx_empty_ack(buf); + } + return rc; +} + +int +bletest_hci_le_set_scan_enable(uint8_t enable, uint8_t filter_dups) +{ + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_SET_SCAN_ENABLE_LEN]; + + ble_hs_hci_cmd_build_le_set_scan_enable(enable, filter_dups, buf, sizeof buf); + return ble_hs_hci_cmd_tx_empty_ack(buf); +} + +int +bletest_hci_le_create_connection(struct hci_create_conn *hcc) +{ + int rc; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_CREATE_CONN_LEN]; + + rc = ble_hs_hci_cmd_build_le_create_connection(hcc, buf, sizeof buf); + if (!rc) { + rc = ble_hs_hci_cmd_tx_empty_ack(buf); + } + return rc; +} + +int +bletest_hci_le_add_resolv_list(uint8_t *local_irk, uint8_t *peer_irk, + uint8_t *peer_ident_addr, uint8_t addr_type) +{ + int rc; + struct hci_add_dev_to_resolving_list padd; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_ADD_TO_RESOLV_LIST_LEN]; + + padd.addr_type = addr_type; + memcpy(padd.addr, peer_ident_addr, BLE_DEV_ADDR_LEN); + swap_buf(padd.local_irk, local_irk, 16); + swap_buf(padd.peer_irk, peer_irk, 16); + rc = ble_hs_hci_cmd_build_add_to_resolv_list(&padd, buf, sizeof buf); + if (!rc) { + rc = ble_hs_hci_cmd_tx_empty_ack(buf); + } + return rc; +} + +int +bletest_hci_le_enable_resolv_list(uint8_t enable) +{ + int rc; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_SET_ADDR_RESOL_ENA_LEN]; + + + rc = ble_hs_hci_cmd_build_set_addr_res_en(enable, buf, sizeof buf); + if (!rc) { + rc = ble_hs_hci_cmd_tx_empty_ack(buf); + } + return rc; +} + diff --git a/apps/bletest/src/bletest_priv.h b/apps/bletest/src/bletest_priv.h new file mode 100644 index 000000000..1f1386a53 --- /dev/null +++ b/apps/bletest/src/bletest_priv.h @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef H_BLETEST_PRIV_ +#define H_BLETEST_PRIV_ + +#ifdef __cplusplus +extern "C" { +#endif + +void bletest_send_conn_update(uint16_t handle); + +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION) == 1) +void bletest_ltk_req_reply(uint16_t handle); +int bletest_send_ltk_req_neg_reply(uint16_t handle); +int bletest_send_ltk_req_reply(uint16_t handle); +int bletest_hci_le_start_encrypt(struct hci_start_encrypt *cmd); +int bletest_hci_le_encrypt(uint8_t *key, uint8_t *pt); +#endif + +int bletest_hci_reset_ctlr(void); +int bletest_hci_rd_bd_addr(void); +int bletest_hci_le_set_datalen(uint16_t handle, uint16_t txoctets, + uint16_t txtime); +int bletest_hci_le_write_sugg_datalen(uint16_t txoctets, uint16_t txtime); +int bletest_hci_le_rd_sugg_datalen(void); +int bletest_hci_rd_local_version(void); +int bletest_hci_rd_local_feat(void); +int bletest_hci_rd_local_supp_cmd(void); +int bletest_hci_le_read_supp_states(void); +int bletest_hci_le_rd_max_datalen(void); +int bletest_hci_le_read_rem_used_feat(uint16_t handle); +int bletest_hci_le_set_rand_addr(uint8_t *addr); +int bletest_hci_rd_rem_version(uint16_t handle); +int bletest_hci_le_set_host_chan_class(uint8_t *chanmap); +int bletest_hci_le_rd_chanmap(uint16_t handle); +int bletest_hci_le_set_event_mask(uint64_t event_mask); +int bletest_hci_set_event_mask(uint64_t event_mask); +int bletest_hci_le_add_to_whitelist(uint8_t *addr, uint8_t addr_type); +int bletest_hci_le_set_scan_enable(uint8_t enable, uint8_t filter_dups); +int bletest_hci_le_create_connection(struct hci_create_conn *hcc); +int bletest_hci_le_set_scan_params(uint8_t scan_type, uint16_t scan_itvl, + uint16_t scan_window, uint8_t own_addr_type, + uint8_t filter_policy); +int bletest_hci_le_add_resolv_list(uint8_t *local_irk, uint8_t *peer_irk, + uint8_t *peer_ident_addr, uint8_t addr_type); +int bletest_hci_le_enable_resolv_list(uint8_t enable); + +#if MYNEWT_VAL(BLE_ANDROID_MULTI_ADV_SUPPORT) +int bletest_hci_le_set_multi_rand_addr(uint8_t *addr, uint8_t instance); +int bletest_hci_le_set_multi_adv_data(uint8_t *data, uint8_t len, + uint8_t instance); +int bletest_hci_le_set_multi_adv_params(struct hci_multi_adv_params *adv, + uint8_t instance); +int bletest_hci_le_set_multi_adv_enable(uint8_t enable, uint8_t instance); +int bletest_hci_le_set_multi_scan_rsp_data(uint8_t *data, uint8_t len, + uint8_t instance); +#else +int bletest_hci_le_set_adv_data(uint8_t *data, uint8_t len); +int bletest_hci_le_set_adv_params(struct hci_adv_params *adv); +int bletest_hci_le_set_adv_enable(uint8_t enable); +int bletest_hci_le_set_scan_rsp_data(uint8_t *data, uint8_t len); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* H_BLETEST_PRIV_*/ diff --git a/apps/bletest/src/main.c b/apps/bletest/src/main.c new file mode 100644 index 000000000..da76e8b29 --- /dev/null +++ b/apps/bletest/src/main.c @@ -0,0 +1,1326 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include "os/mynewt.h" +#include "bsp/bsp.h" +#include "hal/hal_bsp.h" +#include "hal/hal_gpio.h" +#include "hal/hal_flash.h" +#include "console/console.h" +#include "shell/shell.h" +#include "stats/stats.h" +#include "flash_map/flash_map.h" + +/* BLE */ +#include "nimble/ble.h" +#include "nimble/ble_hci_trans.h" +#include "nimble/hci_common.h" +#include "host/ble_hs.h" +#include "controller/ble_ll.h" +#include "controller/ble_ll_hci.h" +#include "controller/ble_ll_conn.h" +#include "controller/ble_ll_scan.h" +#include "controller/ble_ll_adv.h" + +/* RAM HCI transport. */ +#include "transport/ram/ble_hci_ram.h" + +/* XXX: An app should not include private headers from a library. The bletest + * app uses some of nimble's internal details for logging. + */ +#include "../src/ble_hs_priv.h" +#include "bletest_priv.h" + +#define BLETEST_TASK_PRIO 5 + +/* For LED toggling */ +int g_led_pin; + +/* A buffer for host advertising data */ +uint8_t g_host_adv_data[BLE_HCI_MAX_ADV_DATA_LEN]; +uint8_t g_host_adv_len; + +/* Some application configurations */ +#define BLETEST_ROLE_NONE (0) +#define BLETEST_ROLE_ADVERTISER (1) +#define BLETEST_ROLE_SCANNER (2) +#define BLETEST_ROLE_INITIATOR (3) + +#if MYNEWT_VAL(BLETEST_ROLE) == BLETEST_ROLE_ADVERTISER +#define BLETEST_CFG_ROLE BLETEST_ROLE_ADVERTISER +#endif +#if MYNEWT_VAL(BLETEST_ROLE) == BLETEST_ROLE_SCANNER +#define BLETEST_CFG_ROLE BLETEST_ROLE_SCANNER +#endif +#if MYNEWT_VAL(BLETEST_ROLE) == BLETEST_ROLE_INITIATOR +#define BLETEST_CFG_ROLE BLETEST_ROLE_INITIATOR +#endif + +#ifndef BLETEST_CFG_ROLE +#error "No role defined! Must define a valid role in syscfg.yml in apps/bletest" +#endif + +/* Advertiser config */ +#define BLETEST_CFG_ADV_OWN_ADDR_TYPE (BLE_HCI_ADV_OWN_ADDR_PUBLIC) +#define BLETEST_CFG_ADV_PEER_ADDR_TYPE (BLE_HCI_ADV_PEER_ADDR_PUBLIC) +#define BLETEST_CFG_ADV_ITVL (60000 / BLE_HCI_ADV_ITVL) +#define BLETEST_CFG_ADV_TYPE BLE_HCI_ADV_TYPE_ADV_IND +#define BLETEST_CFG_ADV_FILT_POLICY (BLE_HCI_ADV_FILT_NONE) +#define BLETEST_CFG_ADV_ADDR_RES_EN (0) + +/* Multi-adv config */ +/* + * Number of advertising instances to start up, not including the default + * instance. The default instance is used to connect. If this number is greater + * than the number of available advertising instances, we only use the number + * of available advertising instances (defined by the configuration setting: + * BLE_MULTI_ADV_INSTANCES. + */ +#define BLETEST_CFG_ADV_TEST_INSTANCES (8) + +struct bletest_multi_adv_interval +{ + uint8_t adv_type; + /* + * Note: if own addr type greater than 1, we use own addr field; otherwise + * we use the set multi random address call to set the random address + */ + uint8_t adv_own_addr_type; + uint16_t adv_itvl; +}; + +/* + * NOTE: currently, these are all NONCONN_IND. Thus, must be 100 msecs or + * greater + */ +const struct bletest_multi_adv_interval +bletest_multi_adv_instances[BLETEST_CFG_ADV_TEST_INSTANCES] = { + {BLE_HCI_ADV_TYPE_ADV_NONCONN_IND, + BLE_HCI_ADV_OWN_ADDR_PUBLIC, + (100000 / BLE_HCI_ADV_ITVL)}, + + {BLE_HCI_ADV_TYPE_ADV_SCAN_IND, + BLE_HCI_ADV_OWN_ADDR_RANDOM, + (110000 / BLE_HCI_ADV_ITVL)}, + + {BLE_HCI_ADV_TYPE_ADV_NONCONN_IND, + BLE_HCI_ADV_OWN_ADDR_RANDOM, + (120000 / BLE_HCI_ADV_ITVL)}, + + {BLE_HCI_ADV_TYPE_ADV_NONCONN_IND, + BLE_HCI_ADV_OWN_ADDR_PUBLIC, + (130000 / BLE_HCI_ADV_ITVL)}, + + {BLE_HCI_ADV_TYPE_ADV_SCAN_IND, + BLE_HCI_ADV_OWN_ADDR_MAX + 1, + (140000 / BLE_HCI_ADV_ITVL)}, + + {BLE_HCI_ADV_TYPE_ADV_NONCONN_IND, + BLE_HCI_ADV_OWN_ADDR_MAX + 1, + (150000 / BLE_HCI_ADV_ITVL)}, + + {BLE_HCI_ADV_TYPE_ADV_NONCONN_IND, + BLE_HCI_ADV_OWN_ADDR_PUBLIC, + (160000 / BLE_HCI_ADV_ITVL)}, + + {BLE_HCI_ADV_TYPE_ADV_SCAN_IND, + BLE_HCI_ADV_OWN_ADDR_PUBLIC, + (170000 / BLE_HCI_ADV_ITVL)} +}; + +/* + * Determines if own address contains random address or set through the + * multi-adv set random address command + */ +#define BLETEST_CFG_MULTI_ADV_RANDOM_OWN (0) + +/* Scan config */ +#define BLETEST_CFG_SCAN_ITVL (700000 / BLE_HCI_SCAN_ITVL) +#define BLETEST_CFG_SCAN_WINDOW (700000 / BLE_HCI_SCAN_ITVL) +#define BLETEST_CFG_SCAN_TYPE (BLE_HCI_SCAN_TYPE_PASSIVE) +#define BLETEST_CFG_SCAN_OWN_ADDR_TYPE (BLE_HCI_ADV_OWN_ADDR_PUBLIC) +#define BLETEST_CFG_SCAN_FILT_POLICY (BLE_HCI_SCAN_FILT_NO_WL) +#define BLETEST_CFG_FILT_DUP_ADV (1) + +/* Connection config */ +#define BLETEST_CFG_CONN_ITVL (128) /* in 1.25 msec increments */ +#define BLETEST_CFG_SLAVE_LATENCY (0) +#define BLETEST_CFG_INIT_FILTER_POLICY (BLE_HCI_CONN_FILT_NO_WL) +#define BLETEST_CFG_CONN_SPVN_TMO (1000) /* 10 msec increments */ +#define BLETEST_CFG_MIN_CE_LEN (6) +#define BLETEST_CFG_MAX_CE_LEN (BLETEST_CFG_CONN_ITVL) +#define BLETEST_CFG_CONN_PEER_ADDR_TYPE (BLE_HCI_CONN_PEER_ADDR_PUBLIC) +#define BLETEST_CFG_CONN_OWN_ADDR_TYPE (BLE_HCI_ADV_OWN_ADDR_PUBLIC) +#define BLETEST_CFG_CONCURRENT_CONNS (1) + +/* Test packet config */ +#define BLETEST_CFG_RAND_PKT_SIZE (1) +#define BLETEST_CFG_SUGG_DEF_TXOCTETS (251) +#define BLETEST_CFG_SUGG_DEF_TXTIME \ + ble_ll_pdu_tx_time_get(BLETEST_CFG_SUGG_DEF_TXOCTETS + 4, BLE_PHY_1M) + +/* Test configurations. One of these should be set to 1 */ +#if !defined(BLETEST_CONCURRENT_CONN_TEST) && !defined(BLETEST_THROUGHPUT_TEST) + #define BLETEST_CONCURRENT_CONN_TEST (1) +#endif + +/* BLETEST variables */ +#undef BLETEST_ADV_PKT_NUM +#define BLETEST_MAX_PKT_SIZE (247) +#define BLETEST_PKT_SIZE (247) +#define BLETEST_STACK_SIZE (256) +uint32_t g_next_os_time; +int g_bletest_state; +struct os_eventq g_bletest_evq; +struct os_callout g_bletest_timer; +struct os_task bletest_task; +bssnz_t os_stack_t bletest_stack[BLETEST_STACK_SIZE]; +uint32_t g_bletest_conn_end; +int g_bletest_start_update; +uint32_t g_bletest_conn_upd_time; +uint8_t g_bletest_current_conns; +uint8_t g_bletest_cur_peer_addr[BLE_DEV_ADDR_LEN]; +uint8_t g_last_handle_used; +uint8_t g_bletest_led_state; +uint32_t g_bletest_led_rate; +uint32_t g_bletest_next_led_time; +uint16_t g_bletest_handle; +uint16_t g_bletest_completed_pkts; +uint16_t g_bletest_outstanding_pkts; +uint16_t g_bletest_ltk_reply_handle; +uint32_t g_bletest_hw_id[4]; +struct hci_create_conn g_cc; + +/* --- For LE encryption testing --- */ +/* Key: 0x4C68384139F574D836BCF34E9DFB01BF */ +const uint8_t g_ble_ll_encrypt_test_key[16] = +{ + 0x4c, 0x68, 0x38, 0x41, 0x39, 0xf5, 0x74, 0xd8, + 0x36, 0xbc, 0xf3, 0x4e, 0x9d, 0xfb, 0x01, 0xbf +}; + +/* Plaint text: 0x0213243546576879acbdcedfe0f10213 */ +const uint8_t g_ble_ll_encrypt_test_plain_text[16] = +{ + 0x02, 0x13, 0x24, 0x35, 0x46, 0x57, 0x68, 0x79, + 0xac, 0xbd, 0xce, 0xdf, 0xe0, 0xf1, 0x02, 0x13 +}; + +/* Encrypted data: 0x99ad1b5226a37e3e058e3b8e27c2c666 */ +const uint8_t g_ble_ll_encrypt_test_encrypted_data[16] = +{ + 0x99, 0xad, 0x1b, 0x52, 0x26, 0xa3, 0x7e, 0x3e, + 0x05, 0x8e, 0x3b, 0x8e, 0x27, 0xc2, 0xc6, 0x66 +}; + +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY) == 1) +uint8_t g_bletest_adv_irk[16] = { + 0xec, 0x02, 0x34, 0xa3, 0x57, 0xc8, 0xad, 0x05, + 0x34, 0x10, 0x10, 0xa6, 0x0a, 0x39, 0x7d, 0x9b +}; + +uint8_t g_bletest_init_irk[16] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 +}; +#endif + +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION) == 1) +/* LTK 0x4C68384139F574D836BCF34E9DFB01BF */ +const uint8_t g_bletest_LTK[16] = +{ + 0x4C,0x68,0x38,0x41,0x39,0xF5,0x74,0xD8, + 0x36,0xBC,0xF3,0x4E,0x9D,0xFB,0x01,0xBF +}; +uint16_t g_bletest_EDIV = 0x2474; +uint64_t g_bletest_RAND = 0xABCDEF1234567890; +uint64_t g_bletest_SKDm = 0xACBDCEDFE0F10213; +uint64_t g_bletest_SKDs = 0x0213243546576879; +uint32_t g_bletest_IVm = 0xBADCAB24; +uint32_t g_bletest_IVs = 0xDEAFBABE; +#endif + +#if (BLETEST_THROUGHPUT_TEST == 1) +void +bletest_completed_pkt(uint16_t handle) +{ + os_sr_t sr; + + OS_ENTER_CRITICAL(sr); + if (handle == g_bletest_handle) { + ++g_bletest_completed_pkts; + } + OS_EXIT_CRITICAL(sr); +} +#endif + +#ifdef BLETEST_ADV_PKT_NUM +void +bletest_inc_adv_pkt_num(void) +{ + int rc; + uint8_t *dptr; + uint8_t digit; + + if (g_host_adv_len != 0) { + dptr = &g_host_adv_data[18]; + while (dptr >= &g_host_adv_data[13]) { + digit = *dptr; + ++digit; + if (digit == 58) { + digit = 48; + *dptr = digit; + --dptr; + } else { + *dptr = digit; + break; + } + } + + rc = bletest_hci_le_set_adv_data(g_host_adv_data, g_host_adv_len); + assert(rc == 0); + } +} +#endif + +/** + * Sets the advertising data to be sent in advertising pdu's which contain + * advertising data. + * + * @param dptr + * @return uint8_t + */ +uint8_t +bletest_set_adv_data(uint8_t *dptr, uint8_t *addr) +{ + uint8_t len; + + /* Place flags in first */ + dptr[0] = 0x02; + dptr[1] = 0x01; /* Flags identifier */ + dptr[2] = 0x06; + dptr += 3; + len = 3; + + /* Add HID service */ + dptr[0] = 0x03; + dptr[1] = 0x03; + dptr[2] = 0x12; + dptr[3] = 0x18; + dptr += 4; + len += 4; + + /* Add local name */ + dptr[0] = 12; /* Length of this data, not including the length */ + dptr[1] = 0x09; + dptr[2] = 'r'; + dptr[3] = 'u'; + dptr[4] = 'n'; + dptr[5] = 't'; + dptr[6] = 'i'; + dptr[7] = 'm'; + dptr[8] = 'e'; + dptr[9] = '-'; + dptr[10] = '0'; + dptr[11] = '0'; + dptr[12] = '7'; + dptr += 13; + len += 13; + + /* Add local device address */ + dptr[0] = 0x08; + dptr[1] = 0x1B; + dptr[2] = 0x00; + memcpy(dptr + 3, addr, BLE_DEV_ADDR_LEN); + len += 9; + + g_host_adv_len = len; + + return len; +} + +#if (BLETEST_CFG_ROLE == BLETEST_ROLE_ADVERTISER) +#if MYNEWT_VAL(BLE_ANDROID_MULTI_ADV_SUPPORT) +void +bletest_init_adv_instances(void) +{ + uint8_t i; + int rc; + uint8_t *addr; + uint8_t adv_len; + uint8_t inst_allowed; + uint8_t rand_addr[BLE_DEV_ADDR_LEN]; + struct hci_multi_adv_params adv; + + inst_allowed = MYNEWT_VAL(BLE_MULTI_ADV_INSTANCES); + if (inst_allowed > BLETEST_CFG_ADV_TEST_INSTANCES) { + inst_allowed = BLETEST_CFG_ADV_TEST_INSTANCES; + } + + /* Start up all the instances */ + for (i = 1; i <= inst_allowed; ++i) { + memset(&adv, 0, sizeof(struct hci_multi_adv_params)); + + adv.own_addr_type = bletest_multi_adv_instances[i-1].adv_own_addr_type; + if (adv.own_addr_type == BLE_HCI_ADV_OWN_ADDR_PUBLIC) { + addr = g_dev_addr; + } else { + memcpy(rand_addr, g_dev_addr, BLE_DEV_ADDR_LEN); + rand_addr[5] |= 0xc0; + rand_addr[0] = i; + /* + * NOTE: we overload own address type with a special case + * to denote if we use own address or call to set multi random + * address. + */ + if (adv.own_addr_type == BLE_HCI_ADV_OWN_ADDR_RANDOM) { + rc = bletest_hci_le_set_multi_rand_addr(rand_addr, i); + assert(rc == 0); + addr = rand_addr; + } else { + adv.own_addr_type = BLE_HCI_ADV_OWN_ADDR_RANDOM; + addr = rand_addr; + memcpy(adv.own_addr, addr, BLE_DEV_ADDR_LEN); + } + } + + adv.adv_type = bletest_multi_adv_instances[i - 1].adv_type; + adv.adv_channel_map = 0x07; + adv.adv_filter_policy = BLE_HCI_ADV_FILT_NONE; + adv.peer_addr_type = BLE_HCI_ADV_PEER_ADDR_PUBLIC; + adv_len = bletest_set_adv_data(&g_host_adv_data[0], addr); + + adv.adv_itvl_min = bletest_multi_adv_instances[i - 1].adv_itvl; + adv.adv_itvl_max = bletest_multi_adv_instances[i - 1].adv_itvl; + adv.adv_tx_pwr = -1 * i; + + /* Set the advertising parameters */ + rc = bletest_hci_le_set_multi_adv_params(&adv, i); + assert(rc == 0); + + /* Set advertising data */ + if (adv_len != 0) { + rc = bletest_hci_le_set_multi_adv_data(&g_host_adv_data[0], adv_len, + i); + assert(rc == 0); + + /* Set scan response data */ + rc = bletest_hci_le_set_multi_scan_rsp_data(&g_host_adv_data[0], + adv_len, i); + assert(rc == 0); + } + + /* Set the advertising parameters */ + rc = bletest_hci_le_set_multi_adv_enable(1, i); + assert(rc == 0); + } +} + +void +bletest_init_advertising(uint8_t instance, int8_t txpwr) +{ + int rc; + int set_peer_addr; + uint8_t adv_len; + uint8_t *addr; + uint8_t rand_addr[BLE_DEV_ADDR_LEN]; + struct hci_multi_adv_params adv; + + /* Make sure it is a valid instance */ + assert(instance < BLE_LL_ADV_INSTANCES); + + /* Just zero out advertising */ + set_peer_addr = 0; + memset(&adv, 0, sizeof(struct hci_multi_adv_params)); + + /* If we are using a random address, we need to set it */ + adv.own_addr_type = BLETEST_CFG_ADV_OWN_ADDR_TYPE; + if (adv.own_addr_type & 1) { + memcpy(rand_addr, g_dev_addr, BLE_DEV_ADDR_LEN); + rand_addr[5] |= 0xc0; + if (BLETEST_CFG_MULTI_ADV_RANDOM_OWN == 1) { + addr = rand_addr; + memcpy(adv.own_addr, addr, BLE_DEV_ADDR_LEN); + } else { + rc = bletest_hci_le_set_multi_rand_addr(rand_addr, instance); + assert(rc == 0); + addr = rand_addr; + } + } else { + addr = g_dev_addr; + } + + /* Set advertising parameters */ + adv.adv_type = BLETEST_CFG_ADV_TYPE; + adv.adv_channel_map = 0x07; + adv.adv_filter_policy = BLETEST_CFG_ADV_FILT_POLICY; + if ((adv.adv_filter_policy & 1) || (BLETEST_CFG_ADV_ADDR_RES_EN == 1)) { + set_peer_addr = 1; + } + adv.peer_addr_type = BLETEST_CFG_ADV_PEER_ADDR_TYPE; + if ((adv.adv_type == BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD) || + (adv.adv_type == BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD)) { + set_peer_addr = 1; + adv_len = 0; + } else { + adv_len = bletest_set_adv_data(&g_host_adv_data[0], addr); + } + + /* Not allowed for multi-adv command */ + if (adv.own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM) { + assert(0); + } + + if (set_peer_addr) { + memcpy(adv.peer_addr, g_bletest_cur_peer_addr, BLE_DEV_ADDR_LEN); + if (adv.peer_addr_type == BLE_HCI_ADV_PEER_ADDR_RANDOM) { + adv.peer_addr[5] |= 0xc0; + } + } + + console_printf("Trying to connect to %x.%x.%x.%x.%x.%x\n", + adv.peer_addr[0], adv.peer_addr[1], adv.peer_addr[2], + adv.peer_addr[3], adv.peer_addr[4], adv.peer_addr[5]); + + if (adv.adv_type == BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD) { + adv.adv_itvl_min = 0; + adv.adv_itvl_max = 0; + } else { + adv.adv_itvl_min = BLETEST_CFG_ADV_ITVL; + adv.adv_itvl_max = BLETEST_CFG_ADV_ITVL; /* Advertising interval */ + } + + adv.adv_tx_pwr = txpwr; + + /* Set the advertising parameters */ + rc = bletest_hci_le_set_multi_adv_params(&adv, instance); + assert(rc == 0); + + /* Set advertising data */ + if (adv_len != 0) { + rc = bletest_hci_le_set_multi_adv_data(&g_host_adv_data[0], adv_len, + instance); + assert(rc == 0); + + /* Set scan response data */ + rc = bletest_hci_le_set_multi_scan_rsp_data(&g_host_adv_data[0],adv_len, + instance); + assert(rc == 0); + } +} +#else +void +bletest_init_advertising(void) +{ + int rc; + int set_peer_addr; + uint8_t adv_len; + uint8_t *addr; + uint8_t rand_addr[BLE_DEV_ADDR_LEN]; + struct hci_adv_params adv; + + /* Just zero out advertising */ + set_peer_addr = 0; + memset(&adv, 0, sizeof(struct hci_adv_params)); + + /* If we are using a random address, we need to set it */ + adv.own_addr_type = BLETEST_CFG_ADV_OWN_ADDR_TYPE; + if (adv.own_addr_type & 1) { + memcpy(rand_addr, g_dev_addr, BLE_DEV_ADDR_LEN); + rand_addr[5] |= 0xc0; + rc = bletest_hci_le_set_rand_addr(rand_addr); + assert(rc == 0); + addr = rand_addr; + } else { + addr = g_dev_addr; + } + + /* Set advertising parameters */ + adv.adv_type = BLETEST_CFG_ADV_TYPE; + adv.adv_channel_map = 0x07; + adv.adv_filter_policy = BLETEST_CFG_ADV_FILT_POLICY; + if ((adv.adv_filter_policy & 1) || (BLETEST_CFG_ADV_ADDR_RES_EN == 1)) { + set_peer_addr = 1; + } + adv.peer_addr_type = BLETEST_CFG_ADV_PEER_ADDR_TYPE; + if ((adv.adv_type == BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD) || + (adv.adv_type == BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD)) { + set_peer_addr = 1; + adv_len = 0; + } else { + adv_len = bletest_set_adv_data(&g_host_adv_data[0], addr); + } + + if (adv.own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM) { + set_peer_addr = 1; + } + + if (set_peer_addr) { + memcpy(adv.peer_addr, g_bletest_cur_peer_addr, BLE_DEV_ADDR_LEN); + if (adv.peer_addr_type == BLE_HCI_ADV_PEER_ADDR_RANDOM) { + adv.peer_addr[5] |= 0xc0; + } + } + + console_printf("Trying to connect to %x.%x.%x.%x.%x.%x\n", + adv.peer_addr[0], adv.peer_addr[1], adv.peer_addr[2], + adv.peer_addr[3], adv.peer_addr[4], adv.peer_addr[5]); + + if (adv.adv_type == BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD) { + adv.adv_itvl_min = 0; + adv.adv_itvl_max = 0; + } else { + adv.adv_itvl_min = BLETEST_CFG_ADV_ITVL; + adv.adv_itvl_max = BLETEST_CFG_ADV_ITVL; /* Advertising interval */ + } + + /* Set the advertising parameters */ + rc = bletest_hci_le_set_adv_params(&adv); + assert(rc == 0); + +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY) == 1) + if ((adv.own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM) || + (BLETEST_CFG_ADV_ADDR_RES_EN == 1)) { + rc = bletest_hci_le_add_resolv_list(g_bletest_adv_irk, + g_bletest_init_irk, + adv.peer_addr, + adv.peer_addr_type); + assert(rc == 0); + + rc = bletest_hci_le_enable_resolv_list(1); + assert(rc == 0); + } +#endif + + /* Set advertising data */ + if (adv_len != 0) { + rc = bletest_hci_le_set_adv_data(&g_host_adv_data[0], adv_len); + assert(rc == 0); + + /* Set scan response data */ + rc = bletest_hci_le_set_scan_rsp_data(&g_host_adv_data[0], adv_len); + assert(rc == 0); + } +} +#endif /* MULTI_ADV SUPPORT */ +#endif /* BLETEST_ROLE_ADVERTISER */ + +#if (BLETEST_CFG_ROLE == BLETEST_ROLE_SCANNER) +void +bletest_init_scanner(void) +{ + int rc; + uint8_t own_addr_type; + uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_SET_SCAN_PARAM_LEN]; + uint8_t add_whitelist; + + own_addr_type = BLETEST_CFG_SCAN_OWN_ADDR_TYPE; + rc = ble_hs_hci_cmd_build_le_set_scan_params(BLETEST_CFG_SCAN_TYPE, + BLETEST_CFG_SCAN_ITVL, + BLETEST_CFG_SCAN_WINDOW, + own_addr_type, + BLETEST_CFG_SCAN_FILT_POLICY, + buf, sizeof buf); + assert(rc == 0); + rc = ble_hs_hci_cmd_tx_empty_ack(buf); + if (rc == 0) { + add_whitelist = BLETEST_CFG_SCAN_FILT_POLICY; +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY) == 1) + if (own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM) { + rc = bletest_hci_le_add_resolv_list(g_bletest_init_irk, + g_bletest_adv_irk, + g_bletest_cur_peer_addr, + BLETEST_CFG_ADV_PEER_ADDR_TYPE); + assert(rc == 0); + + rc = bletest_hci_le_enable_resolv_list(1); + assert(rc == 0); + } +#endif + if (add_whitelist & 1) { + rc = bletest_hci_le_add_to_whitelist(g_bletest_cur_peer_addr, + BLE_ADDR_RANDOM); + assert(rc == 0); + } + } +} + +void +bletest_execute_scanner(void) +{ + int rc; + + /* Enable scanning */ + if ((int32_t)(os_time_get() - g_next_os_time) >= 0) { + if (g_bletest_state) { + rc = bletest_hci_le_set_scan_enable(0, BLETEST_CFG_FILT_DUP_ADV); + assert(rc == 0); + g_bletest_state = 0; + } else { + rc = bletest_hci_le_set_scan_enable(1, BLETEST_CFG_FILT_DUP_ADV); + assert(rc == 0); + g_bletest_state = 1; + } + g_next_os_time += (OS_TICKS_PER_SEC * 60); + } +} +#endif + +#if (BLETEST_CFG_ROLE == BLETEST_ROLE_INITIATOR) +void +bletest_init_initiator(void) +{ + int rc; + uint8_t rand_addr[BLE_DEV_ADDR_LEN]; + struct hci_create_conn *hcc; + + /* Enable initiating */ + hcc = &g_cc; + hcc->conn_itvl_max = BLETEST_CFG_CONN_ITVL; + hcc->conn_itvl_min = BLETEST_CFG_CONN_ITVL; + hcc->conn_latency = BLETEST_CFG_SLAVE_LATENCY; + hcc->filter_policy = BLETEST_CFG_INIT_FILTER_POLICY; + hcc->supervision_timeout = BLETEST_CFG_CONN_SPVN_TMO; + hcc->scan_itvl = BLETEST_CFG_SCAN_ITVL; + hcc->scan_window = BLETEST_CFG_SCAN_WINDOW; + hcc->peer_addr_type = BLETEST_CFG_CONN_PEER_ADDR_TYPE; + memcpy(hcc->peer_addr, g_bletest_cur_peer_addr, BLE_DEV_ADDR_LEN); + if (hcc->peer_addr_type == BLE_HCI_CONN_PEER_ADDR_RANDOM) { + hcc->peer_addr[5] |= 0xc0; + } + hcc->own_addr_type = BLETEST_CFG_CONN_OWN_ADDR_TYPE; + hcc->min_ce_len = BLETEST_CFG_MIN_CE_LEN; + hcc->max_ce_len = BLETEST_CFG_MAX_CE_LEN; + + console_printf("Trying to connect to %x.%x.%x.%x.%x.%x\n", + hcc->peer_addr[0], hcc->peer_addr[1], hcc->peer_addr[2], + hcc->peer_addr[3], hcc->peer_addr[4], hcc->peer_addr[5]); + + /* If we are using a random address, we need to set it */ + if (hcc->own_addr_type == BLE_HCI_ADV_OWN_ADDR_RANDOM) { + memcpy(rand_addr, g_dev_addr, BLE_DEV_ADDR_LEN); + rand_addr[5] |= 0xc0; + rc = bletest_hci_le_set_rand_addr(rand_addr); + assert(rc == 0); + } + +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY) == 1) + if ((hcc->peer_addr_type > BLE_HCI_CONN_PEER_ADDR_RANDOM) || + (hcc->own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM)) { + rc = bletest_hci_le_add_resolv_list(g_bletest_init_irk, + g_bletest_adv_irk, + g_bletest_cur_peer_addr, + BLETEST_CFG_ADV_PEER_ADDR_TYPE); + assert(rc == 0); + + rc = bletest_hci_le_enable_resolv_list(1); + assert(rc == 0); + } +#endif + + bletest_hci_le_create_connection(hcc); +} + +void +bletest_execute_initiator(void) +{ + int i; + int rc; + int8_t rssi; + uint16_t handle; + uint8_t new_chan_map[5]; + + /* + * Determine if there is an active connection for the current handle + * we are trying to create. If so, start looking for the next one + */ + if (g_bletest_current_conns < BLETEST_CFG_CONCURRENT_CONNS) { + handle = g_bletest_current_conns + 1; + if (ble_ll_conn_find_active_conn(handle)) { + /* Set LED to slower blink rate */ + g_bletest_led_rate = OS_TICKS_PER_SEC; + + /* Ask for version information */ + rc = bletest_hci_rd_rem_version(handle); + + /* Ask for remote used features */ + rc = bletest_hci_le_read_rem_used_feat(handle); + + /* Scanning better be stopped! */ + assert(ble_ll_scan_enabled() == 0); + + /* Add to current connections */ + if (!rc) { + ++g_bletest_current_conns; + + /* Move to next connection */ + if (g_bletest_current_conns < BLETEST_CFG_CONCURRENT_CONNS) { + /* restart initiating */ + g_bletest_cur_peer_addr[5] += 1; + g_dev_addr[5] += 1; + bletest_init_initiator(); + } + } + } else { + if (ble_ll_scan_enabled() == 0) { + bletest_hci_le_create_connection(&g_cc); + } + } + } else { + if ((int32_t)(os_time_get() - g_next_os_time) >= 0) { + if ((g_bletest_state == 1) || (g_bletest_state == 3)) { + for (i = 0; i < g_bletest_current_conns; ++i) { + if (ble_ll_conn_find_active_conn(i + 1)) { + bletest_hci_le_rd_chanmap(i+1); + } + } + } else if (g_bletest_state == 2) { + new_chan_map[0] = 0; + new_chan_map[1] = 0x3; + new_chan_map[2] = 0; + new_chan_map[3] = 0x1F; + new_chan_map[4] = 0; + bletest_hci_le_set_host_chan_class(new_chan_map); + } else if (g_bletest_state == 4) { +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION) == 1) + struct hci_start_encrypt hsle; + for (i = 0; i < g_bletest_current_conns; ++i) { + if (ble_ll_conn_find_active_conn(i + 1)) { + hsle.connection_handle = i + 1; + hsle.encrypted_diversifier = g_bletest_EDIV; + hsle.random_number = g_bletest_RAND; + swap_buf(hsle.long_term_key, (uint8_t *)g_bletest_LTK, + 16); + bletest_hci_le_start_encrypt(&hsle); + } + } +#endif + } else if (g_bletest_state == 8) { +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION) == 1) + struct hci_start_encrypt hsle; + for (i = 0; i < g_bletest_current_conns; ++i) { + if (ble_ll_conn_find_active_conn(i + 1)) { + hsle.connection_handle = i + 1; + hsle.encrypted_diversifier = g_bletest_EDIV; + hsle.random_number = ~g_bletest_RAND; + swap_buf(hsle.long_term_key, (uint8_t *)g_bletest_LTK, + 16); + bletest_hci_le_start_encrypt(&hsle); + } + } +#endif + } else { + for (i = 0; i < g_bletest_current_conns; ++i) { + if (ble_ll_conn_find_active_conn(i + 1)) { + ble_hs_hci_util_read_rssi(i+1, &rssi); + } + } + } + + ++g_bletest_state; + if (g_bletest_state > 9) { + g_bletest_state = 9; + } + g_next_os_time = os_time_get() + OS_TICKS_PER_SEC * 3; + } + } +} +#endif + +#if (BLETEST_CFG_ROLE == BLETEST_ROLE_ADVERTISER) +/* + * Test wrapper to get packets. Only get a packet if we have more than half + * left + */ +static struct os_mbuf * +bletest_get_packet(void) +{ + struct os_mbuf *om; + + om = NULL; + if (os_msys_num_free() >= 5) { + om = os_msys_get_pkthdr(0, sizeof(struct ble_mbuf_hdr)); + } + return om; +} + +static struct os_mbuf * +bletest_send_packet(uint16_t handle) +{ + int j; + uint8_t val; + struct os_mbuf *om; + uint16_t pktlen; + + om = bletest_get_packet(); + if (om) { + /* set payload length */ +#if BLETEST_THROUGHPUT_TEST + pktlen = BLETEST_PKT_SIZE; +#else +#if (BLETEST_CFG_RAND_PKT_SIZE == 1) + pktlen = rand() % (BLETEST_MAX_PKT_SIZE + 1); +#else + pktlen = BLETEST_PKT_SIZE; +#endif +#endif + + /* Put the HCI header in the mbuf */ + put_le16(om->om_data, handle); + put_le16(om->om_data + 2, pktlen + 4); + + /* Place L2CAP header in packet */ + put_le16(om->om_data + 4, pktlen); + om->om_data[6] = 0; + om->om_data[7] = 0; + om->om_len = 8; + OS_MBUF_PKTHDR(om)->omp_len = 8; + + /* Fill with incrementing pattern (starting from 1) */ + for (j = 0; j < pktlen; ++j) { + val = j + 1; + os_mbuf_append(om, &val, 1); + } + + /* Transmit it */ + ble_hci_trans_hs_acl_tx(om); + } + + return om; +} + +static void +bletest_execute_advertiser(void) +{ + int i; +#if (BLETEST_CONCURRENT_CONN_TEST == 1) +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION) == 1) + uint16_t mask; + uint16_t reply_handle; +#endif +#endif + int rc; + uint16_t handle; + struct os_mbuf *om; +#if (BLETEST_THROUGHPUT_TEST == 1) + os_sr_t sr; + uint16_t completed_pkts; +#endif + + /* See if we should start advertising again */ + if (g_bletest_current_conns < BLETEST_CFG_CONCURRENT_CONNS) { + handle = g_bletest_current_conns + 1; + if (ble_ll_conn_find_active_conn(handle)) { + /* Set LED to slower blink rate */ + g_bletest_led_rate = OS_TICKS_PER_SEC; + +#if (BLETEST_THROUGHPUT_TEST == 1) + /* Set next os time to 10 seconds after 1st connection */ + if (g_next_os_time == 0) { + g_next_os_time = os_time_get() + (10 * OS_TICKS_PER_SEC); + g_bletest_handle = handle; + } +#endif + + /* advertising better be stopped! */ + assert(ble_ll_adv_enabled() == 0); + + /* Send the remote used features command */ + rc = bletest_hci_le_read_rem_used_feat(handle); + if (rc) { + return; + } + + /* Send the remote read version command */ + rc = bletest_hci_rd_rem_version(handle); + if (rc) { + return; + } + + /* set conn update time */ + g_bletest_conn_upd_time = os_time_get() + (OS_TICKS_PER_SEC * 5); + g_bletest_start_update = 1; + + /* Add to current connections */ + ++g_bletest_current_conns; + + /* Move to next connection */ + if (g_bletest_current_conns < BLETEST_CFG_CONCURRENT_CONNS) { + /* restart initiating */ + g_bletest_cur_peer_addr[5] += 1; + g_dev_addr[5] += 1; +#if MYNEWT_VAL(BLE_ANDROID_MULTI_ADV_SUPPORT) + bletest_init_advertising(0,0); + bletest_hci_le_set_multi_adv_enable(1, 0); +#else + bletest_init_advertising(); + bletest_hci_le_set_adv_enable(1); +#endif + } + } else { + /* If we failed to start advertising we should keep trying */ + if (ble_ll_adv_enabled() == 0) { +#if MYNEWT_VAL(BLE_ANDROID_MULTI_ADV_SUPPORT) + bletest_hci_le_set_multi_adv_enable(1, 0); +#else + bletest_hci_le_set_adv_enable(1); +#endif + } + } + } +#if 0 + if (g_bletest_start_update) { + if ((int32_t)(os_time_get() - g_bletest_conn_upd_time) >= 0) { + bletest_send_conn_update(1); + g_bletest_start_update = 0; + } + } +#endif + +#if (BLETEST_CONCURRENT_CONN_TEST == 1) + /* See if it is time to hand a data packet to the connection */ + if ((int32_t)(os_time_get() - g_next_os_time) >= 0) { +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION) == 1) + /* Do we need to send a LTK reply? */ + mask = 1; + reply_handle = 1; + while (g_bletest_ltk_reply_handle && mask) { + if (g_bletest_ltk_reply_handle & mask) { + bletest_send_ltk_req_reply(reply_handle); + //bletest_send_ltk_req_neg_reply(reply_handle); + g_bletest_ltk_reply_handle &= ~mask; + } + ++reply_handle; + mask <<= 1; + } +#endif + if (g_bletest_current_conns) { + for (i = 0; i < g_bletest_current_conns; ++i) { + if ((g_last_handle_used == 0) || + (g_last_handle_used > g_bletest_current_conns)) { + g_last_handle_used = 1; + } + handle = g_last_handle_used; + if (ble_ll_conn_find_active_conn(handle)) { + om = bletest_send_packet(handle); + if (om) { + /* Increment last handle used */ + ++g_last_handle_used; + } + } else { + ++g_last_handle_used; + } + } + } + g_next_os_time = os_time_get() + OS_TICKS_PER_SEC; + } +#endif + +#if (BLETEST_THROUGHPUT_TEST == 1) + /* Nothing to do if no connections */ + if (!g_bletest_current_conns) { + return; + } + + /* See if it is time to start throughput testing */ + if ((int32_t)(os_time_get() - g_next_os_time) >= 0) { + /* Keep window full */ + OS_ENTER_CRITICAL(sr); + completed_pkts = g_bletest_completed_pkts; + g_bletest_completed_pkts = 0; + OS_EXIT_CRITICAL(sr); + + assert(g_bletest_outstanding_pkts >= completed_pkts); + g_bletest_outstanding_pkts -= completed_pkts; + + while (g_bletest_outstanding_pkts < 20) { + om = bletest_send_packet(g_bletest_handle); + if (om) { + ++g_bletest_outstanding_pkts; + } + } + } +#endif /* XXX: throughput test */ +} +#endif /* XXX: BLETEST_ROLE_ADVERTISER */ + +/** + * Main bletest function. Called by the task timer every 50 msecs. + * + */ +void +bletest_execute(void) +{ + /* Toggle LED at set rate */ + if ((int32_t)(os_time_get() - g_bletest_next_led_time) >= 0) { + hal_gpio_toggle(LED_BLINK_PIN); + g_bletest_next_led_time = os_time_get() + g_bletest_led_rate; + } +#if (BLETEST_CFG_ROLE == BLETEST_ROLE_ADVERTISER) + bletest_execute_advertiser(); +#endif +#if (BLETEST_CFG_ROLE == BLETEST_ROLE_SCANNER) + bletest_execute_scanner(); +#endif +#if (BLETEST_CFG_ROLE == BLETEST_ROLE_INITIATOR) + bletest_execute_initiator(); +#endif +} + +/** + * Callback when BLE test timer expires. + * + * @param arg + */ +void +bletest_timer_cb(struct os_event *ev) +{ + /* Call the bletest code */ + bletest_execute(); + + /* Re-start the timer (run every 10 msecs) */ + os_callout_reset(&g_bletest_timer, OS_TICKS_PER_SEC / 100); +} + +/** + * BLE test task + * + * @param arg + */ +void +bletest_task_handler(void *arg) +{ + int rc; + uint64_t rand64; + uint64_t event_mask; + + /* Set LED blink rate */ + g_bletest_led_rate = OS_TICKS_PER_SEC / 20; + + /* Wait one second before starting test task */ + os_time_delay(OS_TICKS_PER_SEC); + + /* Initialize the host timer */ + os_callout_init(&g_bletest_timer, &g_bletest_evq, bletest_timer_cb, + NULL); + + ble_hs_dbg_set_sync_state(BLE_HS_SYNC_STATE_GOOD); + + /* Send the reset command first */ + rc = bletest_hci_reset_ctlr(); + assert(rc == 0); + +#if (BLETEST_CFG_ROLE == BLETEST_ROLE_ADVERTISER) + /* Initialize the advertiser */ + console_printf("Starting BLE test task as advertiser\n"); +#if MYNEWT_VAL(BLE_ANDROID_MULTI_ADV_SUPPORT) + /* Start up all advertising instances except default one */ + bletest_init_adv_instances(); + + /* Start advertising on instance 0 at 0 dbm */ + bletest_init_advertising(0, 0); +#else + bletest_init_advertising(); +#endif +#endif + +#if (BLETEST_CFG_ROLE == BLETEST_ROLE_SCANNER) + /* Initialize the scanner */ + console_printf("Starting BLE test task as scanner\n"); + bletest_init_scanner(); +#endif + +#if (BLETEST_CFG_ROLE == BLETEST_ROLE_INITIATOR) + /* Initialize the scanner */ + console_printf("Starting BLE test task as initiator\n"); + bletest_init_initiator(); +#endif + + /* Read unique HW id */ + rc = hal_bsp_hw_id((void *)&g_bletest_hw_id[0], sizeof(g_bletest_hw_id)); + assert(rc == 16); + console_printf("HW id=%04x%04x%04x%04x\n", + (unsigned int)g_bletest_hw_id[0], + (unsigned int)g_bletest_hw_id[1], + (unsigned int)g_bletest_hw_id[2], + (unsigned int)g_bletest_hw_id[3]); + + /* Set the event mask we want to display */ + event_mask = 0x7FF; + rc = bletest_hci_le_set_event_mask(event_mask); + assert(rc == 0); + + /* Turn on all events */ + event_mask = 0xffffffffffffffff; + rc = bletest_hci_set_event_mask(event_mask); + assert(rc == 0); + + /* Read device address */ + rc = bletest_hci_rd_bd_addr(); + assert(rc == 0); + + /* Read local features */ + rc = bletest_hci_rd_local_feat(); + assert(rc == 0); + + /* Read local commands */ + rc = bletest_hci_rd_local_supp_cmd(); + assert(rc == 0); + + /* Read version */ + rc = bletest_hci_rd_local_version(); + assert(rc == 0); + + /* Read supported states */ + rc = bletest_hci_le_read_supp_states(); + assert(rc == 0); + + /* Read maximum data length */ + rc = bletest_hci_le_rd_max_datalen(); + assert(rc == 0); + +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_DATA_LEN_EXT) == 1) + /* Read suggested data length */ + rc = bletest_hci_le_rd_sugg_datalen(); + assert(rc == 0); + + /* write suggested default data length */ + rc = bletest_hci_le_write_sugg_datalen(BLETEST_CFG_SUGG_DEF_TXOCTETS, + BLETEST_CFG_SUGG_DEF_TXTIME); + assert(rc == 0); + + /* Read suggested data length */ + rc = bletest_hci_le_rd_sugg_datalen(); + assert(rc == 0); + + /* Set data length (note: we know there is no connection; just a test) */ + rc = bletest_hci_le_set_datalen(0x1234, BLETEST_CFG_SUGG_DEF_TXOCTETS, + BLETEST_CFG_SUGG_DEF_TXTIME); + assert(rc != 0); +#endif + + /* Encrypt a block */ +#if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION) == 1) + rc = bletest_hci_le_encrypt((uint8_t *)g_ble_ll_encrypt_test_key, + (uint8_t *)g_ble_ll_encrypt_test_plain_text); + assert(rc == 0); +#endif + + /* Get a random number */ + rc = ble_hs_hci_util_rand(&rand64, 8); + assert(rc == 0); + + /* Wait some time before starting */ + os_time_delay(OS_TICKS_PER_SEC); + + /* Init state */ + g_bletest_state = 0; + + /* Begin advertising if we are an advertiser */ +#if (BLETEST_CFG_ROLE == BLETEST_ROLE_ADVERTISER) +#if MYNEWT_VAL(BLE_ANDROID_MULTI_ADV_SUPPORT) + rc = bletest_hci_le_set_multi_adv_enable(1, 0); + assert(rc == 0); +#else + rc = bletest_hci_le_set_adv_enable(1); + assert(rc == 0); +#endif +#endif + + bletest_timer_cb(NULL); + + while (1) { + os_eventq_run(&g_bletest_evq); + } +} + +/** + * main + * + * The main task for the project. This function initializes the packages, + * then starts serving events from default event queue. + * + * @return int NOTE: this function should never return! + */ +int +main(void) +{ + int rc; + + /* Initialize OS */ + sysinit(); + + /* Dummy device address */ +#if BLETEST_CFG_ROLE == BLETEST_ROLE_ADVERTISER + g_dev_addr[0] = 0x00; + g_dev_addr[1] = 0x00; + g_dev_addr[2] = 0x00; + g_dev_addr[3] = 0x88; + g_dev_addr[4] = 0x88; + g_dev_addr[5] = 0x08; + + g_bletest_cur_peer_addr[0] = 0x00; + g_bletest_cur_peer_addr[1] = 0x00; + g_bletest_cur_peer_addr[2] = 0x00; + g_bletest_cur_peer_addr[3] = 0x99; + g_bletest_cur_peer_addr[4] = 0x99; + g_bletest_cur_peer_addr[5] = 0x09; +#else + g_dev_addr[0] = 0x00; + g_dev_addr[1] = 0x00; + g_dev_addr[2] = 0x00; + g_dev_addr[3] = 0x99; + g_dev_addr[4] = 0x99; + g_dev_addr[5] = 0x09; + + g_bletest_cur_peer_addr[0] = 0x00; + g_bletest_cur_peer_addr[1] = 0x00; + g_bletest_cur_peer_addr[2] = 0x00; + g_bletest_cur_peer_addr[3] = 0x88; + g_bletest_cur_peer_addr[4] = 0x88; + g_bletest_cur_peer_addr[5] = 0x08; +#endif + + log_register("ble_hs", &ble_hs_log, &log_console_handler, NULL, + LOG_SYSLEVEL); + + /* Set the led pin as an output */ + g_led_pin = LED_BLINK_PIN; + hal_gpio_init_out(g_led_pin, 1); + + /* Initialize eventq for bletest task */ + os_eventq_init(&g_bletest_evq); + + rc = os_task_init(&bletest_task, "bletest", bletest_task_handler, NULL, + BLETEST_TASK_PRIO, OS_WAIT_FOREVER, bletest_stack, + BLETEST_STACK_SIZE); + assert(rc == 0); + + while (1) { + os_eventq_run(os_eventq_dflt_get()); + } + /* Never returns */ + + /* os start should never return. If it does, this should be an error */ + assert(0); + + return rc; +} diff --git a/apps/bletest/syscfg.yml b/apps/bletest/syscfg.yml new file mode 100644 index 000000000..ba44607b4 --- /dev/null +++ b/apps/bletest/syscfg.yml @@ -0,0 +1,34 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Package: apps/bletest +syscfg.defs: + BLETEST_ROLE: + description: > + The role of the bletest code. Should be set to: + 1: Advertiser + 2: Scanner + 3: Initiator + value: 1 + +syscfg.vals: + MSYS_1_BLOCK_COUNT: 16 + BLE_SM_LEGACY: 0 + + # Default task settings + OS_MAIN_STACK_SIZE: 256 diff --git a/apps/btshell/pkg.yml b/apps/btshell/pkg.yml new file mode 100644 index 000000000..23ceded7e --- /dev/null +++ b/apps/btshell/pkg.yml @@ -0,0 +1,38 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +pkg.name: apps/btshell +pkg.type: app +pkg.description: Shell application exposing the nimble GAP and GATT. +pkg.author: "Apache Mynewt " +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + +pkg.deps: + - "@apache-mynewt-core/kernel/os" + - "@apache-mynewt-core/sys/log/full" + - "@apache-mynewt-core/sys/stats/full" + - "@apache-mynewt-core/sys/console/full" + - "@apache-mynewt-core/sys/shell" + - nimble/host + - nimble/host/services/gap + - nimble/host/services/gatt + - nimble/host/store/ram + - nimble/transport + +pkg.deps.BTSHELL_ANS: + - nimble/host/services/ans diff --git a/apps/btshell/src/btshell.h b/apps/btshell/src/btshell.h new file mode 100644 index 000000000..8b181f22d --- /dev/null +++ b/apps/btshell/src/btshell.h @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef H_BTSHELL_PRIV_ +#define H_BTSHELL_PRIV_ + +#include +#include "os/mynewt.h" +#include "nimble/ble.h" +#include "nimble/nimble_opt.h" +#include "log/log.h" + +#include "host/ble_gatt.h" +#include "host/ble_gap.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ble_gap_white_entry; +struct ble_hs_adv_fields; +struct ble_gap_upd_params; +struct ble_gap_conn_params; +struct hci_adv_params; +struct ble_l2cap_sig_update_req; +struct ble_l2cap_sig_update_params; +union ble_store_value; +union ble_store_key; +struct ble_gap_adv_params; +struct ble_gap_conn_desc; +struct ble_gap_disc_params; + +struct btshell_dsc { + SLIST_ENTRY(btshell_dsc) next; + struct ble_gatt_dsc dsc; +}; +SLIST_HEAD(btshell_dsc_list, btshell_dsc); + +struct btshell_chr { + SLIST_ENTRY(btshell_chr) next; + struct ble_gatt_chr chr; + + struct btshell_dsc_list dscs; +}; +SLIST_HEAD(btshell_chr_list, btshell_chr); + +struct btshell_svc { + SLIST_ENTRY(btshell_svc) next; + struct ble_gatt_svc svc; + struct btshell_chr_list chrs; +}; + +SLIST_HEAD(btshell_svc_list, btshell_svc); + +struct btshell_l2cap_coc { + SLIST_ENTRY(btshell_l2cap_coc) next; + struct ble_l2cap_chan *chan; +}; + +SLIST_HEAD(btshell_l2cap_coc_list, btshell_l2cap_coc); + +struct btshell_conn { + uint16_t handle; + struct btshell_svc_list svcs; + struct btshell_l2cap_coc_list coc_list; +}; + +struct btshell_scan_opts { + uint16_t limit; + uint8_t ignore_legacy:1; +}; + +extern struct btshell_conn btshell_conns[MYNEWT_VAL(BLE_MAX_CONNECTIONS)]; +extern int btshell_num_conns; + +extern struct log btshell_log; +int btshell_exchange_mtu(uint16_t conn_handle); +int btshell_disc_svcs(uint16_t conn_handle); +int btshell_disc_svc_by_uuid(uint16_t conn_handle, const ble_uuid_t *uuid); +int btshell_disc_all_chrs(uint16_t conn_handle, uint16_t start_handle, + uint16_t end_handle); +int btshell_disc_chrs_by_uuid(uint16_t conn_handle, uint16_t start_handle, + uint16_t end_handle, const ble_uuid_t *uuid); +int btshell_disc_all_dscs(uint16_t conn_handle, uint16_t start_handle, + uint16_t end_handle); +int btshell_disc_full(uint16_t conn_handle); +int btshell_find_inc_svcs(uint16_t conn_handle, uint16_t start_handle, + uint16_t end_handle); +int btshell_read(uint16_t conn_handle, uint16_t attr_handle); +int btshell_read_long(uint16_t conn_handle, uint16_t attr_handle, + uint16_t offset); +int btshell_read_by_uuid(uint16_t conn_handle, uint16_t start_handle, + uint16_t end_handle, const ble_uuid_t *uuid); +int btshell_read_mult(uint16_t conn_handle, uint16_t *attr_handles, + int num_attr_handles); +int btshell_write(uint16_t conn_handle, uint16_t attr_handle, + struct os_mbuf *om); +int btshell_write_no_rsp(uint16_t conn_handle, uint16_t attr_handle, + struct os_mbuf *om); +int btshell_write_long(uint16_t conn_handle, uint16_t attr_handle, + uint16_t offset, struct os_mbuf *om); +int btshell_write_reliable(uint16_t conn_handle, + struct ble_gatt_attr *attrs, int num_attrs); +#if MYNEWT_VAL(BLE_EXT_ADV) +int btshell_ext_adv_configure(uint8_t instance, + const struct ble_gap_ext_adv_params *params, + int8_t *selected_tx_power); +#endif +int btshell_adv_start(uint8_t own_addr_type, const ble_addr_t *direct_addr, + int32_t duration_ms, + const struct ble_gap_adv_params *params); +int btshell_adv_stop(void); +int btshell_conn_initiate(uint8_t own_addr_type, const ble_addr_t *peer_addr, + int32_t duration_ms, + struct ble_gap_conn_params *params); +int btshell_ext_conn_initiate(uint8_t own_addr_type, + const ble_addr_t *peer_addr, + int32_t duration_ms, + struct ble_gap_conn_params *phy_1m_params, + struct ble_gap_conn_params *phy_2m_params, + struct ble_gap_conn_params *phy_coded_params); +int btshell_conn_cancel(void); +int btshell_term_conn(uint16_t conn_handle, uint8_t reason); +int btshell_wl_set(ble_addr_t *addrs, int addrs_count); +int btshell_scan(uint8_t own_addr_type, int32_t duration_ms, + const struct ble_gap_disc_params *disc_params, void *cb_args); +int btshell_ext_scan(uint8_t own_addr_type, uint16_t duration, uint16_t period, + uint8_t filter_duplicates, uint8_t filter_policy, + uint8_t limited, + const struct ble_gap_ext_disc_params *uncoded_params, + const struct ble_gap_ext_disc_params *coded_params, + void *cb_args); +int btshell_scan_cancel(void); +int btshell_update_conn(uint16_t conn_handle, + struct ble_gap_upd_params *params); +void btshell_notify(uint16_t attr_handle); +int btshell_datalen(uint16_t conn_handle, uint16_t tx_octets, + uint16_t tx_time); +int btshell_l2cap_update(uint16_t conn_handle, + struct ble_l2cap_sig_update_params *params); +int btshell_sec_start(uint16_t conn_handle); +int btshell_sec_pair(uint16_t conn_handle); +int btshell_sec_restart(uint16_t conn_handle, uint8_t *ltk, uint16_t ediv, + uint64_t rand_val, int auth); +int btshell_tx_start(uint16_t handle, uint16_t len, uint16_t rate, + uint16_t num); +int btshell_rssi(uint16_t conn_handle, int8_t *out_rssi); +int btshell_l2cap_create_srv(uint16_t psm, int accept_response); +int btshell_l2cap_connect(uint16_t conn, uint16_t psm); +int btshell_l2cap_disconnect(uint16_t conn, uint16_t idx); +int btshell_l2cap_send(uint16_t conn, uint16_t idx, uint16_t bytes); +#define BTSHELL_LOG_MODULE (LOG_MODULE_PERUSER + 0) +#define BTSHELL_LOG(lvl, ...) \ + LOG_ ## lvl(&btshell_log, BTSHELL_LOG_MODULE, __VA_ARGS__) + +/** GATT server. */ +#define GATT_SVR_SVC_ALERT_UUID 0x1811 +#define GATT_SVR_CHR_SUP_NEW_ALERT_CAT_UUID 0x2A47 +#define GATT_SVR_CHR_NEW_ALERT 0x2A46 +#define GATT_SVR_CHR_SUP_UNR_ALERT_CAT_UUID 0x2A48 +#define GATT_SVR_CHR_UNR_ALERT_STAT_UUID 0x2A45 +#define GATT_SVR_CHR_ALERT_NOT_CTRL_PT 0x2A44 + +void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg); +int gatt_svr_init(void); +void gatt_svr_print_svcs(void); + +/** Misc. */ +void print_bytes(const uint8_t *bytes, int len); +void print_mbuf(const struct os_mbuf *om); +void print_addr(const void *addr); +void print_uuid(const ble_uuid_t *uuid); +int svc_is_empty(const struct btshell_svc *svc); +uint16_t chr_end_handle(const struct btshell_svc *svc, + const struct btshell_chr *chr); +int chr_is_empty(const struct btshell_svc *svc, const struct btshell_chr *chr); +void print_conn_desc(const struct ble_gap_conn_desc *desc); +void print_svc(struct btshell_svc *svc); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/apps/btshell/src/cmd.c b/apps/btshell/src/cmd.c new file mode 100644 index 000000000..123ff2034 --- /dev/null +++ b/apps/btshell/src/cmd.c @@ -0,0 +1,3764 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include "os/mynewt.h" +#include "bsp/bsp.h" + +#include "nimble/ble.h" +#include "nimble/nimble_opt.h" +#include "nimble/hci_common.h" +#include "host/ble_gap.h" +#include "host/ble_hs_adv.h" +#include "host/ble_sm.h" +#include "host/ble_eddystone.h" +#include "host/ble_hs_id.h" +#include "services/gatt/ble_svc_gatt.h" +#include "../src/ble_hs_priv.h" + +#include "console/console.h" +#include "shell/shell.h" + +#include "cmd.h" +#include "btshell.h" +#include "cmd_gatt.h" +#include "cmd_l2cap.h" + +#define BTSHELL_MODULE "btshell" + +int +cmd_parse_conn_start_end(uint16_t *out_conn, uint16_t *out_start, + uint16_t *out_end) +{ + int rc; + + *out_conn = parse_arg_uint16("conn", &rc); + if (rc != 0) { + return rc; + } + + *out_start = parse_arg_uint16("start", &rc); + if (rc != 0) { + return rc; + } + + *out_end = parse_arg_uint16("end", &rc); + if (rc != 0) { + return rc; + } + + return 0; +} + +static const struct kv_pair cmd_own_addr_types[] = { + { "public", BLE_OWN_ADDR_PUBLIC }, + { "random", BLE_OWN_ADDR_RANDOM }, + { "rpa_pub", BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT }, + { "rpa_rnd", BLE_OWN_ADDR_RPA_RANDOM_DEFAULT }, + { NULL } +}; + +static const struct kv_pair cmd_peer_addr_types[] = { + { "public", BLE_ADDR_PUBLIC }, + { "random", BLE_ADDR_RANDOM }, + { "public_id", BLE_ADDR_PUBLIC_ID }, + { "random_id", BLE_ADDR_RANDOM_ID }, + { NULL } +}; + +static const struct kv_pair cmd_addr_type[] = { + { "public", BLE_ADDR_PUBLIC }, + { "random", BLE_ADDR_RANDOM }, + { NULL } +}; + + + +/***************************************************************************** + * $advertise * + *****************************************************************************/ +static const struct kv_pair cmd_adv_filt_types[] = { + { "none", BLE_HCI_ADV_FILT_NONE }, + { "scan", BLE_HCI_ADV_FILT_SCAN }, + { "conn", BLE_HCI_ADV_FILT_CONN }, + { "both", BLE_HCI_ADV_FILT_BOTH }, + { NULL } +}; + +#if MYNEWT_VAL(BLE_EXT_ADV) +static struct kv_pair cmd_ext_adv_phy_opts[] = { + { "1M", 0x01 }, + { "2M", 0x02 }, + { "coded", 0x03 }, + { NULL } +}; + +static bool adv_instances[BLE_ADV_INSTANCES]; + +static int +cmd_advertise_configure(int argc, char **argv) +{ + struct ble_gap_ext_adv_params params; + int8_t selected_tx_power; + uint8_t instance; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + instance = parse_arg_uint8_dflt("instance", 0, &rc); + if (rc != 0 || instance >= BLE_ADV_INSTANCES) { + console_printf("invalid instance\n"); + return rc; + } + + if (adv_instances[instance]) { + console_printf("instance already configured\n"); + return rc; + } + + memset(¶ms, 0, sizeof(params)); + + params.connectable = parse_arg_bool_dflt("connectable", 0, &rc); + if (rc != 0) { + console_printf("invalid 'connectable' parameter\n"); + return rc; + } + + params.scannable = parse_arg_bool_dflt("scannable", 0, &rc); + if (rc != 0) { + console_printf("invalid 'scannable' parameter\n"); + return rc; + } + + params.high_duty_directed = parse_arg_bool_dflt("high_duty", 0, &rc); + if (rc != 0) { + console_printf("invalid 'high_duty' parameter\n"); + return rc; + } + + params.anonymous = parse_arg_bool_dflt("anonymous", 0, &rc); + if (rc != 0) { + console_printf("invalid 'anonymous' parameter\n"); + return rc; + } + + params.legacy_pdu = parse_arg_bool_dflt("legacy", 0, &rc); + if (rc != 0) { + console_printf("invalid 'legacy' parameter\n"); + return rc; + } + + params.include_tx_power = parse_arg_bool_dflt("include_tx_power", 0, &rc); + if (rc != 0) { + console_printf("invalid 'include_tx_power' parameter\n"); + return rc; + } + + params.scan_req_notif = parse_arg_bool_dflt("scan_req_notif", 0, &rc); + if (rc != 0) { + console_printf("invalid 'scan_req_notif' parameter\n"); + return rc; + } + + rc = parse_arg_mac("peer_addr", params.peer.val); + if (rc == 0) { + params.directed = 1; + + params.peer.type = parse_arg_kv_dflt("peer_addr_type", + cmd_peer_addr_types, + BLE_ADDR_PUBLIC, &rc); + if (rc != 0) { + console_printf("invalid 'peer_addr_type' parameter\n"); + return rc; + } + } + else if (rc == ENOENT) { + /* skip, no peer address provided */ + } else { + console_printf("invalid 'peer_addr' parameter\n"); + return rc; + } + + params.own_addr_type = parse_arg_kv_dflt("own_addr_type", + cmd_own_addr_types, + BLE_OWN_ADDR_PUBLIC, &rc); + if (rc != 0) { + console_printf("invalid 'own_addr_type' parameter\n"); + return rc; + } + + params.channel_map = parse_arg_uint8_dflt("channel_map", 0, &rc); + if (rc != 0) { + console_printf("invalid 'channel_map' parameter\n"); + return rc; + } + + params.filter_policy = parse_arg_kv_dflt("filter", cmd_adv_filt_types, + BLE_HCI_ADV_FILT_NONE, &rc); + if (rc != 0) { + console_printf("invalid 'filter' parameter\n"); + return rc; + } + + params.itvl_min = parse_arg_uint32_dflt("interval_min", 0, &rc); + if (rc != 0) { + console_printf("invalid 'interval_min' parameter\n"); + return rc; + } + + params.itvl_max = parse_arg_uint32_dflt("interval_max", 0, &rc); + if (rc != 0) { + console_printf("invalid 'interval_max' parameter\n"); + return rc; + } + + params.tx_power = parse_arg_long_bounds_dflt("tx_power", + -127, 127, 127, &rc); + if (rc != 0) { + console_printf("invalid 'tx_power' parameter\n"); + return rc; + } + + params.primary_phy = parse_arg_kv_dflt("primary_phy", cmd_ext_adv_phy_opts, + 1, &rc); + if (rc != 0) { + console_printf("invalid 'primary_phy' parameter\n"); + return rc; + } + + params.secondary_phy = parse_arg_kv_dflt("secondary_phy", + cmd_ext_adv_phy_opts, + params.primary_phy, &rc); + if (rc != 0) { + console_printf("invalid 'secondary_phy' parameter\n"); + return rc; + } + + params.sid = parse_arg_uint8_dflt("sid", 0, &rc); + if (rc != 0) { + console_printf("invalid 'sid' parameter\n"); + return rc; + } + + rc = btshell_ext_adv_configure(instance, ¶ms, &selected_tx_power); + if (rc) { + console_printf("failed to configure advertising instance\n"); + return rc; + } + + console_printf("Instance %u configured (selected tx power: %d)\n", + instance, selected_tx_power); + + adv_instances[instance] = true; + + return 0; +} + +static int +cmd_advertise_set_addr(int argc, char **argv) +{ + ble_addr_t addr; + uint8_t instance; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + instance = parse_arg_uint8_dflt("instance", 0, &rc); + if (rc != 0 || instance >= BLE_ADV_INSTANCES) { + console_printf("invalid instance\n"); + return rc; + } + + if (!adv_instances[instance]) { + console_printf("instance not configured\n"); + return rc; + } + + rc = parse_arg_mac("addr", addr.val); + if (rc != 0) { + console_printf("invalid 'addr' parameter\n"); + return rc; + } + + addr.type = BLE_ADDR_RANDOM; + + rc = ble_gap_ext_adv_set_addr(instance, &addr); + if (rc) { + console_printf("failed to start advertising instance\n"); + return rc; + } + + return 0; +} + +static int +cmd_advertise_start(int argc, char **argv) +{ + int max_events; + uint8_t instance; + int duration; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + instance = parse_arg_uint8_dflt("instance", 0, &rc); + if (rc != 0 || instance >= BLE_ADV_INSTANCES) { + console_printf("invalid instance\n"); + return rc; + } + + if (!adv_instances[instance]) { + console_printf("instance not configured\n"); + return rc; + } + + duration = parse_arg_uint16_dflt("duration", 0, &rc); + if (rc != 0) { + console_printf("invalid 'duration' parameter\n"); + return rc; + } + + max_events = parse_arg_uint8_dflt("max_events", 0, &rc); + if (rc != 0) { + console_printf("invalid 'max_events' parameter\n"); + return rc; + } + + rc = ble_gap_ext_adv_start(instance, duration, max_events); + if (rc) { + console_printf("failed to start advertising instance\n"); + return rc; + } + + return 0; +} + +static int +cmd_advertise_stop(int argc, char **argv) +{ + uint8_t instance; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + instance = parse_arg_uint8_dflt("instance", 0, &rc); + if (rc != 0 || instance >= BLE_ADV_INSTANCES) { + console_printf("invalid instance\n"); + return rc; + } + + if (!adv_instances[instance]) { + console_printf("instance not configured\n"); + return rc; + } + + rc = ble_gap_ext_adv_stop(instance); + if (rc) { + console_printf("failed to stop advertising instance\n"); + return rc; + } + + return 0; +} + +static int +cmd_advertise_remove(int argc, char **argv) +{ + uint8_t instance; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + instance = parse_arg_uint8_dflt("instance", 0, &rc); + if (rc != 0 || instance >= BLE_ADV_INSTANCES) { + console_printf("invalid instance\n"); + return rc; + } + + if (!adv_instances[instance]) { + console_printf("instance not configured\n"); + return rc; + } + + rc = ble_gap_ext_adv_remove(instance); + if (rc) { + console_printf("failed to remove advertising instance\n"); + return rc; + } + + adv_instances[instance] = false; + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param advertise_configure_params[] = { + {"instance", "default: 0"}, + {"connectable", "connectable advertising, usage: =[0-1], default: 0"}, + {"scannable", "scannable advertising, usage: =[0-1], default: 0"}, + {"peer_addr_type", "usage: =[public|random|public_id|random_id], default: public"}, + {"peer_addr", "usage: =[XX:XX:XX:XX:XX:XX]"}, + {"own_addr_type", "usage: =[public|random|rpa_pub|rpa_rnd], default: public"}, + {"channel_map", "usage: =[0x00-0xff], default: 0"}, + {"filter", "usage: =[none|scan|conn|both], default: none"}, + {"interval_min", "usage: =[0-UINT32_MAX], default: 0"}, + {"interval_max", "usage: =[0-UINT32_MAX], default: 0"}, + {"tx_power", "usage: =[-127-127], default: 127"}, + {"primary_phy", "usage: =[1M|coded], default: 1M"}, + {"secondary_phy", "usage: =[1M|2M|coded], default: primary_phy"}, + {"sid", "usage: =[0-UINT8_MAX], default: 0"}, + {"high_duty", "usage: =[0-1], default: 0"}, + {"anonymous", "enable anonymous advertising, usage: =[0-1], default: 0"}, + {"legacy", "use legacy PDUs, usage: =[0-1], default: 0"}, + {"include_tx_power", "include TX power in PDU, usage: =[0-1], default: 0"}, + {"scan_req_notif", "enable Scan Request notification usage: =[0-1], default: 0"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help advertise_configure_help = { + .summary = "configure new advertising instance", + .usage = NULL, + .params = advertise_configure_params, +}; + +static const struct shell_param advertise_set_addr_params[] = { + {"instance", "default: 0"}, + {"addr", "usage: =[XX:XX:XX:XX:XX:XX]"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help advertise_set_addr_help = { + .summary = "set advertising instance random address", + .usage = NULL, + .params = advertise_set_addr_params, +}; + +static const struct shell_param advertise_start_params[] = { + {"instance", "default: 0"}, + {"duration", "advertising duration in 10ms units, default: 0 (forever)"}, + {"max_events", "max number of advertising events, default: 0 (no limit)"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help advertise_start_help = { + .summary = "start advertising instance", + .usage = NULL, + .params = advertise_start_params, +}; + +static const struct shell_param advertise_stop_params[] = { + {"instance", "default: 0"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help advertise_stop_help = { + .summary = "stop advertising instance", + .usage = NULL, + .params = advertise_stop_params, +}; + +static const struct shell_param advertise_remove_params[] = { + {"instance", "default: 0"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help advertise_remove_help = { + .summary = "remove advertising instance", + .usage = NULL, + .params = advertise_remove_params, +}; +#endif + +#else +static const struct kv_pair cmd_adv_conn_modes[] = { + { "non", BLE_GAP_CONN_MODE_NON }, + { "und", BLE_GAP_CONN_MODE_UND }, + { "dir", BLE_GAP_CONN_MODE_DIR }, + { NULL } +}; + +static const struct kv_pair cmd_adv_disc_modes[] = { + { "non", BLE_GAP_DISC_MODE_NON }, + { "ltd", BLE_GAP_DISC_MODE_LTD }, + { "gen", BLE_GAP_DISC_MODE_GEN }, + { NULL } +}; + +static int +cmd_advertise(int argc, char **argv) +{ + struct ble_gap_adv_params params; + int32_t duration_ms; + ble_addr_t peer_addr; + ble_addr_t *peer_addr_param = &peer_addr; + uint8_t own_addr_type; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + if (argc > 1 && strcmp(argv[1], "stop") == 0) { + rc = btshell_adv_stop(); + if (rc != 0) { + console_printf("advertise stop fail: %d\n", rc); + return rc; + } + + return 0; + } + + params.conn_mode = parse_arg_kv_dflt("conn", cmd_adv_conn_modes, + BLE_GAP_CONN_MODE_UND, &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + params.disc_mode = parse_arg_kv_dflt("discov", cmd_adv_disc_modes, + BLE_GAP_DISC_MODE_GEN, &rc); + if (rc != 0) { + console_printf("invalid 'discov' parameter\n"); + return rc; + } + + peer_addr.type = parse_arg_kv_dflt("peer_addr_type", cmd_peer_addr_types, + BLE_ADDR_PUBLIC, &rc); + if (rc != 0) { + console_printf("invalid 'peer_addr_type' parameter\n"); + return rc; + } + + rc = parse_arg_mac("peer_addr", peer_addr.val); + if (rc == ENOENT) { + peer_addr_param = NULL; + } else if (rc != 0) { + console_printf("invalid 'peer_addr' parameter\n"); + return rc; + } + + own_addr_type = parse_arg_kv_dflt("own_addr_type", cmd_own_addr_types, + BLE_OWN_ADDR_PUBLIC, &rc); + if (rc != 0) { + console_printf("invalid 'own_addr_type' parameter\n"); + return rc; + } + + params.channel_map = parse_arg_uint8_dflt("channel_map", 0, &rc); + if (rc != 0) { + console_printf("invalid 'channel_map' parameter\n"); + return rc; + } + + params.filter_policy = parse_arg_kv_dflt("filter", cmd_adv_filt_types, + BLE_HCI_ADV_FILT_NONE, &rc); + if (rc != 0) { + console_printf("invalid 'filter' parameter\n"); + return rc; + } + + params.itvl_min = parse_arg_uint16_dflt("interval_min", 0, &rc); + if (rc != 0) { + console_printf("invalid 'interval_min' parameter\n"); + return rc; + } + + params.itvl_max = parse_arg_uint16_dflt("interval_max", 0, &rc); + if (rc != 0) { + console_printf("invalid 'interval_max' parameter\n"); + return rc; + } + + params.high_duty_cycle = parse_arg_bool_dflt("high_duty", 0, &rc); + if (rc != 0) { + console_printf("invalid 'high_duty' parameter\n"); + return rc; + } + + duration_ms = parse_arg_long_bounds_dflt("duration", 1, INT32_MAX, + BLE_HS_FOREVER, &rc); + if (rc != 0) { + console_printf("invalid 'duration' parameter\n"); + return rc; + } + + rc = btshell_adv_start(own_addr_type, peer_addr_param, duration_ms, + ¶ms); + if (rc != 0) { + console_printf("advertise fail: %d\n", rc); + return rc; + } + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param advertise_params[] = { + {"stop", "stop advertising procedure"}, + {"conn", "connectable mode, usage: =[non|und|dir], default: und"}, + {"discov", "discoverable mode, usage: =[non|ltd|gen], default: gen"}, + {"peer_addr_type", "usage: =[public|random|public_id|random_id], default: public"}, + {"peer_addr", "usage: =[XX:XX:XX:XX:XX:XX]"}, + {"own_addr_type", "usage: =[public|random|rpa_pub|rpa_rnd], default: public"}, + {"channel_map", "usage: =[0x00-0xff], default: 0"}, + {"filter", "usage: =[none|scan|conn|both], default: none"}, + {"interval_min", "usage: =[0-UINT16_MAX], default: 0"}, + {"interval_max", "usage: =[0-UINT16_MAX], default: 0"}, + {"high_duty", "usage: =[0-1], default: 0"}, + {"duration", "usage: =[1-INT32_MAX], default: INT32_MAX"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help advertise_help = { + .summary = "start/stop advertising with specific parameters", + .usage = NULL, + .params = advertise_params, +}; +#endif +#endif + +/***************************************************************************** + * $connect * + *****************************************************************************/ + +static struct kv_pair cmd_ext_conn_phy_opts[] = { + { "none", 0x00 }, + { "1M", 0x01 }, + { "coded", 0x02 }, + { "both", 0x03 }, + { "all", 0x04 }, + { NULL } +}; + +static int +cmd_connect(int argc, char **argv) +{ + struct ble_gap_conn_params phy_1M_params = {0}; + struct ble_gap_conn_params phy_coded_params = {0}; + struct ble_gap_conn_params phy_2M_params = {0}; + uint8_t ext; + int32_t duration_ms; + ble_addr_t peer_addr; + ble_addr_t *peer_addr_param = &peer_addr; + int own_addr_type; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + if (argc > 1 && strcmp(argv[1], "cancel") == 0) { + rc = btshell_conn_cancel(); + if (rc != 0) { + console_printf("connection cancel fail: %d\n", rc); + return rc; + } + + return 0; + } + + ext = parse_arg_kv_dflt("extended", cmd_ext_conn_phy_opts, 0, &rc); + if (rc != 0) { + console_printf("invalid 'extended' parameter\n"); + return rc; + } + + peer_addr.type = parse_arg_kv_dflt("peer_addr_type", cmd_peer_addr_types, + BLE_ADDR_PUBLIC, &rc); + if (rc != 0) { + console_printf("invalid 'peer_addr_type' parameter\n"); + return rc; + } + + rc = parse_arg_mac("peer_addr", peer_addr.val); + if (rc == ENOENT) { + /* Allow "addr" for backwards compatibility. */ + rc = parse_arg_mac("addr", peer_addr.val); + } + + if (rc == ENOENT) { + /* With no "peer_addr" specified we'll use white list */ + peer_addr_param = NULL; + } else if (rc != 0) { + console_printf("invalid 'peer_addr' parameter\n"); + return rc; + } + + own_addr_type = parse_arg_kv_dflt("own_addr_type", cmd_own_addr_types, + BLE_OWN_ADDR_PUBLIC, &rc); + if (rc != 0) { + console_printf("invalid 'own_addr_type' parameter\n"); + return rc; + } + + duration_ms = parse_arg_long_bounds_dflt("duration", 1, INT32_MAX, 0, &rc); + if (rc != 0) { + console_printf("invalid 'duration' parameter\n"); + return rc; + } + + phy_1M_params.scan_itvl = parse_arg_uint16_dflt("scan_interval", 0x0010, &rc); + if (rc != 0) { + console_printf("invalid 'scan_interval' parameter\n"); + return rc; + } + + phy_1M_params.scan_window = parse_arg_uint16_dflt("scan_window", 0x0010, &rc); + if (rc != 0) { + console_printf("invalid 'scan_window' parameter\n"); + return rc; + } + + phy_1M_params.itvl_min = parse_arg_uint16_dflt("interval_min", + BLE_GAP_INITIAL_CONN_ITVL_MIN, + &rc); + if (rc != 0) { + console_printf("invalid 'interval_min' parameter\n"); + return rc; + } + + phy_1M_params.itvl_max = parse_arg_uint16_dflt("interval_max", + BLE_GAP_INITIAL_CONN_ITVL_MAX, + &rc); + if (rc != 0) { + console_printf("invalid 'interval_max' parameter\n"); + return rc; + } + + phy_1M_params.latency = parse_arg_uint16_dflt("latency", 0, &rc); + if (rc != 0) { + console_printf("invalid 'latency' parameter\n"); + return rc; + } + + phy_1M_params.supervision_timeout = parse_arg_uint16_dflt("timeout", 0x0100, &rc); + if (rc != 0) { + console_printf("invalid 'timeout' parameter\n"); + return rc; + } + + phy_1M_params.min_ce_len = parse_arg_uint16_dflt("min_conn_event_len", + 0x0010, &rc); + if (rc != 0) { + console_printf("invalid 'min_conn_event_len' parameter\n"); + return rc; + } + + phy_1M_params.max_ce_len = parse_arg_uint16_dflt("max_conn_event_len", + 0x0300, &rc); + if (rc != 0) { + console_printf("invalid 'max_conn_event_len' parameter\n"); + return rc; + } + + if (ext == 0x00) { + rc = btshell_conn_initiate(own_addr_type, peer_addr_param, duration_ms, + &phy_1M_params); + console_printf("error connecting; rc=%d\n", rc); + return rc; + } + + if (ext == 0x01) { + rc = btshell_ext_conn_initiate(own_addr_type, peer_addr_param, + duration_ms, &phy_1M_params, + NULL, NULL); + console_printf("error connecting; rc=%d\n", rc); + return rc; + } + + /* Get coded params */ + phy_coded_params.scan_itvl = parse_arg_uint16_dflt("coded_scan_interval", + 0x0010, &rc); + if (rc != 0) { + console_printf("invalid 'coded_scan_interval' parameter\n"); + return rc; + } + + phy_coded_params.scan_window = parse_arg_uint16_dflt("coded_scan_window", + 0x0010, &rc); + if (rc != 0) { + console_printf("invalid 'coded_scan_window' parameter\n"); + return rc; + } + + phy_coded_params.itvl_min = parse_arg_uint16_dflt("coded_interval_min", + BLE_GAP_INITIAL_CONN_ITVL_MIN, + &rc); + if (rc != 0) { + console_printf("invalid 'coded_interval_min' parameter\n"); + return rc; + } + + phy_coded_params.itvl_max = parse_arg_uint16_dflt("coded_interval_max", + BLE_GAP_INITIAL_CONN_ITVL_MAX, + &rc); + if (rc != 0) { + console_printf("invalid 'coded_interval_max' parameter\n"); + return rc; + } + + phy_coded_params.latency = + parse_arg_uint16_dflt("coded_latency", 0, &rc); + if (rc != 0) { + console_printf("invalid 'coded_latency' parameter\n"); + return rc; + } + + phy_coded_params.supervision_timeout = + parse_arg_uint16_dflt("coded_timeout", 0x0100, &rc); + + if (rc != 0) { + console_printf("invalid 'coded_timeout' parameter\n"); + return rc; + } + + phy_coded_params.min_ce_len = + parse_arg_uint16_dflt("coded_min_conn_event", 0x0010, &rc); + if (rc != 0) { + console_printf("invalid 'coded_min_conn_event' parameter\n"); + return rc; + } + + phy_coded_params.max_ce_len = parse_arg_uint16_dflt("coded_max_conn_event", + 0x0300, &rc); + if (rc != 0) { + console_printf("invalid 'coded_max_conn_event' parameter\n"); + return rc; + } + + /* Get 2M params */ + phy_2M_params.itvl_min = parse_arg_uint16_dflt("2M_interval_min", + BLE_GAP_INITIAL_CONN_ITVL_MIN, + &rc); + if (rc != 0) { + console_printf("invalid '2M_interval_min' parameter\n"); + return rc; + } + + phy_2M_params.itvl_max = parse_arg_uint16_dflt("2M_interval_max", + BLE_GAP_INITIAL_CONN_ITVL_MAX, &rc); + if (rc != 0) { + console_printf("invalid '2M_interval_max' parameter\n"); + return rc; + } + + phy_2M_params.latency = + parse_arg_uint16_dflt("2M_latency", 0, &rc); + if (rc != 0) { + console_printf("invalid '2M_latency' parameter\n"); + return rc; + } + + phy_2M_params.supervision_timeout = parse_arg_uint16_dflt("2M_timeout", + 0x0100, &rc); + + if (rc != 0) { + console_printf("invalid '2M_timeout' parameter\n"); + return rc; + } + + phy_2M_params.min_ce_len = parse_arg_uint16_dflt("2M_min_conn_event", 0x0010, + &rc); + if (rc != 0) { + console_printf("invalid '2M_min_conn_event' parameter\n"); + return rc; + } + + phy_2M_params.max_ce_len = parse_arg_uint16_dflt("2M_max_conn_event", + 0x0300, &rc); + if (rc != 0) { + console_printf("invalid '2M_max_conn_event' parameter\n"); + return rc; + } + + if (ext == 0x02) { + rc = btshell_ext_conn_initiate(own_addr_type, peer_addr_param, + duration_ms, NULL, NULL, &phy_coded_params); + return rc; + } + + if (ext == 0x03) { + rc = btshell_ext_conn_initiate(own_addr_type, peer_addr_param, + duration_ms, &phy_1M_params, NULL, + &phy_coded_params); + return rc; + } + + rc = btshell_ext_conn_initiate(own_addr_type, peer_addr_param, + duration_ms, &phy_1M_params, + &phy_2M_params, + &phy_coded_params); + return rc; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param connect_params[] = { + {"cancel", "cancel connection procedure"}, + {"extended", "usage: =[none|1M|coded|both|all], default: none"}, + {"peer_addr_type", "usage: =[public|random|public_id|random_id], default: public"}, + {"peer_addr", "usage: =[XX:XX:XX:XX:XX:XX]"}, + {"own_addr_type", "usage: =[public|random|rpa_pub|rpa_rnd], default: public"}, + {"duration", "usage: =[1-INT32_MAX], default: 0"}, + {"scan_interval", "usage: =[0-UINT16_MAX], default: 0x0010"}, + {"scan_window", "usage: =[0-UINT16_MAX], default: 0x0010"}, + {"interval_min", "usage: =[0-UINT16_MAX], default: 30"}, + {"interval_max", "usage: =[0-UINT16_MAX], default: 50"}, + {"latency", "usage: =[UINT16], default: 0"}, + {"timeout", "usage: =[UINT16], default: 0x0100"}, + {"min_conn_event_len", "usage: =[UINT16], default: 0x0010"}, + {"max_conn_event_len", "usage: =[UINT16], default: 0x0300"}, + {"coded_scan_interval", "usage: =[0-UINT16_MAX], default: 0x0010"}, + {"coded_scan_window", "usage: =[0-UINT16_MAX], default: 0x0010"}, + {"coded_interval_min", "usage: =[0-UINT16_MAX], default: 30"}, + {"coded_interval_max", "usage: =[0-UINT16_MAX], default: 50"}, + {"coded_latency", "usage: =[UINT16], default: 0"}, + {"coded_timeout", "usage: =[UINT16], default: 0x0100"}, + {"coded_min_conn_event_len", "usage: =[UINT16], default: 0x0010"}, + {"coded_max_conn_event_len", "usage: =[UINT16], default: 0x0300"}, + {"2M_interval_min", "usage: =[0-UINT16_MAX], default: 30"}, + {"2M_interval_max", "usage: =[0-UINT16_MAX], default: 50"}, + {"2M_latency", "usage: =[UINT16], default: 0"}, + {"2M_timeout", "usage: =[UINT16], default: 0x0100"}, + {"2M_min_conn_event_len", "usage: =[UINT16], default: 0x0010"}, + {"2M_max_conn_event_len", "usage: =[UINT16], default: 0x0300"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help connect_help = { + .summary = "start/stop connection procedure with specific parameters", + .usage = NULL, + .params = connect_params, +}; +#endif + +/***************************************************************************** + * $disconnect * + *****************************************************************************/ + +static int +cmd_disconnect(int argc, char **argv) +{ + uint16_t conn_handle; + uint8_t reason; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + reason = parse_arg_uint8_dflt("reason", BLE_ERR_REM_USER_CONN_TERM, &rc); + if (rc != 0) { + console_printf("invalid 'reason' parameter\n"); + return rc; + } + + rc = btshell_term_conn(conn_handle, reason); + if (rc != 0) { + console_printf("error terminating connection; rc=%d\n", rc); + return rc; + } + + return 0; +} + +static int +cmd_show_conn(int argc, char **argv) +{ + struct ble_gap_conn_desc conn_desc; + struct btshell_conn *conn; + int rc; + int i; + + for (i = 0; i < btshell_num_conns; i++) { + conn = btshell_conns + i; + + rc = ble_gap_conn_find(conn->handle, &conn_desc); + if (rc == 0) { + print_conn_desc(&conn_desc); + } + } + + return 0; +} + +static int +cmd_show_addr(int argc, char **argv) +{ + uint8_t id_addr[6]; + int rc; + + console_printf("public_id_addr="); + rc = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, id_addr, NULL); + if (rc == 0) { + print_addr(id_addr); + } else { + console_printf("none"); + } + + console_printf(" random_id_addr="); + rc = ble_hs_id_copy_addr(BLE_ADDR_RANDOM, id_addr, NULL); + if (rc == 0) { + print_addr(id_addr); + } else { + console_printf("none"); + } + console_printf("\n"); + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param disconnect_params[] = { + {"conn", "connection handle parameter, usage: ="}, + {"reason", "disconnection reason, usage: =[UINT8], default: 19 (remote user terminated connection)"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help disconnect_help = { + .summary = "disconnect command", + .usage = NULL, + .params = disconnect_params, +}; +#endif + +/***************************************************************************** + * $set-scan-opts * + *****************************************************************************/ + +static struct btshell_scan_opts g_scan_opts = { + .limit = UINT16_MAX, + .ignore_legacy = 0, +}; + +static int +cmd_set_scan_opts(int argc, char **argv) +{ + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + g_scan_opts.limit = parse_arg_uint16_dflt("decode_limit", UINT16_MAX, &rc); + if (rc != 0) { + console_printf("invalid 'decode_limit' parameter\n"); + return rc; + } + + g_scan_opts.ignore_legacy = parse_arg_bool_dflt("ignore_legacy", 0, &rc); + if (rc != 0) { + console_printf("invalid 'ignore_legacy' parameter\n"); + return rc; + } + + return rc; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param set_scan_opts_params[] = { + {"decode_limit", "usage: =[0-UINT16_MAX], default: UINT16_MAX"}, + {"ignore_legacy", "usage: =[0-1], default: 0"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help set_scan_opts_help = { + .summary = "set scan options", + .usage = NULL, + .params = set_scan_opts_params, +}; +#endif + +/***************************************************************************** + * $scan * + *****************************************************************************/ + +static const struct kv_pair cmd_scan_filt_policies[] = { + { "no_wl", BLE_HCI_SCAN_FILT_NO_WL }, + { "use_wl", BLE_HCI_SCAN_FILT_USE_WL }, + { "no_wl_inita", BLE_HCI_SCAN_FILT_NO_WL_INITA }, + { "use_wl_inita", BLE_HCI_SCAN_FILT_USE_WL_INITA }, + { NULL } +}; + +static struct kv_pair cmd_scan_ext_types[] = { + { "none", 0x00 }, + { "1M", 0x01 }, + { "coded", 0x02 }, + { "both", 0x03 }, + { NULL } +}; + +static struct btshell_scan_opts g_scan_opts; + +static int +cmd_scan(int argc, char **argv) +{ + struct ble_gap_disc_params params = {0}; + struct ble_gap_ext_disc_params uncoded = {0}; + struct ble_gap_ext_disc_params coded = {0}; + uint8_t extended; + int32_t duration_ms; + uint8_t own_addr_type; + uint16_t duration; + uint16_t period; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + if (argc > 1 && strcmp(argv[1], "cancel") == 0) { + rc = btshell_scan_cancel(); + if (rc != 0) { + console_printf("scan cancel fail: %d\n", rc); + return rc; + } + return 0; + } + + extended = parse_arg_kv_dflt("extended", cmd_scan_ext_types, 0, &rc); + if (rc != 0) { + console_printf("invalid 'extended' parameter\n"); + return rc; + } + + duration_ms = parse_arg_long_bounds_dflt("duration", 1, INT32_MAX, + BLE_HS_FOREVER, &rc); + if (rc != 0) { + console_printf("invalid 'duration' parameter\n"); + return rc; + } + + params.limited = parse_arg_bool_dflt("limited", 0, &rc); + if (rc != 0) { + console_printf("invalid 'limited' parameter\n"); + return rc; + } + + params.passive = parse_arg_bool_dflt("passive", 0, &rc); + if (rc != 0) { + console_printf("invalid 'passive' parameter\n"); + return rc; + } + + params.itvl = parse_arg_uint16_dflt("interval", 0, &rc); + if (rc != 0) { + console_printf("invalid 'interval' parameter\n"); + return rc; + } + + params.window = parse_arg_uint16_dflt("window", 0, &rc); + if (rc != 0) { + console_printf("invalid 'window' parameter\n"); + return rc; + } + + params.filter_policy = parse_arg_kv_dflt("filter", cmd_scan_filt_policies, + BLE_HCI_SCAN_FILT_NO_WL, &rc); + if (rc != 0) { + console_printf("invalid 'filter' parameter\n"); + return rc; + } + + params.filter_duplicates = parse_arg_bool_dflt("nodups", 0, &rc); + if (rc != 0) { + console_printf("invalid 'nodups' parameter\n"); + return rc; + } + + own_addr_type = parse_arg_kv_dflt("own_addr_type", cmd_own_addr_types, + BLE_OWN_ADDR_PUBLIC, &rc); + if (rc != 0) { + console_printf("invalid 'own_addr_type' parameter\n"); + return rc; + } + + if (extended == 0) { + rc = btshell_scan(own_addr_type, duration_ms, ¶ms, &g_scan_opts); + if (rc != 0) { + console_printf("error scanning; rc=%d\n", rc); + return rc; + } + + return 0; + } + + /* Copy above parameters to uncoded params */ + uncoded.passive = params.passive; + uncoded.itvl = params.itvl; + uncoded.window = params.window; + + duration = parse_arg_uint16_dflt("extended_duration", 0, &rc); + if (rc != 0) { + console_printf("invalid 'extended_duration' parameter\n"); + return rc; + } + + period = parse_arg_uint16_dflt("extended_period", 0, &rc); + if (rc != 0) { + console_printf("invalid 'extended_period' parameter\n"); + return rc; + } + + coded.itvl = parse_arg_uint16_dflt("longrange_interval", 0, &rc); + if (rc != 0) { + console_printf("invalid 'longrange_interval' parameter\n"); + return rc; + } + + coded.window = parse_arg_uint16_dflt("longrange_window", 0, &rc); + if (rc != 0) { + console_printf("invalid 'longrange_window' parameter\n"); + return rc; + } + + coded.passive = parse_arg_uint16_dflt("longrange_passive", 0, &rc); + if (rc != 0) { + console_printf("invalid 'longrange_passive' parameter\n"); + return rc; + } + + switch (extended) { + case 0x01: + rc = btshell_ext_scan(own_addr_type, duration, period, + params.filter_duplicates, params.filter_policy, + params.limited, &uncoded, NULL, + &g_scan_opts); + break; + case 0x02: + rc = btshell_ext_scan(own_addr_type, duration, period, + params.filter_duplicates, params.filter_policy, + params.limited, NULL, &coded, + &g_scan_opts); + break; + case 0x03: + rc = btshell_ext_scan(own_addr_type, duration, period, + params.filter_duplicates, params.filter_policy, + params.limited, &uncoded, &coded, + &g_scan_opts); + break; + default: + rc = -1; + console_printf("invalid 'extended' parameter\n"); + break; + } + + return rc; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param scan_params[] = { + {"cancel", "cancel scan procedure"}, + {"extended", "usage: =[none|1M|coded|both], default: none"}, + {"duration", "usage: =[1-INT32_MAX], default: INT32_MAX"}, + {"limited", "usage: =[0-1], default: 0"}, + {"passive", "usage: =[0-1], default: 0"}, + {"interval", "usage: =[0-UINT16_MAX], default: 0"}, + {"window", "usage: =[0-UINT16_MAX], default: 0"}, + {"filter", "usage: =[no_wl|use_wl|no_wl_inita|use_wl_inita], default: no_wl"}, + {"nodups", "usage: =[0-1], default: 0"}, + {"own_addr_type", "usage: =[public|random|rpa_pub|rpa_rnd], default: public"}, + {"extended_duration", "usage: =[0-UINT16_MAX], default: 0"}, + {"extended_period", "usage: =[0-UINT16_MAX], default: 0"}, + {"longrange_interval", "usage: =[0-UINT16_MAX], default: 0"}, + {"longrange_window", "usage: =[0-UINT16_MAX], default: 0"}, + {"longrange_passive", "usage: =[0-1], default: 0"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help scan_help = { + .summary = "start/stop scan procedure with specific parameters", + .usage = NULL, + .params = scan_params, +}; +#endif + +/***************************************************************************** + * $set * + *****************************************************************************/ + +static const struct kv_pair cmd_set_addr_types[] = { + { "public", BLE_ADDR_PUBLIC }, + { "random", BLE_ADDR_RANDOM }, + { NULL } +}; + +static int +cmd_set_addr(void) +{ + uint8_t addr[6]; + int addr_type; + int rc; + + addr_type = parse_arg_kv_dflt("addr_type", cmd_set_addr_types, + BLE_ADDR_PUBLIC, &rc); + if (rc != 0) { + console_printf("invalid 'addr_type' parameter\n"); + return rc; + } + + rc = parse_arg_mac("addr", addr); + if (rc != 0) { + console_printf("invalid 'addr' parameter\n"); + return rc; + } + + switch (addr_type) { +#if MYNEWT_VAL(BLE_DEVICE) + case BLE_ADDR_PUBLIC: + /* We shouldn't be writing to the controller's address (g_dev_addr). + * There is no standard way to set the local public address, so this is + * our only option at the moment. + */ + memcpy(g_dev_addr, addr, 6); + ble_hs_id_set_pub(g_dev_addr); + break; +#endif + + case BLE_ADDR_RANDOM: + rc = ble_hs_id_set_rnd(addr); + if (rc != 0) { + return rc; + } + break; + + default: + return BLE_HS_EUNKNOWN; + } + + return 0; +} + +static int +cmd_set(int argc, char **argv) +{ + uint16_t mtu; + uint8_t irk[16]; + int good = 0; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + rc = parse_arg_find_idx("addr"); + if (rc != -1) { + rc = cmd_set_addr(); + if (rc != 0) { + return rc; + } + good = 1; + } + + mtu = parse_arg_uint16("mtu", &rc); + if (rc == 0) { + rc = ble_att_set_preferred_mtu(mtu); + if (rc == 0) { + good = 1; + } + } else if (rc != ENOENT) { + console_printf("invalid 'mtu' parameter\n"); + return rc; + } + + rc = parse_arg_byte_stream_exact_length("irk", irk, 16); + if (rc == 0) { + good = 1; + ble_hs_pvcy_set_our_irk(irk); + } else if (rc != ENOENT) { + console_printf("invalid 'irk' parameter\n"); + return rc; + } + + if (!good) { + console_printf("Error: no valid settings specified\n"); + return -1; + } + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param set_params[] = { + {"addr", "set device address, usage: =[XX:XX:XX:XX:XX:XX]"}, + {"addr_type", "set device address type, usage: =[public|random], default: public"}, + {"mtu", "Maximum Transimssion Unit, usage: =[0-UINT16_MAX]"}, + {"irk", "Identity Resolving Key, usage: =[XX:XX...], len=16 octets"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help set_help = { + .summary = "set device parameters", + .usage = NULL, + .params = set_params, +}; +#endif + +/***************************************************************************** + * $set-adv-data * + *****************************************************************************/ + +#define CMD_ADV_DATA_MAX_UUIDS16 8 +#define CMD_ADV_DATA_MAX_UUIDS32 8 +#define CMD_ADV_DATA_MAX_UUIDS128 2 +#define CMD_ADV_DATA_MAX_PUBLIC_TGT_ADDRS 8 +#define CMD_ADV_DATA_SVC_DATA_UUID16_MAX_LEN BLE_HS_ADV_MAX_FIELD_SZ +#define CMD_ADV_DATA_SVC_DATA_UUID32_MAX_LEN BLE_HS_ADV_MAX_FIELD_SZ +#define CMD_ADV_DATA_SVC_DATA_UUID128_MAX_LEN BLE_HS_ADV_MAX_FIELD_SZ +#define CMD_ADV_DATA_URI_MAX_LEN BLE_HS_ADV_MAX_FIELD_SZ +#define CMD_ADV_DATA_MFG_DATA_MAX_LEN BLE_HS_ADV_MAX_FIELD_SZ + +#if MYNEWT_VAL(BLE_EXT_ADV) +static void +update_pattern(uint8_t *buf, int counter) +{ + int i; + + for (i = 0; i < 10; i += 2) { + counter += 2; + buf[i] = (counter / 1000) << 4 | (counter / 100 % 10); + buf[i + 1] = (counter / 10 % 10) << 4 | (counter % 10); + } +} +#endif + +static int +cmd_set_adv_data_or_scan_rsp(int argc, char **argv, bool scan_rsp) +{ + static bssnz_t ble_uuid16_t uuids16[CMD_ADV_DATA_MAX_UUIDS16]; + static bssnz_t ble_uuid32_t uuids32[CMD_ADV_DATA_MAX_UUIDS32]; + static bssnz_t ble_uuid128_t uuids128[CMD_ADV_DATA_MAX_UUIDS128]; + static bssnz_t uint8_t + public_tgt_addrs[CMD_ADV_DATA_MAX_PUBLIC_TGT_ADDRS] + [BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN]; + static bssnz_t uint8_t slave_itvl_range[BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN]; + static bssnz_t uint8_t + svc_data_uuid16[CMD_ADV_DATA_SVC_DATA_UUID16_MAX_LEN]; + static bssnz_t uint8_t + svc_data_uuid32[CMD_ADV_DATA_SVC_DATA_UUID32_MAX_LEN]; + static bssnz_t uint8_t + svc_data_uuid128[CMD_ADV_DATA_SVC_DATA_UUID128_MAX_LEN]; + static bssnz_t uint8_t uri[CMD_ADV_DATA_URI_MAX_LEN]; + static bssnz_t uint8_t mfg_data[CMD_ADV_DATA_MFG_DATA_MAX_LEN]; + struct ble_hs_adv_fields adv_fields; + uint32_t uuid32; + uint16_t uuid16; + uint8_t uuid128[16]; + uint8_t public_tgt_addr[BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN]; + uint8_t eddystone_url_body_len; + uint8_t eddystone_url_suffix; + uint8_t eddystone_url_scheme; + char eddystone_url_body[BLE_EDDYSTONE_URL_MAX_LEN]; + char *eddystone_url_full; + int svc_data_uuid16_len; + int svc_data_uuid32_len; + int svc_data_uuid128_len; + int uri_len; + int mfg_data_len; + int tmp; + int rc; +#if MYNEWT_VAL(BLE_EXT_ADV) + uint8_t instance; + uint8_t extra_data[10]; + uint16_t counter; + uint16_t extra_data_len; + struct os_mbuf *adv_data; +#endif + + memset(&adv_fields, 0, sizeof adv_fields); + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + +#if MYNEWT_VAL(BLE_EXT_ADV) + instance = parse_arg_uint8_dflt("instance", 0, &rc); + if (rc != 0 || instance >= BLE_ADV_INSTANCES) { + console_printf("invalid instance\n"); + return rc; + } + + if (!adv_instances[instance]) { + console_printf("instance not configured\n"); + return rc; + } +#endif + + tmp = parse_arg_uint8("flags", &rc); + if (rc == 0) { + adv_fields.flags = tmp; + } else if (rc != ENOENT) { + console_printf("invalid 'flags' parameter\n"); + return rc; + } + + while (1) { + uuid16 = parse_arg_uint16("uuid16", &rc); + if (rc == 0) { + if (adv_fields.num_uuids16 >= CMD_ADV_DATA_MAX_UUIDS16) { + console_printf("invalid 'uuid16' parameter\n"); + return EINVAL; + } + uuids16[adv_fields.num_uuids16] = (ble_uuid16_t) BLE_UUID16_INIT(uuid16); + adv_fields.num_uuids16++; + } else if (rc == ENOENT) { + break; + } else { + console_printf("invalid 'uuid16' parameter\n"); + return rc; + } + } + if (adv_fields.num_uuids16 > 0) { + adv_fields.uuids16 = uuids16; + } + + tmp = parse_arg_bool_dflt("uuids16_is_complete", 0, &rc); + if (rc != 0) { + console_printf("invalid 'uuids16_is_complete' parameter\n"); + return rc; + } + + while (1) { + uuid32 = parse_arg_uint32("uuid32", &rc); + if (rc == 0) { + if (adv_fields.num_uuids32 >= CMD_ADV_DATA_MAX_UUIDS32) { + console_printf("invalid 'uuid32' parameter\n"); + return EINVAL; + } + uuids32[adv_fields.num_uuids32] = (ble_uuid32_t) BLE_UUID32_INIT(uuid32); + adv_fields.num_uuids32++; + } else if (rc == ENOENT) { + break; + } else { + console_printf("invalid 'uuid32' parameter\n"); + return rc; + } + } + if (adv_fields.num_uuids32 > 0) { + adv_fields.uuids32 = uuids32; + } + + tmp = parse_arg_bool_dflt("uuids32_is_complete", 0, &rc); + if (rc != 0) { + console_printf("invalid 'uuids32_is_complete' parameter\n"); + return rc; + } + + while (1) { + rc = parse_arg_byte_stream_exact_length("uuid128", uuid128, 16); + if (rc == 0) { + if (adv_fields.num_uuids128 >= CMD_ADV_DATA_MAX_UUIDS128) { + console_printf("invalid 'uuid128' parameter\n"); + return EINVAL; + } + ble_uuid_init_from_buf((ble_uuid_any_t *) &uuids128[adv_fields.num_uuids128], + uuid128, 16); + adv_fields.num_uuids128++; + } else if (rc == ENOENT) { + break; + } else { + console_printf("invalid 'uuid128' parameter\n"); + return rc; + } + } + if (adv_fields.num_uuids128 > 0) { + adv_fields.uuids128 = uuids128; + } + + tmp = parse_arg_bool_dflt("uuids128_is_complete", 0, &rc); + if (rc != 0) { + console_printf("invalid 'uuids128_is_complete' parameter\n"); + return rc; + } + + adv_fields.name = (uint8_t *)parse_arg_extract("name"); + if (adv_fields.name != NULL) { + adv_fields.name_len = strlen((char *)adv_fields.name); + } + + tmp = parse_arg_long_bounds("tx_power_level", INT8_MIN, INT8_MAX, &rc); + if (rc == 0) { + adv_fields.tx_pwr_lvl = tmp; + adv_fields.tx_pwr_lvl_is_present = 1; + } else if (rc != ENOENT) { + console_printf("invalid 'tx_power_level' parameter\n"); + return rc; + } + + rc = parse_arg_byte_stream_exact_length("slave_interval_range", + slave_itvl_range, + BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN); + if (rc == 0) { + adv_fields.slave_itvl_range = slave_itvl_range; + } else if (rc != ENOENT) { + console_printf("invalid 'slave_interval_range' parameter\n"); + return rc; + } + + rc = parse_arg_byte_stream("service_data_uuid16", + CMD_ADV_DATA_SVC_DATA_UUID16_MAX_LEN, + svc_data_uuid16, &svc_data_uuid16_len); + if (rc == 0) { + adv_fields.svc_data_uuid16 = svc_data_uuid16; + adv_fields.svc_data_uuid16_len = svc_data_uuid16_len; + } else if (rc != ENOENT) { + console_printf("invalid 'service_data_uuid16' parameter\n"); + return rc; + } + + while (1) { + rc = parse_arg_byte_stream_exact_length( + "public_target_address", public_tgt_addr, + BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN); + if (rc == 0) { + if (adv_fields.num_public_tgt_addrs >= + CMD_ADV_DATA_MAX_PUBLIC_TGT_ADDRS) { + + console_printf("invalid 'public_target_address' parameter\n"); + return EINVAL; + } + memcpy(public_tgt_addrs[adv_fields.num_public_tgt_addrs], + public_tgt_addr, BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN); + adv_fields.num_public_tgt_addrs++; + } else if (rc == ENOENT) { + break; + } else { + console_printf("invalid 'public_target_address' parameter\n"); + return rc; + } + } + if (adv_fields.num_public_tgt_addrs > 0) { + adv_fields.public_tgt_addr = (void *)public_tgt_addrs; + } + + adv_fields.appearance = parse_arg_uint16("appearance", &rc); + if (rc == 0) { + adv_fields.appearance_is_present = 1; + } else if (rc != ENOENT) { + console_printf("invalid 'appearance' parameter\n"); + return rc; + } + + adv_fields.adv_itvl = parse_arg_uint16("advertising_interval", &rc); + if (rc == 0) { + adv_fields.adv_itvl_is_present = 1; + } else if (rc != ENOENT) { + console_printf("invalid 'advertising_interval' parameter\n"); + return rc; + } + + rc = parse_arg_byte_stream("service_data_uuid32", + CMD_ADV_DATA_SVC_DATA_UUID32_MAX_LEN, + svc_data_uuid32, &svc_data_uuid32_len); + if (rc == 0) { + adv_fields.svc_data_uuid32 = svc_data_uuid32; + adv_fields.svc_data_uuid32_len = svc_data_uuid32_len; + } else if (rc != ENOENT) { + console_printf("invalid 'service_data_uuid32' parameter\n"); + return rc; + } + + rc = parse_arg_byte_stream("service_data_uuid128", + CMD_ADV_DATA_SVC_DATA_UUID128_MAX_LEN, + svc_data_uuid128, &svc_data_uuid128_len); + if (rc == 0) { + adv_fields.svc_data_uuid128 = svc_data_uuid128; + adv_fields.svc_data_uuid128_len = svc_data_uuid128_len; + } else if (rc != ENOENT) { + console_printf("invalid 'service_data_uuid128' parameter\n"); + return rc; + } + + rc = parse_arg_byte_stream("uri", CMD_ADV_DATA_URI_MAX_LEN, uri, &uri_len); + if (rc == 0) { + adv_fields.uri = uri; + adv_fields.uri_len = uri_len; + } else if (rc != ENOENT) { + console_printf("invalid 'uri' parameter\n"); + return rc; + } + + rc = parse_arg_byte_stream("mfg_data", CMD_ADV_DATA_MFG_DATA_MAX_LEN, + mfg_data, &mfg_data_len); + if (rc == 0) { + adv_fields.mfg_data = mfg_data; + adv_fields.mfg_data_len = mfg_data_len; + } else if (rc != ENOENT) { + console_printf("invalid 'mfg_data' parameter\n"); + return rc; + } + + eddystone_url_full = parse_arg_extract("eddystone_url"); + if (eddystone_url_full != NULL) { + rc = parse_eddystone_url(eddystone_url_full, &eddystone_url_scheme, + eddystone_url_body, + &eddystone_url_body_len, + &eddystone_url_suffix); + if (rc != 0) { + goto done; + } + + rc = ble_eddystone_set_adv_data_url(&adv_fields, eddystone_url_scheme, + eddystone_url_body, + eddystone_url_body_len, + eddystone_url_suffix); + } else { +#if MYNEWT_VAL(BLE_EXT_ADV) + /* Default to legacy PDUs size, mbuf chain will be increased if needed + */ + adv_data = os_msys_get_pkthdr(BLE_HCI_MAX_ADV_DATA_LEN, 0); + if (!adv_data) { + rc = ENOMEM; + goto done; + } + + rc = ble_hs_adv_set_fields_mbuf(&adv_fields, adv_data); + if (rc) { + goto done; + } + + /* Append some extra data, if requested */ + extra_data_len = parse_arg_uint16("extra_data_len", &rc); + if (rc == 0) { + counter = 0; + extra_data_len = min(extra_data_len, 1650); + while (counter < extra_data_len) { + update_pattern(extra_data, counter); + + os_mbuf_append(adv_data, extra_data, + min(extra_data_len - counter, 10)); + + counter += 10; + } + } + + if (scan_rsp) { + rc = ble_gap_ext_adv_rsp_set_data(instance, adv_data); + } else { + rc = ble_gap_ext_adv_set_data(instance, adv_data); + } +#else + if (scan_rsp) { + rc = ble_gap_adv_rsp_set_fields(&adv_fields); + } else { + rc = ble_gap_adv_set_fields(&adv_fields); + } +#endif + } +done: + if (rc != 0) { + console_printf("error setting advertisement data; rc=%d\n", rc); + return rc; + } + + return 0; +} + +static int +cmd_set_adv_data(int argc, char **argv) +{ + return cmd_set_adv_data_or_scan_rsp(argc, argv, false); +} + +static int +cmd_set_scan_rsp(int argc, char **argv) +{ + return cmd_set_adv_data_or_scan_rsp(argc, argv, true); +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param set_adv_data_params[] = { + {"instance", "default: 0"}, + {"flags", "usage: =[0-UINT8_MAX]"}, + {"uuid16", "usage: =[UINT16]"}, + {"uuid16_is_complete", "usage: =[0-1], default=0"}, + {"uuid32", "usage: =[UINT32]"}, + {"uuid32_is_complete", "usage: =[0-1], default=0"}, + {"uuid128", "usage: =[XX:XX...], len=16 octets"}, + {"uuid128_is_complete", "usage: =[0-1], default=0"}, + {"tx_power_level", "usage: =[INT8_MIN-INT8_MAX]"}, + {"slave_interval_range", "usage: =[XX:XX:XX:XX]"}, + {"public_target_address", "usage: =[XX:XX:XX:XX:XX:XX]"}, + {"appearance", "usage: =[UINT16]"}, + {"name", "usage: =[string]"}, + {"advertising_interval", "usage: =[UINT16]"}, + {"service_data_uuid16", "usage: =[XX:XX...]"}, + {"service_data_uuid32", "usage: =[XX:XX...]"}, + {"service_data_uuid128", "usage: =[XX:XX...]"}, + {"uri", "usage: =[XX:XX...]"}, + {"mfg_data", "usage: =[XX:XX...]"}, + {"eddystone_url", "usage: =[string]"}, +#if MYNEWT_VAL(BLE_EXT_ADV) + {"extra_data_len", "usage: =[UINT16]"}, +#endif + {NULL, NULL} +}; + +static const struct shell_cmd_help set_adv_data_help = { + .summary = "set advertising data", + .usage = NULL, + .params = set_adv_data_params, +}; + +static const struct shell_cmd_help set_scan_rsp_help = { + .summary = "set scan response", + .usage = NULL, + .params = set_adv_data_params, +}; +#endif + +/***************************************************************************** + * $set-priv-mode * + *****************************************************************************/ + +static int +cmd_set_priv_mode(int argc, char **argv) +{ + ble_addr_t addr; + uint8_t priv_mode; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + addr.type = parse_arg_kv_dflt("addr_type", cmd_set_addr_types, + BLE_ADDR_PUBLIC, &rc); + if (rc != 0) { + console_printf("invalid 'addr_type' parameter\n"); + return rc; + } + + rc = parse_arg_mac("addr", addr.val); + if (rc != 0) { + console_printf("invalid 'addr' parameter\n"); + return rc; + } + + priv_mode = parse_arg_uint8("mode", &rc); + if (rc != 0) { + console_printf("missing mode\n"); + return rc; + } + + return ble_gap_set_priv_mode(&addr, priv_mode); +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param set_priv_mode_params[] = { + {"addr", "set priv mode for device address, usage: =[XX:XX:XX:XX:XX:XX]"}, + {"addr_type", "set priv mode for device address type, usage: =[public|random], default: public"}, + {"mode", "set priv mode, usage: =[0-UINT8_MAX]"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help set_priv_mode_help = { + .summary = "set priv mode", + .usage = NULL, + .params = set_priv_mode_params, +}; +#endif + +/***************************************************************************** + * $white-list * + *****************************************************************************/ + +#define CMD_WL_MAX_SZ 8 + +static int +cmd_white_list(int argc, char **argv) +{ + static ble_addr_t addrs[CMD_WL_MAX_SZ]; + int addrs_cnt; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + addrs_cnt = 0; + while (1) { + if (addrs_cnt >= CMD_WL_MAX_SZ) { + return EINVAL; + } + + rc = parse_arg_mac("addr", addrs[addrs_cnt].val); + if (rc == ENOENT) { + break; + } else if (rc != 0) { + console_printf("invalid 'addr' parameter\n"); + return rc; + } + + addrs[addrs_cnt].type = parse_arg_kv("addr_type", cmd_addr_type, &rc); + if (rc != 0) { + console_printf("invalid 'addr' parameter\n"); + return rc; + } + + addrs_cnt++; + } + + if (addrs_cnt == 0) { + return EINVAL; + } + + btshell_wl_set(addrs, addrs_cnt); + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param white_list_params[] = { + {"addr", "white-list device addresses, usage: =[XX:XX:XX:XX:XX:XX]"}, + {"addr_type", "white-list address types, usage: =[public|random]"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help white_list_help = { + .summary = "set white-list addresses", + .usage = NULL, + .params = white_list_params, +}; +#endif + +/***************************************************************************** + * $conn-rssi * + *****************************************************************************/ + +static int +cmd_conn_rssi(int argc, char **argv) +{ + uint16_t conn_handle; + int8_t rssi; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + rc = btshell_rssi(conn_handle, &rssi); + if (rc != 0) { + console_printf("error reading rssi; rc=%d\n", rc); + return rc; + } + + console_printf("conn=%d rssi=%d\n", conn_handle, rssi); + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param conn_rssi_params[] = { + {"conn", "connection handle parameter, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help conn_rssi_help = { + .summary = "check connection rssi", + .usage = NULL, + .params = conn_rssi_params, +}; +#endif + +/***************************************************************************** + * $conn-update-params * + *****************************************************************************/ + +static int +cmd_conn_update_params(int argc, char **argv) +{ + struct ble_gap_upd_params params; + uint16_t conn_handle; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + params.itvl_min = parse_arg_uint16_dflt("interval_min", + BLE_GAP_INITIAL_CONN_ITVL_MIN, + &rc); + if (rc != 0) { + console_printf("invalid 'interval_min' parameter\n"); + return rc; + } + + params.itvl_max = parse_arg_uint16_dflt("interval_max", + BLE_GAP_INITIAL_CONN_ITVL_MAX, + &rc); + if (rc != 0) { + console_printf("invalid 'interval_max' parameter\n"); + return rc; + } + + params.latency = parse_arg_uint16_dflt("latency", 0, &rc); + if (rc != 0) { + console_printf("invalid 'latency' parameter\n"); + return rc; + } + + params.supervision_timeout = parse_arg_uint16_dflt("timeout", 0x0100, &rc); + if (rc != 0) { + console_printf("invalid 'timeout' parameter\n"); + return rc; + } + + params.min_ce_len = parse_arg_uint16_dflt("min_conn_event_len", + 0x0010, &rc); + if (rc != 0) { + console_printf("invalid 'min_conn_event_len' parameter\n"); + return rc; + } + + params.max_ce_len = parse_arg_uint16_dflt("max_conn_event_len", + 0x0300, &rc); + if (rc != 0) { + console_printf("invalid 'max_conn_event_len' parameter\n"); + return rc; + } + + rc = btshell_update_conn(conn_handle, ¶ms); + if (rc != 0) { + console_printf("error updating connection; rc=%d\n", rc); + return rc; + } + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param conn_update_params_params[] = { + {"conn", "conn_update_paramsion handle, usage: ="}, + {"interval_min", "usage: =[0-UINT16_MAX], default: 30"}, + {"interval_max", "usage: =[0-UINT16_MAX], default: 50"}, + {"latency", "usage: =[UINT16], default: 0"}, + {"timeout", "usage: =[UINT16], default: 0x0100"}, + {"min_conn_event_len", "usage: =[UINT16], default: 0x0010"}, + {"max_conn_event_len", "usage: =[UINT16], default: 0x0300"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help conn_update_params_help = { + .summary = "update connection parameters", + .usage = "conn_update_params usage", + .params = conn_update_params_params, +}; +#endif + +/***************************************************************************** + * $conn-datalen * + *****************************************************************************/ + +static int +cmd_conn_datalen(int argc, char **argv) +{ + uint16_t conn_handle; + uint16_t tx_octets; + uint16_t tx_time; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + tx_octets = parse_arg_uint16("octets", &rc); + if (rc != 0) { + console_printf("invalid 'octets' parameter\n"); + return rc; + } + + tx_time = parse_arg_uint16("time", &rc); + if (rc != 0) { + console_printf("invalid 'time' parameter\n"); + return rc; + } + + rc = btshell_datalen(conn_handle, tx_octets, tx_time); + if (rc != 0) { + console_printf("error setting data length; rc=%d\n", rc); + return rc; + } + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param conn_datalen_params[] = { + {"conn", "conn_datalen handle, usage: ="}, + {"octets", "usage: ="}, + {"time", "usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help conn_datalen_help = { + .summary = "set data length parameters for connection", + .usage = NULL, + .params = conn_datalen_params, +}; +#endif + +/***************************************************************************** + * keystore * + *****************************************************************************/ + +static const struct kv_pair cmd_keystore_entry_type[] = { + { "msec", BLE_STORE_OBJ_TYPE_PEER_SEC }, + { "ssec", BLE_STORE_OBJ_TYPE_OUR_SEC }, + { "cccd", BLE_STORE_OBJ_TYPE_CCCD }, + { NULL } +}; + +static int +cmd_keystore_parse_keydata(int argc, char **argv, union ble_store_key *out, + int *obj_type) +{ + int rc; + + memset(out, 0, sizeof(*out)); + *obj_type = parse_arg_kv("type", cmd_keystore_entry_type, &rc); + if (rc != 0) { + console_printf("invalid 'type' parameter\n"); + return rc; + } + + switch (*obj_type) { + case BLE_STORE_OBJ_TYPE_PEER_SEC: + case BLE_STORE_OBJ_TYPE_OUR_SEC: + out->sec.peer_addr.type = parse_arg_kv("addr_type", + cmd_addr_type, &rc); + if (rc != 0) { + console_printf("invalid 'addr_type' parameter\n"); + return rc; + } + + rc = parse_arg_mac("addr", out->sec.peer_addr.val); + if (rc != 0) { + console_printf("invalid 'addr' parameter\n"); + return rc; + } + + out->sec.ediv = parse_arg_uint16("ediv", &rc); + if (rc != 0) { + console_printf("invalid 'ediv' parameter\n"); + return rc; + } + + out->sec.rand_num = parse_arg_uint64("rand", &rc); + if (rc != 0) { + console_printf("invalid 'rand' parameter\n"); + return rc; + } + return 0; + + default: + return EINVAL; + } +} + +static int +cmd_keystore_parse_valuedata(int argc, char **argv, + int obj_type, + union ble_store_key *key, + union ble_store_value *out) +{ + int rc; + int valcnt = 0; + memset(out, 0, sizeof(*out)); + + switch (obj_type) { + case BLE_STORE_OBJ_TYPE_PEER_SEC: + case BLE_STORE_OBJ_TYPE_OUR_SEC: + rc = parse_arg_byte_stream_exact_length("ltk", out->sec.ltk, 16); + if (rc == 0) { + out->sec.ltk_present = 1; + swap_in_place(out->sec.ltk, 16); + valcnt++; + } else if (rc != ENOENT) { + console_printf("invalid 'ltk' parameter\n"); + return rc; + } + rc = parse_arg_byte_stream_exact_length("irk", out->sec.irk, 16); + if (rc == 0) { + out->sec.irk_present = 1; + swap_in_place(out->sec.irk, 16); + valcnt++; + } else if (rc != ENOENT) { + console_printf("invalid 'irk' parameter\n"); + return rc; + } + rc = parse_arg_byte_stream_exact_length("csrk", out->sec.csrk, 16); + if (rc == 0) { + out->sec.csrk_present = 1; + swap_in_place(out->sec.csrk, 16); + valcnt++; + } else if (rc != ENOENT) { + console_printf("invalid 'csrk' parameter\n"); + return rc; + } + out->sec.peer_addr = key->sec.peer_addr; + out->sec.ediv = key->sec.ediv; + out->sec.rand_num = key->sec.rand_num; + break; + } + + if (valcnt) { + return 0; + } + return -1; +} + +/***************************************************************************** + * keystore-add * + *****************************************************************************/ + +static int +cmd_keystore_add(int argc, char **argv) +{ + union ble_store_key key; + union ble_store_value value; + int obj_type; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + rc = cmd_keystore_parse_keydata(argc, argv, &key, &obj_type); + + if (rc) { + return rc; + } + + rc = cmd_keystore_parse_valuedata(argc, argv, obj_type, &key, &value); + + if (rc) { + return rc; + } + + switch(obj_type) { + case BLE_STORE_OBJ_TYPE_PEER_SEC: + rc = ble_store_write_peer_sec(&value.sec); + break; + case BLE_STORE_OBJ_TYPE_OUR_SEC: + rc = ble_store_write_our_sec(&value.sec); + break; + case BLE_STORE_OBJ_TYPE_CCCD: + rc = ble_store_write_cccd(&value.cccd); + break; + default: + rc = ble_store_write(obj_type, &value); + } + return rc; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param keystore_add_params[] = { + {"type", "entry type, usage: ="}, + {"addr_type", "usage: ="}, + {"addr", "usage: ="}, + {"ediv", "usage: ="}, + {"rand", "usage: ="}, + {"ltk", "usage: =, len=16 octets"}, + {"irk", "usage: =, len=16 octets"}, + {"csrk", "usage: =, len=16 octets"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help keystore_add_help = { + .summary = "add data to keystore", + .usage = NULL, + .params = keystore_add_params, +}; +#endif + +/***************************************************************************** + * keystore-del * + *****************************************************************************/ + +static int +cmd_keystore_del(int argc, char **argv) +{ + union ble_store_key key; + int obj_type; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + rc = cmd_keystore_parse_keydata(argc, argv, &key, &obj_type); + + if (rc) { + return rc; + } + rc = ble_store_delete(obj_type, &key); + return rc; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param keystore_del_params[] = { + {"type", "entry type, usage: ="}, + {"addr_type", "usage: ="}, + {"addr", "usage: ="}, + {"ediv", "usage: ="}, + {"rand", "usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help keystore_del_help = { + .summary = "remove data from keystore", + .usage = NULL, + .params = keystore_del_params, +}; +#endif + +/***************************************************************************** + * keystore-show * + *****************************************************************************/ + +static int +cmd_keystore_iterator(int obj_type, + union ble_store_value *val, + void *cookie) { + + switch (obj_type) { + case BLE_STORE_OBJ_TYPE_PEER_SEC: + case BLE_STORE_OBJ_TYPE_OUR_SEC: + console_printf("Key: "); + if (ble_addr_cmp(&val->sec.peer_addr, BLE_ADDR_ANY) == 0) { + console_printf("ediv=%u ", val->sec.ediv); + console_printf("ediv=%llu ", val->sec.rand_num); + } else { + console_printf("addr_type=%u ", val->sec.peer_addr.type); + print_addr(val->sec.peer_addr.val); + } + console_printf("\n"); + + if (val->sec.ltk_present) { + console_printf(" LTK: "); + print_bytes(val->sec.ltk, 16); + console_printf("\n"); + } + if (val->sec.irk_present) { + console_printf(" IRK: "); + print_bytes(val->sec.irk, 16); + console_printf("\n"); + } + if (val->sec.csrk_present) { + console_printf(" CSRK: "); + print_bytes(val->sec.csrk, 16); + console_printf("\n"); + } + break; + } + return 0; +} + +static int +cmd_keystore_show(int argc, char **argv) +{ + int type; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + type = parse_arg_kv("type", cmd_keystore_entry_type, &rc); + if (rc != 0) { + console_printf("invalid 'type' parameter\n"); + return rc; + } + + ble_store_iterate(type, &cmd_keystore_iterator, NULL); + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param keystore_show_params[] = { + {"type", "entry type, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help keystore_show_help = { + .summary = "show data in keystore", + .usage = NULL, + .params = keystore_show_params, +}; +#endif + +#if NIMBLE_BLE_SM +/***************************************************************************** + * $auth-passkey * + *****************************************************************************/ + +static int +cmd_auth_passkey(int argc, char **argv) +{ + uint16_t conn_handle; + struct ble_sm_io pk; + char *yesno; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + pk.action = parse_arg_uint16("action", &rc); + if (rc != 0) { + console_printf("invalid 'action' parameter\n"); + return rc; + } + + switch (pk.action) { + case BLE_SM_IOACT_INPUT: + case BLE_SM_IOACT_DISP: + /* passkey is 6 digit number */ + pk.passkey = parse_arg_long_bounds("key", 0, 999999, &rc); + if (rc != 0) { + console_printf("invalid 'key' parameter\n"); + return rc; + } + break; + + case BLE_SM_IOACT_OOB: + rc = parse_arg_byte_stream_exact_length("oob", pk.oob, 16); + if (rc != 0) { + console_printf("invalid 'oob' parameter\n"); + return rc; + } + break; + + case BLE_SM_IOACT_NUMCMP: + yesno = parse_arg_extract("yesno"); + if (yesno == NULL) { + console_printf("invalid 'yesno' parameter\n"); + return EINVAL; + } + + switch (yesno[0]) { + case 'y': + case 'Y': + pk.numcmp_accept = 1; + break; + case 'n': + case 'N': + pk.numcmp_accept = 0; + break; + + default: + console_printf("invalid 'yesno' parameter\n"); + return EINVAL; + } + break; + + default: + console_printf("invalid passkey action action=%d\n", pk.action); + return EINVAL; + } + + rc = ble_sm_inject_io(conn_handle, &pk); + if (rc != 0) { + console_printf("error providing passkey; rc=%d\n", rc); + return rc; + } + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param auth_passkey_params[] = { + {"conn", "connection handle, usage: ="}, + {"action", "auth action type, usage: ="}, + {"key", "usage: =[0-999999]"}, + {"oob", "usage: =[XX:XX...], len=16 octets"}, + {"yesno", "usage: =[string]"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help auth_passkey_help = { + .summary = "set authorization passkey options", + .usage = NULL, + .params = auth_passkey_params, +}; +#endif + +/***************************************************************************** + * $security-pair * + *****************************************************************************/ + +static int +cmd_security_pair(int argc, char **argv) +{ + uint16_t conn_handle; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + rc = btshell_sec_pair(conn_handle); + if (rc != 0) { + console_printf("error initiating pairing; rc=%d\n", rc); + return rc; + } + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param security_pair_params[] = { + {"conn", "connection handle, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help security_pair_help = { + .summary = "start pairing procedure for connection", + .usage = NULL, + .params = security_pair_params, +}; +#endif + +/***************************************************************************** + * $security-start * + *****************************************************************************/ + +static int +cmd_security_start(int argc, char **argv) +{ + uint16_t conn_handle; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + rc = btshell_sec_start(conn_handle); + if (rc != 0) { + console_printf("error starting security; rc=%d\n", rc); + return rc; + } + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param security_start_params[] = { + {"conn", "connection handle, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help security_start_help = { + .summary = "start security procedure for connection", + .usage = NULL, + .params = security_start_params, +}; +#endif + +/***************************************************************************** + * $security-encryption * + *****************************************************************************/ + +static int +cmd_security_encryption(int argc, char **argv) +{ + uint16_t conn_handle; + uint16_t ediv; + uint64_t rand_val; + uint8_t ltk[16]; + int rc; + int auth; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + ediv = parse_arg_uint16("ediv", &rc); + if (rc == ENOENT) { + rc = btshell_sec_restart(conn_handle, NULL, 0, 0, 0); + } else { + rand_val = parse_arg_uint64("rand", &rc); + if (rc != 0) { + console_printf("invalid 'rand' parameter\n"); + return rc; + } + + auth = parse_arg_bool("auth", &rc); + if (rc != 0) { + console_printf("invalid 'auth' parameter\n"); + return rc; + } + + rc = parse_arg_byte_stream_exact_length("ltk", ltk, 16); + if (rc != 0) { + console_printf("invalid 'ltk' parameter\n"); + return rc; + } + + rc = btshell_sec_restart(conn_handle, ltk, ediv, rand_val, auth); + } + + if (rc != 0) { + console_printf("error initiating encryption; rc=%d\n", rc); + return rc; + } + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param security_encryption_params[] = { + {"conn", "connection handle, usage: ="}, + {"ediv", "usage: =[UINT16]"}, + {"rand", "usage: =[UINT64]"}, + {"auth", "usage: =[0-1]"}, + {"ltk", "usage: =[XX:XX...], len=16 octets"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help security_encryption_help = { + .summary = "start encryption procedure for connection", + .usage = NULL, + .params = security_encryption_params, +}; +#endif + +/***************************************************************************** + * $security-set-data * + *****************************************************************************/ + +static int +cmd_security_set_data(int argc, char **argv) +{ + uint8_t tmp; + int good; + int rc; + + good = 0; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + tmp = parse_arg_bool("oob_flag", &rc); + if (rc == 0) { + ble_hs_cfg.sm_oob_data_flag = tmp; + good++; + } else if (rc != ENOENT) { + console_printf("invalid 'oob_flag' parameter\n"); + return rc; + } + + tmp = parse_arg_bool("mitm_flag", &rc); + if (rc == 0) { + good++; + ble_hs_cfg.sm_mitm = tmp; + } else if (rc != ENOENT) { + console_printf("invalid 'mitm_flag' parameter\n"); + return rc; + } + + tmp = parse_arg_uint8("io_capabilities", &rc); + if (rc == 0) { + good++; + ble_hs_cfg.sm_io_cap = tmp; + } else if (rc != ENOENT) { + console_printf("invalid 'io_capabilities' parameter\n"); + return rc; + } + + tmp = parse_arg_uint8("our_key_dist", &rc); + if (rc == 0) { + good++; + ble_hs_cfg.sm_our_key_dist = tmp; + } else if (rc != ENOENT) { + console_printf("invalid 'our_key_dist' parameter\n"); + return rc; + } + + tmp = parse_arg_uint8("their_key_dist", &rc); + if (rc == 0) { + good++; + ble_hs_cfg.sm_their_key_dist = tmp; + } else if (rc != ENOENT) { + console_printf("invalid 'their_key_dist' parameter\n"); + return rc; + } + + tmp = parse_arg_bool("bonding", &rc); + if (rc == 0) { + good++; + ble_hs_cfg.sm_bonding = tmp; + } else if (rc != ENOENT) { + console_printf("invalid 'bonding' parameter\n"); + return rc; + } + + tmp = parse_arg_bool("sc", &rc); + if (rc == 0) { + good++; + ble_hs_cfg.sm_sc = tmp; + } else if (rc != ENOENT) { + console_printf("invalid 'sc' parameter\n"); + return rc; + } + + if (!good) { + console_printf("Error: no valid settings specified\n"); + return -1; + } + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param security_set_data_params[] = { + {"oob_flag", "usage: =[0-1]"}, + {"mitm_flag", "usage: =[0-1]"}, + {"io_capabilities", "usage: =[UINT8]"}, + {"our_key_dist", "usage: =[UINT8]"}, + {"their_key_dist", "usage: =[UINT8]"}, + {"bonding", "usage: =[0-1]"}, + {"sc", "usage: =[0-1]"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help security_set_data_help = { + .summary = "set security data", + .usage = NULL, + .params = security_set_data_params, +}; +#endif +#endif + +/***************************************************************************** + * $test-tx * + * * + * Command to transmit 'num' packets of size 'len' at rate 'r' to + * handle 'h' Note that length must be <= 251. The rate is in msecs. + * + *****************************************************************************/ + +static int +cmd_test_tx(int argc, char **argv) +{ + int rc; + uint16_t rate; + uint16_t len; + uint16_t handle; + uint16_t num; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + rate = parse_arg_uint16("rate", &rc); + if (rc != 0) { + console_printf("invalid 'rate' parameter\n"); + return rc; + } + + len = parse_arg_uint16("length", &rc); + if (rc != 0) { + console_printf("invalid 'length' parameter\n"); + return rc; + } + if ((len > 251) || (len < 4)) { + console_printf("error: len must be between 4 and 251, inclusive"); + } + + num = parse_arg_uint16("num", &rc); + if (rc != 0) { + console_printf("invalid 'num' parameter\n"); + return rc; + } + + handle = parse_arg_uint16("handle", &rc); + if (rc != 0) { + console_printf("invalid 'handle' parameter\n"); + return rc; + } + + rc = btshell_tx_start(handle, len, rate, num); + return rc; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param test_tx_params[] = { + {"num", "number of packets, usage: ="}, + {"length", "size of packet, usage: ="}, + {"rate", "rate of tx, usage: ="}, + {"handle", "handle to tx to, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help test_tx_help = { + .summary = "test packet transmission", + .usage = NULL, + .params = test_tx_params, +}; +#endif + +/***************************************************************************** + * $phy-set * + *****************************************************************************/ + +static int +cmd_phy_set(int argc, char **argv) +{ + uint16_t conn; + uint8_t tx_phys_mask; + uint8_t rx_phys_mask; + uint16_t phy_opts; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + tx_phys_mask = parse_arg_uint8("tx_phys_mask", &rc); + if (rc != 0) { + console_printf("invalid 'tx_phys_mask' parameter\n"); + return rc; + } + + rx_phys_mask = parse_arg_uint8("rx_phys_mask", &rc); + if (rc != 0) { + console_printf("invalid 'rx_phys_mask' parameter\n"); + return rc; + } + + phy_opts = parse_arg_uint16("phy_opts", &rc); + if (rc != 0) { + console_printf("invalid 'phy_opts' parameter\n"); + return rc; + } + + return ble_gap_set_prefered_le_phy(conn, tx_phys_mask, rx_phys_mask, + phy_opts); +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param phy_set_params[] = { + {"conn", "connection handle, usage: ="}, + {"tx_phys_mask", "usage: ="}, + {"rx_phys_mask", "usage: ="}, + {"phy_opts", "usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help phy_set_help = { + .summary = "set preferred PHYs", + .usage = NULL, + .params = phy_set_params, +}; +#endif + +/***************************************************************************** + * $phy-set-default * + *****************************************************************************/ + +static int +cmd_phy_set_default(int argc, char **argv) +{ + uint8_t tx_phys_mask; + uint8_t rx_phys_mask; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + tx_phys_mask = parse_arg_uint8("tx_phys_mask", &rc); + if (rc != 0) { + console_printf("invalid 'tx_phys_mask' parameter\n"); + return rc; + } + + rx_phys_mask = parse_arg_uint8("rx_phys_mask", &rc); + if (rc != 0) { + console_printf("invalid 'rx_phys_mask' parameter\n"); + return rc; + } + + return ble_gap_set_prefered_default_le_phy(tx_phys_mask, rx_phys_mask); +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param phy_set_default_params[] = { + {"tx_phys_mask", "usage: ="}, + {"rx_phys_mask", "usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help phy_set_default_help = { + .summary = "set preferred default PHYs", + .usage = NULL, + .params = phy_set_default_params, +}; +#endif + +/***************************************************************************** + * $phy-read * + *****************************************************************************/ + +static int +cmd_phy_read(int argc, char **argv) +{ + uint16_t conn = 0; + uint8_t tx_phy; + uint8_t rx_phy; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + rc = ble_gap_read_le_phy(conn, &tx_phy, &rx_phy); + if (rc != 0) { + console_printf("Could not read PHY error: %d\n", rc); + return rc; + } + + console_printf("TX_PHY: %d\n", tx_phy); + console_printf("RX_PHY: %d\n", tx_phy); + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param phy_read_params[] = { + {"conn", "connection handle, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help phy_read_help = { + .summary = "read PHYs", + .usage = NULL, + .params = phy_read_params, +}; + +/***************************************************************************** + * $gatt-discover * + *****************************************************************************/ + +static const struct shell_param gatt_discover_characteristic_params[] = { + {"conn", "connection handle, usage: ="}, + {"uuid", "discover by uuid, usage: =[UUID]"}, + {"start", "start handle, usage: ="}, + {"end", "end handle, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help gatt_discover_characteristic_help = { + .summary = "perform characteristic discovery procedure", + .usage = NULL, + .params = gatt_discover_characteristic_params, +}; + +static const struct shell_param gatt_discover_descriptor_params[] = { + {"conn", "connection handle, usage: ="}, + {"start", "start handle, usage: ="}, + {"end", "end handle, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help gatt_discover_descriptor_help = { + .summary = "perform descriptor discovery procedure", + .usage = NULL, + .params = gatt_discover_descriptor_params, +}; + +static const struct shell_param gatt_discover_service_params[] = { + {"conn", "connection handle, usage: ="}, + {"uuid", "discover by uuid, usage: =[UUID]"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help gatt_discover_service_help = { + .summary = "perform service discovery procedure", + .usage = NULL, + .params = gatt_discover_service_params, +}; + +static const struct shell_param gatt_discover_full_params[] = { + {"conn", "connection handle, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help gatt_discover_full_help = { + .summary = "perform full discovery procedure", + .usage = NULL, + .params = gatt_discover_full_params, +}; + +/***************************************************************************** + * $gatt-exchange-mtu * + *****************************************************************************/ + +static const struct shell_param gatt_exchange_mtu_params[] = { + {"conn", "connection handle, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help gatt_exchange_mtu_help = { + .summary = "perform mtu exchange procedure", + .usage = NULL, + .params = gatt_exchange_mtu_params, +}; + +/***************************************************************************** + * $gatt-find-included-services * + *****************************************************************************/ + +static const struct shell_param gatt_find_included_services_params[] = { + {"conn", "connection handle, usage: ="}, + {"start", "start handle, usage: ="}, + {"end", "end handle, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help gatt_find_included_services_help = { + .summary = "perform find included services procedure", + .usage = NULL, + .params = gatt_find_included_services_params, +}; + +/***************************************************************************** + * $gatt-notify * + *****************************************************************************/ + +static const struct shell_param gatt_notify_params[] = { + {"attr", "attribute handle, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help gatt_notify_help = { + .summary = "notify about attribute value changed", + .usage = NULL, + .params = gatt_notify_params, +}; + +/***************************************************************************** + * $gatt-read * + *****************************************************************************/ + +static const struct shell_param gatt_read_params[] = { + {"conn", "connection handle, usage: ="}, + {"long", "is read long, usage: =[0-1], default=0"}, + {"attr", "attribute handle, usage: ="}, + {"offset", "offset value, usage: ="}, + {"uuid", "read by uuid, usage: =[UUID]"}, + {"start", "start handle, usage: ="}, + {"end", "end handle, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help gatt_read_help = { + .summary = "perform gatt read procedure", + .usage = NULL, + .params = gatt_read_params, +}; + +/***************************************************************************** + * $gatt-service-changed * + *****************************************************************************/ + +static const struct shell_param gatt_service_changed_params[] = { + {"start", "start handle, usage: ="}, + {"end", "end handle, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help gatt_service_changed_help = { + .summary = "send service changed indication", + .usage = NULL, + .params = gatt_service_changed_params, +}; + +/***************************************************************************** + * $gatt-service-visibility * + *****************************************************************************/ + +static const struct shell_param gatt_service_visibility_params[] = { + {"handle", "usage: ="}, + {"visibility", "usage: =<0-1>"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help gatt_service_visibility_help = { + .summary = "change service visibility", + .usage = NULL, + .params = gatt_service_visibility_params, +}; + +/***************************************************************************** + * $gatt-show * + *****************************************************************************/ + +static const struct shell_param gatt_show_params[] = { + {NULL, NULL} +}; + +static const struct shell_cmd_help gatt_show_help = { + .summary = "show discovered gatt database", + .usage = NULL, + .params = gatt_show_params, +}; + +static const struct shell_cmd_help gatt_show_local_help = { + .summary = "show local gatt database", + .usage = NULL, + .params = gatt_show_params, +}; + +static const struct shell_cmd_help gatt_show_addr_help = { + .summary = "show device address", + .usage = NULL, + .params = gatt_show_params, +}; + +static const struct shell_cmd_help gatt_show_conn_help = { + .summary = "show connections information", + .usage = NULL, + .params = gatt_show_params, +}; + +/***************************************************************************** + * $gatt-write * + *****************************************************************************/ + +static const struct shell_param gatt_write_params[] = { + {"conn", "connection handle, usage: ="}, + {"no_rsp", "write without response, usage: =[0-1], default=0"}, + {"long", "is write long, usage: =[0-1], default=0"}, + {"attr", "attribute handle, usage: ="}, + {"offset", "attribute handle, usage: ="}, + {"value", "usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help gatt_write_help = { + .summary = "perform gatt write procedure", + .usage = NULL, + .params = gatt_write_params, +}; +#endif + +#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) +#if MYNEWT_VAL(SHELL_CMD_HELP) +/***************************************************************************** + * $l2cap-update * + *****************************************************************************/ + +static const struct shell_param l2cap_update_params[] = { + {"conn", "connection handle, usage: ="}, + {"interval_min", "usage: =[0-UINT16_MAX], default: 30"}, + {"interval_max", "usage: =[0-UINT16_MAX], default: 50"}, + {"latency", "usage: =[UINT16], default: 0"}, + {"timeout", "usage: =[UINT16], default: 0x0100"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help l2cap_update_help = { + .summary = "update l2cap parameters for connection", + .usage = NULL, + .params = l2cap_update_params, +}; + +/***************************************************************************** + * $l2cap-create-server * + *****************************************************************************/ + +static const struct shell_param l2cap_create_server_params[] = { + {"psm", "usage: ="}, + {"error", "usage: used for PTS testing:"}, + {"", "0 - always accept"}, + {"", "1 - reject with insufficient authentication"}, + {"", "2 - reject with insufficient authorization"}, + {"", "3 - reject with insufficient key size"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help l2cap_create_server_help = { + .summary = "create l2cap server", + .usage = NULL, + .params = l2cap_create_server_params, +}; + +/***************************************************************************** + * $l2cap-connect * + *****************************************************************************/ + +static const struct shell_param l2cap_connect_params[] = { + {"conn", "connection handle, usage: ="}, + {"psm", "usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help l2cap_connect_help = { + .summary = "perform l2cap connect procedure", + .usage = NULL, + .params = l2cap_connect_params, +}; + +/***************************************************************************** + * $l2cap-disconnect * + *****************************************************************************/ + +static const struct shell_param l2cap_disconnect_params[] = { + {"conn", "connection handle, usage: ="}, + {"idx", "usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help l2cap_disconnect_help = { + .summary = "perform l2cap disconnect procedure", + .usage = "use gatt-show-coc to get the parameters", + .params = l2cap_disconnect_params, +}; + +/***************************************************************************** + * $l2cap-send * + *****************************************************************************/ + +static const struct shell_param l2cap_send_params[] = { + {"conn", "connection handle, usage: ="}, + {"idx", "usage: ="}, + {"bytes", "number of bytes to send, usage: ="}, + {NULL, NULL} +}; + +static const struct shell_cmd_help l2cap_send_help = { + .summary = "perform l2cap send procedure", + .usage = "use l2cap-show-coc to get the parameters", + .params = l2cap_send_params, +}; + +/***************************************************************************** + * $l2cap-show-coc * + *****************************************************************************/ + +static const struct shell_param l2cap_show_coc_params[] = { + {NULL, NULL} +}; + +static const struct shell_cmd_help l2cap_show_coc_help = { + .summary = "show coc information", + .usage = NULL, + .params = l2cap_show_coc_params, +}; + +#endif +#endif + +static const struct shell_cmd btshell_commands[] = { +#if MYNEWT_VAL(BLE_EXT_ADV) + { + .sc_cmd = "advertise-configure", + .sc_cmd_func = cmd_advertise_configure, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &advertise_configure_help, +#endif + }, + { + .sc_cmd = "advertise-set-addr", + .sc_cmd_func = cmd_advertise_set_addr, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &advertise_set_addr_help, +#endif + }, + { + .sc_cmd = "advertise-set-adv-data", + .sc_cmd_func = cmd_set_adv_data, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &set_adv_data_help, +#endif + }, + { + .sc_cmd = "advertise-set-scan-rsp", + .sc_cmd_func = cmd_set_scan_rsp, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &set_scan_rsp_help, +#endif + }, + { + .sc_cmd = "advertise-start", + .sc_cmd_func = cmd_advertise_start, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &advertise_start_help, +#endif + }, + { + .sc_cmd = "advertise-stop", + .sc_cmd_func = cmd_advertise_stop, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &advertise_stop_help, +#endif + }, + { + .sc_cmd = "advertise-remove", + .sc_cmd_func = cmd_advertise_remove, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &advertise_remove_help, +#endif + }, +#else + { + .sc_cmd = "advertise", + .sc_cmd_func = cmd_advertise, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &advertise_help, +#endif + }, +#endif + { + .sc_cmd = "connect", + .sc_cmd_func = cmd_connect, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &connect_help, +#endif + }, + { + .sc_cmd = "disconnect", + .sc_cmd_func = cmd_disconnect, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &disconnect_help, +#endif + }, + { + .sc_cmd = "show-addr", + .sc_cmd_func = cmd_show_addr, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_show_addr_help, +#endif + }, + { + .sc_cmd = "show-conn", + .sc_cmd_func = cmd_show_conn, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_show_conn_help, +#endif + }, + { + .sc_cmd = "set-scan-opts", + .sc_cmd_func = cmd_set_scan_opts, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &set_scan_opts_help, +#endif + }, + { + .sc_cmd = "scan", + .sc_cmd_func = cmd_scan, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &scan_help, +#endif + }, + { + .sc_cmd = "set", + .sc_cmd_func = cmd_set, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &set_help, +#endif + }, +#if !MYNEWT_VAL(BLE_EXT_ADV) + { + .sc_cmd = "set-adv-data", + .sc_cmd_func = cmd_set_adv_data, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &set_adv_data_help, +#endif + }, + { + .sc_cmd = "set-scan-rsp", + .sc_cmd_func = cmd_set_scan_rsp, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &set_scan_rsp_help, +#endif + }, +#endif + { + .sc_cmd = "set-priv-mode", + .sc_cmd_func = cmd_set_priv_mode, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &set_priv_mode_help, +#endif + }, + { + .sc_cmd = "white-list", + .sc_cmd_func = cmd_white_list, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &white_list_help, +#endif + }, + { + .sc_cmd = "conn-rssi", + .sc_cmd_func = cmd_conn_rssi, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &conn_rssi_help, +#endif + }, + { + .sc_cmd = "conn-update-params", + .sc_cmd_func = cmd_conn_update_params, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &conn_update_params_help, +#endif + }, + { + .sc_cmd = "conn-datalen", + .sc_cmd_func = cmd_conn_datalen, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &conn_datalen_help, +#endif + }, + { + .sc_cmd = "gatt-discover-characteristic", + .sc_cmd_func = cmd_gatt_discover_characteristic, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_discover_characteristic_help, +#endif + }, + { + .sc_cmd = "gatt-discover-descriptor", + .sc_cmd_func = cmd_gatt_discover_descriptor, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_discover_descriptor_help, +#endif + }, + { + .sc_cmd = "gatt-discover-service", + .sc_cmd_func = cmd_gatt_discover_service, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_discover_service_help, +#endif + }, + { + .sc_cmd = "gatt-discover-full", + .sc_cmd_func = cmd_gatt_discover_full, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_discover_full_help, +#endif + }, + { + .sc_cmd = "gatt-find-included-services", + .sc_cmd_func = cmd_gatt_find_included_services, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_find_included_services_help, +#endif + }, + { + .sc_cmd = "gatt-exchange-mtu", + .sc_cmd_func = cmd_gatt_exchange_mtu, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_exchange_mtu_help, +#endif + }, + { + .sc_cmd = "gatt-read", + .sc_cmd_func = cmd_gatt_read, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_read_help, +#endif + }, + { + .sc_cmd = "gatt-notify", + .sc_cmd_func = cmd_gatt_notify, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_notify_help, +#endif + }, + { + .sc_cmd = "gatt-service-changed", + .sc_cmd_func = cmd_gatt_service_changed, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_service_changed_help, +#endif + }, + { + .sc_cmd = "gatt-service-visibility", + .sc_cmd_func = cmd_gatt_service_visibility, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_service_visibility_help, +#endif + }, + { + .sc_cmd = "gatt-show", + .sc_cmd_func = cmd_gatt_show, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_show_help, +#endif + }, + { + .sc_cmd = "gatt-show-local", + .sc_cmd_func = cmd_gatt_show_local, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_show_local_help, +#endif + }, + { + .sc_cmd = "gatt-write", + .sc_cmd_func = cmd_gatt_write, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_write_help, +#endif + }, +#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) + { + .sc_cmd = "l2cap-update", + .sc_cmd_func = cmd_l2cap_update, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &l2cap_update_help, +#endif + }, + { + .sc_cmd = "l2cap-create-server", + .sc_cmd_func = cmd_l2cap_create_server, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &l2cap_create_server_help, +#endif + }, + { + .sc_cmd = "l2cap-connect", + .sc_cmd_func = cmd_l2cap_connect, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &l2cap_connect_help, +#endif + }, + { + .sc_cmd = "l2cap-disconnect", + .sc_cmd_func = cmd_l2cap_disconnect, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &l2cap_disconnect_help, +#endif + }, + { + .sc_cmd = "l2cap-send", + .sc_cmd_func = cmd_l2cap_send, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &l2cap_send_help, +#endif + }, + { + .sc_cmd = "l2cap-show-coc", + .sc_cmd_func = cmd_l2cap_show_coc, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &l2cap_show_coc_help, +#endif + }, +#endif + { + .sc_cmd = "keystore-add", + .sc_cmd_func = cmd_keystore_add, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &keystore_add_help, +#endif + }, + { + .sc_cmd = "keystore-del", + .sc_cmd_func = cmd_keystore_del, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &keystore_del_help, +#endif + }, + { + .sc_cmd = "keystore-show", + .sc_cmd_func = cmd_keystore_show, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &keystore_show_help, +#endif + }, +#if NIMBLE_BLE_SM + { + .sc_cmd = "auth-passkey", + .sc_cmd_func = cmd_auth_passkey, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &auth_passkey_help, +#endif + }, + { + .sc_cmd = "security-pair", + .sc_cmd_func = cmd_security_pair, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &security_pair_help, +#endif + }, + { + .sc_cmd = "security-start", + .sc_cmd_func = cmd_security_start, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &security_start_help, +#endif + }, + { + .sc_cmd = "security-encryption", + .sc_cmd_func = cmd_security_encryption, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &security_encryption_help, +#endif + }, + { + .sc_cmd = "security-set-data", + .sc_cmd_func = cmd_security_set_data, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &security_set_data_help, +#endif + }, +#endif + { + .sc_cmd = "test-tx", + .sc_cmd_func = cmd_test_tx, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &test_tx_help, +#endif + }, + { + .sc_cmd = "phy-set", + .sc_cmd_func = cmd_phy_set, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &phy_set_help, +#endif + }, + { + .sc_cmd = "phy-set-default", + .sc_cmd_func = cmd_phy_set_default, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &phy_set_default_help, +#endif + }, + { + .sc_cmd = "phy-read", + .sc_cmd_func = cmd_phy_read, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &phy_read_help, +#endif + }, + { NULL, NULL, NULL }, +}; + + +void +cmd_init(void) +{ + shell_register(BTSHELL_MODULE, btshell_commands); + shell_register_default_module(BTSHELL_MODULE); +} diff --git a/apps/btshell/src/cmd.h b/apps/btshell/src/cmd.h new file mode 100644 index 000000000..a3620c039 --- /dev/null +++ b/apps/btshell/src/cmd.h @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CMD_H +#define CMD_H + +#include +#include "host/ble_uuid.h" + +struct kv_pair { + char *key; + int val; +}; + +const struct kv_pair *parse_kv_find(const struct kv_pair *kvs, char *name); +int parse_arg_find_idx(const char *key); +char *parse_arg_extract(const char *key); +long parse_arg_long_bounds(char *name, long min, long max, int *out_status); +long parse_arg_long_bounds_dflt(char *name, long min, long max, + long dflt, int *out_status); +uint64_t parse_arg_uint64_bounds(char *name, uint64_t min, + uint64_t max, int *out_status); +long parse_arg_long(char *name, int *staus); +uint8_t parse_arg_bool(char *name, int *status); +uint8_t parse_arg_bool_dflt(char *name, uint8_t dflt, int *out_status); +uint8_t parse_arg_uint8(char *name, int *status); +uint8_t parse_arg_uint8_dflt(char *name, uint8_t dflt, int *out_status); +uint16_t parse_arg_uint16(char *name, int *status); +uint16_t parse_arg_uint16_dflt(char *name, uint16_t dflt, int *out_status); +uint32_t parse_arg_uint32(char *name, int *out_status); +uint32_t parse_arg_uint32_dflt(char *name, uint32_t dflt, int *out_status); +uint64_t parse_arg_uint64(char *name, int *out_status); +int parse_arg_kv(char *name, const struct kv_pair *kvs, int *out_status); +int parse_arg_kv_dflt(char *name, const struct kv_pair *kvs, int def_val, + int *out_status); +int parse_arg_byte_stream(char *name, int max_len, uint8_t *dst, int *out_len); +int parse_arg_byte_stream_exact_length(char *name, uint8_t *dst, int len); +int parse_arg_mac(char *name, uint8_t *dst); +int parse_arg_uuid(char *name, ble_uuid_any_t *uuid); +int parse_arg_all(int argc, char **argv); +int parse_eddystone_url(char *full_url, uint8_t *out_scheme, char *out_body, + uint8_t *out_body_len, uint8_t *out_suffix); +int cmd_parse_conn_start_end(uint16_t *out_conn, uint16_t *out_start, + uint16_t *out_end); + +void cmd_init(void); + +#endif diff --git a/apps/btshell/src/cmd_gatt.c b/apps/btshell/src/cmd_gatt.c new file mode 100644 index 000000000..ba3799e56 --- /dev/null +++ b/apps/btshell/src/cmd_gatt.c @@ -0,0 +1,587 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include + +#include "bsp/bsp.h" +#include "host/ble_hs_mbuf.h" +#include "host/ble_gap.h" +#include "services/gatt/ble_svc_gatt.h" +#include "console/console.h" +#include "btshell.h" +#include "cmd.h" +#include "cmd_gatt.h" + +#define CMD_BUF_SZ 256 +static bssnz_t uint8_t cmd_buf[CMD_BUF_SZ]; + +/***************************************************************************** + * $gatt-discover * + *****************************************************************************/ + +int +cmd_gatt_discover_characteristic(int argc, char **argv) +{ + uint16_t start_handle; + uint16_t conn_handle; + uint16_t end_handle; + ble_uuid_any_t uuid; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + rc = cmd_parse_conn_start_end(&conn_handle, &start_handle, &end_handle); + if (rc != 0) { + console_printf("invalid 'conn start end' parameter\n"); + return rc; + } + + rc = parse_arg_uuid("uuid", &uuid); + if (rc == 0) { + rc = btshell_disc_chrs_by_uuid(conn_handle, start_handle, end_handle, + &uuid.u); + } else if (rc == ENOENT) { + rc = btshell_disc_all_chrs(conn_handle, start_handle, end_handle); + } else { + console_printf("invalid 'uuid' parameter\n"); + return rc; + } + if (rc != 0) { + console_printf("error discovering characteristics; rc=%d\n", rc); + return rc; + } + + return 0; +} + +int +cmd_gatt_discover_descriptor(int argc, char **argv) +{ + uint16_t start_handle; + uint16_t conn_handle; + uint16_t end_handle; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + rc = cmd_parse_conn_start_end(&conn_handle, &start_handle, &end_handle); + if (rc != 0) { + console_printf("invalid 'conn start end' parameter\n"); + return rc; + } + + rc = btshell_disc_all_dscs(conn_handle, start_handle, end_handle); + if (rc != 0) { + console_printf("error discovering descriptors; rc=%d\n", rc); + return rc; + } + + return 0; +} + +int +cmd_gatt_discover_service(int argc, char **argv) +{ + ble_uuid_any_t uuid; + int conn_handle; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + rc = parse_arg_uuid("uuid", &uuid); + if (rc == 0) { + rc = btshell_disc_svc_by_uuid(conn_handle, &uuid.u); + } else if (rc == ENOENT) { + rc = btshell_disc_svcs(conn_handle); + } else { + console_printf("invalid 'uuid' parameter\n"); + return rc; + } + + if (rc != 0) { + console_printf("error discovering services; rc=%d\n", rc); + return rc; + } + + return 0; +} + +int +cmd_gatt_discover_full(int argc, char **argv) +{ + int conn_handle; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + rc = btshell_disc_full(conn_handle); + if (rc != 0) { + console_printf("error discovering all; rc=%d\n", rc); + return rc; + } + + return 0; +} + +/***************************************************************************** + * $gatt-exchange-mtu * + *****************************************************************************/ + +int +cmd_gatt_exchange_mtu(int argc, char **argv) +{ + uint16_t conn_handle; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + rc = btshell_exchange_mtu(conn_handle); + if (rc != 0) { + console_printf("error exchanging mtu; rc=%d\n", rc); + return rc; + } + + return 0; +} + +/***************************************************************************** + * $gatt-notify * + *****************************************************************************/ + +int +cmd_gatt_notify(int argc, char **argv) +{ + uint16_t attr_handle; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + attr_handle = parse_arg_uint16("attr", &rc); + if (rc != 0) { + console_printf("invalid 'attr' parameter\n"); + return rc; + } + + btshell_notify(attr_handle); + + return 0; +} + +/***************************************************************************** + * $gatt-read * + *****************************************************************************/ + +#define CMD_READ_MAX_ATTRS 8 + +int +cmd_gatt_read(int argc, char **argv) +{ + static uint16_t attr_handles[CMD_READ_MAX_ATTRS]; + uint16_t conn_handle; + uint16_t start; + uint16_t end; + uint16_t offset; + ble_uuid_any_t uuid; + uint8_t num_attr_handles; + int is_uuid; + int is_long; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + is_long = parse_arg_long("long", &rc); + if (rc == ENOENT) { + is_long = 0; + } else if (rc != 0) { + console_printf("invalid 'long' parameter\n"); + return rc; + } + + for (num_attr_handles = 0; + num_attr_handles < CMD_READ_MAX_ATTRS; + num_attr_handles++) { + + attr_handles[num_attr_handles] = parse_arg_uint16("attr", &rc); + if (rc == ENOENT) { + break; + } else if (rc != 0) { + console_printf("invalid 'attr' parameter\n"); + return rc; + } + } + + rc = parse_arg_uuid("uuid", &uuid); + if (rc == ENOENT) { + is_uuid = 0; + } else if (rc == 0) { + is_uuid = 1; + } else { + console_printf("invalid 'uuid' parameter\n"); + return rc; + } + + start = parse_arg_uint16("start", &rc); + if (rc == ENOENT) { + start = 0; + } else if (rc != 0) { + console_printf("invalid 'start' parameter\n"); + return rc; + } + + end = parse_arg_uint16("end", &rc); + if (rc == ENOENT) { + end = 0; + } else if (rc != 0) { + console_printf("invalid 'end' parameter\n"); + return rc; + } + + offset = parse_arg_uint16("offset", &rc); + if (rc == ENOENT) { + offset = 0; + } else if (rc != 0) { + console_printf("invalid 'offset' parameter\n"); + return rc; + } + + if (num_attr_handles == 1) { + if (is_long) { + rc = btshell_read_long(conn_handle, attr_handles[0], offset); + } else { + rc = btshell_read(conn_handle, attr_handles[0]); + } + } else if (num_attr_handles > 1) { + rc = btshell_read_mult(conn_handle, attr_handles, num_attr_handles); + } else if (is_uuid) { + if (start == 0 || end == 0) { + rc = EINVAL; + } else { + rc = btshell_read_by_uuid(conn_handle, start, end, &uuid.u); + } + } else { + rc = EINVAL; + } + + if (rc != 0) { + console_printf("error reading characteristic; rc=%d\n", rc); + return rc; + } + + return 0; +} + + +/***************************************************************************** + * $gatt-service-changed * + *****************************************************************************/ + +int +cmd_gatt_service_changed(int argc, char **argv) +{ + uint16_t start; + uint16_t end; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + start = parse_arg_uint16("start", &rc); + if (rc != 0) { + console_printf("invalid 'start' parameter\n"); + return rc; + } + + end = parse_arg_uint16("end", &rc); + if (rc != 0) { + console_printf("invalid 'end' parameter\n"); + return rc; + } + + ble_svc_gatt_changed(start, end); + + return 0; +} + +/***************************************************************************** + * $gatt-service-visibility * + *****************************************************************************/ + +int +cmd_gatt_service_visibility(int argc, char **argv) +{ + uint16_t handle; + bool vis; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + handle = parse_arg_uint16("handle", &rc); + if (rc != 0) { + console_printf("invalid 'handle' parameter\n"); + return rc; + } + + vis = parse_arg_bool("visibility", &rc); + if (rc != 0) { + console_printf("invalid 'visibility' parameter\n"); + return rc; + } + + ble_gatts_svc_set_visibility(handle, vis); + + return 0; +} + +/***************************************************************************** + * $gatt-find-included-services * + *****************************************************************************/ + +int +cmd_gatt_find_included_services(int argc, char **argv) +{ + uint16_t start_handle; + uint16_t conn_handle; + uint16_t end_handle; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + rc = cmd_parse_conn_start_end(&conn_handle, &start_handle, &end_handle); + if (rc != 0) { + console_printf("invalid 'conn start end' parameter\n"); + return rc; + } + + rc = btshell_find_inc_svcs(conn_handle, start_handle, end_handle); + if (rc != 0) { + console_printf("error finding included services; rc=%d\n", rc); + return rc; + } + + return 0; +} + +/***************************************************************************** + * $gatt-show * + *****************************************************************************/ + +int +cmd_gatt_show(int argc, char **argv) +{ + struct btshell_conn *conn; + struct btshell_svc *svc; + int i; + + for (i = 0; i < btshell_num_conns; i++) { + conn = btshell_conns + i; + + console_printf("CONNECTION: handle=%d\n", conn->handle); + + SLIST_FOREACH(svc, &conn->svcs, next) { + print_svc(svc); + } + } + + return 0; +} + +int +cmd_gatt_show_local(int argc, char **argv) +{ + gatt_svr_print_svcs(); + return 0; +} + +/***************************************************************************** + * $gatt-write * + *****************************************************************************/ + +int +cmd_gatt_write(int argc, char **argv) +{ + struct ble_gatt_attr attrs[MYNEWT_VAL(BLE_GATT_WRITE_MAX_ATTRS)] = { { 0 } }; + uint16_t attr_handle; + uint16_t conn_handle; + uint16_t offset; + int total_attr_len; + int num_attrs; + int attr_len; + int is_long; + int no_rsp; + int rc; + int i; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + no_rsp = parse_arg_bool_dflt("no_rsp", 0, &rc); + if (rc != 0) { + console_printf("invalid 'no_rsp' parameter\n"); + return rc; + } + + is_long = parse_arg_bool_dflt("long", 0, &rc); + if (rc != 0) { + console_printf("invalid 'long' parameter\n"); + return rc; + } + + total_attr_len = 0; + num_attrs = 0; + while (1) { + attr_handle = parse_arg_uint16("attr", &rc); + if (rc == ENOENT) { + break; + } else if (rc != 0) { + rc = -rc; + console_printf("invalid 'attr' parameter\n"); + goto done; + } + + rc = parse_arg_byte_stream("value", sizeof cmd_buf - total_attr_len, + cmd_buf + total_attr_len, &attr_len); + if (rc == ENOENT) { + break; + } else if (rc != 0) { + console_printf("invalid 'value' parameter\n"); + goto done; + } + + offset = parse_arg_uint16("offset", &rc); + if (rc == ENOENT) { + offset = 0; + } else if (rc != 0) { + console_printf("invalid 'offset' parameter\n"); + return rc; + } + + if (num_attrs >= sizeof attrs / sizeof attrs[0]) { + rc = -EINVAL; + goto done; + } + + attrs[num_attrs].handle = attr_handle; + attrs[num_attrs].offset = offset; + attrs[num_attrs].om = ble_hs_mbuf_from_flat(cmd_buf + total_attr_len, + attr_len); + if (attrs[num_attrs].om == NULL) { + goto done; + } + + total_attr_len += attr_len; + num_attrs++; + } + + if (no_rsp) { + if (num_attrs != 1) { + rc = -EINVAL; + goto done; + } + rc = btshell_write_no_rsp(conn_handle, attrs[0].handle, attrs[0].om); + attrs[0].om = NULL; + } else if (is_long) { + if (num_attrs != 1) { + rc = -EINVAL; + goto done; + } + rc = btshell_write_long(conn_handle, attrs[0].handle, + attrs[0].offset, attrs[0].om); + attrs[0].om = NULL; + } else if (num_attrs > 1) { + rc = btshell_write_reliable(conn_handle, attrs, num_attrs); + } else if (num_attrs == 1) { + rc = btshell_write(conn_handle, attrs[0].handle, attrs[0].om); + attrs[0].om = NULL; + } else { + rc = -EINVAL; + } + +done: + for (i = 0; i < sizeof attrs / sizeof attrs[0]; i++) { + os_mbuf_free_chain(attrs[i].om); + } + + if (rc != 0) { + console_printf("error writing characteristic; rc=%d\n", rc); + } + + return rc; +} diff --git a/apps/btshell/src/cmd_gatt.h b/apps/btshell/src/cmd_gatt.h new file mode 100644 index 000000000..70536d03c --- /dev/null +++ b/apps/btshell/src/cmd_gatt.h @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CMD_GATT_H +#define CMD_GATT_H + +#include "cmd.h" + +int cmd_gatt_discover_characteristic(int argc, char **argv); +int cmd_gatt_discover_descriptor(int argc, char **argv); +int cmd_gatt_discover_service(int argc, char **argv); +int cmd_gatt_discover_full(int argc, char **argv); +int cmd_gatt_find_included_services(int argc, char **argv); +int cmd_gatt_exchange_mtu(int argc, char **argv); +int cmd_gatt_notify(int argc, char **argv); +int cmd_gatt_read(int argc, char **argv); +int cmd_gatt_service_changed(int argc, char **argv); +int cmd_gatt_service_visibility(int argc, char **argv); +int cmd_gatt_show(int argc, char **argv); +int cmd_gatt_show_local(int argc, char **argv); +int cmd_gatt_write(int argc, char **argv); + +#endif diff --git a/apps/btshell/src/cmd_l2cap.c b/apps/btshell/src/cmd_l2cap.c new file mode 100644 index 000000000..0fa068188 --- /dev/null +++ b/apps/btshell/src/cmd_l2cap.c @@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include + +#include "host/ble_gap.h" +#include "host/ble_l2cap.h" +#include "console/console.h" +#include "btshell.h" +#include "cmd.h" +#include "cmd_l2cap.h" + + +/***************************************************************************** + * $l2cap-update * + *****************************************************************************/ + +int +cmd_l2cap_update(int argc, char **argv) +{ + struct ble_l2cap_sig_update_params params; + uint16_t conn_handle; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + params.itvl_min = parse_arg_uint16_dflt("interval_min", + BLE_GAP_INITIAL_CONN_ITVL_MIN, + &rc); + if (rc != 0) { + console_printf("invalid 'interval_min' parameter\n"); + return rc; + } + + params.itvl_max = parse_arg_uint16_dflt("interval_max", + BLE_GAP_INITIAL_CONN_ITVL_MAX, + &rc); + if (rc != 0) { + console_printf("invalid 'interval_max' parameter\n"); + return rc; + } + + params.slave_latency = parse_arg_uint16_dflt("latency", 0, &rc); + if (rc != 0) { + console_printf("invalid 'latency' parameter\n"); + return rc; + } + + params.timeout_multiplier = parse_arg_uint16_dflt("timeout", 0x0100, &rc); + if (rc != 0) { + console_printf("invalid 'timeout' parameter\n"); + return rc; + } + + rc = btshell_l2cap_update(conn_handle, ¶ms); + if (rc != 0) { + console_printf("error txing l2cap update; rc=%d\n", rc); + return rc; + } + + return 0; +} + +/***************************************************************************** + * $l2cap-create-server * + *****************************************************************************/ + +int +cmd_l2cap_create_server(int argc, char **argv) +{ + uint16_t psm = 0; + int error; + int accept_response = 0; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + psm = parse_arg_uint16("psm", &rc); + if (rc != 0) { + console_printf("invalid 'psm' parameter\n"); + return rc; + } + + error = parse_arg_uint32_dflt("error", 0, &rc); + if (rc != 0) { + console_printf("invalid 'error' parameter\n"); + return rc; + } + + switch (error) { + case 1: + accept_response = BLE_HS_EAUTHEN; + break; + case 2: + accept_response = BLE_HS_EAUTHOR; + break; + case 3: + accept_response = BLE_HS_EENCRYPT_KEY_SZ; + break; + } + + rc = btshell_l2cap_create_srv(psm, accept_response); + if (rc) { + console_printf("Server create error: 0x%02x", rc); + } + + return 0; +} + +/***************************************************************************** + * $l2cap-connect * + *****************************************************************************/ + +int +cmd_l2cap_connect(int argc, char **argv) +{ + uint16_t conn = 0; + uint16_t psm = 0; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + psm = parse_arg_uint16("psm", &rc); + if (rc != 0) { + console_printf("invalid 'psm' parameter\n"); + return rc; + } + + return btshell_l2cap_connect(conn, psm); +} + +/***************************************************************************** + * $l2cap-disconnect * + *****************************************************************************/ + +int +cmd_l2cap_disconnect(int argc, char **argv) +{ + uint16_t conn; + uint16_t idx; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + idx = parse_arg_uint16("idx", &rc); + if (rc != 0) { + console_printf("invalid 'idx' parameter\n"); + return rc; + } + + return btshell_l2cap_disconnect(conn, idx); +} + +/***************************************************************************** + * $l2cap-send * + *****************************************************************************/ + +int +cmd_l2cap_send(int argc, char **argv) +{ + uint16_t conn; + uint16_t idx; + uint16_t bytes; + int rc; + + rc = parse_arg_all(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + idx = parse_arg_uint16("idx", &rc); + if (rc != 0) { + console_printf("invalid 'idx' parameter\n"); + return rc; + } + + bytes = parse_arg_uint16("bytes", &rc); + if (rc != 0) { + console_printf("invalid 'bytes' parameter\n"); + return rc; + } + + return btshell_l2cap_send(conn, idx, bytes); +} + +int +cmd_l2cap_show_coc(int argc, char **argv) +{ + struct btshell_conn *conn = NULL; + struct btshell_l2cap_coc *coc; + int i, j; + + for (i = 0; i < btshell_num_conns; i++) { + conn = btshell_conns + i; + + if (SLIST_EMPTY(&conn->coc_list)) { + continue; + } + + console_printf("conn_handle: 0x%04x\n", conn->handle); + j = 0; + SLIST_FOREACH(coc, &conn->coc_list, next) { + console_printf(" idx: %i, chan pointer = %p\n", j++, coc->chan); + } + } + + return 0; +} diff --git a/apps/btshell/src/cmd_l2cap.h b/apps/btshell/src/cmd_l2cap.h new file mode 100644 index 000000000..164b02dc8 --- /dev/null +++ b/apps/btshell/src/cmd_l2cap.h @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CMD_L2CAP_H +#define CMD_L2CAP_H + +#include "cmd.h" + +int cmd_l2cap_update(int argc, char **argv); +int cmd_l2cap_create_server(int argc, char **argv); +int cmd_l2cap_connect(int argc, char **argv); +int cmd_l2cap_disconnect(int argc, char **argv); +int cmd_l2cap_send(int argc, char **argv); +int cmd_l2cap_show_coc(int argc, char **argv); + +#endif diff --git a/apps/btshell/src/gatt_svr.c b/apps/btshell/src/gatt_svr.c new file mode 100644 index 000000000..792a41772 --- /dev/null +++ b/apps/btshell/src/gatt_svr.c @@ -0,0 +1,595 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include "bsp/bsp.h" +#include "console/console.h" +#include "host/ble_hs.h" +#include "host/ble_uuid.h" +#include "host/ble_gatt.h" +#include "btshell.h" + +/* 0000xxxx-8c26-476f-89a7-a108033a69c7 */ +#define PTS_UUID_DECLARE(uuid16) \ + ((const ble_uuid_t *) (&(ble_uuid128_t) BLE_UUID128_INIT( \ + 0xc7, 0x69, 0x3a, 0x03, 0x08, 0xa1, 0xa7, 0x89, \ + 0x6f, 0x47, 0x26, 0x8c, uuid16, uuid16 >> 8, 0x00, 0x00 \ + ))) + +#define PTS_SVC 0x0001 +#define PTS_CHR_READ 0x0002 +#define PTS_CHR_WRITE 0x0003 +#define PTS_CHR_RELIABLE_WRITE 0x0004 +#define PTS_CHR_WRITE_NO_RSP 0x0005 +#define PTS_CHR_READ_WRITE 0x0006 +#define PTS_CHR_READ_WRITE_ENC 0x0007 +#define PTS_CHR_READ_WRITE_AUTHEN 0x0008 +#define PTS_DSC_READ 0x0009 +#define PTS_DSC_WRITE 0x000a +#define PTS_DSC_READ_WRITE 0x000b +#define PTS_DSC_READ_WRITE_ENC 0x000c +#define PTS_DSC_READ_WRITE_AUTHEN 0x000d + +#define PTS_LONG_SVC 0x0011 +#define PTS_LONG_CHR_READ 0x0012 +#define PTS_LONG_CHR_WRITE 0x0013 +#define PTS_LONG_CHR_RELIABLE_WRITE 0x0014 +#define PTS_LONG_CHR_READ_WRITE 0x0015 +#define PTS_LONG_CHR_READ_WRITE_ALT 0x0016 +#define PTS_LONG_CHR_READ_WRITE_ENC 0x0017 +#define PTS_LONG_CHR_READ_WRITE_AUTHEN 0x0018 +#define PTS_LONG_DSC_READ 0x0019 +#define PTS_LONG_DSC_WRITE 0x001a +#define PTS_LONG_DSC_READ_WRITE 0x001b +#define PTS_LONG_DSC_READ_WRITE_ENC 0x001c +#define PTS_LONG_DSC_READ_WRITE_AUTHEN 0x001d + +/** + * The vendor specific security test service consists of two characteristics: + * o random-number-generator: generates a random 32-bit number each time + * it is read. This characteristic can only be read over an encrypted + * connection. + * o static-value: a single-byte characteristic that can always be read, + * but can only be written over an encrypted connection. + */ + +/* 59462f12-9543-9999-12c8-58b459a2712d */ +static const ble_uuid128_t gatt_svr_svc_sec_test_uuid = + BLE_UUID128_INIT(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12, + 0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59); + +/* 5c3a659e-897e-45e1-b016-007107c96df6 */ +static const ble_uuid128_t gatt_svr_chr_sec_test_rand_uuid = + BLE_UUID128_INIT(0xf6, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0, + 0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c); + +/* 5c3a659e-897e-45e1-b016-007107c96df7 */ +static const ble_uuid128_t gatt_svr_chr_sec_test_static_uuid = + BLE_UUID128_INIT(0xf7, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0, + 0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c); + +/* 5c3a659e-897e-45e1-b016-007107c96df8 */ +static const ble_uuid128_t gatt_svr_chr_sec_test_static_auth_uuid = + BLE_UUID128_INIT(0xf8, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0, + 0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c); + +static uint8_t gatt_svr_sec_test_static_val; + +static uint8_t gatt_svr_pts_static_val; +static uint8_t gatt_svr_pts_static_long_val[30]; +static uint8_t gatt_svr_pts_static_long_val_alt[30]; + +static int +gatt_svr_chr_access_sec_test(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg); + +static int +gatt_svr_access_test(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg); + +static int +gatt_svr_long_access_test(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg); + +static const struct ble_gatt_svc_def gatt_svr_svcs[] = { + { + /*** Service: PTS test. */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = PTS_UUID_DECLARE(PTS_SVC), + .characteristics = (struct ble_gatt_chr_def[]) { { + .uuid = PTS_UUID_DECLARE(PTS_CHR_READ), + .access_cb = gatt_svr_access_test, + .flags = BLE_GATT_CHR_F_READ, + }, { + .uuid = PTS_UUID_DECLARE(PTS_CHR_WRITE), + .access_cb = gatt_svr_access_test, + .flags = BLE_GATT_CHR_F_WRITE, + }, { + .uuid = PTS_UUID_DECLARE(PTS_CHR_RELIABLE_WRITE), + .access_cb = gatt_svr_access_test, + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_RELIABLE_WRITE, + }, { + .uuid = PTS_UUID_DECLARE(PTS_CHR_WRITE_NO_RSP), + .access_cb = gatt_svr_access_test, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE_NO_RSP, + }, { + .uuid = PTS_UUID_DECLARE(PTS_CHR_READ_WRITE), + .access_cb = gatt_svr_access_test, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE, + }, { + .uuid = PTS_UUID_DECLARE(PTS_CHR_READ_WRITE_ENC), + .access_cb = gatt_svr_access_test, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC | + BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC, + .min_key_size = 16, + }, { + .uuid = PTS_UUID_DECLARE(PTS_CHR_READ_WRITE_AUTHEN), + .access_cb = gatt_svr_access_test, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_AUTHEN | + BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_AUTHEN, + + .descriptors = (struct ble_gatt_dsc_def[]){ { + .uuid = PTS_UUID_DECLARE(PTS_DSC_READ), + .access_cb = gatt_svr_access_test, + .att_flags = BLE_ATT_F_READ, + }, { + .uuid = PTS_UUID_DECLARE(PTS_DSC_WRITE), + .access_cb = gatt_svr_access_test, + .att_flags = BLE_ATT_F_WRITE, + }, { + .uuid = PTS_UUID_DECLARE(PTS_DSC_READ_WRITE), + .access_cb = gatt_svr_access_test, + .att_flags = BLE_ATT_F_READ | BLE_ATT_F_WRITE, + }, { + .uuid = PTS_UUID_DECLARE(PTS_DSC_READ_WRITE_ENC), + .access_cb = gatt_svr_access_test, + .att_flags = BLE_ATT_F_READ | BLE_ATT_F_READ_ENC | + BLE_ATT_F_WRITE | BLE_ATT_F_WRITE_ENC, + .min_key_size = 16, + }, { + .uuid = PTS_UUID_DECLARE(PTS_DSC_READ_WRITE_AUTHEN), + .access_cb = gatt_svr_access_test, + .att_flags = BLE_ATT_F_READ | BLE_ATT_F_READ_AUTHEN | + BLE_ATT_F_WRITE | BLE_ATT_F_WRITE_AUTHEN, + }, { + 0, /* No more descriptors in this characteristic. */ + } } + }, { + 0, /* No more characteristics in this service. */ + } }, + }, + + { + /*** Service: PTS long test. */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = PTS_UUID_DECLARE(PTS_LONG_SVC), + .characteristics = (struct ble_gatt_chr_def[]) { { + .uuid = PTS_UUID_DECLARE(PTS_LONG_CHR_READ), + .access_cb = gatt_svr_long_access_test, + .flags = BLE_GATT_CHR_F_READ, + }, { + .uuid = PTS_UUID_DECLARE(PTS_LONG_CHR_WRITE), + .access_cb = gatt_svr_long_access_test, + .flags = BLE_GATT_CHR_F_WRITE, + }, { + .uuid = PTS_UUID_DECLARE(PTS_LONG_CHR_RELIABLE_WRITE), + .access_cb = gatt_svr_long_access_test, + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_RELIABLE_WRITE, + }, { + .uuid = PTS_UUID_DECLARE(PTS_LONG_CHR_READ_WRITE), + .access_cb = gatt_svr_long_access_test, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE, + }, { + .uuid = PTS_UUID_DECLARE(PTS_LONG_CHR_READ_WRITE_ALT), + .access_cb = gatt_svr_long_access_test, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE, + }, { + .uuid = PTS_UUID_DECLARE(PTS_LONG_CHR_READ_WRITE_ENC), + .access_cb = gatt_svr_long_access_test, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC | + BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC, + .min_key_size = 16, + }, { + .uuid = PTS_UUID_DECLARE(PTS_LONG_CHR_READ_WRITE_AUTHEN), + .access_cb = gatt_svr_long_access_test, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_AUTHEN | + BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_AUTHEN, + + .descriptors = (struct ble_gatt_dsc_def[]){ { + .uuid = PTS_UUID_DECLARE(PTS_LONG_DSC_READ), + .access_cb = gatt_svr_long_access_test, + .att_flags = BLE_ATT_F_READ, + }, { + .uuid = PTS_UUID_DECLARE(PTS_LONG_DSC_WRITE), + .access_cb = gatt_svr_long_access_test, + .att_flags = BLE_ATT_F_WRITE, + }, { + .uuid = PTS_UUID_DECLARE(PTS_LONG_DSC_READ_WRITE), + .access_cb = gatt_svr_long_access_test, + .att_flags = BLE_ATT_F_READ | BLE_ATT_F_WRITE, + }, { + .uuid = PTS_UUID_DECLARE(PTS_LONG_DSC_READ_WRITE_ENC), + .access_cb = gatt_svr_long_access_test, + .att_flags = BLE_ATT_F_READ | BLE_ATT_F_READ_ENC | + BLE_ATT_F_WRITE | BLE_ATT_F_WRITE_ENC, + .min_key_size = 16, + }, { + .uuid = PTS_UUID_DECLARE(PTS_LONG_DSC_READ_WRITE_AUTHEN), + .access_cb = gatt_svr_long_access_test, + .att_flags = BLE_ATT_F_READ | BLE_ATT_F_READ_AUTHEN | + BLE_ATT_F_WRITE | BLE_ATT_F_WRITE_AUTHEN, + }, { + 0, /* No more descriptors in this characteristic. */ + } } + }, { + 0, /* No more characteristics in this service. */ + } }, + }, + + { + /*** Service: Security test. */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = &gatt_svr_svc_sec_test_uuid.u, + .characteristics = (struct ble_gatt_chr_def[]) { { + /*** Characteristic: Random number generator. */ + .uuid = &gatt_svr_chr_sec_test_rand_uuid.u, + .access_cb = gatt_svr_chr_access_sec_test, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC, + }, { + /*** Characteristic: Static value. */ + .uuid = &gatt_svr_chr_sec_test_static_uuid.u, + .access_cb = gatt_svr_chr_access_sec_test, + .flags = BLE_GATT_CHR_F_READ | + BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC, + }, { + /*** Characteristic: Static value. */ + .uuid = &gatt_svr_chr_sec_test_static_auth_uuid.u, + .access_cb = gatt_svr_chr_access_sec_test, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_AUTHEN, + }, { + 0, /* No more characteristics in this service. */ + } }, + }, + + { + 0, /* No more services. */ + }, +}; + +static int +gatt_svr_chr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len, + void *dst, uint16_t *len) +{ + uint16_t om_len; + int rc; + + om_len = OS_MBUF_PKTLEN(om); + if (om_len < min_len || om_len > max_len) { + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + + rc = ble_hs_mbuf_to_flat(om, dst, max_len, len); + if (rc != 0) { + return BLE_ATT_ERR_UNLIKELY; + } + + return 0; +} + +static int +gatt_svr_chr_access_sec_test(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg) +{ + const ble_uuid_t *uuid; + int rand_num; + int rc; + + uuid = ctxt->chr->uuid; + + /* Determine which characteristic is being accessed by examining its + * 128-bit UUID. + */ + + if (ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_rand_uuid.u) == 0) { + assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR); + + /* Respond with a 32-bit random number. */ + rand_num = rand(); + rc = os_mbuf_append(ctxt->om, &rand_num, sizeof rand_num); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + if (ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_static_uuid.u) == 0 || + ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_static_auth_uuid.u) == 0) { + switch (ctxt->op) { + case BLE_GATT_ACCESS_OP_READ_CHR: + rc = os_mbuf_append(ctxt->om, &gatt_svr_sec_test_static_val, + sizeof gatt_svr_sec_test_static_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + + case BLE_GATT_ACCESS_OP_WRITE_CHR: + rc = gatt_svr_chr_write(ctxt->om, + sizeof gatt_svr_sec_test_static_val, + sizeof gatt_svr_sec_test_static_val, + &gatt_svr_sec_test_static_val, NULL); + return rc; + + default: + assert(0); + return BLE_ATT_ERR_UNLIKELY; + } + } + + /* Unknown characteristic; the nimble stack should not have called this + * function. + */ + assert(0); + return BLE_ATT_ERR_UNLIKELY; +} + +/* This method is used for PTS testing only, to extract 16 bit value + * from 128 bit vendor specific UUID. + */ +static uint16_t +extract_uuid16_from_pts_uuid128(const ble_uuid_t *uuid) +{ + const uint8_t *u8ptr; + uint16_t uuid16; + + u8ptr = BLE_UUID128(uuid)->value; + uuid16 = u8ptr[12]; + uuid16 |= (uint16_t)u8ptr[13] << 8; + return uuid16; +} + +static int +gatt_svr_access_test(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg) +{ + uint16_t uuid16; + int rc; + + uuid16 = extract_uuid16_from_pts_uuid128(ctxt->chr->uuid); + assert(uuid16 != 0); + + switch (uuid16) { + case PTS_CHR_READ: + assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR); + rc = os_mbuf_append(ctxt->om, &gatt_svr_pts_static_val, + sizeof gatt_svr_pts_static_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + + case PTS_CHR_WRITE: + case PTS_CHR_RELIABLE_WRITE: + case PTS_CHR_WRITE_NO_RSP: + if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { + rc = gatt_svr_chr_write(ctxt->om,0, + sizeof gatt_svr_pts_static_val, + &gatt_svr_pts_static_val, NULL); + return rc; + } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { + rc = os_mbuf_append(ctxt->om, &gatt_svr_pts_static_val, + sizeof gatt_svr_pts_static_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + case PTS_CHR_READ_WRITE: + case PTS_CHR_READ_WRITE_ENC: + case PTS_CHR_READ_WRITE_AUTHEN: + if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { + rc = gatt_svr_chr_write(ctxt->om,0, + sizeof gatt_svr_pts_static_val, + &gatt_svr_pts_static_val, NULL); + return rc; + } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { + rc = os_mbuf_append(ctxt->om, &gatt_svr_pts_static_val, + sizeof gatt_svr_pts_static_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + case PTS_DSC_READ: + assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC); + rc = os_mbuf_append(ctxt->om, &gatt_svr_pts_static_val, + sizeof gatt_svr_pts_static_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + + case PTS_DSC_WRITE: + assert(ctxt->op == BLE_GATT_ACCESS_OP_WRITE_DSC); + rc = gatt_svr_chr_write(ctxt->om,0, + sizeof gatt_svr_pts_static_val, + &gatt_svr_pts_static_val, NULL); + return rc; + + case PTS_DSC_READ_WRITE: + case PTS_DSC_READ_WRITE_ENC: + case PTS_DSC_READ_WRITE_AUTHEN: + if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_DSC) { + rc = gatt_svr_chr_write(ctxt->om,0, + sizeof gatt_svr_pts_static_val, + &gatt_svr_pts_static_val, NULL); + return rc; + } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC) { + rc = os_mbuf_append(ctxt->om, &gatt_svr_pts_static_val, + sizeof gatt_svr_pts_static_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + default: + assert(0); + return BLE_ATT_ERR_UNLIKELY; + } +} + +static int +gatt_svr_long_access_test(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg) +{ + uint16_t uuid16; + int rc; + + uuid16 = extract_uuid16_from_pts_uuid128(ctxt->chr->uuid); + assert(uuid16 != 0); + + switch (uuid16) { + case PTS_LONG_CHR_READ: + assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR); + rc = os_mbuf_append(ctxt->om, &gatt_svr_pts_static_long_val, + sizeof gatt_svr_pts_static_long_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + + case PTS_LONG_CHR_WRITE: + case PTS_LONG_CHR_RELIABLE_WRITE: + assert(ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR); + rc = gatt_svr_chr_write(ctxt->om,0, + sizeof gatt_svr_pts_static_long_val, + &gatt_svr_pts_static_long_val, NULL); + return rc; + + case PTS_LONG_CHR_READ_WRITE: + if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { + rc = gatt_svr_chr_write(ctxt->om,0, + sizeof gatt_svr_pts_static_long_val, + &gatt_svr_pts_static_long_val, NULL); + return rc; + } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { + rc = os_mbuf_append(ctxt->om, &gatt_svr_pts_static_long_val, + sizeof gatt_svr_pts_static_long_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + case PTS_LONG_CHR_READ_WRITE_ALT: + if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { + rc = gatt_svr_chr_write(ctxt->om,0, + sizeof gatt_svr_pts_static_long_val_alt, + &gatt_svr_pts_static_long_val_alt, NULL); + return rc; + } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { + rc = os_mbuf_append(ctxt->om, &gatt_svr_pts_static_long_val_alt, + sizeof gatt_svr_pts_static_long_val_alt); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + case PTS_LONG_CHR_READ_WRITE_ENC: + case PTS_LONG_CHR_READ_WRITE_AUTHEN: + if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { + rc = gatt_svr_chr_write(ctxt->om,0, + sizeof gatt_svr_pts_static_long_val, + &gatt_svr_pts_static_long_val, NULL); + return rc; + } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { + rc = os_mbuf_append(ctxt->om, &gatt_svr_pts_static_long_val, + sizeof gatt_svr_pts_static_long_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + case PTS_LONG_DSC_READ: + assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC); + rc = os_mbuf_append(ctxt->om, &gatt_svr_pts_static_long_val, + sizeof gatt_svr_pts_static_long_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + + case PTS_LONG_DSC_WRITE: + assert(ctxt->op == BLE_GATT_ACCESS_OP_WRITE_DSC); + rc = gatt_svr_chr_write(ctxt->om,0, + sizeof gatt_svr_pts_static_long_val, + &gatt_svr_pts_static_long_val, NULL); + return rc; + + case PTS_LONG_DSC_READ_WRITE: + case PTS_LONG_DSC_READ_WRITE_ENC: + case PTS_LONG_DSC_READ_WRITE_AUTHEN: + if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_DSC) { + rc = gatt_svr_chr_write(ctxt->om,0, + sizeof gatt_svr_pts_static_long_val, + &gatt_svr_pts_static_long_val, NULL); + return rc; + } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC) { + rc = os_mbuf_append(ctxt->om, &gatt_svr_pts_static_long_val, + sizeof gatt_svr_pts_static_long_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + default: + assert(0); + return BLE_ATT_ERR_UNLIKELY; + } +} + +void +gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) +{ + char buf[BLE_UUID_STR_LEN]; + + switch (ctxt->op) { + case BLE_GATT_REGISTER_OP_SVC: + BTSHELL_LOG(DEBUG, "registered service %s with handle=%d\n", + ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf), + ctxt->svc.handle); + break; + + case BLE_GATT_REGISTER_OP_CHR: + BTSHELL_LOG(DEBUG, "registering characteristic %s with " + "def_handle=%d val_handle=%d\n", + ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), + ctxt->chr.def_handle, + ctxt->chr.val_handle); + break; + + case BLE_GATT_REGISTER_OP_DSC: + BTSHELL_LOG(DEBUG, "registering descriptor %s with handle=%d\n", + ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf), + ctxt->dsc.handle); + break; + + default: + assert(0); + break; + } +} + +void +gatt_svr_print_svcs(void) +{ + ble_gatts_show_local(); +} + +int +gatt_svr_init(void) +{ + int rc; + + rc = ble_gatts_count_cfg(gatt_svr_svcs); + if (rc != 0) { + return rc; + } + + rc = ble_gatts_add_svcs(gatt_svr_svcs); + if (rc != 0) { + return rc; + } + + return 0; +} diff --git a/apps/btshell/src/main.c b/apps/btshell/src/main.c new file mode 100644 index 000000000..103668270 --- /dev/null +++ b/apps/btshell/src/main.c @@ -0,0 +1,2116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include "os/mynewt.h" +#include "bsp/bsp.h" +#include "log/log.h" +#include "stats/stats.h" +#include "bsp/bsp.h" +#include "hal/hal_gpio.h" +#include "console/console.h" +#include "btshell.h" +#include "cmd.h" + +/* BLE */ +#include "nimble/ble.h" +#include "nimble/nimble_opt.h" +#include "nimble/ble_hci_trans.h" +#include "host/ble_hs.h" +#include "host/ble_hs_adv.h" +#include "host/ble_uuid.h" +#include "host/ble_att.h" +#include "host/ble_gap.h" +#include "host/ble_gatt.h" +#include "host/ble_store.h" +#include "host/ble_sm.h" + +/* Mandatory services. */ +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" + +/* XXX: An app should not include private headers from a library. The btshell + * app uses some of nimble's internal details for logging. + */ +#include "../src/ble_hs_conn_priv.h" +#include "../src/ble_hs_atomic_priv.h" +#include "../src/ble_hs_hci_priv.h" + +#if MYNEWT_VAL(BLE_ROLE_CENTRAL) +#define BTSHELL_MAX_SVCS 32 +#define BTSHELL_MAX_CHRS 64 +#define BTSHELL_MAX_DSCS 64 +#else +#define BTSHELL_MAX_SVCS 1 +#define BTSHELL_MAX_CHRS 1 +#define BTSHELL_MAX_DSCS 1 +#endif + +#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) +#define BTSHELL_COC_MTU (256) +/* We use same pool for incoming and outgoing sdu */ +#define BTSHELL_COC_BUF_COUNT (3 * MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM)) + +#define INT_TO_PTR(x) (void *)((intptr_t)(x)) +#define PTR_TO_INT(x) (int) ((intptr_t)(x)) +#endif + +struct log btshell_log; + +bssnz_t struct btshell_conn btshell_conns[MYNEWT_VAL(BLE_MAX_CONNECTIONS)]; +int btshell_num_conns; + +static os_membuf_t btshell_svc_mem[ + OS_MEMPOOL_SIZE(BTSHELL_MAX_SVCS, sizeof(struct btshell_svc)) +]; +static struct os_mempool btshell_svc_pool; + +static os_membuf_t btshell_chr_mem[ + OS_MEMPOOL_SIZE(BTSHELL_MAX_CHRS, sizeof(struct btshell_chr)) +]; +static struct os_mempool btshell_chr_pool; + +static os_membuf_t btshell_dsc_mem[ + OS_MEMPOOL_SIZE(BTSHELL_MAX_DSCS, sizeof(struct btshell_dsc)) +]; +static struct os_mempool btshell_dsc_pool; + +#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) +static os_membuf_t btshell_coc_conn_mem[ + OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM), + sizeof(struct btshell_l2cap_coc)) +]; +static struct os_mempool btshell_coc_conn_pool; + +static os_membuf_t btshell_sdu_coc_mem[ + OS_MEMPOOL_SIZE(BTSHELL_COC_BUF_COUNT, BTSHELL_COC_MTU) +]; +struct os_mbuf_pool sdu_os_mbuf_pool; +static struct os_mempool sdu_coc_mbuf_mempool; +#endif + +static struct os_callout btshell_tx_timer; +struct btshell_tx_data_s +{ + uint16_t tx_num; + uint16_t tx_rate; + uint16_t tx_handle; + uint16_t tx_len; +}; +static struct btshell_tx_data_s btshell_tx_data; +int btshell_full_disc_prev_chr_val; + +#define XSTR(s) STR(s) +#ifndef STR +#define STR(s) #s +#endif + + +#ifdef DEVICE_NAME +#define BTSHELL_AUTO_DEVICE_NAME XSTR(DEVICE_NAME) +#else +#define BTSHELL_AUTO_DEVICE_NAME "" +#endif + +static void +btshell_print_error(char *msg, uint16_t conn_handle, + const struct ble_gatt_error *error) +{ + if (msg == NULL) { + msg = "ERROR"; + } + + console_printf("%s: conn_handle=%d status=%d att_handle=%d\n", + msg, conn_handle, error->status, error->att_handle); +} + +static void +btshell_print_adv_fields(const struct ble_hs_adv_fields *fields) +{ + uint8_t *u8p; + int i; + + if (fields->flags != 0) { + console_printf(" flags=0x%02x:\n", fields->flags); + + if (!(fields->flags & BLE_HS_ADV_F_DISC_LTD) && + !(fields->flags & BLE_HS_ADV_F_DISC_GEN)) { + console_printf(" Non-discoverable mode\n"); + } + + if (fields->flags & BLE_HS_ADV_F_DISC_LTD) { + console_printf(" Limited discoverable mode\n"); + } + + if (fields->flags & BLE_HS_ADV_F_DISC_GEN) { + console_printf(" General discoverable mode\n"); + } + + if (fields->flags & BLE_HS_ADV_F_BREDR_UNSUP) { + console_printf(" BR/EDR not supported\n"); + } + } + + if (fields->uuids16 != NULL) { + console_printf(" uuids16(%scomplete)=", + fields->uuids16_is_complete ? "" : "in"); + for (i = 0; i < fields->num_uuids16; i++) { + print_uuid(&fields->uuids16[i].u); + console_printf(" "); + } + console_printf("\n"); + } + + if (fields->uuids32 != NULL) { + console_printf(" uuids32(%scomplete)=", + fields->uuids32_is_complete ? "" : "in"); + for (i = 0; i < fields->num_uuids32; i++) { + print_uuid(&fields->uuids32[i].u); + console_printf(" "); + } + console_printf("\n"); + } + + if (fields->uuids128 != NULL) { + console_printf(" uuids128(%scomplete)=", + fields->uuids128_is_complete ? "" : "in"); + for (i = 0; i < fields->num_uuids128; i++) { + print_uuid(&fields->uuids128[i].u); + console_printf(" "); + } + console_printf("\n"); + } + + if (fields->name != NULL) { + console_printf(" name(%scomplete)=", + fields->name_is_complete ? "" : "in"); + console_write((char *)fields->name, fields->name_len); + console_printf("\n"); + } + + if (fields->tx_pwr_lvl_is_present) { + console_printf(" tx_pwr_lvl=%d\n", fields->tx_pwr_lvl); + } + + if (fields->slave_itvl_range != NULL) { + console_printf(" slave_itvl_range="); + print_bytes(fields->slave_itvl_range, + BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN); + console_printf("\n"); + } + + if (fields->svc_data_uuid16 != NULL) { + console_printf(" svc_data_uuid16="); + print_bytes(fields->svc_data_uuid16, + fields->svc_data_uuid16_len); + console_printf("\n"); + } + + if (fields->public_tgt_addr != NULL) { + console_printf(" public_tgt_addr="); + u8p = fields->public_tgt_addr; + for (i = 0; i < fields->num_public_tgt_addrs; i++) { + print_addr(u8p); + u8p += BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN; + } + console_printf("\n"); + } + + if (fields->appearance_is_present) { + console_printf(" appearance=0x%04x\n", fields->appearance); + } + + if (fields->adv_itvl_is_present) { + console_printf(" adv_itvl=0x%04x\n", fields->adv_itvl); + } + + if (fields->svc_data_uuid32 != NULL) { + console_printf(" svc_data_uuid32="); + print_bytes(fields->svc_data_uuid32, + fields->svc_data_uuid32_len); + console_printf("\n"); + } + + if (fields->svc_data_uuid128 != NULL) { + console_printf(" svc_data_uuid128="); + print_bytes(fields->svc_data_uuid128, + fields->svc_data_uuid128_len); + console_printf("\n"); + } + + if (fields->uri != NULL) { + console_printf(" uri="); + print_bytes(fields->uri, fields->uri_len); + console_printf("\n"); + } + + if (fields->mfg_data != NULL) { + console_printf(" mfg_data="); + print_bytes(fields->mfg_data, fields->mfg_data_len); + console_printf("\n"); + } +} + +static int +btshell_conn_find_idx(uint16_t handle) +{ + int i; + + for (i = 0; i < btshell_num_conns; i++) { + if (btshell_conns[i].handle == handle) { + return i; + } + } + + return -1; +} + +static struct btshell_conn * +btshell_conn_find(uint16_t handle) +{ + int idx; + + idx = btshell_conn_find_idx(handle); + if (idx == -1) { + return NULL; + } else { + return btshell_conns + idx; + } +} + +static struct btshell_svc * +btshell_svc_find_prev(struct btshell_conn *conn, uint16_t svc_start_handle) +{ + struct btshell_svc *prev; + struct btshell_svc *svc; + + prev = NULL; + SLIST_FOREACH(svc, &conn->svcs, next) { + if (svc->svc.start_handle >= svc_start_handle) { + break; + } + + prev = svc; + } + + return prev; +} + +static struct btshell_svc * +btshell_svc_find(struct btshell_conn *conn, uint16_t svc_start_handle, + struct btshell_svc **out_prev) +{ + struct btshell_svc *prev; + struct btshell_svc *svc; + + prev = btshell_svc_find_prev(conn, svc_start_handle); + if (prev == NULL) { + svc = SLIST_FIRST(&conn->svcs); + } else { + svc = SLIST_NEXT(prev, next); + } + + if (svc != NULL && svc->svc.start_handle != svc_start_handle) { + svc = NULL; + } + + if (out_prev != NULL) { + *out_prev = prev; + } + return svc; +} + +static struct btshell_svc * +btshell_svc_find_range(struct btshell_conn *conn, uint16_t attr_handle) +{ + struct btshell_svc *svc; + + SLIST_FOREACH(svc, &conn->svcs, next) { + if (svc->svc.start_handle <= attr_handle && + svc->svc.end_handle >= attr_handle) { + + return svc; + } + } + + return NULL; +} + +static void +btshell_chr_delete(struct btshell_chr *chr) +{ + struct btshell_dsc *dsc; + + while ((dsc = SLIST_FIRST(&chr->dscs)) != NULL) { + SLIST_REMOVE_HEAD(&chr->dscs, next); + os_memblock_put(&btshell_dsc_pool, dsc); + } + + os_memblock_put(&btshell_chr_pool, chr); +} + +static void +btshell_svc_delete(struct btshell_svc *svc) +{ + struct btshell_chr *chr; + + while ((chr = SLIST_FIRST(&svc->chrs)) != NULL) { + SLIST_REMOVE_HEAD(&svc->chrs, next); + btshell_chr_delete(chr); + } + + os_memblock_put(&btshell_svc_pool, svc); +} + +static struct btshell_svc * +btshell_svc_add(uint16_t conn_handle, const struct ble_gatt_svc *gatt_svc) +{ + struct btshell_conn *conn; + struct btshell_svc *prev; + struct btshell_svc *svc; + + conn = btshell_conn_find(conn_handle); + if (conn == NULL) { + BTSHELL_LOG(DEBUG, "RECEIVED SERVICE FOR UNKNOWN CONNECTION; " + "HANDLE=%d\n", + conn_handle); + return NULL; + } + + svc = btshell_svc_find(conn, gatt_svc->start_handle, &prev); + if (svc != NULL) { + /* Service already discovered. */ + return svc; + } + + svc = os_memblock_get(&btshell_svc_pool); + if (svc == NULL) { + BTSHELL_LOG(DEBUG, "OOM WHILE DISCOVERING SERVICE\n"); + return NULL; + } + memset(svc, 0, sizeof *svc); + + svc->svc = *gatt_svc; + SLIST_INIT(&svc->chrs); + + if (prev == NULL) { + SLIST_INSERT_HEAD(&conn->svcs, svc, next); + } else { + SLIST_INSERT_AFTER(prev, svc, next); + } + + return svc; +} + +static struct btshell_chr * +btshell_chr_find_prev(const struct btshell_svc *svc, uint16_t chr_val_handle) +{ + struct btshell_chr *prev; + struct btshell_chr *chr; + + prev = NULL; + SLIST_FOREACH(chr, &svc->chrs, next) { + if (chr->chr.val_handle >= chr_val_handle) { + break; + } + + prev = chr; + } + + return prev; +} + +static struct btshell_chr * +btshell_chr_find(const struct btshell_svc *svc, uint16_t chr_val_handle, + struct btshell_chr **out_prev) +{ + struct btshell_chr *prev; + struct btshell_chr *chr; + + prev = btshell_chr_find_prev(svc, chr_val_handle); + if (prev == NULL) { + chr = SLIST_FIRST(&svc->chrs); + } else { + chr = SLIST_NEXT(prev, next); + } + + if (chr != NULL && chr->chr.val_handle != chr_val_handle) { + chr = NULL; + } + + if (out_prev != NULL) { + *out_prev = prev; + } + return chr; +} + +static struct btshell_chr * +btshell_chr_add(uint16_t conn_handle, uint16_t svc_start_handle, + const struct ble_gatt_chr *gatt_chr) +{ + struct btshell_conn *conn; + struct btshell_chr *prev; + struct btshell_chr *chr; + struct btshell_svc *svc; + + conn = btshell_conn_find(conn_handle); + if (conn == NULL) { + BTSHELL_LOG(DEBUG, "RECEIVED SERVICE FOR UNKNOWN CONNECTION; " + "HANDLE=%d\n", + conn_handle); + return NULL; + } + + svc = btshell_svc_find(conn, svc_start_handle, NULL); + if (svc == NULL) { + BTSHELL_LOG(DEBUG, "CAN'T FIND SERVICE FOR DISCOVERED CHR; HANDLE=%d\n", + conn_handle); + return NULL; + } + + chr = btshell_chr_find(svc, gatt_chr->val_handle, &prev); + if (chr != NULL) { + /* Characteristic already discovered. */ + return chr; + } + + chr = os_memblock_get(&btshell_chr_pool); + if (chr == NULL) { + BTSHELL_LOG(DEBUG, "OOM WHILE DISCOVERING CHARACTERISTIC\n"); + return NULL; + } + memset(chr, 0, sizeof *chr); + + chr->chr = *gatt_chr; + + if (prev == NULL) { + SLIST_INSERT_HEAD(&svc->chrs, chr, next); + } else { + SLIST_NEXT(prev, next) = chr; + } + + return chr; +} + +static struct btshell_dsc * +btshell_dsc_find_prev(const struct btshell_chr *chr, uint16_t dsc_handle) +{ + struct btshell_dsc *prev; + struct btshell_dsc *dsc; + + prev = NULL; + SLIST_FOREACH(dsc, &chr->dscs, next) { + if (dsc->dsc.handle >= dsc_handle) { + break; + } + + prev = dsc; + } + + return prev; +} + +static struct btshell_dsc * +btshell_dsc_find(const struct btshell_chr *chr, uint16_t dsc_handle, + struct btshell_dsc **out_prev) +{ + struct btshell_dsc *prev; + struct btshell_dsc *dsc; + + prev = btshell_dsc_find_prev(chr, dsc_handle); + if (prev == NULL) { + dsc = SLIST_FIRST(&chr->dscs); + } else { + dsc = SLIST_NEXT(prev, next); + } + + if (dsc != NULL && dsc->dsc.handle != dsc_handle) { + dsc = NULL; + } + + if (out_prev != NULL) { + *out_prev = prev; + } + return dsc; +} + +static struct btshell_dsc * +btshell_dsc_add(uint16_t conn_handle, uint16_t chr_val_handle, + const struct ble_gatt_dsc *gatt_dsc) +{ + struct btshell_conn *conn; + struct btshell_dsc *prev; + struct btshell_dsc *dsc; + struct btshell_svc *svc; + struct btshell_chr *chr; + + conn = btshell_conn_find(conn_handle); + if (conn == NULL) { + BTSHELL_LOG(DEBUG, "RECEIVED SERVICE FOR UNKNOWN CONNECTION; " + "HANDLE=%d\n", + conn_handle); + return NULL; + } + + svc = btshell_svc_find_range(conn, chr_val_handle); + if (svc == NULL) { + BTSHELL_LOG(DEBUG, "CAN'T FIND SERVICE FOR DISCOVERED DSC; HANDLE=%d\n", + conn_handle); + return NULL; + } + + chr = btshell_chr_find(svc, chr_val_handle, NULL); + if (chr == NULL) { + BTSHELL_LOG(DEBUG, "CAN'T FIND CHARACTERISTIC FOR DISCOVERED DSC; " + "HANDLE=%d\n", + conn_handle); + return NULL; + } + + dsc = btshell_dsc_find(chr, gatt_dsc->handle, &prev); + if (dsc != NULL) { + /* Descriptor already discovered. */ + return dsc; + } + + dsc = os_memblock_get(&btshell_dsc_pool); + if (dsc == NULL) { + console_printf("OOM WHILE DISCOVERING DESCRIPTOR\n"); + return NULL; + } + memset(dsc, 0, sizeof *dsc); + + dsc->dsc = *gatt_dsc; + + if (prev == NULL) { + SLIST_INSERT_HEAD(&chr->dscs, dsc, next); + } else { + SLIST_NEXT(prev, next) = dsc; + } + + return dsc; +} + +static struct btshell_conn * +btshell_conn_add(struct ble_gap_conn_desc *desc) +{ + struct btshell_conn *conn; + + assert(btshell_num_conns < MYNEWT_VAL(BLE_MAX_CONNECTIONS)); + + conn = btshell_conns + btshell_num_conns; + btshell_num_conns++; + + conn->handle = desc->conn_handle; + SLIST_INIT(&conn->svcs); + SLIST_INIT(&conn->coc_list); + + return conn; +} + +static void +btshell_conn_delete_idx(int idx) +{ + struct btshell_conn *conn; + struct btshell_svc *svc; + + assert(idx >= 0 && idx < btshell_num_conns); + + conn = btshell_conns + idx; + while ((svc = SLIST_FIRST(&conn->svcs)) != NULL) { + SLIST_REMOVE_HEAD(&conn->svcs, next); + btshell_svc_delete(svc); + } + + /* This '#if' is not strictly necessary. It is here to prevent a spurious + * warning from being reported. + */ +#if MYNEWT_VAL(BLE_MAX_CONNECTIONS) > 1 + int i; + for (i = idx + 1; i < btshell_num_conns; i++) { + btshell_conns[i - 1] = btshell_conns[i]; + } +#endif + + btshell_num_conns--; +} + +static int +btshell_on_mtu(uint16_t conn_handle, const struct ble_gatt_error *error, + uint16_t mtu, void *arg) +{ + switch (error->status) { + case 0: + console_printf("mtu exchange complete: conn_handle=%d mtu=%d\n", + conn_handle, mtu); + break; + + default: + btshell_print_error(NULL, conn_handle, error); + break; + } + + return 0; +} + +static void +btshell_full_disc_complete(int rc) +{ + console_printf("full discovery complete; rc=%d\n", rc); + btshell_full_disc_prev_chr_val = 0; +} + +static void +btshell_disc_full_dscs(uint16_t conn_handle) +{ + struct btshell_conn *conn; + struct btshell_chr *chr; + struct btshell_svc *svc; + int rc; + + conn = btshell_conn_find(conn_handle); + if (conn == NULL) { + BTSHELL_LOG(DEBUG, "Failed to discover descriptors for conn=%d; " + "not connected\n", conn_handle); + btshell_full_disc_complete(BLE_HS_ENOTCONN); + return; + } + + SLIST_FOREACH(svc, &conn->svcs, next) { + SLIST_FOREACH(chr, &svc->chrs, next) { + if (!chr_is_empty(svc, chr) && + SLIST_EMPTY(&chr->dscs) && + btshell_full_disc_prev_chr_val <= chr->chr.def_handle) { + + rc = btshell_disc_all_dscs(conn_handle, + chr->chr.val_handle, + chr_end_handle(svc, chr)); + if (rc != 0) { + btshell_full_disc_complete(rc); + } + + btshell_full_disc_prev_chr_val = chr->chr.val_handle; + return; + } + } + } + + /* All descriptors discovered. */ + btshell_full_disc_complete(0); +} + +static void +btshell_disc_full_chrs(uint16_t conn_handle) +{ + struct btshell_conn *conn; + struct btshell_svc *svc; + int rc; + + conn = btshell_conn_find(conn_handle); + if (conn == NULL) { + BTSHELL_LOG(DEBUG, "Failed to discover characteristics for conn=%d; " + "not connected\n", conn_handle); + btshell_full_disc_complete(BLE_HS_ENOTCONN); + return; + } + + SLIST_FOREACH(svc, &conn->svcs, next) { + if (!svc_is_empty(svc) && SLIST_EMPTY(&svc->chrs)) { + rc = btshell_disc_all_chrs(conn_handle, svc->svc.start_handle, + svc->svc.end_handle); + if (rc != 0) { + btshell_full_disc_complete(rc); + } + return; + } + } + + /* All characteristics discovered. */ + btshell_disc_full_dscs(conn_handle); +} + +static int +btshell_on_disc_s(uint16_t conn_handle, const struct ble_gatt_error *error, + const struct ble_gatt_svc *service, void *arg) +{ + switch (error->status) { + case 0: + btshell_svc_add(conn_handle, service); + break; + + case BLE_HS_EDONE: + console_printf("service discovery successful\n"); + if (btshell_full_disc_prev_chr_val > 0) { + btshell_disc_full_chrs(conn_handle); + } + break; + + default: + btshell_print_error(NULL, conn_handle, error); + break; + } + + return 0; +} + +static int +btshell_on_disc_c(uint16_t conn_handle, const struct ble_gatt_error *error, + const struct ble_gatt_chr *chr, void *arg) +{ + intptr_t svc_start_handle; + + svc_start_handle = (intptr_t)arg; + + switch (error->status) { + case 0: + btshell_chr_add(conn_handle, svc_start_handle, chr); + break; + + case BLE_HS_EDONE: + console_printf("characteristic discovery successful\n"); + if (btshell_full_disc_prev_chr_val > 0) { + btshell_disc_full_chrs(conn_handle); + } + break; + + default: + btshell_print_error(NULL, conn_handle, error); + break; + } + + return 0; +} + +static int +btshell_on_disc_d(uint16_t conn_handle, const struct ble_gatt_error *error, + uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc, + void *arg) +{ + switch (error->status) { + case 0: + btshell_dsc_add(conn_handle, chr_val_handle, dsc); + break; + + case BLE_HS_EDONE: + console_printf("descriptor discovery successful\n"); + if (btshell_full_disc_prev_chr_val > 0) { + btshell_disc_full_dscs(conn_handle); + } + break; + + default: + btshell_print_error(NULL, conn_handle, error); + break; + } + + return 0; +} + +static int +btshell_on_read(uint16_t conn_handle, const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, void *arg) +{ + switch (error->status) { + case 0: + console_printf("characteristic read; conn_handle=%d " + "attr_handle=%d len=%d value=", conn_handle, + attr->handle, OS_MBUF_PKTLEN(attr->om)); + print_mbuf(attr->om); + console_printf("\n"); + break; + + case BLE_HS_EDONE: + console_printf("characteristic read complete\n"); + break; + + default: + btshell_print_error(NULL, conn_handle, error); + break; + } + + return 0; +} + +static int +btshell_on_write(uint16_t conn_handle, const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, void *arg) +{ + switch (error->status) { + case 0: + console_printf("characteristic write complete; conn_handle=%d " + "attr_handle=%d\n", conn_handle, attr->handle); + break; + + default: + btshell_print_error(NULL, conn_handle, error); + break; + } + + return 0; +} + +static int +btshell_on_write_reliable(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attrs, uint8_t num_attrs, + void *arg) +{ + int i; + + switch (error->status) { + case 0: + console_printf("characteristic write reliable complete; " + "conn_handle=%d", conn_handle); + + for (i = 0; i < num_attrs; i++) { + console_printf(" attr_handle=%d len=%d value=", attrs[i].handle, + OS_MBUF_PKTLEN(attrs[i].om)); + print_mbuf(attrs[i].om); + } + console_printf("\n"); + break; + + default: + btshell_print_error(NULL, conn_handle, error); + break; + } + + return 0; +} + +static void +btshell_decode_adv_data(uint8_t *adv_data, uint8_t adv_data_len, void *arg) +{ + struct btshell_scan_opts *scan_opts = arg; + struct ble_hs_adv_fields fields; + + console_printf(" length_data=%d data=", adv_data_len); + + if (scan_opts) { + adv_data_len = min(adv_data_len, scan_opts->limit); + } + + print_bytes(adv_data, adv_data_len); + console_printf(" fields:\n"); + ble_hs_adv_parse_fields(&fields, adv_data, adv_data_len); + btshell_print_adv_fields(&fields); + console_printf("\n"); +} + +#if MYNEWT_VAL(BLE_EXT_ADV) +static void +btshell_decode_event_type(struct ble_gap_ext_disc_desc *desc, void *arg) +{ + struct btshell_scan_opts *scan_opts = arg; + uint8_t directed = 0; + + if (desc->props & BLE_HCI_ADV_LEGACY_MASK) { + if (scan_opts && scan_opts->ignore_legacy) { + return; + } + + console_printf("Legacy PDU type %d", desc->legacy_event_type); + if (desc->legacy_event_type == BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { + directed = 1; + } + goto common_data; + } + + console_printf("Extended adv: "); + if (desc->props & BLE_HCI_ADV_CONN_MASK) { + console_printf("'conn' "); + } + if (desc->props & BLE_HCI_ADV_SCAN_MASK) { + console_printf("'scan' "); + } + if (desc->props & BLE_HCI_ADV_DIRECT_MASK) { + console_printf("'dir' "); + directed = 1; + } + + if (desc->props & BLE_HCI_ADV_SCAN_RSP_MASK) { + console_printf("'scan rsp' "); + } + + switch(desc->data_status) { + case BLE_GAP_EXT_ADV_DATA_STATUS_COMPLETE: + console_printf("complete"); + break; + case BLE_GAP_EXT_ADV_DATA_STATUS_INCOMPLETE: + console_printf("incomplete"); + break; + case BLE_GAP_EXT_ADV_DATA_STATUS_TRUNCATED: + console_printf("truncated"); + break; + default: + console_printf("reserved %d", desc->data_status); + break; + } + +common_data: + console_printf(" rssi=%d txpower=%d, pphy=%d, sphy=%d, sid=%d," + " addr_type=%d addr=", + desc->rssi, desc->tx_power, desc->prim_phy, desc->sec_phy, + desc->sid, desc->addr.type); + print_addr(desc->addr.val); + if (directed) { + console_printf(" init_addr_type=%d inita=", desc->direct_addr.type); + print_addr(desc->direct_addr.val); + } + + console_printf("\n"); + + if(!desc->length_data) { + return; + } + + btshell_decode_adv_data(desc->data, desc->length_data, arg); +} +#endif + +static int +btshell_gap_event(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + int conn_idx; + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + console_printf("connection %s; status=%d ", + event->connect.status == 0 ? "established" : "failed", + event->connect.status); + + if (event->connect.status == 0) { + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + print_conn_desc(&desc); + btshell_conn_add(&desc); + } + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + console_printf("disconnect; reason=%d ", event->disconnect.reason); + print_conn_desc(&event->disconnect.conn); + + conn_idx = btshell_conn_find_idx(event->disconnect.conn.conn_handle); + if (conn_idx != -1) { + btshell_conn_delete_idx(conn_idx); + } + return 0; +#if MYNEWT_VAL(BLE_EXT_ADV) + case BLE_GAP_EVENT_EXT_DISC: + btshell_decode_event_type(&event->ext_disc, arg); + return 0; +#endif + case BLE_GAP_EVENT_DISC: + console_printf("received advertisement; event_type=%d rssi=%d " + "addr_type=%d addr=", event->disc.event_type, + event->disc.rssi, event->disc.addr.type); + print_addr(event->disc.addr.val); + + /* + * There is no adv data to print in case of connectable + * directed advertising + */ + if (event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { + console_printf("\nConnectable directed advertising event\n"); + return 0; + } + + btshell_decode_adv_data(event->disc.data, event->disc.length_data, arg); + + return 0; + + case BLE_GAP_EVENT_CONN_UPDATE: + console_printf("connection updated; status=%d ", + event->conn_update.status); + rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc); + assert(rc == 0); + print_conn_desc(&desc); + return 0; + + case BLE_GAP_EVENT_CONN_UPDATE_REQ: + console_printf("connection update request\n"); + *event->conn_update_req.self_params = + *event->conn_update_req.peer_params; + return 0; + + case BLE_GAP_EVENT_PASSKEY_ACTION: + console_printf("passkey action event; action=%d", + event->passkey.params.action); + if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { + console_printf(" numcmp=%lu", + (unsigned long)event->passkey.params.numcmp); + } + console_printf("\n"); + return 0; + + + case BLE_GAP_EVENT_DISC_COMPLETE: + console_printf("discovery complete; reason=%d\n", + event->disc_complete.reason); + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: +#if MYNEWT_VAL(BLE_EXT_ADV) + console_printf("advertise complete; reason=%d, instance=%u, handle=%d\n", + event->adv_complete.reason, event->adv_complete.instance, + event->adv_complete.conn_handle); +#else + console_printf("advertise complete; reason=%d\n", + event->adv_complete.reason); +#endif + return 0; + + case BLE_GAP_EVENT_ENC_CHANGE: + console_printf("encryption change event; status=%d ", + event->enc_change.status); + rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); + assert(rc == 0); + print_conn_desc(&desc); + return 0; + + case BLE_GAP_EVENT_NOTIFY_RX: + console_printf("notification rx event; attr_handle=%d indication=%d " + "len=%d data=", + event->notify_rx.attr_handle, + event->notify_rx.indication, + OS_MBUF_PKTLEN(event->notify_rx.om)); + + print_mbuf(event->notify_rx.om); + console_printf("\n"); + return 0; + + case BLE_GAP_EVENT_NOTIFY_TX: + console_printf("notification tx event; status=%d attr_handle=%d " + "indication=%d\n", + event->notify_tx.status, + event->notify_tx.attr_handle, + event->notify_tx.indication); + return 0; + + case BLE_GAP_EVENT_SUBSCRIBE: + console_printf("subscribe event; conn_handle=%d attr_handle=%d " + "reason=%d prevn=%d curn=%d previ=%d curi=%d\n", + event->subscribe.conn_handle, + event->subscribe.attr_handle, + event->subscribe.reason, + event->subscribe.prev_notify, + event->subscribe.cur_notify, + event->subscribe.prev_indicate, + event->subscribe.cur_indicate); + return 0; + + case BLE_GAP_EVENT_MTU: + console_printf("mtu update event; conn_handle=%d cid=%d mtu=%d\n", + event->mtu.conn_handle, + event->mtu.channel_id, + event->mtu.value); + return 0; + + case BLE_GAP_EVENT_IDENTITY_RESOLVED: + console_printf("identity resolved "); + rc = ble_gap_conn_find(event->identity_resolved.conn_handle, &desc); + assert(rc == 0); + print_conn_desc(&desc); + return 0; + + case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: + console_printf("PHY update complete; status=%d, conn_handle=%d " + " tx_phy=%d, rx_phy=%d\n", + event->phy_updated.status, + event->phy_updated.conn_handle, + event->phy_updated.tx_phy, + event->phy_updated.rx_phy); + return 0; + + case BLE_GAP_EVENT_REPEAT_PAIRING: + /* We already have a bond with the peer, but it is attempting to + * establish a new secure link. This app sacrifices security for + * convenience: just throw away the old bond and accept the new link. + */ + + /* Delete the old bond. */ + rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); + assert(rc == 0); + ble_store_util_delete_peer(&desc.peer_id_addr); + + /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should + * continue with the pairing operation. + */ + return BLE_GAP_REPEAT_PAIRING_RETRY; + + default: + return 0; + } +} + +static void +btshell_on_l2cap_update(uint16_t conn_handle, int status, void *arg) +{ + console_printf("l2cap update complete; conn_handle=%d status=%d\n", + conn_handle, status); +} + +static void +btshell_tx_timer_cb(struct os_event *ev) +{ + int i; + uint8_t len; + int32_t timeout; + uint8_t *dptr; + struct os_mbuf *om; + + if ((btshell_tx_data.tx_num == 0) || (btshell_tx_data.tx_len == 0)) { + return; + } + + len = btshell_tx_data.tx_len; + + om = NULL; + if (os_msys_num_free() >= 4) { + om = os_msys_get_pkthdr(len + 4, sizeof(struct ble_mbuf_hdr)); + } + + if (om) { + /* Put the HCI header in the mbuf */ + om->om_len = len + 4; + put_le16(om->om_data, btshell_tx_data.tx_handle); + put_le16(om->om_data + 2, len); + dptr = om->om_data + 4; + + /* + * NOTE: first byte gets 0xff so not confused with l2cap channel. + * The rest of the data gets filled with incrementing pattern starting + * from 0. + */ + put_le16(dptr, len - 4); + dptr[2] = 0xff; + dptr[3] = 0xff; + dptr += 4; + len -= 4; + + for (i = 0; i < len; ++i) { + *dptr = i; + ++dptr; + } + + /* Set packet header length */ + OS_MBUF_PKTHDR(om)->omp_len = om->om_len; + ble_hci_trans_hs_acl_tx(om); + + --btshell_tx_data.tx_num; + } + + if (btshell_tx_data.tx_num) { + timeout = (int32_t)btshell_tx_data.tx_rate; + timeout = (timeout * OS_TICKS_PER_SEC) / 1000; + os_callout_reset(&btshell_tx_timer, timeout); + } +} + +int +btshell_exchange_mtu(uint16_t conn_handle) +{ + int rc; + + rc = ble_gattc_exchange_mtu(conn_handle, btshell_on_mtu, NULL); + return rc; +} + +int +btshell_disc_all_chrs(uint16_t conn_handle, uint16_t start_handle, + uint16_t end_handle) +{ + intptr_t svc_start_handle; + int rc; + + svc_start_handle = start_handle; + rc = ble_gattc_disc_all_chrs(conn_handle, start_handle, end_handle, + btshell_on_disc_c, (void *)svc_start_handle); + return rc; +} + +int +btshell_disc_chrs_by_uuid(uint16_t conn_handle, uint16_t start_handle, + uint16_t end_handle, const ble_uuid_t *uuid) +{ + intptr_t svc_start_handle; + int rc; + + svc_start_handle = start_handle; + rc = ble_gattc_disc_chrs_by_uuid(conn_handle, start_handle, end_handle, + uuid, btshell_on_disc_c, + (void *)svc_start_handle); + return rc; +} + +int +btshell_disc_svcs(uint16_t conn_handle) +{ + int rc; + + rc = ble_gattc_disc_all_svcs(conn_handle, btshell_on_disc_s, NULL); + return rc; +} + +int +btshell_disc_svc_by_uuid(uint16_t conn_handle, const ble_uuid_t *uuid) +{ + int rc; + + rc = ble_gattc_disc_svc_by_uuid(conn_handle, uuid, + btshell_on_disc_s, NULL); + return rc; +} + +int +btshell_disc_all_dscs(uint16_t conn_handle, uint16_t start_handle, + uint16_t end_handle) +{ + int rc; + + rc = ble_gattc_disc_all_dscs(conn_handle, start_handle, end_handle, + btshell_on_disc_d, NULL); + return rc; +} + +int +btshell_disc_full(uint16_t conn_handle) +{ + struct btshell_conn *conn; + struct btshell_svc *svc; + + /* Undiscover everything first. */ + conn = btshell_conn_find(conn_handle); + if (conn == NULL) { + return BLE_HS_ENOTCONN; + } + + while ((svc = SLIST_FIRST(&conn->svcs)) != NULL) { + SLIST_REMOVE_HEAD(&conn->svcs, next); + btshell_svc_delete(svc); + } + + btshell_full_disc_prev_chr_val = 1; + btshell_disc_svcs(conn_handle); + + return 0; +} + +int +btshell_find_inc_svcs(uint16_t conn_handle, uint16_t start_handle, + uint16_t end_handle) +{ + int rc; + + rc = ble_gattc_find_inc_svcs(conn_handle, start_handle, end_handle, + btshell_on_disc_s, NULL); + return rc; +} + +int +btshell_read(uint16_t conn_handle, uint16_t attr_handle) +{ + struct os_mbuf *om; + int rc; + + if (conn_handle == BLE_HS_CONN_HANDLE_NONE) { + rc = ble_att_svr_read_local(attr_handle, &om); + if (rc == 0) { + console_printf("read local; attr_handle=%d len=%d value=", + attr_handle, OS_MBUF_PKTLEN(om)); + print_mbuf(om); + console_printf("\n"); + + os_mbuf_free_chain(om); + } + } else { + rc = ble_gattc_read(conn_handle, attr_handle, btshell_on_read, NULL); + } + return rc; +} + +int +btshell_read_long(uint16_t conn_handle, uint16_t attr_handle, uint16_t offset) +{ + int rc; + + rc = ble_gattc_read_long(conn_handle, attr_handle, offset, + btshell_on_read, NULL); + return rc; +} + +int +btshell_read_by_uuid(uint16_t conn_handle, uint16_t start_handle, + uint16_t end_handle, const ble_uuid_t *uuid) +{ + int rc; + + rc = ble_gattc_read_by_uuid(conn_handle, start_handle, end_handle, uuid, + btshell_on_read, NULL); + return rc; +} + +int +btshell_read_mult(uint16_t conn_handle, uint16_t *attr_handles, + int num_attr_handles) +{ + int rc; + + rc = ble_gattc_read_mult(conn_handle, attr_handles, num_attr_handles, + btshell_on_read, NULL); + return rc; +} + +int +btshell_write(uint16_t conn_handle, uint16_t attr_handle, struct os_mbuf *om) +{ + int rc; + + if (conn_handle == BLE_HS_CONN_HANDLE_NONE) { + rc = ble_att_svr_write_local(attr_handle, om); + } else { + rc = ble_gattc_write(conn_handle, attr_handle, om, + btshell_on_write, NULL); + } + + return rc; +} + +int +btshell_write_no_rsp(uint16_t conn_handle, uint16_t attr_handle, + struct os_mbuf *om) +{ + int rc; + + rc = ble_gattc_write_no_rsp(conn_handle, attr_handle, om); + + return rc; +} + +int +btshell_write_long(uint16_t conn_handle, uint16_t attr_handle, + uint16_t offset, struct os_mbuf *om) +{ + int rc; + + rc = ble_gattc_write_long(conn_handle, attr_handle, offset, + om, btshell_on_write, NULL); + return rc; +} + +int +btshell_write_reliable(uint16_t conn_handle, + struct ble_gatt_attr *attrs, + int num_attrs) +{ + int rc; + + rc = ble_gattc_write_reliable(conn_handle, attrs, num_attrs, + btshell_on_write_reliable, NULL); + return rc; +} + +#if MYNEWT_VAL(BLE_EXT_ADV) +int +btshell_ext_adv_configure(uint8_t instance, + const struct ble_gap_ext_adv_params *params, + int8_t *selected_tx_power) +{ + return ble_gap_ext_adv_configure(instance, params, selected_tx_power, + btshell_gap_event, NULL); +} +#endif + +int +btshell_adv_stop(void) +{ + int rc; + + rc = ble_gap_adv_stop(); + return rc; +} + +int +btshell_adv_start(uint8_t own_addr_type, const ble_addr_t *direct_addr, + int32_t duration_ms, const struct ble_gap_adv_params *params) +{ + int rc; + + rc = ble_gap_adv_start(own_addr_type, direct_addr, duration_ms, params, + btshell_gap_event, NULL); + return rc; +} + +int +btshell_conn_initiate(uint8_t own_addr_type, const ble_addr_t *peer_addr, + int32_t duration_ms, struct ble_gap_conn_params *params) +{ + int rc; + + rc = ble_gap_connect(own_addr_type, peer_addr, duration_ms, params, + btshell_gap_event, NULL); + + return rc; +} + +int +btshell_ext_conn_initiate(uint8_t own_addr_type, const ble_addr_t *peer_addr, + int32_t duration_ms, + struct ble_gap_conn_params *phy_1m_params, + struct ble_gap_conn_params *phy_2m_params, + struct ble_gap_conn_params *phy_coded_params) +{ +#if !MYNEWT_VAL(BLE_EXT_ADV) + console_printf("BLE extended advertising not supported."); + console_printf(" Configure nimble host to enable it\n"); + return 0; +#else + int rc; + uint8_t phy_mask = 0; + + if (phy_1m_params) { + phy_mask |= BLE_GAP_LE_PHY_1M_MASK; + } + + if (phy_2m_params) { + phy_mask |= BLE_GAP_LE_PHY_2M_MASK; + } + + if (phy_coded_params) { + phy_mask |= BLE_GAP_LE_PHY_CODED_MASK; + } + + rc = ble_gap_ext_connect(own_addr_type, peer_addr, duration_ms, phy_mask, + phy_1m_params, phy_2m_params, phy_coded_params, + btshell_gap_event, NULL); + + return rc; +#endif +} + +int +btshell_conn_cancel(void) +{ + int rc; + + rc = ble_gap_conn_cancel(); + return rc; +} + +int +btshell_term_conn(uint16_t conn_handle, uint8_t reason) +{ + int rc; + + rc = ble_gap_terminate(conn_handle, reason); + return rc; +} + +int +btshell_wl_set(ble_addr_t *addrs, int addrs_count) +{ + int rc; + + rc = ble_gap_wl_set(addrs, addrs_count); + return rc; +} + +int +btshell_scan(uint8_t own_addr_type, int32_t duration_ms, + const struct ble_gap_disc_params *disc_params, void *cb_args) +{ + int rc; + + rc = ble_gap_disc(own_addr_type, duration_ms, disc_params, + btshell_gap_event, cb_args); + return rc; +} + +int +btshell_ext_scan(uint8_t own_addr_type, uint16_t duration, uint16_t period, + uint8_t filter_duplicates, uint8_t filter_policy, + uint8_t limited, + const struct ble_gap_ext_disc_params *uncoded_params, + const struct ble_gap_ext_disc_params *coded_params, + void *cb_args) +{ +#if !MYNEWT_VAL(BLE_EXT_ADV) + console_printf("BLE extended advertising not supported."); + console_printf(" Configure nimble host to enable it\n"); + return 0; +#else + int rc; + + rc = ble_gap_ext_disc(own_addr_type, duration, period, filter_duplicates, + filter_policy, limited, uncoded_params, coded_params, + btshell_gap_event, cb_args); + return rc; +#endif +} + +int +btshell_scan_cancel(void) +{ + int rc; + + rc = ble_gap_disc_cancel(); + return rc; +} + +int +btshell_update_conn(uint16_t conn_handle, struct ble_gap_upd_params *params) +{ + int rc; + + rc = ble_gap_update_params(conn_handle, params); + return rc; +} + +void +btshell_notify(uint16_t attr_handle) +{ + ble_gatts_chr_updated(attr_handle); +} + +int +btshell_datalen(uint16_t conn_handle, uint16_t tx_octets, uint16_t tx_time) +{ + int rc; + + rc = ble_hs_hci_util_set_data_len(conn_handle, tx_octets, tx_time); + return rc; +} + +int +btshell_l2cap_update(uint16_t conn_handle, + struct ble_l2cap_sig_update_params *params) +{ + int rc; + + rc = ble_l2cap_sig_update(conn_handle, params, btshell_on_l2cap_update, + NULL); + return rc; +} + +int +btshell_sec_pair(uint16_t conn_handle) +{ +#if !NIMBLE_BLE_SM + return BLE_HS_ENOTSUP; +#endif + + int rc; + + rc = ble_gap_pair_initiate(conn_handle); + return rc; +} + +int +btshell_sec_start(uint16_t conn_handle) +{ +#if !NIMBLE_BLE_SM + return BLE_HS_ENOTSUP; +#endif + + int rc; + + rc = ble_gap_security_initiate(conn_handle); + return rc; +} + +int +btshell_sec_restart(uint16_t conn_handle, + uint8_t *ltk, + uint16_t ediv, + uint64_t rand_val, + int auth) +{ +#if !NIMBLE_BLE_SM + return BLE_HS_ENOTSUP; +#endif + + struct ble_store_value_sec value_sec; + struct ble_store_key_sec key_sec; + struct ble_gap_conn_desc desc; + ble_hs_conn_flags_t conn_flags; + int rc; + + if (ltk == NULL) { + /* The user is requesting a store lookup. */ + rc = ble_gap_conn_find(conn_handle, &desc); + if (rc != 0) { + return rc; + } + + memset(&key_sec, 0, sizeof key_sec); + key_sec.peer_addr = desc.peer_id_addr; + + rc = ble_hs_atomic_conn_flags(conn_handle, &conn_flags); + if (rc != 0) { + return rc; + } + if (conn_flags & BLE_HS_CONN_F_MASTER) { + rc = ble_store_read_peer_sec(&key_sec, &value_sec); + } else { + rc = ble_store_read_our_sec(&key_sec, &value_sec); + } + if (rc != 0) { + return rc; + } + + ltk = value_sec.ltk; + ediv = value_sec.ediv; + rand_val = value_sec.rand_num; + auth = value_sec.authenticated; + } + + rc = ble_gap_encryption_initiate(conn_handle, ltk, ediv, rand_val, auth); + return rc; +} + +/** + * Called to start transmitting 'num' packets at rate 'rate' of size 'size' + * to connection handle 'handle' + * + * @param handle + * @param len + * @param rate + * @param num + * + * @return int + */ +int +btshell_tx_start(uint16_t handle, uint16_t len, uint16_t rate, uint16_t num) +{ + /* Cannot be currently in a session */ + if (num == 0) { + return 0; + } + + /* Do not allow start if already in progress */ + if (btshell_tx_data.tx_num != 0) { + return -1; + } + + /* XXX: for now, must have contiguous mbuf space */ + if ((len + 4) > MYNEWT_VAL_MSYS_1_BLOCK_SIZE) { + return -2; + } + + btshell_tx_data.tx_num = num; + btshell_tx_data.tx_rate = rate; + btshell_tx_data.tx_len = len; + btshell_tx_data.tx_handle = handle; + + os_callout_reset(&btshell_tx_timer, 0); + + return 0; +} + +int +btshell_rssi(uint16_t conn_handle, int8_t *out_rssi) +{ + int rc; + + rc = ble_gap_conn_rssi(conn_handle, out_rssi); + if (rc != 0) { + return rc; + } + + return 0; +} + +static void +btshell_on_reset(int reason) +{ + console_printf("Error: Resetting state; reason=%d\n", reason); +} + +#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) != 0 + +static int +btshell_l2cap_coc_add(uint16_t conn_handle, struct ble_l2cap_chan *chan) +{ + struct btshell_conn *conn; + struct btshell_l2cap_coc *coc; + struct btshell_l2cap_coc *prev, *cur; + + conn = btshell_conn_find(conn_handle); + assert(conn != NULL); + + coc = os_memblock_get(&btshell_coc_conn_pool); + if (!coc) { + return ENOMEM; + } + + coc->chan = chan; + + prev = NULL; + SLIST_FOREACH(cur, &conn->coc_list, next) { + prev = cur; + } + + if (prev == NULL) { + SLIST_INSERT_HEAD(&conn->coc_list, coc, next); + } else { + SLIST_INSERT_AFTER(prev, coc, next); + } + + return 0; +} + +static void +btshell_l2cap_coc_remove(uint16_t conn_handle, struct ble_l2cap_chan *chan) +{ + struct btshell_conn *conn; + struct btshell_l2cap_coc *coc; + struct btshell_l2cap_coc *cur; + + conn = btshell_conn_find(conn_handle); + assert(conn != NULL); + + coc = NULL; + SLIST_FOREACH(cur, &conn->coc_list, next) { + if (cur->chan == chan) { + coc = cur; + break; + } + } + + if (!coc) { + return; + } + + SLIST_REMOVE(&conn->coc_list, coc, btshell_l2cap_coc, next); + os_memblock_put(&btshell_coc_conn_pool, coc); +} + +static void +btshell_l2cap_coc_recv(struct ble_l2cap_chan *chan, struct os_mbuf *sdu) +{ + console_printf("LE CoC SDU received, chan: 0x%08lx, data len %d\n", + (uint32_t) chan, OS_MBUF_PKTLEN(sdu)); + + os_mbuf_free_chain(sdu); + sdu = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + assert(sdu != NULL); + + ble_l2cap_recv_ready(chan, sdu); +} + +static int +btshell_l2cap_coc_accept(uint16_t conn_handle, uint16_t peer_mtu, + struct ble_l2cap_chan *chan) +{ + struct os_mbuf *sdu_rx; + + console_printf("LE CoC accepting, chan: 0x%08lx, peer_mtu %d\n", + (uint32_t) chan, peer_mtu); + + sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + if (!sdu_rx) { + return BLE_HS_ENOMEM; + } + + ble_l2cap_recv_ready(chan, sdu_rx); + + return 0; +} + +static int +btshell_l2cap_event(struct ble_l2cap_event *event, void *arg) +{ + int accept_response; + + switch(event->type) { + case BLE_L2CAP_EVENT_COC_CONNECTED: + if (event->connect.status) { + console_printf("LE COC error: %d\n", event->connect.status); + return 0; + } + + console_printf("LE COC connected, conn: %d, chan: 0x%08lx\n", + event->connect.conn_handle, + (uint32_t) event->connect.chan); + + btshell_l2cap_coc_add(event->connect.conn_handle, + event->connect.chan); + + return 0; + case BLE_L2CAP_EVENT_COC_DISCONNECTED: + console_printf("LE CoC disconnected, chan: 0x%08lx\n", + (uint32_t) event->disconnect.chan); + + btshell_l2cap_coc_remove(event->disconnect.conn_handle, + event->disconnect.chan); + return 0; + case BLE_L2CAP_EVENT_COC_ACCEPT: + accept_response = PTR_TO_INT(arg); + if (accept_response) { + return accept_response; + } + + return btshell_l2cap_coc_accept(event->accept.conn_handle, + event->accept.peer_sdu_size, + event->accept.chan); + + case BLE_L2CAP_EVENT_COC_DATA_RECEIVED: + btshell_l2cap_coc_recv(event->receive.chan, event->receive.sdu_rx); + return 0; + default: + return 0; + } +} +#endif + +int +btshell_l2cap_create_srv(uint16_t psm, int accept_response) +{ +#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) == 0 + console_printf("BLE L2CAP LE COC not supported."); + console_printf(" Configure nimble host to enable it\n"); + return 0; +#else + + return ble_l2cap_create_server(psm, BTSHELL_COC_MTU, btshell_l2cap_event, + INT_TO_PTR(accept_response)); +#endif +} + +int +btshell_l2cap_connect(uint16_t conn_handle, uint16_t psm) +{ +#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) == 0 + console_printf("BLE L2CAP LE COC not supported."); + console_printf(" Configure nimble host to enable it\n"); + return 0; +#else + + struct os_mbuf *sdu_rx; + + sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + assert(sdu_rx != NULL); + + return ble_l2cap_connect(conn_handle, psm, BTSHELL_COC_MTU, sdu_rx, + btshell_l2cap_event, NULL); +#endif +} + +int +btshell_l2cap_disconnect(uint16_t conn_handle, uint16_t idx) +{ +#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) == 0 + console_printf("BLE L2CAP LE COC not supported."); + console_printf(" Configure nimble host to enable it\n"); + return 0; +#else + + struct btshell_conn *conn; + struct btshell_l2cap_coc *coc; + int i; + int rc = 0; + + conn = btshell_conn_find(conn_handle); + assert(conn != NULL); + + i = 0; + SLIST_FOREACH(coc, &conn->coc_list, next) { + if (i == idx) { + break; + } + i++; + } + assert(coc != NULL); + + rc = ble_l2cap_disconnect(coc->chan); + if (rc) { + console_printf("Could not disconnect channel rc=%d\n", rc); + } + + return rc; +#endif +} + +int +btshell_l2cap_send(uint16_t conn_handle, uint16_t idx, uint16_t bytes) +{ +#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) == 0 + console_printf("BLE L2CAP LE COC not supported."); + console_printf(" Configure nimble host to enable it\n"); + return 0; +#else + + struct btshell_conn *conn; + struct btshell_l2cap_coc *coc; + struct os_mbuf *sdu_tx; + uint8_t b[] = {0x00, 0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88, 0x99}; + int i; + int rc; + + console_printf("conn=%d, idx=%d, bytes=%d\n", conn_handle, idx, bytes); + + conn = btshell_conn_find(conn_handle); + if (conn == NULL) { + console_printf("conn=%d does not exist\n", conn_handle); + return 0; + } + + i = 0; + SLIST_FOREACH(coc, &conn->coc_list, next) { + if (i == idx) { + break; + } + i++; + } + if (coc == NULL) { + console_printf("Are you sure your channel exist?\n"); + return 0; + } + + sdu_tx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + if (sdu_tx == NULL) { + console_printf("No memory in the test sdu pool\n"); + return 0; + } + + /* For the testing purpose we fill up buffer with known data, easy + * to validate on other side. In this loop we add as many full chunks as we + * can + */ + for (i = 0; i < bytes / sizeof(b); i++) { + rc = os_mbuf_append(sdu_tx, b, sizeof(b)); + if (rc) { + console_printf("Cannot append data %i !\n", i); + os_mbuf_free_chain(sdu_tx); + return rc; + } + } + + /* Here we add the rest < sizeof(b) */ + rc = os_mbuf_append(sdu_tx, b, bytes - (sizeof(b) * i)); + if (rc) { + console_printf("Cannot append data %i !\n", i); + os_mbuf_free_chain(sdu_tx); + return rc; + } + + rc = ble_l2cap_send(coc->chan, sdu_tx); + if (rc) { + console_printf("Could not send data rc=%d\n", rc); + os_mbuf_free_chain(sdu_tx); + } + + return rc; + +#endif +} +/** + * main + * + * The main task for the project. This function initializes the packages, + * then starts serving events from default event queue. + * + * @return int NOTE: this function should never return! + */ +int +main(int argc, char **argv) +{ + int rc; + +#ifdef ARCH_sim + mcu_sim_parse_args(argc, argv); +#endif + + /* Initialize OS */ + sysinit(); + + /* Initialize some application specific memory pools. */ + rc = os_mempool_init(&btshell_svc_pool, BTSHELL_MAX_SVCS, + sizeof (struct btshell_svc), btshell_svc_mem, + "btshell_svc_pool"); + assert(rc == 0); + + rc = os_mempool_init(&btshell_chr_pool, BTSHELL_MAX_CHRS, + sizeof (struct btshell_chr), btshell_chr_mem, + "btshell_chr_pool"); + assert(rc == 0); + + rc = os_mempool_init(&btshell_dsc_pool, BTSHELL_MAX_DSCS, + sizeof (struct btshell_dsc), btshell_dsc_mem, + "btshell_dsc_pool"); + assert(rc == 0); + +#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) != 0 + /* For testing we want to support all the available channels */ + rc = os_mempool_init(&sdu_coc_mbuf_mempool, BTSHELL_COC_BUF_COUNT, + BTSHELL_COC_MTU, btshell_sdu_coc_mem, + "btshell_coc_sdu_pool"); + assert(rc == 0); + + rc = os_mbuf_pool_init(&sdu_os_mbuf_pool, &sdu_coc_mbuf_mempool, + BTSHELL_COC_MTU, BTSHELL_COC_BUF_COUNT); + assert(rc == 0); + + rc = os_mempool_init(&btshell_coc_conn_pool, + MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM), + sizeof (struct btshell_l2cap_coc), btshell_coc_conn_mem, + "btshell_coc_conn_pool"); + assert(rc == 0); +#endif + + /* Initialize the logging system. */ + log_register("btshell", &btshell_log, &log_console_handler, NULL, + LOG_SYSLEVEL); + + /* Initialize the NimBLE host configuration. */ + log_register("ble_hs", &ble_hs_log, &log_console_handler, NULL, + LOG_SYSLEVEL); + ble_hs_cfg.reset_cb = btshell_on_reset; + ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + rc = gatt_svr_init(); + assert(rc == 0); + + cmd_init(); + + /* Set the default device name. */ + rc = ble_svc_gap_device_name_set("nimble-btshell"); + assert(rc == 0); + + /* Create a callout (timer). This callout is used by the "tx" btshell + * command to repeatedly send packets of sequential data bytes. + */ + os_callout_init(&btshell_tx_timer, os_eventq_dflt_get(), + btshell_tx_timer_cb, NULL); + + while (1) { + os_eventq_run(os_eventq_dflt_get()); + } + /* os start should never return. If it does, this should be an error */ + assert(0); + + return 0; +} diff --git a/apps/btshell/src/misc.c b/apps/btshell/src/misc.c new file mode 100644 index 000000000..9a8e43352 --- /dev/null +++ b/apps/btshell/src/misc.c @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "console/console.h" +#include "host/ble_uuid.h" +#include "host/ble_gap.h" + +#include "btshell.h" + +/** + * Utility function to log an array of bytes. + */ +void +print_bytes(const uint8_t *bytes, int len) +{ + int i; + + for (i = 0; i < len; i++) { + console_printf("%s0x%02x", i != 0 ? ":" : "", bytes[i]); + } +} + +void +print_mbuf(const struct os_mbuf *om) +{ + int colon; + + colon = 0; + while (om != NULL) { + if (colon) { + console_printf(":"); + } else { + colon = 1; + } + print_bytes(om->om_data, om->om_len); + om = SLIST_NEXT(om, om_next); + } +} + +void +print_addr(const void *addr) +{ + const uint8_t *u8p; + + u8p = addr; + console_printf("%02x:%02x:%02x:%02x:%02x:%02x", + u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]); +} + +void +print_uuid(const ble_uuid_t *uuid) +{ + char buf[BLE_UUID_STR_LEN]; + + ble_uuid_to_str(uuid, buf); + + console_printf("%s", buf); +} + +int +svc_is_empty(const struct btshell_svc *svc) +{ + return svc->svc.end_handle <= svc->svc.start_handle; +} + +uint16_t +chr_end_handle(const struct btshell_svc *svc, const struct btshell_chr *chr) +{ + const struct btshell_chr *next_chr; + + next_chr = SLIST_NEXT(chr, next); + if (next_chr != NULL) { + return next_chr->chr.def_handle - 1; + } else { + return svc->svc.end_handle; + } +} + +int +chr_is_empty(const struct btshell_svc *svc, const struct btshell_chr *chr) +{ + return chr_end_handle(svc, chr) <= chr->chr.val_handle; +} + +void +print_conn_desc(const struct ble_gap_conn_desc *desc) +{ + console_printf("handle=%d our_ota_addr_type=%d our_ota_addr=", + desc->conn_handle, desc->our_ota_addr.type); + print_addr(desc->our_ota_addr.val); + console_printf(" our_id_addr_type=%d our_id_addr=", + desc->our_id_addr.type); + print_addr(desc->our_id_addr.val); + console_printf(" peer_ota_addr_type=%d peer_ota_addr=", + desc->peer_ota_addr.type); + print_addr(desc->peer_ota_addr.val); + console_printf(" peer_id_addr_type=%d peer_id_addr=", + desc->peer_id_addr.type); + print_addr(desc->peer_id_addr.val); + console_printf(" conn_itvl=%d conn_latency=%d supervision_timeout=%d " + "encrypted=%d authenticated=%d bonded=%d\n", + desc->conn_itvl, desc->conn_latency, + desc->supervision_timeout, + desc->sec_state.encrypted, + desc->sec_state.authenticated, + desc->sec_state.bonded); +} + +static void +print_dsc(struct btshell_dsc *dsc) +{ + console_printf(" dsc_handle=%d uuid=", dsc->dsc.handle); + print_uuid(&dsc->dsc.uuid.u); + console_printf("\n"); +} + +static void +print_chr(struct btshell_chr *chr) +{ + struct btshell_dsc *dsc; + + console_printf(" def_handle=%d val_handle=%d properties=0x%02x " + "uuid=", chr->chr.def_handle, chr->chr.val_handle, + chr->chr.properties); + print_uuid(&chr->chr.uuid.u); + console_printf("\n"); + + SLIST_FOREACH(dsc, &chr->dscs, next) { + print_dsc(dsc); + } +} + +void +print_svc(struct btshell_svc *svc) +{ + struct btshell_chr *chr; + + console_printf(" start=%d end=%d uuid=", svc->svc.start_handle, + svc->svc.end_handle); + print_uuid(&svc->svc.uuid.u); + console_printf("\n"); + + SLIST_FOREACH(chr, &svc->chrs, next) { + print_chr(chr); + } +} diff --git a/apps/btshell/src/parse.c b/apps/btshell/src/parse.c new file mode 100644 index 000000000..66db9d7f4 --- /dev/null +++ b/apps/btshell/src/parse.c @@ -0,0 +1,587 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include +#include +#include "console/console.h" +#include "host/ble_hs.h" +#include "host/ble_uuid.h" +#include "host/ble_eddystone.h" +#include "cmd.h" +#include "btshell.h" + +#define CMD_MAX_ARGS 16 + +static char *cmd_args[CMD_MAX_ARGS][2]; +static int cmd_num_args; + +int +parse_arg_find_idx(const char *key) +{ + int i; + + for (i = 0; i < cmd_num_args; i++) { + if (strcmp(cmd_args[i][0], key) == 0) { + return i; + } + } + + return -1; +} + +char * +parse_arg_peek(const char *key) +{ + int i; + + for (i = 0; i < cmd_num_args; i++) { + if (strcmp(cmd_args[i][0], key) == 0) { + return cmd_args[i][1]; + } + } + + return NULL; +} + +char * +parse_arg_extract(const char *key) +{ + int i; + + for (i = 0; i < cmd_num_args; i++) { + if (strcmp(cmd_args[i][0], key) == 0) { + /* Erase parameter. */ + cmd_args[i][0][0] = '\0'; + + return cmd_args[i][1]; + } + } + + return NULL; +} + +/** + * Determines which number base to use when parsing the specified numeric + * string. This just avoids base '0' so that numbers don't get interpreted as + * octal. + */ +static int +parse_arg_long_base(char *sval) +{ + if (sval[0] == '0' && sval[1] == 'x') { + return 0; + } else { + return 10; + } +} + +long +parse_long_bounds(char *sval, long min, long max, int *out_status) +{ + char *endptr; + long lval; + + lval = strtol(sval, &endptr, parse_arg_long_base(sval)); + if (sval[0] != '\0' && *endptr == '\0' && + lval >= min && lval <= max) { + + *out_status = 0; + return lval; + } + + *out_status = EINVAL; + return 0; +} + +long +parse_arg_long_bounds_peek(char *name, long min, long max, int *out_status) +{ + char *sval; + + sval = parse_arg_peek(name); + if (sval == NULL) { + *out_status = ENOENT; + return 0; + } + return parse_long_bounds(sval, min, max, out_status); +} + +long +parse_arg_long_bounds(char *name, long min, long max, int *out_status) +{ + char *sval; + + sval = parse_arg_extract(name); + if (sval == NULL) { + *out_status = ENOENT; + return 0; + } + return parse_long_bounds(sval, min, max, out_status); +} + +long +parse_arg_long_bounds_dflt(char *name, long min, long max, + long dflt, int *out_status) +{ + long val; + int rc; + + val = parse_arg_long_bounds(name, min, max, &rc); + if (rc == ENOENT) { + rc = 0; + val = dflt; + } + + *out_status = rc; + + return val; +} + +uint64_t +parse_arg_uint64_bounds(char *name, uint64_t min, uint64_t max, int *out_status) +{ + char *endptr; + char *sval; + uint64_t lval; + + sval = parse_arg_extract(name); + if (sval == NULL) { + *out_status = ENOENT; + return 0; + } + + lval = strtoull(sval, &endptr, parse_arg_long_base(sval)); + if (sval[0] != '\0' && *endptr == '\0' && + lval >= min && lval <= max) { + + *out_status = 0; + return lval; + } + + *out_status = EINVAL; + return 0; +} + +long +parse_arg_long(char *name, int *out_status) +{ + return parse_arg_long_bounds(name, LONG_MIN, LONG_MAX, out_status); +} + +uint8_t +parse_arg_bool(char *name, int *out_status) +{ + return parse_arg_long_bounds(name, 0, 1, out_status); +} + +uint8_t +parse_arg_bool_dflt(char *name, uint8_t dflt, int *out_status) +{ + return parse_arg_long_bounds_dflt(name, 0, 1, dflt, out_status); +} + +uint8_t +parse_arg_uint8(char *name, int *out_status) +{ + return parse_arg_long_bounds(name, 0, UINT8_MAX, out_status); +} + +uint16_t +parse_arg_uint16(char *name, int *out_status) +{ + return parse_arg_long_bounds(name, 0, UINT16_MAX, out_status); +} + +uint16_t +parse_arg_uint16_peek(char *name, int *out_status) +{ + return parse_arg_long_bounds_peek(name, 0, UINT16_MAX, out_status); +} + +uint32_t +parse_arg_uint32(char *name, int *out_status) +{ + return parse_arg_uint64_bounds(name, 0, UINT32_MAX, out_status); +} + +uint64_t +parse_arg_uint64(char *name, int *out_status) +{ + return parse_arg_uint64_bounds(name, 0, UINT64_MAX, out_status); +} + +uint8_t +parse_arg_uint8_dflt(char *name, uint8_t dflt, int *out_status) +{ + uint8_t val; + int rc; + + val = parse_arg_uint8(name, &rc); + if (rc == ENOENT) { + val = dflt; + rc = 0; + } + + *out_status = rc; + return val; +} + +uint16_t +parse_arg_uint16_dflt(char *name, uint16_t dflt, int *out_status) +{ + uint16_t val; + int rc; + + val = parse_arg_uint16(name, &rc); + if (rc == ENOENT) { + val = dflt; + rc = 0; + } + + *out_status = rc; + return val; +} + +uint32_t +parse_arg_uint32_dflt(char *name, uint32_t dflt, int *out_status) +{ + uint32_t val; + int rc; + + val = parse_arg_uint32(name, &rc); + if (rc == ENOENT) { + val = dflt; + rc = 0; + } + + *out_status = rc; + return val; +} + +const struct kv_pair * +parse_kv_find(const struct kv_pair *kvs, char *name) +{ + const struct kv_pair *kv; + int i; + + for (i = 0; kvs[i].key != NULL; i++) { + kv = kvs + i; + if (strcmp(name, kv->key) == 0) { + return kv; + } + } + + return NULL; +} + +int +parse_arg_kv(char *name, const struct kv_pair *kvs, int *out_status) +{ + const struct kv_pair *kv; + char *sval; + + sval = parse_arg_extract(name); + if (sval == NULL) { + *out_status = ENOENT; + return -1; + } + + kv = parse_kv_find(kvs, sval); + if (kv == NULL) { + *out_status = EINVAL; + return -1; + } + + *out_status = 0; + return kv->val; +} + +int +parse_arg_kv_dflt(char *name, const struct kv_pair *kvs, int def_val, + int *out_status) +{ + int val; + int rc; + + val = parse_arg_kv(name, kvs, &rc); + if (rc == ENOENT) { + rc = 0; + val = def_val; + } + + *out_status = rc; + + return val; +} + + +static int +parse_arg_byte_stream_delim(char *sval, char *delims, int max_len, + uint8_t *dst, int *out_len) +{ + unsigned long ul; + char *endptr; + char *token; + int i; + + i = 0; + for (token = strtok(sval, delims); + token != NULL; + token = strtok(NULL, delims)) { + + if (i >= max_len) { + return EINVAL; + } + + ul = strtoul(token, &endptr, 16); + if (sval[0] == '\0' || *endptr != '\0' || ul > UINT8_MAX) { + return -1; + } + + dst[i] = ul; + i++; + } + + *out_len = i; + + return 0; +} + +int +parse_arg_byte_stream(char *name, int max_len, uint8_t *dst, int *out_len) +{ + char *sval; + + sval = parse_arg_extract(name); + if (sval == NULL) { + return ENOENT; + } + + return parse_arg_byte_stream_delim(sval, ":-", max_len, dst, out_len); +} + +int +parse_arg_byte_stream_exact_length(char *name, uint8_t *dst, int len) +{ + int actual_len; + int rc; + + rc = parse_arg_byte_stream(name, len, dst, &actual_len); + if (rc != 0) { + return rc; + } + + if (actual_len != len) { + return EINVAL; + } + + return 0; +} + +static void +parse_reverse_bytes(uint8_t *bytes, int len) +{ + uint8_t tmp; + int i; + + for (i = 0; i < len / 2; i++) { + tmp = bytes[i]; + bytes[i] = bytes[len - i - 1]; + bytes[len - i - 1] = tmp; + } +} + +int +parse_arg_mac(char *name, uint8_t *dst) +{ + int rc; + + rc = parse_arg_byte_stream_exact_length(name, dst, 6); + if (rc != 0) { + return rc; + } + + parse_reverse_bytes(dst, 6); + + return 0; +} + +int +parse_arg_uuid(char *str, ble_uuid_any_t *uuid) +{ + uint16_t uuid16; + uint8_t val[16]; + int len; + int rc; + + uuid16 = parse_arg_uint16_peek(str, &rc); + switch (rc) { + case ENOENT: + parse_arg_extract(str); + return ENOENT; + + case 0: + len = 2; + val[0] = uuid16; + val[1] = uuid16 >> 8; + parse_arg_extract(str); + break; + + default: + len = 16; + rc = parse_arg_byte_stream_exact_length(str, val, 16); + if (rc != 0) { + return EINVAL; + } + parse_reverse_bytes(val, 16); + break; + } + + rc = ble_uuid_init_from_buf(uuid, val, len); + if (rc != 0) { + return EINVAL; + } else { + return 0; + } +} + +int +parse_arg_all(int argc, char **argv) +{ + char *key; + char *val; + int i; + + cmd_num_args = 0; + + for (i = 0; i < argc; i++) { + key = strtok(argv[i], "="); + val = strtok(NULL, "="); + + if (key != NULL && val != NULL) { + if (strlen(key) == 0) { + console_printf("Error: invalid argument: %s\n", argv[i]); + return -1; + } + + if (cmd_num_args >= CMD_MAX_ARGS) { + console_printf("Error: too many arguments"); + return -1; + } + + cmd_args[cmd_num_args][0] = key; + cmd_args[cmd_num_args][1] = val; + cmd_num_args++; + } + } + + return 0; +} + +int +parse_eddystone_url(char *full_url, uint8_t *out_scheme, char *out_body, + uint8_t *out_body_len, uint8_t *out_suffix) +{ + static const struct { + char *s; + uint8_t scheme; + } schemes[] = { + { "http://www.", BLE_EDDYSTONE_URL_SCHEME_HTTP_WWW }, + { "https://www.", BLE_EDDYSTONE_URL_SCHEME_HTTPS_WWW }, + { "http://", BLE_EDDYSTONE_URL_SCHEME_HTTP }, + { "https://", BLE_EDDYSTONE_URL_SCHEME_HTTPS }, + }; + + static const struct { + char *s; + uint8_t code; + } suffixes[] = { + { ".com/", BLE_EDDYSTONE_URL_SUFFIX_COM_SLASH }, + { ".org/", BLE_EDDYSTONE_URL_SUFFIX_ORG_SLASH }, + { ".edu/", BLE_EDDYSTONE_URL_SUFFIX_EDU_SLASH }, + { ".net/", BLE_EDDYSTONE_URL_SUFFIX_NET_SLASH }, + { ".info/", BLE_EDDYSTONE_URL_SUFFIX_INFO_SLASH }, + { ".biz/", BLE_EDDYSTONE_URL_SUFFIX_BIZ_SLASH }, + { ".gov/", BLE_EDDYSTONE_URL_SUFFIX_GOV_SLASH }, + { ".com", BLE_EDDYSTONE_URL_SUFFIX_COM }, + { ".org", BLE_EDDYSTONE_URL_SUFFIX_ORG }, + { ".edu", BLE_EDDYSTONE_URL_SUFFIX_EDU }, + { ".net", BLE_EDDYSTONE_URL_SUFFIX_NET }, + { ".info", BLE_EDDYSTONE_URL_SUFFIX_INFO }, + { ".biz", BLE_EDDYSTONE_URL_SUFFIX_BIZ }, + { ".gov", BLE_EDDYSTONE_URL_SUFFIX_GOV }, + }; + + char *prefix; + char *suffix; + int full_url_len; + int prefix_len; + int suffix_len; + int suffix_idx; + int rc; + int i; + + full_url_len = strlen(full_url); + + rc = BLE_HS_EINVAL; + for (i = 0; i < sizeof schemes / sizeof schemes[0]; i++) { + prefix = schemes[i].s; + prefix_len = strlen(schemes[i].s); + + if (full_url_len >= prefix_len && + memcmp(full_url, prefix, prefix_len) == 0) { + + *out_scheme = i; + rc = 0; + break; + } + } + if (rc != 0) { + return rc; + } + + rc = BLE_HS_EINVAL; + for (i = 0; i < sizeof suffixes / sizeof suffixes[0]; i++) { + suffix = suffixes[i].s; + suffix_len = strlen(suffixes[i].s); + + suffix_idx = full_url_len - suffix_len; + if (suffix_idx >= prefix_len && + memcmp(full_url + suffix_idx, suffix, suffix_len) == 0) { + + *out_suffix = i; + rc = 0; + break; + } + } + if (rc != 0) { + *out_suffix = BLE_EDDYSTONE_URL_SUFFIX_NONE; + *out_body_len = full_url_len - prefix_len; + } else { + *out_body_len = full_url_len - prefix_len - suffix_len; + } + + memcpy(out_body, full_url + prefix_len, *out_body_len); + + return 0; +} diff --git a/apps/btshell/syscfg.yml b/apps/btshell/syscfg.yml new file mode 100644 index 000000000..c10f66dd5 --- /dev/null +++ b/apps/btshell/syscfg.yml @@ -0,0 +1,44 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Package: apps/btshell + +syscfg.defs: + BTSHELL_ANS: + description: Include support for the alert notification service. + value: 1 + +syscfg.vals: + # Enable the shell task. + SHELL_TASK: 1 + + # Set log level to info (disable debug logging). + LOG_LEVEL: 1 + + # Disable security manager (pairing and bonding). + BLE_SM_LEGACY: 0 + BLE_SM_SC: 0 + + # Default task settings + OS_MAIN_STACK_SIZE: 512 + + # Newtmgr is not supported in this app, so disable newtmgr-over-shell. + SHELL_NEWTMGR: 0 + +syscfg.vals.BLE_MESH: + MSYS_1_BLOCK_COUNT: 16