blob: bb9d4bdc7193cf4b72f6b8e22c397b125f4137c3 [file] [log] [blame]
/*
* 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 <assert.h>
#include <string.h>
#include "os/mynewt.h"
#include "bsp/bsp.h"
#include "console/console.h"
/* BLE */
#include "nimble/ble.h"
#include "controller/ble_ll.h"
#include "host/ble_hs.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 "bsncent.h"
#define BSNCENT_PRINT_RATE (OS_TICKS_PER_SEC * 10)
static uint32_t num_notify_pkts_rx;
static uint32_t num_notify_bytes_rx;
/* Prints statistics every 10 seconds. */
static struct os_callout bsncent_print_timer;
/* c66f3301-33b3-4687-850a-d52b0d5d1e3c */
static const ble_uuid128_t bsncent_svc_gendata_uuid =
BLE_UUID128_INIT(0x3c, 0x1e, 0x5d, 0x0d, 0x2b, 0xd5, 0x0a, 0x85,
0x87, 0x46, 0xb3, 0x33, 0x01, 0x33, 0x6f, 0xc6);
/* c66f3301-33b3-4687-850a-d52b0d5d1e3d */
static const ble_uuid128_t bsncent_chr_gendata_uuid =
BLE_UUID128_INIT(0x3d, 0x1e, 0x5d, 0x0d, 0x2b, 0xd5, 0x0a, 0x85,
0x87, 0x46, 0xb3, 0x33, 0x01, 0x33, 0x6f, 0xc6);
static const uint8_t bsncent_cent_public_addr[6] = {
0x0a, 0x0b, 0x09, 0x09, 0x09, 0x00,
};
static const ble_addr_t bsncent_peer_addrs[] = {
{ BLE_ADDR_PUBLIC, { 0x0a, 0x0b, 0x09, 0x09, 0x09, 0x01 } },
{ BLE_ADDR_PUBLIC, { 0x0a, 0x0b, 0x09, 0x09, 0x09, 0x02 } },
{ BLE_ADDR_PUBLIC, { 0x0a, 0x0b, 0x09, 0x09, 0x09, 0x03 } },
{ BLE_ADDR_PUBLIC, { 0x0a, 0x0b, 0x09, 0x09, 0x09, 0x04 } },
{ BLE_ADDR_PUBLIC, { 0x0a, 0x0b, 0x09, 0x09, 0x09, 0x05 } },
};
static const int bsncent_num_peer_addrs =
sizeof bsncent_peer_addrs / sizeof bsncent_peer_addrs[0];
static int bsncent_gap_event(struct ble_gap_event *event, void *arg);
static const struct ble_gap_conn_params ble_gap_conn_params_bsn = {
.scan_itvl = 0x0010,
.scan_window = 0x0010,
.itvl_min = 13,
.itvl_max = 13,
.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,
};
/**
* Application callback. Called when the attempt to subscribe to notifications
* for the ANS Unread Alert Status characteristic has completed.
*/
static int
bsncent_on_subscribe(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
MODLOG_DFLT(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
bsncent_subscribe(const struct peer *peer)
{
const struct peer_dsc *dsc;
uint8_t value[2];
int rc;
/* Subscribe to notifications for the gendata 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,
&bsncent_svc_gendata_uuid.u,
&bsncent_chr_gendata_uuid.u,
BLE_UUID16_DECLARE(BLE_GATT_DSC_CLT_CFG_UUID16));
if (dsc == NULL) {
MODLOG_DFLT(ERROR, "Error: Peer lacks a CCCD for the generic data "
"characteristic\n");
goto err;
}
value[0] = 1;
value[1] = 0;
rc = ble_gattc_write_flat(peer->conn_handle, dsc->dsc.handle,
value, sizeof value, bsncent_on_subscribe, NULL);
if (rc != 0) {
MODLOG_DFLT(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
bsncent_on_disc_complete(const struct peer *peer, int status, void *arg)
{
if (status != 0) {
/* Service discovery failed. Terminate the connection. */
MODLOG_DFLT(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.
*/
MODLOG_DFLT(ERROR, "Service discovery complete; status=%d "
"conn_handle=%d\n", status, peer->conn_handle);
/* Now subscribe to the gendata characterustic. */
bsncent_subscribe(peer);
}
static int
bsncent_on_mtu_exchanged(uint16_t conn_handle,
const struct ble_gatt_error *error,
uint16_t mtu, void *arg)
{
int rc;
if (error->status != 0) {
MODLOG_DFLT(ERROR, "MTU exchange failed; rc=%d\n", error->status);
ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
return 0;
}
/* Perform service discovery. */
rc = peer_disc_all(conn_handle, bsncent_on_disc_complete, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Failed to discover services; rc=%d\n", rc);
ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
return 0;
}
return 0;
}
static void
bsncent_connect(void)
{
int rc;
rc = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER,
&ble_gap_conn_params_bsn, bsncent_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error connecting; rc=%d\n", rc);
if (!((rc == BLE_HS_EALREADY) || (rc == BLE_HS_EBUSY))) {
/* Only assert if we are not already trying */
assert(0);
}
}
}
static void
bsncent_fill_wl(void)
{
int rc;
rc = ble_gap_wl_set(bsncent_peer_addrs, bsncent_num_peer_addrs);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error setting white list; rc=%d\n", rc);
assert(0);
}
}
static void
bsncent_print_timer_reset(void)
{
int rc;
rc = os_callout_reset(&bsncent_print_timer, BSNCENT_PRINT_RATE);
assert(rc == 0);
}
/**
* Prints statistics every 10 seconds.
*/
static void
bsncent_print_timer_exp(struct os_event *ev)
{
static uint32_t prev_bytes;
static uint32_t prev_pkts;
uint32_t diff_bytes;
uint32_t diff_pkts;
console_printf("--\n");
console_printf("%8d connections\n", peer_count());
console_printf("%8lu packets received\n",
(unsigned long)num_notify_pkts_rx);
console_printf("%8lu bytes received\n",
(unsigned long)num_notify_bytes_rx);
if (prev_pkts != 0) {
diff_pkts = num_notify_pkts_rx - prev_pkts;
diff_bytes = num_notify_bytes_rx - prev_bytes;
console_printf("%8lu packets per second\n",
(unsigned long)(diff_pkts /
(BSNCENT_PRINT_RATE /
OS_TICKS_PER_SEC)));
console_printf("%8lu bytes per second\n",
(unsigned long)(diff_bytes /
(BSNCENT_PRINT_RATE /
OS_TICKS_PER_SEC)));
}
prev_pkts = num_notify_pkts_rx;
prev_bytes = num_notify_bytes_rx;
bsncent_print_timer_reset();
}
/**
* The nimble host executes this callback when a GAP event occurs. The
* application associates a GAP event callback with each connection that is
* established. bsncent uses the same callback for all connections.
*
* @param event The event being signalled.
* @param arg Application-specified argument; unused by
* bsncent.
*
* @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
bsncent_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. */
if (event->connect.status == 0) {
/* Connection successfully established. */
MODLOG_DFLT(INFO, "Connection established ");
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
assert(rc == 0);
print_conn_desc(&desc);
MODLOG_DFLT(INFO, "\n");
/* Remember peer. */
rc = peer_add(event->connect.conn_handle);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Failed to add peer; rc=%d\n", rc);
assert(0);
}
/* Try to connect to next peer if any remain unconnected. */
if (peer_count() < bsncent_num_peer_addrs) {
bsncent_connect();
}
/* Negotiate ATT MTU. */
rc = ble_gattc_exchange_mtu(event->connect.conn_handle,
bsncent_on_mtu_exchanged, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Failed to exchange MTU; rc=%d\n", rc);
return 0;
}
} else {
/* Connection attempt failed; resume connecting. */
MODLOG_DFLT(ERROR, "Error: Connection failed; status=%d\n",
event->connect.status);
bsncent_connect();
}
return 0;
case BLE_GAP_EVENT_DISCONNECT:
/* Connection terminated. */
MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason);
print_conn_desc(&event->disconnect.conn);
MODLOG_DFLT(INFO, "\n");
/* Forget about peer. */
peer_delete(event->disconnect.conn.conn_handle);
/* Resume scanning. */
bsncent_connect();
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->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. */
MODLOG_DFLT(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));
num_notify_pkts_rx++;
num_notify_bytes_rx += OS_MBUF_PKTLEN(event->notify_rx.om);
/* Attribute data is contained in event->notify_rx.attr_data. */
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;
default:
return 0;
}
}
static void
bsncent_on_reset(int reason)
{
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
}
static void
bsncent_on_sync(void)
{
/* Start printing statistics. */
bsncent_print_timer_reset();
/* Add the five known peripherals to the white list. */
bsncent_fill_wl();
/* Attempt to connect to the first peripheral. */
bsncent_connect();
}
/**
* 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;
/* Set initial BLE device address. */
memcpy(g_dev_addr, bsncent_cent_public_addr, 6);
/* Initialize OS */
sysinit();
/* Configure the host. */
ble_hs_cfg.reset_cb = bsncent_on_reset;
ble_hs_cfg.sync_cb = bsncent_on_sync;
os_callout_init(&bsncent_print_timer, os_eventq_dflt_get(),
bsncent_print_timer_exp, NULL);
/* XXX: I think some of these need to be based on # of connections */
/* Initialize data structures to track connected peers. */
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 96, 64);
assert(rc == 0);
/* Set the default device name. */
rc = ble_svc_gap_device_name_set(MYNEWT_VAL(BSNCENT_BLE_NAME));
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;
}