blob: ba0a1e257fef0149f0aa4f237e6f0507d2a6249d [file] [log] [blame]
/*
* Copyright 2015 Google Inc. All rights reserved.
*
* 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.
*/
/*
* A tool to advertise an Eddystone-UID beacon.
* https://github.com/google/eddystone
*/
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
uint8_t nibble(char c)
{
switch(c) {
case '0' ... '9':
return c - '0';
break;
case 'a' ... 'f':
return c - 'a' + 10;
break;
case 'A' ... 'F':
return c - 'A' + 10;
break;
}
return 0;
}
void hex_to_uint8(const char *str, uint8_t *bin, int len)
{
int i;
uint8_t n;
for (i = 0; i < len; ++i) {
n = nibble(str[(2 * i)]) << 4;
n |= nibble(str[(2 * i) + 1]);
bin[i] = n;
}
}
void populate_beacon(const uint8_t *nid, const uint8_t *instance,
int8_t txpower, le_set_advertising_data_cp *advertising_data)
{
uint8_t *adata = advertising_data->data;
adata[0] = 0x02; // length of flags
adata[1] = 0x01; // flags type
adata[2] = 0x06; // Flags
adata[3] = 0x03; // length
adata[4] = 0x03; // data type (list of UUIDs)
adata[5] = 0xaa; // Eddystone 16 bit UUID
adata[6] = 0xfe; // Eddystone 16 bit UUID
adata[7] = 0x17; // Service Data length
adata[8] = 0x16; // Service Data type
adata[9] = 0xaa; // Eddystone 16 bit UUID
adata[10] = 0xfe; // Eddystone 16 bit UUID
adata[11] = 0x00; // Eddystone-UID type
adata[12] = txpower;
memcpy(adata + 13, nid, 10); // 10 byte namespace id
memcpy(adata + 23, instance, 6); // 6 byte instance
adata[29] = 0;
adata[30] = 0;
advertising_data->length = 31;
}
void set_adv_data(int s, const uint8_t *nid, const uint8_t *instance,
uint8_t txpower)
{
le_set_advertising_data_cp adata;
struct hci_request hcirq;
uint8_t status;
memset(&adata, 0, sizeof(adata));
populate_beacon(nid, instance, txpower, &adata);
memset(&hcirq, 0, sizeof(hcirq));
hcirq.ogf = OGF_LE_CTL;
hcirq.ocf = OCF_LE_SET_ADVERTISING_DATA;
hcirq.cparam = &adata;
hcirq.clen = LE_SET_ADVERTISING_DATA_CP_SIZE;
hcirq.rparam = &status;
hcirq.rlen = sizeof(status);
if (hci_send_req(s, &hcirq, 1000)) {
perror("hci_send_req OCF_LE_SET_ADVERTISING_DATA");
hci_close_dev(s);
exit(1);
}
if (status) {
fprintf(stderr, "OCF_LE_SET_ADVERTISING_DATA status %d\n", status);
hci_close_dev(s);
exit(1);
}
}
void set_adv_params(int s, int interval_ms)
{
le_set_advertising_parameters_cp aparams;
struct hci_request hcirq;
uint8_t status;
memset(&aparams, 0, sizeof(aparams));
aparams.min_interval = htobs(interval_ms);
aparams.max_interval = htobs(interval_ms);
aparams.advtype = 3; // advertising non-connectable
aparams.chan_map = 7; // all three advertising channels
memset(&hcirq, 0, sizeof(hcirq));
hcirq.ogf = OGF_LE_CTL;
hcirq.ocf = OCF_LE_SET_ADVERTISING_PARAMETERS;
hcirq.cparam = &aparams;
hcirq.clen = LE_SET_ADVERTISING_PARAMETERS_CP_SIZE;
hcirq.rparam = &status;
hcirq.rlen = 1;
if (hci_send_req(s, &hcirq, 1000)) {
perror("hci_send_req OCF_LE_SET_ADVERTISING_PARAMETERS");
hci_close_dev(s);
exit(1);
}
if (status) {
fprintf(stderr, "OCF_LE_SET_ADVERTISING_PARAMETERS status %d\n", status);
hci_close_dev(s);
exit(1);
}
}
void set_adv_enable(int s, uint8_t enable)
{
le_set_advertise_enable_cp aenable;
struct hci_request hcirq;
uint8_t status;
memset(&aenable, 0, sizeof(aenable));
aenable.enable = enable;
memset(&hcirq, 0, sizeof(hcirq));
hcirq.ogf = OGF_LE_CTL;
hcirq.ocf = OCF_LE_SET_ADVERTISE_ENABLE;
hcirq.cparam = &aenable;
hcirq.clen = LE_SET_ADVERTISE_ENABLE_CP_SIZE;
hcirq.rparam = &status;
hcirq.rlen = 1;
if (hci_send_req(s, &hcirq, 1000)) {
perror("hci_send_req OCF_LE_SET_ADVERTISE_ENABLE");
hci_close_dev(s);
exit(1);
}
if (status) {
fprintf(stderr, "OCF_LE_SET_ADVERTISE_ENABLE status %d\n", status);
hci_close_dev(s);
exit(1);
}
}
int advertise_beacon(int enable, const uint8_t *nid, const uint8_t *instance,
uint8_t txpower)
{
int dev_id = hci_get_route(NULL);
int s = -1;
if ((s = hci_open_dev(dev_id)) < 0) {
perror("hci_open_dev");
return(1);
}
if (enable) {
set_adv_data(s, nid, instance, txpower);
set_adv_params(s, 200);
set_adv_enable(s, 1);
} else {
set_adv_enable(s, 0);
}
hci_close_dev(s);
return(0);
}
void usage(const char *progname)
{
fprintf(stderr, "usage: %s [-d | -n nid -i instance -t txpower]\n",
progname);
fprintf(stderr, "\t-d: disable BTLE advertisement.\n");
fprintf(stderr, "\t-n namespace: 10 byte hex like 00112233445566778899\n");
fprintf(stderr, "\t-i instance: 6 byte hex like aabbccddeeff\n");
fprintf(stderr, "\t-t txpower: Power level to expect at 0 meters\n");
exit(1);
}
int main(int argc, char **argv)
{
int c;
const char *nidstr = NULL;
const char *instancestr = NULL;
int do_disable = 0;
int txpower_set = 0;
int8_t txpower = -1;
uint8_t nid[10];
uint8_t instance[6];
while ((c = getopt(argc, argv, "di:n:t:")) != -1) {
switch(c) {
case 'd':
do_disable = 1;
break;
case 'i':
instancestr = optarg;
break;
case 'n':
nidstr = optarg;
break;
case 't':
txpower_set = 1;
txpower = atoi(optarg);
break;
default:
usage(argv[0]);
break;
}
}
if (do_disable) {
advertise_beacon(0, NULL, NULL, 0);
return 0;
}
if ((nidstr == NULL) || (instancestr == NULL) || !txpower_set ||
(strlen(nidstr) != 20) || (strlen(instancestr) != 12)) {
usage(argv[0]);
}
hex_to_uint8(nidstr, nid, sizeof(nid));
hex_to_uint8(instancestr, instance, sizeof(instance));
advertise_beacon(1, nid, instance, txpower);
return 0;
}