Original library sources.

This commit is contained in:
Pavel Brychta 2018-07-20 20:21:55 +02:00
parent 87830b42e3
commit 9e41194f84
9 changed files with 1277 additions and 17 deletions

256
BTLE.cpp Normal file
View File

@ -0,0 +1,256 @@
/*
* Copyright (C) 2013 Florian Echtler <floe@butterbrot.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 3 as published by the Free Software Foundation.
*/
#include <BTLE.h>
const uint8_t channel[3] = {37,38,39}; // logical BTLE channel number (37-39)
const uint8_t frequency[3] = { 2,26,80}; // physical frequency (2400+x MHz)
// This is a rather convoluted hack to extract the month number from the build date in
// the __DATE__ macro using a small hash function + lookup table. Since all inputs are
// const, this can be fully resolved by the compiler and saves over 200 bytes of code.
#define month(m) month_lookup[ (( ((( (m[0] % 24) * 13) + m[1]) % 24) * 13) + m[2]) % 24 ]
const uint8_t month_lookup[24] = { 0,6,0,4,0,1,0,17,0,8,0,0,3,0,0,0,18,2,16,5,9,0,1,7 };
// change buffer contents to "wire bit order"
void BTLE::swapbuf( uint8_t len ) {
uint8_t* buf = (uint8_t*)&buffer;
while (len--) {
uint8_t a = *buf;
uint8_t v = 0;
if (a & 0x80) v |= 0x01;
if (a & 0x40) v |= 0x02;
if (a & 0x20) v |= 0x04;
if (a & 0x10) v |= 0x08;
if (a & 0x08) v |= 0x10;
if (a & 0x04) v |= 0x20;
if (a & 0x02) v |= 0x40;
if (a & 0x01) v |= 0x80;
*(buf++) = v;
}
}
// constructor
BTLE::BTLE( RF24* _radio ):
radio(_radio),
current(0)
{ }
// Simple converter from arduino float to a nRF_Float.
// Supports values from -167772 to +167772, with two decimal places.
nRF_Float
BTLE::to_nRF_Float(float t) {
int32_t ret;
int32_t exponent = -2;
ret = ((exponent & 0xff) << 24) | (((int32_t)(t * 100)) & 0xffffff);
return ret;
}
// set BTLE-compatible radio parameters
void BTLE::begin( const char* _name ) {
name = _name;
radio->begin();
// set standard parameters
radio->setAutoAck(false);
radio->setDataRate(RF24_1MBPS);
radio->disableCRC();
radio->setChannel( frequency[current] );
radio->setRetries(0,0);
radio->setPALevel(RF24_PA_MAX);
// set advertisement address: 0x8E89BED6 (bit-reversed -> 0x6B7D9171)
radio->setAddressSize(4);
radio->openReadingPipe(0,0x6B7D9171);
radio->openWritingPipe( 0x6B7D9171);
radio->powerUp();
}
// set the current channel (from 37 to 39)
void BTLE::setChannel( uint8_t num ) {
current = min(2,max(0,num-37));
radio->setChannel( frequency[current] );
}
// hop to the next channel
void BTLE::hopChannel() {
current++;
if (current >= sizeof(channel)) current = 0;
radio->setChannel( frequency[current] );
}
// Broadcast an advertisement packet with optional payload
// Data type will be 0xFF (Manufacturer Specific Data)
bool BTLE::advertise( void* buf, uint8_t buflen ) {
return advertise(0xFF, buf, buflen);
}
// Broadcast an advertisement packet with a specific data type
// Standardized data types can be seen here:
// https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile
bool BTLE::advertise( uint8_t data_type, void* buf, uint8_t buflen ) {
// name & total payload size
uint8_t namelen = strlen(name);
uint8_t pls = 0;
// insert pseudo-random MAC address
buffer.mac[0] = ((__TIME__[6]-0x30) << 4) | (__TIME__[7]-0x30);
buffer.mac[1] = ((__TIME__[3]-0x30) << 4) | (__TIME__[4]-0x30);
buffer.mac[2] = ((__TIME__[0]-0x30) << 4) | (__TIME__[1]-0x30);
buffer.mac[3] = ((__DATE__[4]-0x30) << 4) | (__DATE__[5]-0x30);
buffer.mac[4] = month(__DATE__);
buffer.mac[5] = ((__DATE__[9]-0x30) << 4) | (__DATE__[10]-0x30);
// add device descriptor chunk
chunk(buffer,pls)->size = 0x02; // chunk size: 2
chunk(buffer,pls)->type = 0x01; // chunk type: device flags
chunk(buffer,pls)->data[0]= 0x05; // flags: LE-only, limited discovery mode
pls += 3;
// add "complete name" chunk
chunk(buffer,pls)->size = namelen+1; // chunk size
chunk(buffer,pls)->type = 0x09; // chunk type
for (uint8_t i = 0; i < namelen; i++)
chunk(buffer,pls)->data[i] = name[i];
pls += namelen+2;
// add custom data, if applicable
if (buflen > 0) {
chunk(buffer,pls)->size = buflen+1; // chunk size
chunk(buffer,pls)->type = data_type; // chunk type
for (uint8_t i = 0; i < buflen; i++)
chunk(buffer,pls)->data[i] = ((uint8_t*)buf)[i];
pls += buflen+2;
}
// total payload size must be 21 bytes or less
if (pls > 21)
return false;
// assemble header
buffer.pdu_type = 0x42; // PDU type: ADV_NONCONN_IND, TX address is random
buffer.pl_size = pls+6; // set final payload size in header incl. MAC
// calculate CRC over header+MAC+payload, append after payload
uint8_t* outbuf = (uint8_t*)&buffer;
crc( pls+8, outbuf+pls+8);
// whiten header+MAC+payload+CRC, swap bit order
whiten( pls+11 );
swapbuf( pls+11 );
// flush buffers and send
radio->stopListening();
radio->write( outbuf, pls+11 );
return true;
}
// listen for advertisement packets
bool BTLE::listen(int timeout) {
radio->startListening();
delay(timeout);
if (!radio->available())
return false;
bool done = false;
uint8_t total_size = 0;
uint8_t* inbuf = (uint8_t*)&buffer;
while (!done) {
// fetch the payload, and check if there are more left
done = radio->read( inbuf, sizeof(buffer) );
// decode: swap bit order, un-whiten
swapbuf( sizeof(buffer) );
whiten( sizeof(buffer) );
// size is w/o header+CRC -> add 2 bytes header
total_size = inbuf[1]+2;
uint8_t in_crc[3];
// calculate & compare CRC
crc( total_size, in_crc );
for (uint8_t i = 0; i < 3; i++)
if (inbuf[total_size+i] != in_crc[i])
return false;
}
return true;
}
// see BT Core Spec 4.0, Section 6.B.3.2
void BTLE::whiten( uint8_t len ) {
uint8_t* buf = (uint8_t*)&buffer;
// initialize LFSR with current channel, set bit 6
uint8_t lfsr = channel[current] | 0x40;
while (len--) {
uint8_t res = 0;
// LFSR in "wire bit order"
for (uint8_t i = 1; i; i <<= 1) {
if (lfsr & 0x01) {
lfsr ^= 0x88;
res |= i;
}
lfsr >>= 1;
}
*(buf++) ^= res;
}
}
// see BT Core Spec 4.0, Section 6.B.3.1.1
void BTLE::crc( uint8_t len, uint8_t* dst ) {
uint8_t* buf = (uint8_t*)&buffer;
// initialize 24-bit shift register in "wire bit order"
// dst[0] = bits 23-16, dst[1] = bits 15-8, dst[2] = bits 7-0
dst[0] = 0xAA;
dst[1] = 0xAA;
dst[2] = 0xAA;
while (len--) {
uint8_t d = *(buf++);
for (uint8_t i = 1; i; i <<= 1, d >>= 1) {
// save bit 23 (highest-value), left-shift the entire register by one
uint8_t t = dst[0] & 0x01; dst[0] >>= 1;
if (dst[1] & 0x01) dst[0] |= 0x80; dst[1] >>= 1;
if (dst[2] & 0x01) dst[1] |= 0x80; dst[2] >>= 1;
// if the bit just shifted out (former bit 23) and the incoming data
// bit are not equal (i.e. bit_out ^ bit_in == 1) => toggle tap bits
if (t != (d & 1)) {
// toggle register tap bits (=XOR with 1) according to CRC polynom
dst[2] ^= 0xDA; // 0b11011010 inv. = 0b01011011 ^= x^6+x^4+x^3+x+1
dst[1] ^= 0x60; // 0b01100000 inv. = 0b00000110 ^= x^10+x^9
}
}
}
}

95
BTLE.h Normal file
View File

@ -0,0 +1,95 @@
/*
* Copyright (C) 2013 Florian Echtler <floe@butterbrot.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 3 as published by the Free Software Foundation.
*/
#ifndef _BTLE_H_
#define _BTLE_H_
#include "Arduino.h"
#include <RF24.h>
// float as used on the nRF8001 and nRF51822 platforms
// and on the "nRF Master Control Panel" and "nRF Temp 2.0" apps.
// This float representation has the first 8 bits as a base-10 exponent
// and the last 24 bits as the mantissa.
typedef int32_t nRF_Float;
// Service UUIDs used on the nRF8001 and nRF51822 platforms
#define NRF_TEMPERATURE_SERVICE_UUID 0x1809
#define NRF_BATTERY_SERVICE_UUID 0x180F
#define NRF_DEVICE_INFORMATION_SERVICE_UUID 0x180A
// helper struct for sending temperature as BT service data
struct nrf_service_data {
int16_t service_uuid;
nRF_Float value;
};
// advertisement PDU
struct btle_adv_pdu {
// packet header
uint8_t pdu_type; // PDU type
uint8_t pl_size; // payload size
// MAC address
uint8_t mac[6];
// payload (including 3 bytes for CRC)
uint8_t payload[24];
};
// payload chunk in advertisement PDU payload
struct btle_pdu_chunk {
uint8_t size;
uint8_t type;
uint8_t data[];
};
// helper macro to access chunk at specific offset
#define chunk(x,y) ((btle_pdu_chunk*)(x.payload+y))
class BTLE {
public:
BTLE( RF24* _radio );
// convert an arduino float to a nRF_Float
static nRF_Float to_nRF_Float(float t);
void begin( const char* _name ); // set BTLE-compatible radio parameters & name
void setChannel( uint8_t num ); // set the current channel (from 36 to 38)
void hopChannel(); // hop to the next channel
// Broadcast an advertisement packet with a specific data type
// Standardized data types can be seen here:
// https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile
bool advertise( uint8_t data_type, void* buf, uint8_t len );
// Broadcast an advertisement packet with optional payload
// Data type will be 0xFF (Manufacturer Specific Data)
bool advertise( void* buf, uint8_t len );
bool listen( int timeout = 100 ); // listen for advertisement packets (if true: result = buffer)
btle_adv_pdu buffer; // buffer for received BTLE packet (also used for outgoing!)
private:
void whiten( uint8_t len );
void swapbuf( uint8_t len );
void crc( uint8_t len, uint8_t* dst );
RF24* radio; // pointer to the RF24 object managing the radio
uint8_t current; // current channel index
const char* name; // name of local device
};
#endif // _BTLE_H_

689
LICENSE

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,15 @@
# BTLE
BTLE
====
Bluetoot LE library for the NRF24L01+
This is a fork of floe's BTLE library; it has some improvements to make it able
to mimick nRF8001 and nRF51822 beacon packets.
More info on http://simonebaracchi.eu/posts/temperature-beacon/ .
Arduino library for basic Bluetooth Low Energy support using the nRF24L01+
(basic support = sending & receiving on the advertising broadcast channel)
You will also need to install the RF24 library from https://github.com/floe/RF24
Note: the BTLE class and the examples are licensed under GPLv3. However, the
helper functions in btle.inc are (C) 2012 by Dmitry Grinberg under a separate
license (see file for details).

38
bluez_adv.sh Executable file
View File

@ -0,0 +1,38 @@
#!/bin/bash
# bring up the host controller
sudo hciconfig hci0 up
# enable non-connectable undirected advertisements (only works with recent hciconfig)
#sudo hciconfig hci0 leadv 3
# custom hci commands take two parameters:
# 0x08 opcode group (LE)
# 0x0008 opcode command (set LE adv data)
# set random device address
sudo hcitool -i hci0 cmd 0x08 0x0005 12 34 56 78 9A BC
# set advertising parameters
# 00 08 00 08 min/max adv. interval
# 03 non-connectable undirected advertising
# 01 own address is random (see previous command)
# 00 target address is public (not used for undirected advertising)
# 00 00 00 ... target address (not used for undirected advertising)
# 07 adv. channel map (enable all)
# 00 filter policy (allow any)
sudo hcitool -i hci0 cmd 0x08 0x0006 00 08 00 08 03 01 00 00 00 00 00 00 00 07 00
# enable advertising (00 = disable)
sudo hcitool -i hci0 cmd 0x08 0x000A 01
# set advertisement data (_after_ advertising is enabled)
# 0e adv data length (should be at most 0x15 for compatibility with NRF24L01+)
# 02 01 05 flags (LE-only device, non-connectable)
# 07 09 ... name
# 02 ff fe custom data (02 length, ff type, fe data)
# 00 00 ... padding (to 32 bytes including length)
sudo hcitool -i hci0 cmd 0x08 0x0008 0e 02 01 05 07 09 66 6f 6f 62 61 72 02 ff fe 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

101
btle.inc Normal file
View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2012 Dmitry Grinberg
*
* http://dmitry.gr/index.php?r=05.Projects&proj=11.%20Bluetooth%20LE%20fakery
*
* All the code as well as the research that went into this and is published
* here is under this license: you may use it in any way you please if and
* only if it is for non-commercial purposes, you must provide a link to this
* page as well. Any commercial use must be discussed with me.
*
* Some additional comments by Florian Echtler <floe@butterbrot.org>
*/
uint8_t swapbits(uint8_t a) {
uint8_t v = 0;
if (a & 0x80) v |= 0x01;
if (a & 0x40) v |= 0x02;
if (a & 0x20) v |= 0x04;
if (a & 0x10) v |= 0x08;
if (a & 0x08) v |= 0x10;
if (a & 0x04) v |= 0x20;
if (a & 0x02) v |= 0x40;
if (a & 0x01) v |= 0x80;
return v;
}
// see BT Core Spec 4.0, Section 6.B.3.1.1
void btLeCrc(const uint8_t* data, uint8_t len, uint8_t* dst) {
uint8_t v, t, d;
while (len--) {
d = *data++;
for (v = 0; v < 8; v++, d >>= 1) {
// t = bit 23 (highest-value)
t = dst[0] >> 7;
// left-shift the entire register by one
// (dst[0] = bits 23-16, dst[1] = bits 15-8, dst[2] = bits 7-0
dst[0] <<= 1;
if(dst[1] & 0x80) dst[0] |= 1;
dst[1] <<= 1;
if(dst[2] & 0x80) dst[1] |= 1;
dst[2] <<= 1;
// if the bit just shifted out (former bit 23)
// and the incoming data bit are not equal:
// => bit_out ^ bit_in = 1
if (t != (d & 1)) {
// toggle register bits (=XOR with 1) according to CRC polynom
dst[2] ^= 0x5B; // 0b01011011 - x^6+x^4+x^3+x+1
dst[1] ^= 0x06; // 0b00000110 - x^10+x^9
}
}
}
}
// see BT Core Spec 4.0, Section 6.B.3.2
void btLeWhiten(uint8_t* data, uint8_t len, uint8_t whitenCoeff) {
uint8_t m;
while (len--) {
for (m = 1; m; m <<= 1) {
if (whitenCoeff & 0x80) {
whitenCoeff ^= 0x11;
(*data) ^= m;
}
whitenCoeff <<= 1;
}
data++;
}
}
static inline uint8_t btLeWhitenStart(uint8_t chan) {
//the value we actually use is what BT'd use left shifted one...makes our life easier
return swapbits(chan) | 2;
}
void btLePacketEncode(uint8_t* packet, uint8_t len, uint8_t chan) {
//length is of packet, including crc. pre-populate crc in packet with initial crc value!
uint8_t i, dataLen = len - 3;
btLeCrc(packet, dataLen, packet + dataLen);
for (i = 0; i < 3; i++, dataLen++) packet[dataLen] = swapbits(packet[dataLen]);
btLeWhiten(packet, len, btLeWhitenStart(chan));
for (i = 0; i < len; i++) packet[i] = swapbits(packet[i]);
}

30
examples/recv/recv.ino Normal file
View File

@ -0,0 +1,30 @@
#include <SPI.h>
#include <RF24.h>
#include <BTLE.h>
RF24 radio(9,10);
BTLE btle(&radio);
void setup() {
Serial.begin(9600);
while (!Serial) { }
Serial.println("BTLE advertisement receiver");
btle.begin("");
}
void loop() {
Serial.print("Listening... ");
if (btle.listen()) {
Serial.print("Got payload: ");
for (uint8_t i = 0; i < (btle.buffer.pl_size)-6; i++) { Serial.print(btle.buffer.payload[i],HEX); Serial.print(" "); }
}
Serial.println("done.");
btle.hopChannel();
}

23
examples/send/send.ino Normal file
View File

@ -0,0 +1,23 @@
#include <SPI.h>
#include <RF24.h>
#include <BTLE.h>
RF24 radio(9,10);
BTLE btle(&radio);
void setup() {
Serial.begin(9600);
while (!Serial) { }
Serial.println("BTLE advertisement sender");
btle.begin("foobar");
}
void loop() {
btle.advertise(0,0);
btle.hopChannel();
Serial.print(".");
}

View File

@ -0,0 +1,46 @@
/*
* Emulates a nRF8001 temperature beacon;
* reads temperature from a DHT11 and sends it via BTLE.
* Compatible with Nordic Semiconductor apps such as
* nRF Master Control Panel or nRF Temp 2.0.
*/
#include <BTLE.h>
#include <SPI.h>
#include <RF24.h>
#include <dht.h>
RF24 radio(9,10);
BTLE btle(&radio);
dht DHT;
void setup() {
Serial.begin(57600);
while (!Serial) { }
Serial.println("BTLE temperature sender");
Serial.end();
// 8 chars max
btle.begin("SimoTemp");
}
void loop() {
DHT.read11(A0);
nrf_service_data buf;
buf.service_uuid = NRF_TEMPERATURE_SERVICE_UUID;
buf.value = BTLE::to_nRF_Float(DHT.temperature);
if(!btle.advertise(0x16, &buf, sizeof(buf))) {
Serial.begin(57600);
Serial.println("BTLE advertisement failure");
Serial.end();
}
btle.hopChannel();
delay(1000);
}