736 lines
21 KiB
C
736 lines
21 KiB
C
/* main.c - Application main entry point */
|
|
|
|
/*
|
|
* Copyright (c) 2015-2016 Intel Corporation
|
|
*
|
|
* Licensed 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 <stdint.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <misc/printk.h>
|
|
#include <misc/util.h>
|
|
#include <misc/byteorder.h>
|
|
#include <zephyr.h>
|
|
#include <misc/nano_work.h>
|
|
|
|
#include <bluetooth/bluetooth.h>
|
|
#include <bluetooth/hci.h>
|
|
#include <bluetooth/conn.h>
|
|
#include <bluetooth/uuid.h>
|
|
#include <bluetooth/gatt.h>
|
|
|
|
#define DEVICE_NAME "Zephyr Eddystone"
|
|
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
|
|
#define NUMBER_OF_SLOTS 1
|
|
#define EDS_VERSION 0x00
|
|
#define EDS_URL_READ_OFFSET 2
|
|
#define EDS_URL_WRITE_OFFSET 4
|
|
#define EDS_IDLE_TIMEOUT SECONDS(30)
|
|
|
|
/* Idle timer */
|
|
struct nano_delayed_work idle_work;
|
|
|
|
static const struct bt_data ad[] = {
|
|
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
|
|
/* Eddystone Service UUID a3c87500-8ed3-4bdf-8a39-a01bebede295 */
|
|
BT_DATA_BYTES(BT_DATA_UUID128_ALL,
|
|
0x95, 0xe2, 0xed, 0xeb, 0x1b, 0xa0, 0x39, 0x8a,
|
|
0xdf, 0x4b, 0xd3, 0x8e, 0x00, 0x75, 0xc8, 0xa3),
|
|
};
|
|
|
|
/* Set Scan Response data */
|
|
static const struct bt_data sd[] = {
|
|
BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
|
|
};
|
|
|
|
/* Eddystone Service Variables */
|
|
/* Service UUID a3c87500-8ed3-4bdf-8a39-a01bebede295 */
|
|
static struct bt_uuid_128 eds_uuid = BT_UUID_INIT_128(
|
|
0x95, 0xe2, 0xed, 0xeb, 0x1b, 0xa0, 0x39, 0x8a,
|
|
0xdf, 0x4b, 0xd3, 0x8e, 0x00, 0x75, 0xc8, 0xa3);
|
|
|
|
/* Characteristic UUID a3c87501-8ed3-4bdf-8a39-a01bebede295 */
|
|
static struct bt_uuid_128 eds_caps_uuid = BT_UUID_INIT_128(
|
|
0x95, 0xe2, 0xed, 0xeb, 0x1b, 0xa0, 0x39, 0x8a,
|
|
0xdf, 0x4b, 0xd3, 0x8e, 0x01, 0x75, 0xc8, 0xa3);
|
|
|
|
/* Characteristic UUID a3c87502-8ed3-4bdf-8a39-a01bebede295 */
|
|
static struct bt_uuid_128 eds_slot_uuid = BT_UUID_INIT_128(
|
|
0x95, 0xe2, 0xed, 0xeb, 0x1b, 0xa0, 0x39, 0x8a,
|
|
0xdf, 0x4b, 0xd3, 0x8e, 0x02, 0x75, 0xc8, 0xa3);
|
|
|
|
/* Characteristic UUID a3c87503-8ed3-4bdf-8a39-a01bebede295 */
|
|
static struct bt_uuid_128 eds_intv_uuid = BT_UUID_INIT_128(
|
|
0x95, 0xe2, 0xed, 0xeb, 0x1b, 0xa0, 0x39, 0x8a,
|
|
0xdf, 0x4b, 0xd3, 0x8e, 0x03, 0x75, 0xc8, 0xa3);
|
|
|
|
/* Characteristic UUID a3c87504-8ed3-4bdf-8a39-a01bebede295 */
|
|
static struct bt_uuid_128 eds_tx_uuid = BT_UUID_INIT_128(
|
|
0x95, 0xe2, 0xed, 0xeb, 0x1b, 0xa0, 0x39, 0x8a,
|
|
0xdf, 0x4b, 0xd3, 0x8e, 0x04, 0x75, 0xc8, 0xa3);
|
|
|
|
/* Characteristic UUID a3c87505-8ed3-4bdf-8a39-a01bebede295 */
|
|
static struct bt_uuid_128 eds_adv_tx_uuid = BT_UUID_INIT_128(
|
|
0x95, 0xe2, 0xed, 0xeb, 0x1b, 0xa0, 0x39, 0x8a,
|
|
0xdf, 0x4b, 0xd3, 0x8e, 0x05, 0x75, 0xc8, 0xa3);
|
|
|
|
/* Characteristic UUID a3c87506-8ed3-4bdf-8a39-a01bebede295 */
|
|
static struct bt_uuid_128 eds_lock_uuid = BT_UUID_INIT_128(
|
|
0x95, 0xe2, 0xed, 0xeb, 0x1b, 0xa0, 0x39, 0x8a,
|
|
0xdf, 0x4b, 0xd3, 0x8e, 0x06, 0x75, 0xc8, 0xa3);
|
|
|
|
/* Characteristic UUID a3c87507-8ed3-4bdf-8a39-a01bebede295 */
|
|
static struct bt_uuid_128 eds_unlock_uuid = BT_UUID_INIT_128(
|
|
0x95, 0xe2, 0xed, 0xeb, 0x1b, 0xa0, 0x39, 0x8a,
|
|
0xdf, 0x4b, 0xd3, 0x8e, 0x07, 0x75, 0xc8, 0xa3);
|
|
|
|
/* Characteristic UUID a3c87508-8ed3-4bdf-8a39-a01bebede295 */
|
|
static struct bt_uuid_128 eds_ecdh_uuid = BT_UUID_INIT_128(
|
|
0x95, 0xe2, 0xed, 0xeb, 0x1b, 0xa0, 0x39, 0x8a,
|
|
0xdf, 0x4b, 0xd3, 0x8e, 0x08, 0x75, 0xc8, 0xa3);
|
|
|
|
/* Characteristic UUID a3c87509-8ed3-4bdf-8a39-a01bebede295 */
|
|
static struct bt_uuid_128 eds_eid_uuid = BT_UUID_INIT_128(
|
|
0x95, 0xe2, 0xed, 0xeb, 0x1b, 0xa0, 0x39, 0x8a,
|
|
0xdf, 0x4b, 0xd3, 0x8e, 0x09, 0x75, 0xc8, 0xa3);
|
|
|
|
/* Characteristic UUID a3c8750a-8ed3-4bdf-8a39-a01bebede295 */
|
|
static struct bt_uuid_128 eds_data_uuid = BT_UUID_INIT_128(
|
|
0x95, 0xe2, 0xed, 0xeb, 0x1b, 0xa0, 0x39, 0x8a,
|
|
0xdf, 0x4b, 0xd3, 0x8e, 0x0a, 0x75, 0xc8, 0xa3);
|
|
|
|
/* Characteristic UUID a3c8750b-8ed3-4bdf-8a39-a01bebede295 */
|
|
static struct bt_uuid_128 eds_reset_uuid = BT_UUID_INIT_128(
|
|
0x95, 0xe2, 0xed, 0xeb, 0x1b, 0xa0, 0x39, 0x8a,
|
|
0xdf, 0x4b, 0xd3, 0x8e, 0x0b, 0x75, 0xc8, 0xa3);
|
|
|
|
/* Characteristic UUID a3c8750c-8ed3-4bdf-8a39-a01bebede295 */
|
|
static struct bt_uuid_128 eds_connectable_uuid = BT_UUID_INIT_128(
|
|
0x95, 0xe2, 0xed, 0xeb, 0x1b, 0xa0, 0x39, 0x8a,
|
|
0xdf, 0x4b, 0xd3, 0x8e, 0x0c, 0x75, 0xc8, 0xa3);
|
|
|
|
enum {
|
|
EDS_TYPE_UID = 0x00,
|
|
EDS_TYPE_URL = 0x10,
|
|
EDS_TYPE_TLM = 0x20,
|
|
EDS_TYPE_EID = 0x30,
|
|
EDS_TYPE_NONE = 0xff,
|
|
};
|
|
|
|
enum {
|
|
EDS_SLOT_UID = sys_cpu_to_be16(BIT(0)),
|
|
EDS_SLOT_URL = sys_cpu_to_be16(BIT(1)),
|
|
EDS_SLOT_TLM = sys_cpu_to_be16(BIT(2)),
|
|
EDS_SLOT_EID = sys_cpu_to_be16(BIT(3)),
|
|
};
|
|
|
|
struct eds_capabilities {
|
|
uint8_t version;
|
|
uint8_t slots;
|
|
uint8_t uids;
|
|
uint8_t adv_types;
|
|
uint16_t slot_types;
|
|
uint8_t tx_power;
|
|
} __packed;
|
|
|
|
static struct eds_capabilities eds_caps = {
|
|
.version = EDS_VERSION,
|
|
.slots = NUMBER_OF_SLOTS,
|
|
.slot_types = EDS_SLOT_URL, /* TODO: Add support for other slot types */
|
|
};
|
|
|
|
uint8_t eds_active_slot;
|
|
|
|
enum {
|
|
EDS_LOCKED = 0x00,
|
|
EDS_UNLOCKED = 0x01,
|
|
EDS_UNLOCKED_NO_RELOCKING = 0x02,
|
|
};
|
|
|
|
struct eds_slot {
|
|
uint8_t type;
|
|
uint8_t state;
|
|
uint8_t connectable;
|
|
uint16_t interval;
|
|
uint8_t tx_power;
|
|
uint8_t adv_tx_power;
|
|
uint8_t lock[16];
|
|
uint8_t challenge[16];
|
|
struct bt_data ad[3];
|
|
};
|
|
|
|
static struct eds_slot eds_slots[NUMBER_OF_SLOTS] = {
|
|
[0 ... (NUMBER_OF_SLOTS - 1)] = {
|
|
.type = EDS_TYPE_NONE, /* Start as disabled */
|
|
.state = EDS_UNLOCKED, /* Start unlocked */
|
|
.interval = sys_cpu_to_be16(BT_GAP_ADV_FAST_INT_MIN_2),
|
|
.lock = { 'Z', 'e', 'p', 'h', 'y', 'r', ' ', 'E', 'd', 'd',
|
|
'y', 's', 't', 'o', 'n', 'e' },
|
|
.challenge = {},
|
|
.ad = {
|
|
BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),
|
|
BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0xaa, 0xfe),
|
|
BT_DATA_BYTES(BT_DATA_SVC_DATA16,
|
|
0xaa, 0xfe, /* Eddystone UUID */
|
|
0x10, /* Eddystone-URL frame type */
|
|
0x00, /* Calibrated Tx power at 0m */
|
|
0x00, /* URL Scheme Prefix http://www. */
|
|
'z', 'e', 'p', 'h', 'y', 'r',
|
|
'p', 'r', 'o', 'j', 'e', 'c', 't',
|
|
0x08) /* .org */
|
|
},
|
|
},
|
|
};
|
|
|
|
static ssize_t read_caps(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
void *buf, uint16_t len, uint16_t offset)
|
|
{
|
|
const struct eds_capabilities *caps = attr->user_data;
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, caps,
|
|
sizeof(*caps));
|
|
}
|
|
|
|
static ssize_t read_slot(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
void *buf, uint16_t len, uint16_t offset)
|
|
{
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
|
&eds_active_slot, sizeof(eds_active_slot));
|
|
}
|
|
|
|
static ssize_t write_slot(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, const void *buf,
|
|
uint16_t len, uint16_t offset, uint8_t flags)
|
|
{
|
|
uint8_t value;
|
|
|
|
if (offset + len > sizeof(value)) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
}
|
|
|
|
memcpy(&value, buf, len);
|
|
|
|
if (value + 1 > NUMBER_OF_SLOTS) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED);
|
|
}
|
|
|
|
eds_active_slot = value;
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t read_tx_power(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
void *buf, uint16_t len, uint16_t offset)
|
|
{
|
|
struct eds_slot *slot = &eds_slots[eds_active_slot];
|
|
|
|
if (slot->state == EDS_LOCKED) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_READ_NOT_PERMITTED);
|
|
}
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &slot->tx_power,
|
|
sizeof(slot->tx_power));
|
|
}
|
|
|
|
static ssize_t write_tx_power(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
const void *buf, uint16_t len, uint16_t offset,
|
|
uint8_t flags)
|
|
{
|
|
struct eds_slot *slot = &eds_slots[eds_active_slot];
|
|
|
|
if (slot->state == EDS_LOCKED) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED);
|
|
}
|
|
|
|
if (offset + len > sizeof(slot->tx_power)) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
}
|
|
|
|
memcpy(&slot->tx_power, buf, len);
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t read_adv_tx_power(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
void *buf, uint16_t len, uint16_t offset)
|
|
{
|
|
struct eds_slot *slot = &eds_slots[eds_active_slot];
|
|
|
|
if (slot->state == EDS_LOCKED) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_READ_NOT_PERMITTED);
|
|
}
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &slot->tx_power,
|
|
sizeof(slot->tx_power));
|
|
}
|
|
|
|
static ssize_t write_adv_tx_power(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
const void *buf, uint16_t len,
|
|
uint16_t offset,
|
|
uint8_t flags)
|
|
{
|
|
struct eds_slot *slot = &eds_slots[eds_active_slot];
|
|
|
|
if (slot->state == EDS_LOCKED) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED);
|
|
}
|
|
|
|
if (offset + len > sizeof(slot->adv_tx_power)) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
}
|
|
|
|
memcpy(&slot->adv_tx_power, buf, len);
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t read_interval(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
void *buf, uint16_t len, uint16_t offset)
|
|
{
|
|
struct eds_slot *slot = &eds_slots[eds_active_slot];
|
|
|
|
if (slot->state == EDS_LOCKED) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED);
|
|
}
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &slot->interval,
|
|
sizeof(slot->interval));
|
|
}
|
|
|
|
static ssize_t read_lock(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
void *buf, uint16_t len, uint16_t offset)
|
|
{
|
|
struct eds_slot *slot = &eds_slots[eds_active_slot];
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &slot->state,
|
|
sizeof(slot->state));
|
|
}
|
|
|
|
static ssize_t write_lock(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, const void *buf,
|
|
uint16_t len, uint16_t offset, uint8_t flags)
|
|
{
|
|
struct eds_slot *slot = &eds_slots[eds_active_slot];
|
|
uint8_t value;
|
|
|
|
if (slot->state == EDS_LOCKED) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED);
|
|
}
|
|
|
|
if (offset) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
}
|
|
|
|
/* Write 1 byte to lock or 17 bytes to transition to a new lock state */
|
|
if (len != 1) {
|
|
/* TODO: Allow setting new lock code, using AES-128-ECB to
|
|
* decrypt with the existing lock code and set the unencrypted
|
|
* value as the new code.
|
|
*/
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
}
|
|
|
|
memcpy(&value, buf, sizeof(value));
|
|
|
|
if (value > EDS_UNLOCKED_NO_RELOCKING) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED);
|
|
}
|
|
|
|
slot->state = value;
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t read_unlock(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
void *buf, uint16_t len, uint16_t offset)
|
|
{
|
|
struct eds_slot *slot = &eds_slots[eds_active_slot];
|
|
|
|
if (slot->state != EDS_LOCKED) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_READ_NOT_PERMITTED);
|
|
}
|
|
|
|
#if !defined(CONFIG_NBLE)
|
|
/* returns a 128-bit challenge token. This token is for one-time use
|
|
* and cannot be replayed.
|
|
*/
|
|
if (bt_rand(slot->challenge, sizeof(slot->challenge))) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
|
|
}
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, slot->challenge,
|
|
sizeof(slot->challenge));
|
|
#else
|
|
return BT_GATT_ERR(BT_ATT_ERR_NOT_SUPPORTED);
|
|
#endif /* CONFIG_NBLE */
|
|
}
|
|
|
|
static ssize_t write_unlock(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, const void *buf,
|
|
uint16_t len, uint16_t offset, uint8_t flags)
|
|
{
|
|
struct eds_slot *slot = &eds_slots[eds_active_slot];
|
|
|
|
if (slot->state != EDS_LOCKED) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_READ_NOT_PERMITTED);
|
|
}
|
|
|
|
/* TODO: accepts a 128-bit encrypted value that verifies the client
|
|
* knows the beacon's lock code.
|
|
*/
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_NOT_SUPPORTED);
|
|
}
|
|
|
|
static uint8_t eds_ecdh[32] = {}; /* TODO: Add ECDH key */
|
|
|
|
static ssize_t read_ecdh(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
void *buf, uint16_t len, uint16_t offset)
|
|
{
|
|
uint8_t *value = attr->user_data;
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
|
|
sizeof(eds_ecdh));
|
|
}
|
|
|
|
static uint8_t eds_eid[16] = {}; /* TODO: Add EID key */
|
|
|
|
static ssize_t read_eid(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
void *buf, uint16_t len, uint16_t offset)
|
|
{
|
|
uint8_t *value = attr->user_data;
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
|
|
sizeof(eds_eid));
|
|
}
|
|
|
|
static ssize_t read_adv_data(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
struct eds_slot *slot = &eds_slots[eds_active_slot];
|
|
|
|
if (slot->state == EDS_LOCKED) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_READ_NOT_PERMITTED);
|
|
}
|
|
|
|
/* If the slot is currently not broadcasting, reading the slot data
|
|
* shall return either an empty array or a single byte of 0x00.
|
|
*/
|
|
if (slot->type == EDS_TYPE_NONE) {
|
|
return 0;
|
|
}
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
|
slot->ad[2].data + EDS_URL_READ_OFFSET,
|
|
slot->ad[2].data_len - EDS_URL_READ_OFFSET);
|
|
}
|
|
|
|
static int eds_slot_restart(struct eds_slot *slot, uint8_t type)
|
|
{
|
|
int err;
|
|
|
|
/* Restart advertising */
|
|
bt_le_adv_stop();
|
|
|
|
if (type == EDS_TYPE_NONE) {
|
|
/* Restore connectable if slot */
|
|
err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad),
|
|
sd, ARRAY_SIZE(sd));
|
|
} else {
|
|
err = bt_le_adv_start(BT_LE_ADV_NCONN, slot->ad,
|
|
ARRAY_SIZE(slot->ad), sd, ARRAY_SIZE(sd));
|
|
}
|
|
|
|
if (err) {
|
|
printk("Advertising failed to start (err %d)\n", err);
|
|
return err;
|
|
}
|
|
|
|
slot->type = type;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t write_adv_data(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
const void *buf, uint16_t len, uint16_t offset,
|
|
uint8_t flags)
|
|
{
|
|
struct eds_slot *slot = &eds_slots[eds_active_slot];
|
|
uint8_t type;
|
|
|
|
if (slot->state == EDS_LOCKED) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_READ_NOT_PERMITTED);
|
|
}
|
|
|
|
if (offset) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
}
|
|
|
|
/* Writing an empty array, clears the slot and stops Tx. */
|
|
if (!len) {
|
|
eds_slot_restart(slot, EDS_TYPE_NONE);
|
|
return len;
|
|
}
|
|
|
|
/* Write length: 17 bytes (UID), 19 bytes (URL), 1 byte (TLM), 34 or
|
|
* 18 bytes (EID)
|
|
*/
|
|
if (len > 19) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
}
|
|
|
|
memcpy(&type, buf, sizeof(type));
|
|
|
|
switch (type) {
|
|
case EDS_TYPE_URL:
|
|
/* written data is just the frame type and any ID-related
|
|
* information, and doesn't include the Tx power since that is
|
|
* controlled by characteristics 4 (Radio Tx Power) and
|
|
* 5 (Advertised Tx Power).
|
|
*/
|
|
slot->ad[2].data_len = min(slot->ad[2].data_len,
|
|
len + EDS_URL_WRITE_OFFSET);
|
|
memcpy(&slot->ad[2].data + EDS_URL_WRITE_OFFSET, buf,
|
|
slot->ad[2].data_len - EDS_URL_WRITE_OFFSET);
|
|
|
|
/* Restart slot */
|
|
if (eds_slot_restart(slot, type) < 0) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
|
|
}
|
|
|
|
return len;
|
|
case EDS_TYPE_UID:
|
|
case EDS_TYPE_TLM:
|
|
case EDS_TYPE_EID:
|
|
default:
|
|
/* TODO: Add support for other types. */
|
|
return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED);
|
|
}
|
|
}
|
|
|
|
static ssize_t write_reset(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
const void *buf, uint16_t len, uint16_t offset,
|
|
uint8_t flags)
|
|
{
|
|
/* TODO: Power cycle or reload for storage the values */
|
|
return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED);
|
|
}
|
|
|
|
static ssize_t read_connectable(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
uint8_t connectable = 0x01;
|
|
|
|
/* Returning a non-zero value indicates that the beacon is capable
|
|
* of becoming non-connectable
|
|
*/
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
|
&connectable, sizeof(connectable));
|
|
}
|
|
|
|
static ssize_t write_connectable(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
const void *buf, uint16_t len, uint16_t offset,
|
|
uint8_t flags)
|
|
{
|
|
struct eds_slot *slot = &eds_slots[eds_active_slot];
|
|
|
|
if (slot->state == EDS_LOCKED) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED);
|
|
}
|
|
|
|
if (offset) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
}
|
|
|
|
if (len > sizeof(slot->connectable)) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
}
|
|
|
|
/* If any non-zero value is written, the beacon shall remain in its
|
|
* connectable state until any other value is written.
|
|
*/
|
|
memcpy(&slot->connectable, buf, len);
|
|
|
|
return len;
|
|
}
|
|
|
|
/* Eddystone Configuration Service Declaration */
|
|
static struct bt_gatt_attr eds_attrs[] = {
|
|
BT_GATT_PRIMARY_SERVICE(&eds_uuid),
|
|
/* Capabilities */
|
|
BT_GATT_CHARACTERISTIC(&eds_caps_uuid.uuid, BT_GATT_CHRC_READ),
|
|
/* Readable only when unlocked. Never writable. */
|
|
BT_GATT_DESCRIPTOR(&eds_caps_uuid.uuid, BT_GATT_PERM_READ,
|
|
read_caps, NULL, &eds_caps),
|
|
/* Active slot */
|
|
BT_GATT_CHARACTERISTIC(&eds_slot_uuid.uuid,
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE),
|
|
/* Must be unlocked for both read and write. */
|
|
BT_GATT_DESCRIPTOR(&eds_slot_uuid.uuid,
|
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
|
read_slot, write_slot, NULL),
|
|
/* Advertising Interval */
|
|
BT_GATT_CHARACTERISTIC(&eds_intv_uuid.uuid, BT_GATT_CHRC_READ),
|
|
/* Must be unlocked for both read and write. */
|
|
BT_GATT_DESCRIPTOR(&eds_intv_uuid.uuid, BT_GATT_PERM_READ,
|
|
read_interval, NULL, NULL),
|
|
/* Radio TX Power */
|
|
BT_GATT_CHARACTERISTIC(&eds_tx_uuid.uuid,
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE),
|
|
/* Must be unlocked for both read and write. */
|
|
BT_GATT_DESCRIPTOR(&eds_tx_uuid.uuid,
|
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
|
read_tx_power, write_tx_power, NULL),
|
|
/* Advertised TX Power */
|
|
BT_GATT_CHARACTERISTIC(&eds_adv_tx_uuid.uuid,
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE),
|
|
/* Must be unlocked for both read and write. */
|
|
BT_GATT_DESCRIPTOR(&eds_adv_tx_uuid.uuid,
|
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
|
read_adv_tx_power, write_adv_tx_power, NULL),
|
|
/* Lock State */
|
|
BT_GATT_CHARACTERISTIC(&eds_lock_uuid.uuid,
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE),
|
|
/* Readable in locked or unlocked state.
|
|
* Writeable only in unlocked state.
|
|
*/
|
|
BT_GATT_DESCRIPTOR(&eds_lock_uuid.uuid,
|
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
|
read_lock, write_lock, NULL),
|
|
/* Unlock */
|
|
BT_GATT_CHARACTERISTIC(&eds_unlock_uuid.uuid,
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE),
|
|
/* Readable only in locked state.
|
|
* Writeable only in locked state.
|
|
*/
|
|
BT_GATT_DESCRIPTOR(&eds_unlock_uuid.uuid,
|
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
|
read_unlock, write_unlock, NULL),
|
|
/* Public ECDH Key */
|
|
BT_GATT_CHARACTERISTIC(&eds_ecdh_uuid.uuid, BT_GATT_CHRC_READ),
|
|
/* Readable only in unlocked state. Never writable. */
|
|
BT_GATT_DESCRIPTOR(&eds_ecdh_uuid.uuid, BT_GATT_PERM_READ,
|
|
read_ecdh, NULL, &eds_ecdh),
|
|
/* EID Identity Key */
|
|
BT_GATT_CHARACTERISTIC(&eds_eid_uuid.uuid, BT_GATT_CHRC_READ),
|
|
/* Readable only in unlocked state. Never writable. */
|
|
BT_GATT_DESCRIPTOR(&eds_eid_uuid.uuid, BT_GATT_PERM_READ,
|
|
read_eid, NULL, eds_eid),
|
|
/* ADV Slot Data */
|
|
BT_GATT_CHARACTERISTIC(&eds_data_uuid.uuid,
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE),
|
|
/* Must be unlocked for both read and write. */
|
|
BT_GATT_DESCRIPTOR(&eds_eid_uuid.uuid,
|
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
|
read_adv_data, write_adv_data, NULL),
|
|
/* ADV Factory Reset */
|
|
BT_GATT_CHARACTERISTIC(&eds_reset_uuid.uuid, BT_GATT_CHRC_WRITE),
|
|
/* Must be unlocked write. */
|
|
BT_GATT_DESCRIPTOR(&eds_reset_uuid.uuid,
|
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
|
NULL, write_reset, NULL),
|
|
/* ADV Remain Connectable */
|
|
BT_GATT_CHARACTERISTIC(&eds_connectable_uuid.uuid,
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE),
|
|
/* Must be unlocked for write. */
|
|
BT_GATT_DESCRIPTOR(&eds_connectable_uuid.uuid,
|
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
|
read_connectable, write_connectable, NULL),
|
|
};
|
|
|
|
static void bt_ready(int err)
|
|
{
|
|
if (err) {
|
|
printk("Bluetooth init failed (err %d)\n", err);
|
|
return;
|
|
}
|
|
|
|
printk("Bluetooth initialized\n");
|
|
|
|
bt_gatt_register(eds_attrs, ARRAY_SIZE(eds_attrs));
|
|
|
|
/* Start advertising */
|
|
err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad),
|
|
sd, ARRAY_SIZE(sd));
|
|
if (err) {
|
|
printk("Advertising failed to start (err %d)\n", err);
|
|
return;
|
|
}
|
|
|
|
nano_delayed_work_submit(&idle_work, EDS_IDLE_TIMEOUT);
|
|
|
|
printk("Configuration mode: waiting connections...\n");
|
|
}
|
|
|
|
static void idle_timeout(struct nano_work *work)
|
|
{
|
|
if (eds_slots[eds_active_slot].type == EDS_TYPE_NONE) {
|
|
printk("Switching to Beacon mode.\n");
|
|
eds_slot_restart(&eds_slots[eds_active_slot], EDS_TYPE_URL);
|
|
}
|
|
}
|
|
|
|
static void connected(struct bt_conn *conn, uint8_t err)
|
|
{
|
|
if (err) {
|
|
printk("Connection failed (err %u)\n", err);
|
|
} else {
|
|
printk("Connected\n");
|
|
nano_delayed_work_cancel(&idle_work);
|
|
}
|
|
}
|
|
|
|
static void disconnected(struct bt_conn *conn, uint8_t reason)
|
|
{
|
|
struct eds_slot *slot = &eds_slots[eds_active_slot];
|
|
|
|
printk("Disconnected (reason %u)\n", reason);
|
|
|
|
if (!slot->connectable) {
|
|
nano_delayed_work_submit(&idle_work, 0);
|
|
}
|
|
}
|
|
|
|
static struct bt_conn_cb conn_callbacks = {
|
|
.connected = connected,
|
|
.disconnected = disconnected,
|
|
};
|
|
|
|
void main(void)
|
|
{
|
|
int err;
|
|
|
|
bt_conn_cb_register(&conn_callbacks);
|
|
nano_delayed_work_init(&idle_work, idle_timeout);
|
|
|
|
/* Initialize the Bluetooth Subsystem */
|
|
err = bt_enable(bt_ready);
|
|
if (err) {
|
|
printk("Bluetooth init failed (err %d)\n", err);
|
|
}
|
|
}
|