diff --git a/apps/cs_initiator/pkg.yml b/apps/cs_initiator/pkg.yml new file mode 100644 index 0000000000..ddc14056e7 --- /dev/null +++ b/apps/cs_initiator/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/cs_initiator +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" + - "@apache-mynewt-core/sys/log" + - "@apache-mynewt-core/sys/log/modlog" + - "@apache-mynewt-core/sys/stats" + - "@apache-mynewt-core/crypto/mbedtls" + - "@apache-mynewt-core/test/testutil" + - nimble/host + - nimble/host/util + - nimble/host/services/gap + - nimble/host/services/gatt + - nimble/host/services/ras + - nimble/host/store/config diff --git a/apps/cs_initiator/src/cs_initiator.h b/apps/cs_initiator/src/cs_initiator.h new file mode 100644 index 0000000000..fd00d9b216 --- /dev/null +++ b/apps/cs_initiator/src/cs_initiator.h @@ -0,0 +1,50 @@ +/* + * 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_CS_INITIATOR_ +#define H_CS_INITIATOR_ + +#include "os/mynewt.h" +#include "modlog/modlog.h" +#include "host/ble_peer.h" +#ifdef __cplusplus +extern "C" { +#endif + +struct ble_hs_adv_fields; +struct ble_gap_conn_desc; +struct ble_hs_cfgble_hs_cfg; + +#define MODLOG_MODULE_APP 123 +#define LOG(ml_lvl_, ...) MODLOG(ml_lvl_, MODLOG_MODULE_APP, __VA_ARGS__) + +/** 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); +void peer_print_discovered_svcs(const struct ble_peer *peer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/apps/cs_initiator/src/main.c b/apps/cs_initiator/src/main.c new file mode 100644 index 0000000000..abe8e69494 --- /dev/null +++ b/apps/cs_initiator/src/main.c @@ -0,0 +1,611 @@ +/* + * 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 "os/mynewt.h" +#include "bsp/bsp.h" + +/* BLE */ +#include "nimble/ble.h" +#include "host/ble_hs.h" +#include "host/ble_cs.h" +#include "host/ble_peer.h" +#include "host/util/util.h" + +/* Mandatory services. */ +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" +#include "services/ras/ble_svc_ras.h" + +/* Application-specified header. */ +#include "cs_initiator.h" +#include +#include "console/console.h" + +static struct os_callout cs_start_timer; +static int cs_initiator_gap_event(struct ble_gap_event *event, void *arg); +static uint16_t last_conn_handle; +static uint8_t cs_started; + +static void cs_initiator_start_cs(uint16_t conn_handle); + +#define CHARS_PER_TONE_MAX (64) +static char buf[2000]; + +static int16_t +sign_extend_12(uint16_t x) +{ + return (int16_t)(x << 4) >> 4; +} + +static int +cs_print(struct ble_cs_subevent_info *local_subevent_info, + struct ble_cs_subevent_info *remote_subevent_info) +{ + int pos = 0; + int max = sizeof(buf); + uint8_t number_of_tones; + uint32_t tone_pct; + int16_t I12i, Q12i, I12r, Q12r; + uint16_t i; + uint8_t channel; + + if (local_subevent_info->mode2_results_count != remote_subevent_info->mode2_results_count) { + LOG(INFO, "Number of steps, local: %d, remote: %d\n", + local_subevent_info->mode2_results_count, + remote_subevent_info->mode2_results_count); + number_of_tones = min(local_subevent_info->mode2_results_count, + remote_subevent_info->mode2_results_count); + } else { + number_of_tones = local_subevent_info->mode2_results_count; + } + + if (number_of_tones == 0) { + return 1; + } + + pos += snprintf(buf, max, "%u ", number_of_tones); + + for (i = 0; i < number_of_tones; ++i) { + if (pos >= max - CHARS_PER_TONE_MAX) { + break; + } + + channel = local_subevent_info->mode2_results[i].step_channel; + + tone_pct = local_subevent_info->mode2_results[i].tone_pct[0]; + I12i = sign_extend_12(tone_pct & 0x0FFF); + Q12i = sign_extend_12((tone_pct >> 12) & 0x0FFF); + + tone_pct = remote_subevent_info->mode2_results[i].tone_pct[0]; + I12r = sign_extend_12(tone_pct & 0x0FFF); + Q12r = sign_extend_12((tone_pct >> 12) & 0x0FFF); + + pos += snprintf(buf + pos, max - pos, "%u %d %d %d %d ", + channel, I12i, Q12i, I12r, Q12r); + } + + if (pos < max - 2) { + buf[pos++] = '\n'; + buf[pos] = '\0'; + } else { + buf[max - 2] = '\n'; + buf[max - 1] = '\0'; + } + + printf("%s", buf); + + return 0; +} + +static void +cs_initiator_start_cs(uint16_t conn_handle) +{ + struct ble_cs_procedure_start_params cmd; + int rc; + + LOG(INFO, "Starting new CS procedure, conn_handle %d\n", conn_handle); + + last_conn_handle = conn_handle; + + rc = ble_cs_procedure_start(&cmd, conn_handle); + if (rc) { + LOG(INFO, "Failed to start new CS procedure, err %d\n", rc); + } +} + +static void +cs_start_timer_reset(void) +{ + int rc; + + rc = os_callout_reset(&cs_start_timer, os_time_ms_to_ticks32(500)); + assert(rc == 0); +} + +static void +cs_start_timer_exp(struct os_event *ev) +{ + cs_initiator_start_cs(last_conn_handle); +} + +static int +cs_initiator_procedure_complete(struct ble_cs_event *event) +{ + LOG(INFO, "CS procedure completed, status=%d\n", event->procedure_complete.status); + + cs_start_timer_reset(); + + return 0; +} + +static int +cs_initiator_subevent_data_received(struct ble_cs_event *event) +{ + LOG(INFO, "CS subevent data received, status=%d\n", event->subevent_data.status); + + if (event->subevent_data.status == 0) { + cs_print(event->subevent_data.local_subevent_info, + event->subevent_data.remote_subevent_info); + } + + return 0; +} + +static int +cs_initiator_cs_event(struct ble_cs_event *event, void *arg, uint16_t conn_handle) +{ + switch (event->type) { + case BLE_CS_EVENT_CS_PROCEDURE_COMPLETE: + cs_initiator_procedure_complete(event); + break; + case BLE_CS_EVENT_CS_SUBEVENT_DATA: + cs_initiator_subevent_data_received(event); + break; + } + + return 0; +} + +/** + * Initiates the GAP general discovery procedure. + */ +static void +cs_initiator_scan(void) +{ + uint8_t own_addr_type; + struct ble_gap_ext_disc_params params = {0}; + 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) { + LOG(ERROR, "error determining address type; rc=%d\n", rc); + return; + } + + params.itvl = BLE_GAP_SCAN_ITVL_MS(40); + params.passive = 1; + params.window = BLE_GAP_SCAN_FAST_WINDOW; + + rc = ble_gap_ext_disc(own_addr_type, 30000, 0, 0, 0, 0, ¶ms, NULL, + cs_initiator_gap_event, NULL); + if (rc != 0) { + 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 +cs_initiator_should_connect(const ble_addr_t *addr, const struct ble_hs_adv_fields *fields) +{ + int i; + + if (addr == NULL) { + return 0; + } + + if (fields->name != NULL && !memcmp((const char *)fields->name, "Pixel 9", 7)) { + return 1; + } + + for (i = 0; i < fields->num_uuids16; i++) { + if (ble_uuid_u16(&fields->uuids16[i].u) == BLE_SVC_RAS_SVC_RANGING_SERVICE_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 +cs_initiator_connect_if_interesting(const struct ble_gap_disc_desc *disc, + const struct ble_gap_ext_disc_desc *ext_disc) +{ + int rc; + const ble_addr_t *addr; + struct ble_hs_adv_fields fields; + uint8_t own_addr_type; + uint8_t length_data; + const uint8_t *data; + struct ble_gap_conn_params conn_params = { + .scan_itvl = 0x0010, + .scan_window = 0x0010, + .itvl_min = BLE_GAP_INITIAL_CONN_ITVL_MIN, + .itvl_max = BLE_GAP_INITIAL_CONN_ITVL_MAX, + .latency = BLE_GAP_INITIAL_CONN_LATENCY, + .supervision_timeout = BLE_GAP_INITIAL_SUPERVISION_TIMEOUT, + .min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN, + .max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN, + }; + + if (disc) { + if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && + disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { + return; + } + data = disc->data; + length_data = disc->length_data; + addr = &disc->addr; + } else if (ext_disc) { + if (ext_disc->props & BLE_HCI_ADV_LEGACY_MASK && + (ext_disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && + ext_disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND)) { + return; + } else if (!(ext_disc->props & BLE_HCI_ADV_CONN_MASK)) { + return; + } + data = ext_disc->data; + length_data = ext_disc->length_data; + addr = &ext_disc->addr; + } else { + return; + } + + rc = ble_hs_adv_parse_fields(&fields, data, length_data); + if (rc != 0) { + return; + } + + /* An advertisment report was received during GAP discovery. */ +// print_adv_fields(&fields); + + /* Don't do anything if we don't care about this advertiser. */ + if (!cs_initiator_should_connect(addr, &fields)) { + return; + } + + /* Scanning must be stopped before a connection can be initiated. */ + rc = ble_gap_disc_cancel(); + if (rc != 0) { + LOG(DEBUG, "Failed to cancel scan; rc=%d\n", rc); + return; + } + + /* Figure out address to use for connect (no privacy for now) */ + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + LOG(ERROR, "error determining address type; rc=%d\n", rc); + return; + } + + /* Try to connect the the advertiser. Allow 30 seconds (30000 ms) for + * timeout. + */ + if (disc) { + rc = ble_gap_connect(own_addr_type, addr, 0, &conn_params, + cs_initiator_gap_event, NULL); + } else { + rc = ble_gap_ext_connect(own_addr_type, addr, 0, BLE_GAP_LE_PHY_1M_MASK, + &conn_params, NULL, NULL, cs_initiator_gap_event, NULL); + } + + if (rc != 0) { + LOG(ERROR, "Error: Failed to connect to device; addr_type=%d " + "addr=%s; rc=%d\n", disc->addr.type, addr_str(disc->addr.val), rc); + return; + } +} + +static void +cs_initiator_on_disc_complete(const struct ble_peer *peer, int status, void *arg) +{ + + if (status != 0) { + /* Service discovery failed. Terminate the connection. */ + 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. + */ + LOG(INFO, "Service discovery complete; status=%d " + "conn_handle=%d\n", status, peer->conn_handle); + + cs_initiator_start_cs(peer->conn_handle); +} + +static int +cs_mtu_updated(uint16_t conn_handle, const struct ble_gatt_error *error, + uint16_t mtu, void *arg) +{ + int rc; + struct ble_cs_setup_params cmd; + + if (cs_started) { + return 0; + } + + cs_started = 1; + cmd.cb = cs_initiator_cs_event; + cmd.cb_arg = NULL; + cmd.local_role = BLE_HS_CS_ROLE_INITIATOR; + ble_cs_setup(&cmd, conn_handle); + + /* Perform service discovery. */ + rc = ble_peer_disc_all(conn_handle, cs_initiator_on_disc_complete, NULL); + if (rc != 0) { + LOG(ERROR, "Failed to discover services; rc=%d\n", rc); + return 0; + } + + return 0; +} + +/** + * The nimble host executes this callback when a GAP event occurs. The + * application associates a GAP event callback with each connection that is + * established. cs_initiator uses the same callback for all connections. + * + * @param event The event being signalled. + * @param arg Application-specified argument; unused by + * cs_initiator. + * + * @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 +cs_initiator_gap_event(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + struct ble_sm_io pk; + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_EXT_DISC: + cs_initiator_connect_if_interesting(NULL, &event->ext_disc); + return 0; + case BLE_GAP_EVENT_DISC: + cs_initiator_connect_if_interesting(&event->disc, NULL); + 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. */ + LOG(INFO, "Connection established "); + + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + print_conn_desc(&desc); + LOG(INFO, "\n"); + + /* Remember peer. */ + rc = ble_peer_add(event->connect.conn_handle); + if (rc != 0) { + LOG(ERROR, "Failed to add peer; rc=%d\n", rc); + return 0; + } + + rc = ble_gap_security_initiate(event->connect.conn_handle); + if (rc) { + LOG(INFO, "Failed to pair"); + } + } else { + /* Connection attempt failed; resume scanning. */ + LOG(ERROR, "Error: Connection failed; status=%d\n", + event->connect.status); + cs_initiator_scan(); + } + + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + /* Connection terminated. */ + LOG(INFO, "disconnect; reason=%d ", event->disconnect.reason); + print_conn_desc(&event->disconnect.conn); + LOG(INFO, "\n"); + + /* Forget about peer. */ + ble_peer_delete(event->disconnect.conn.conn_handle); + + /* Resume scanning. */ + cs_initiator_scan(); + + return 0; + + case BLE_GAP_EVENT_DISC_COMPLETE: + LOG(INFO, "discovery complete; reason=%d\n", + event->disc_complete.reason); + return 0; + + case BLE_GAP_EVENT_PAIRING_COMPLETE: + LOG(INFO, "received pairing complete: " + "conn_handle=%d status=%d\n", + event->pairing_complete.conn_handle, + event->pairing_complete.status); + + return 0; + + case BLE_GAP_EVENT_ENC_CHANGE: + /* Encryption has been enabled or disabled for this connection. */ + LOG(INFO, "encryption change event; status=%d ", + event->enc_change.status); + + if (event->enc_change.status != 0) { + return 0; + } + + rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); + assert(rc == 0); + print_conn_desc(&desc); + + rc = ble_gattc_exchange_mtu(event->enc_change.conn_handle, cs_mtu_updated, NULL); + assert(rc == 0); + + return 0; + + case BLE_GAP_EVENT_NOTIFY_RX: + /* Peer sent us a notification or indication. */ + LOG(DEBUG, "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: + 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(BLE_SM_SC) + case BLE_GAP_EVENT_PASSKEY_ACTION: + LOG(INFO, "passkey action event; action=%d", event->passkey.params.action); + if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { + LOG(INFO, " numcmp=%lu", (unsigned long) event->passkey.params.numcmp); + + pk.action = BLE_SM_IOACT_NUMCMP; + pk.numcmp_accept = 1; + rc = ble_sm_inject_io(event->passkey.conn_handle, &pk); + assert(rc == 0); + } + return 0; +#endif + default: + return 0; + } +} + +static void +cs_initiator_on_reset(int reason) +{ + LOG(ERROR, "Resetting state; reason=%d\n", reason); +} + +static void +cs_initiator_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. */ + cs_initiator_scan(); +} + +/** + * main + * + * All application logic and NimBLE host work is performed in default task. + * + * @return int NOTE: this function should never return! + */ +int +mynewt_main(int argc, char **argv) +{ + int rc; + + /* Initialize OS */ + sysinit(); + + rc = modlog_register(MODLOG_MODULE_APP, log_console_get(), + LOG_LEVEL_DEBUG, NULL); + assert(rc == 0); + + LOG(DEBUG, "Started CS Initiator\n"); + + /* Configure the host. */ + ble_hs_cfg.reset_cb = cs_initiator_on_reset; + ble_hs_cfg.sync_cb = cs_initiator_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; +#if MYNEWT_VAL(BLE_SM_SC) + ble_hs_cfg.sm_io_cap = BLE_HS_IO_DISPLAY_YESNO; + ble_hs_cfg.sm_bonding = 1; + ble_hs_cfg.sm_mitm = 1; + ble_hs_cfg.sm_sc = 1; +#endif + + os_callout_init(&cs_start_timer, os_eventq_dflt_get(), + cs_start_timer_exp, NULL); + + /* Set the default device name. */ + rc = ble_svc_gap_device_name_set("nimble-cs_initiator"); + 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/cs_initiator/src/misc.c b/apps/cs_initiator/src/misc.c new file mode 100644 index 0000000000..e81556ef2c --- /dev/null +++ b/apps/cs_initiator/src/misc.c @@ -0,0 +1,258 @@ +/* + * 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 "cs_initiator.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++) { + LOG(ERROR, "%s0x%02x", i != 0 ? ":" : "", bytes[i]); + } +} + +void +print_mbuf(const struct os_mbuf *om) +{ + int colon; + + colon = 0; + while (om != NULL) { + if (colon) { + LOG(ERROR, ":"); + } 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]; + + LOG(ERROR, "%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) +{ + LOG(ERROR, "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)); + LOG(ERROR, "our_id_addr_type=%d our_id_addr=%s ", + desc->our_id_addr.type, addr_str(desc->our_id_addr.val)); + LOG(ERROR, "peer_ota_addr_type=%d peer_ota_addr=%s ", + desc->peer_ota_addr.type, addr_str(desc->peer_ota_addr.val)); + LOG(ERROR, "peer_id_addr_type=%d peer_id_addr=%s ", + desc->peer_id_addr.type, addr_str(desc->peer_id_addr.val)); + LOG(ERROR, "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) { + LOG(ERROR, " flags=0x%02x\n", fields->flags); + } + + if (fields->uuids16 != NULL) { + LOG(ERROR, " uuids16(%scomplete)=", + fields->uuids16_is_complete ? "" : "in"); + for (i = 0; i < fields->num_uuids16; i++) { + print_uuid(&fields->uuids16[i].u); + LOG(ERROR, " "); + } + LOG(ERROR, "\n"); + } + + if (fields->uuids32 != NULL) { + LOG(ERROR, " uuids32(%scomplete)=", + fields->uuids32_is_complete ? "" : "in"); + for (i = 0; i < fields->num_uuids32; i++) { + print_uuid(&fields->uuids32[i].u); + LOG(ERROR, " "); + } + LOG(ERROR, "\n"); + } + + if (fields->uuids128 != NULL) { + LOG(ERROR, " uuids128(%scomplete)=", + fields->uuids128_is_complete ? "" : "in"); + for (i = 0; i < fields->num_uuids128; i++) { + print_uuid(&fields->uuids128[i].u); + LOG(ERROR, " "); + } + LOG(ERROR, "\n"); + } + + if (fields->name != NULL) { + assert(fields->name_len < sizeof s - 1); + memcpy(s, fields->name, fields->name_len); + s[fields->name_len] = '\0'; + LOG(ERROR, " name(%scomplete)=%s\n", + fields->name_is_complete ? "" : "in", s); + } + + if (fields->tx_pwr_lvl_is_present) { + LOG(ERROR, " tx_pwr_lvl=%d\n", fields->tx_pwr_lvl); + } + + if (fields->slave_itvl_range != NULL) { + LOG(ERROR, " slave_itvl_range="); + print_bytes(fields->slave_itvl_range, BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN); + LOG(ERROR, "\n"); + } + + if (fields->svc_data_uuid16 != NULL) { + LOG(ERROR, " svc_data_uuid16="); + print_bytes(fields->svc_data_uuid16, fields->svc_data_uuid16_len); + LOG(ERROR, "\n"); + } + + if (fields->public_tgt_addr != NULL) { + LOG(ERROR, " public_tgt_addr="); + u8p = fields->public_tgt_addr; + for (i = 0; i < fields->num_public_tgt_addrs; i++) { + LOG(ERROR, "public_tgt_addr=%s ", addr_str(u8p)); + u8p += BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN; + } + LOG(ERROR, "\n"); + } + + if (fields->appearance_is_present) { + LOG(ERROR, " appearance=0x%04x\n", fields->appearance); + } + + if (fields->adv_itvl_is_present) { + LOG(ERROR, " adv_itvl=0x%04x\n", fields->adv_itvl); + } + + if (fields->svc_data_uuid32 != NULL) { + LOG(ERROR, " svc_data_uuid32="); + print_bytes(fields->svc_data_uuid32, fields->svc_data_uuid32_len); + LOG(ERROR, "\n"); + } + + if (fields->svc_data_uuid128 != NULL) { + LOG(ERROR, " svc_data_uuid128="); + print_bytes(fields->svc_data_uuid128, fields->svc_data_uuid128_len); + LOG(ERROR, "\n"); + } + + if (fields->uri != NULL) { + LOG(ERROR, " uri="); + print_bytes(fields->uri, fields->uri_len); + LOG(ERROR, "\n"); + } + + if (fields->mfg_data != NULL) { + LOG(ERROR, " mfg_data="); + print_bytes(fields->mfg_data, fields->mfg_data_len); + LOG(ERROR, "\n"); + } +} + +static void +peer_print_dsc(const struct ble_peer_dsc *dsc) +{ + LOG(ERROR, " dsc_handle=%d uuid=", dsc->dsc.handle); + print_uuid(&dsc->dsc.uuid.u); + LOG(ERROR, "\n"); +} + +static void +peer_print_chr(const struct ble_peer_chr *chr) +{ + struct ble_peer_dsc *dsc; + + LOG(ERROR, " 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); + LOG(ERROR, "\n"); + + SLIST_FOREACH(dsc, &chr->dscs, next) { + peer_print_dsc(dsc); + } +} + +static void +peer_print_svc(const struct ble_peer_svc *svc) +{ + struct ble_peer_chr *chr; + + LOG(ERROR, " start=%d end=%d uuid=", svc->svc.start_handle, + svc->svc.end_handle); + print_uuid(&svc->svc.uuid.u); + LOG(ERROR, "\n"); + + SLIST_FOREACH(chr, &svc->chrs, next) { + peer_print_chr(chr); + } +} + +void +peer_print_discovered_svcs(const struct ble_peer *peer) +{ + struct ble_peer_svc *svc; + + SLIST_FOREACH(svc, &peer->svcs, next) { + peer_print_svc(svc); + } +} diff --git a/apps/cs_initiator/syscfg.yml b/apps/cs_initiator/syscfg.yml new file mode 100644 index 0000000000..232d78eb26 --- /dev/null +++ b/apps/cs_initiator/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. +# + +syscfg.vals: + CONSOLE_IMPLEMENTATION: full + LOG_IMPLEMENTATION: full + STATS_IMPLEMENTATION: full + + # DEBUG logging is a bit noisy; use INFO. + LOG_LEVEL: 1 + + # Default task settings + OS_MAIN_STACK_SIZE: 2024 + + BLE_ROLE_BROADCASTER: 1 + BLE_ROLE_CENTRAL: 1 + BLE_ROLE_OBSERVER: 1 + BLE_ROLE_PERIPHERAL: 1 + + BLE_VERSION: 60 + BLE_CHANNEL_SOUNDING: 1 + BLE_SVC_RAS_CLIENT: 1 + BLE_PEER: 1 + + BLE_EXT_ADV: 1 + BLE_EXT_ADV_MAX_SIZE: 251 + BLE_PHY_2M: 1 + BLE_SM_SC: 1 + BLE_SM_LVL: 2 diff --git a/apps/cs_reflector/pkg.yml b/apps/cs_reflector/pkg.yml new file mode 100644 index 0000000000..8d2c04e593 --- /dev/null +++ b/apps/cs_reflector/pkg.yml @@ -0,0 +1,46 @@ +# 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/cs_reflector +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" + - "@mcuboot/boot/bootutil" + - "@apache-mynewt-core/kernel/os" + - "@apache-mynewt-core/mgmt/imgmgr" + - "@apache-mynewt-core/mgmt/smp" + - "@apache-mynewt-core/mgmt/smp/transport/ble" + - "@apache-mynewt-core/sys/console" + - "@apache-mynewt-core/sys/log" + - "@apache-mynewt-core/sys/log/modlog" + - "@apache-mynewt-core/sys/stats" + - "@apache-mynewt-core/sys/sysinit" + - "@apache-mynewt-core/sys/id" + - "@apache-mynewt-core/test/testutil" + - nimble/host + - nimble/host/services/ans + - nimble/host/services/dis + - nimble/host/services/gap + - nimble/host/services/gatt + - nimble/host/services/ras + - nimble/host/store/config + - nimble/host/util diff --git a/apps/cs_reflector/src/cs_reflector.h b/apps/cs_reflector/src/cs_reflector.h new file mode 100644 index 0000000000..3bad9c4edb --- /dev/null +++ b/apps/cs_reflector/src/cs_reflector.h @@ -0,0 +1,41 @@ +/* + * 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_CS_REFLECTOR_ +#define H_CS_REFLECTOR_ + +#include +#include "nimble/ble.h" +#include "modlog/modlog.h" +#ifdef __cplusplus +extern "C" { +#endif + +/* PHY support */ +#if MYNEWT_VAL(CS_REFLECTOR_LE_PHY_SUPPORT) +void phy_init(void); +void phy_conn_changed(uint16_t handle); +void phy_update(uint8_t phy); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/apps/cs_reflector/src/main.c b/apps/cs_reflector/src/main.c new file mode 100644 index 0000000000..bed3d93e41 --- /dev/null +++ b/apps/cs_reflector/src/main.c @@ -0,0 +1,386 @@ +/* + * 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" +#if MYNEWT_VAL(BLE_SVC_DIS_FIRMWARE_REVISION_READ_PERM) >= 0 +#include "bootutil/image.h" +#include "imgmgr/imgmgr.h" +#include "services/dis/ble_svc_dis.h" +#endif + +/* BLE */ +#include "nimble/ble.h" +#include "host/ble_hs.h" +#include "host/ble_cs.h" +#include "host/util/util.h" +#include "services/gap/ble_svc_gap.h" +#include "services/ras/ble_svc_ras.h" + +/* Application-specified header. */ +#include "cs_reflector.h" + +static int cs_reflector_gap_event(struct ble_gap_event *event, void *arg); + +static int +cs_reflector_cs_event(struct ble_cs_event *event, void *arg, uint16_t conn_handle) +{ + int rc; + + switch (event->type) { + case BLE_CS_EVENT_CS_PROCEDURE_COMPLETE: + return 0; + default: + return 0; + } +} + +void +print_addr(const void *addr) +{ + const uint8_t *u8p; + + u8p = addr; + MODLOG_DFLT(INFO, "%02x:%02x:%02x:%02x:%02x:%02x", + u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]); +} + +/** + * Logs information about a connection to the console. + */ +static void +cs_reflector_print_conn_desc(struct ble_gap_conn_desc *desc) +{ + MODLOG_DFLT(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); + MODLOG_DFLT(INFO, " our_id_addr_type=%d our_id_addr=", + desc->our_id_addr.type); + print_addr(desc->our_id_addr.val); + MODLOG_DFLT(INFO, " peer_ota_addr_type=%d peer_ota_addr=", + desc->peer_ota_addr.type); + print_addr(desc->peer_ota_addr.val); + MODLOG_DFLT(INFO, " peer_id_addr_type=%d peer_id_addr=", + desc->peer_id_addr.type); + print_addr(desc->peer_id_addr.val); + MODLOG_DFLT(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 +cs_reflector_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) { + MODLOG_DFLT(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(BLE_SVC_RAS_SVC_RANGING_SERVICE_UUID) + }; + fields.num_uuids16 = 1; + fields.uuids16_is_complete = 1; + + rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + MODLOG_DFLT(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, cs_reflector_gap_event, NULL); + if (rc != 0) { + MODLOG_DFLT(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. + * cs_reflector 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 + * cs_reflector. + * + * @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 +cs_reflector_gap_event(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + struct ble_cs_setup_params cmd; + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + MODLOG_DFLT(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); + cs_reflector_print_conn_desc(&desc); + + cmd.cb = cs_reflector_cs_event; + cmd.cb_arg = NULL; + cmd.local_role = BLE_HS_CS_ROLE_REFLECTOR; + ble_cs_setup(&cmd, event->connect.conn_handle); + +#if MYNEWT_VAL(CS_REFLECTOR_LE_PHY_SUPPORT) + phy_conn_changed(event->connect.conn_handle); +#endif + } + MODLOG_DFLT(INFO, "\n"); + + if (event->connect.status != 0) { + /* Connection failed; resume advertising. */ + cs_reflector_advertise(); + } + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason); + cs_reflector_print_conn_desc(&event->disconnect.conn); + MODLOG_DFLT(INFO, "\n"); + +#if MYNEWT_VAL(CS_REFLECTOR_LE_PHY_SUPPORT) + phy_conn_changed(BLE_CONN_HANDLE_INVALID); +#endif + + /* Connection terminated; resume advertising. */ + cs_reflector_advertise(); + return 0; + + case BLE_GAP_EVENT_CONN_UPDATE: + /* The central has updated the connection parameters. */ + MODLOG_DFLT(INFO, "connection updated; status=%d ", + event->conn_update.status); + rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc); + assert(rc == 0); + cs_reflector_print_conn_desc(&desc); + MODLOG_DFLT(INFO, "\n"); + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: + MODLOG_DFLT(INFO, "advertise complete; reason=%d", + event->adv_complete.reason); + cs_reflector_advertise(); + return 0; + + case BLE_GAP_EVENT_ENC_CHANGE: + /* Encryption has been enabled or disabled for this connection. */ + MODLOG_DFLT(INFO, "encryption change event; status=%d ", + event->enc_change.status); + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + cs_reflector_print_conn_desc(&desc); + MODLOG_DFLT(INFO, "\n"); + return 0; + + case BLE_GAP_EVENT_SUBSCRIBE: + MODLOG_DFLT(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: + MODLOG_DFLT(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(CS_REFLECTOR_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 +cs_reflector_on_reset(int reason) +{ + MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason); +} + +static void +cs_reflector_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. */ + cs_reflector_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 +mynewt_main(int argc, char **argv) +{ +#if MYNEWT_VAL(BLE_SVC_DIS_FIRMWARE_REVISION_READ_PERM) >= 0 + struct image_version ver; + static char ver_str[IMGMGR_NMGR_MAX_VER]; +#endif + int rc; + + /* Initialize OS */ + sysinit(); + + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = cs_reflector_on_reset; + ble_hs_cfg.sync_cb = cs_reflector_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + +#if MYNEWT_VAL(BLE_SVC_DIS_FIRMWARE_REVISION_READ_PERM) >= 0 + /* Set firmware version in DIS */ + imgr_my_version(&ver); + imgr_ver_str(&ver, ver_str); + ble_svc_dis_firmware_revision_set(ver_str); +#endif + +#if MYNEWT_VAL(CS_REFLECTOR_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/cs_reflector/src/phy.c b/apps/cs_reflector/src/phy.c new file mode 100644 index 0000000000..b6fc353080 --- /dev/null +++ b/apps/cs_reflector/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 "cs_reflector.h" + +#if MYNEWT_VAL(CS_REFLECTOR_LE_PHY_SUPPORT) + +static const int button_gpio[4] = MYNEWT_VAL(CS_REFLECTOR_LE_PHY_BUTTON_GPIO); +static const int led_gpio[3] = MYNEWT_VAL(CS_REFLECTOR_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 = BLE_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 != BLE_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 != BLE_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 == BLE_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/cs_reflector/syscfg.yml b/apps/cs_reflector/syscfg.yml new file mode 100644 index 0000000000..c20f9ca32e --- /dev/null +++ b/apps/cs_reflector/syscfg.yml @@ -0,0 +1,61 @@ +# 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.defs: + CS_REFLECTOR_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 + CS_REFLECTOR_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 }" + CS_REFLECTOR_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: + CONSOLE_IMPLEMENTATION: full + LOG_IMPLEMENTATION: full + STATS_IMPLEMENTATION: full + + # Disable central and observer roles. + BLE_ROLE_BROADCASTER: 1 + BLE_ROLE_CENTRAL: 0 + BLE_ROLE_OBSERVER: 0 + BLE_ROLE_PERIPHERAL: 1 + + # Configure DIS + BLE_SVC_DIS_FIRMWARE_REVISION_READ_PERM: 1 + + # OS main/default task + OS_MAIN_STACK_SIZE: 2024 + + BLE_SVC_GAP_DEVICE_NAME: '"CS Reflector"' + + BLE_VERSION: 60 + BLE_CHANNEL_SOUNDING: 1 + BLE_SVC_RAS_SERVER: 1