Skip to content

Commit a00e244

Browse files
committed
Merge pull request #6 from RadiusNetworks/bluez_advertising
allow advertising on linux
2 parents 0164adb + 978c581 commit a00e244

22 files changed

Lines changed: 695 additions & 29 deletions

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ yeller.rb
55
pkg/
66
tmp/
77
*.bundle
8-
8+
*.so
99
# Since this is a gem not an app, ignore the lock file
1010
Gemfile.lock

README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ require 'scan_beacon'
1616
scanner = ScanBeacon::CoreBluetoothScanner.new
1717
# to scan using a BLE112 device
1818
scanner = ScanBeacon::BLE112Scanner.new
19+
# to scan using BlueZ on Linux (make sure you have privileges)
20+
scanner = ScanBeacon::BlueZScanner.new
1921
```
2022

2123
## Start a scan, yield beacons in a loop
@@ -54,5 +56,37 @@ scanner.add_parser( ScanBeacon::BeaconParser.new(:mybeacon, "m:2-3=0000,i:4-19,i
5456
...
5557
```
5658

59+
## Advertise as a beacon on Linux using BlueZ
60+
Example:
61+
``` ruby
62+
# altbeacon
63+
beacon = ScanBeacon::Beacon.new(
64+
ids: ["2F234454CF6D4A0FADF2F4911BA9FFA6", 11,11],
65+
power: -59,
66+
mfg_id: 0x0118,
67+
beacon_type: :altbeacon
68+
)
69+
advertiser = ScanBeacon::BlueZAdvertiser.new(beacon: beacon)
70+
advertiser.start
71+
...
72+
advertiser.stop
73+
74+
# Eddystone UID
75+
beacon = ScanBeacon::Beacon.new(
76+
ids: ["2F234454F4911BA9FFA6", 3],
77+
power: -20,
78+
service_uuid: 0xFEAA,
79+
beacon_type: :eddystone_uid
80+
)
81+
advertiser = ScanBeacon::BlueZAdvertiser.new(beacon: beacon)
82+
advertiser.start
83+
...
84+
advertiser.stop
85+
```
86+
87+
5788
# Dependencies
58-
You must have a BLE112 device plugged in to a USB port, or a Mac.
89+
To scan for beacons, you must have a Linux machine with BlueZ installed, or a Mac, or a BLE112 device plugged in to a USB port (on Mac or Linux).
90+
91+
To advertise as a beacon, you must have a Linux machine with BlueZ installed.
92+

Rakefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ require "rake/extensiontask"
44
Rake::ExtensionTask.new "core_bluetooth" do |ext|
55
ext.lib_dir = "lib/scan_beacon"
66
end
7+
8+
Rake::ExtensionTask.new "bluez" do |ext|
9+
ext.lib_dir = "lib/scan_beacon"
10+
end

ext/bluez/advertising.c

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#ifdef linux
2+
#include "ruby.h"
3+
#include <stdio.h>
4+
#include <errno.h>
5+
#include <unistd.h>
6+
#include <sys/ioctl.h>
7+
#include <sys/socket.h>
8+
#include <bluetooth/bluetooth.h>
9+
#include <bluetooth/hci.h>
10+
#include <bluetooth/hci_lib.h>
11+
#pragma pack(1)
12+
13+
14+
#define FLAGS_NOT_CONNECTABLE 0x1A
15+
#define FLAGS_CONNECTABLE 0x18
16+
17+
typedef struct {
18+
uint8_t len;
19+
struct {
20+
uint8_t len;
21+
uint8_t type;
22+
uint8_t data;
23+
} flags;
24+
uint8_t ad_data[28];
25+
} Advertisement;
26+
27+
Advertisement advertisement;
28+
int advertising;
29+
30+
VALUE method_start_advertising();
31+
32+
void init_advertisement()
33+
{
34+
memset(&advertisement, 0, sizeof(advertisement));
35+
advertisement.flags.len = 2;
36+
advertisement.flags.type = 1;
37+
advertisement.flags.data = FLAGS_NOT_CONNECTABLE;
38+
advertising = 0;
39+
}
40+
41+
VALUE method_set_connectable(VALUE self, VALUE connectable)
42+
{
43+
if ( RTEST(connectable) )
44+
{
45+
advertisement.flags.data = FLAGS_CONNECTABLE;
46+
} else {
47+
advertisement.flags.data = FLAGS_NOT_CONNECTABLE;
48+
};
49+
if (advertising) {
50+
method_start_advertising();
51+
}
52+
return connectable;
53+
}
54+
55+
VALUE method_set_advertisement_bytes(VALUE self, VALUE bytes)
56+
{
57+
uint8_t len = RSTRING_LEN(bytes);
58+
if (len > 28) {
59+
len = 28;
60+
}
61+
advertisement.len = len + advertisement.flags.len + 1; // + 1 is to account for the flags length field
62+
memcpy(advertisement.ad_data, RSTRING_PTR(bytes), len);
63+
if (advertising) {
64+
method_start_advertising();
65+
}
66+
return bytes;
67+
}
68+
69+
VALUE method_start_advertising()
70+
{
71+
struct hci_request rq;
72+
le_set_advertising_parameters_cp adv_params_cp;
73+
uint8_t status;
74+
75+
// open connection to the device
76+
int device_id = hci_get_route(NULL);
77+
int device_handle = hci_open_dev(device_id);
78+
79+
// set advertising data
80+
memset(&rq, 0, sizeof(rq));
81+
rq.ogf = OGF_LE_CTL;
82+
rq.ocf = OCF_LE_SET_ADVERTISING_DATA;
83+
rq.cparam = &advertisement;
84+
rq.clen = sizeof(advertisement);
85+
rq.rparam = &status;
86+
rq.rlen = 1;
87+
hci_send_req(device_handle, &rq, 1000);
88+
89+
// set advertising params
90+
memset(&adv_params_cp, 0, sizeof(adv_params_cp));
91+
uint16_t interval_100ms = htobs(0x00A0); // 0xA0 * 0.625ms = 100ms
92+
adv_params_cp.min_interval = interval_100ms;
93+
adv_params_cp.max_interval = interval_100ms;
94+
adv_params_cp.advtype = 0x03; // non-connectable undirected advertising
95+
adv_params_cp.chan_map = 0x07;// all 3 channels
96+
memset(&rq, 0, sizeof(rq));
97+
rq.ogf = OGF_LE_CTL;
98+
rq.ocf = OCF_LE_SET_ADVERTISING_PARAMETERS;
99+
rq.cparam = &adv_params_cp;
100+
rq.clen = LE_SET_ADVERTISING_PARAMETERS_CP_SIZE;
101+
rq.rparam = &status;
102+
rq.rlen = 1;
103+
hci_send_req(device_handle, &rq, 1000);
104+
105+
// turn on advertising
106+
hci_le_set_advertise_enable(device_handle, 0x01, 1000);
107+
108+
// and close the connection
109+
hci_close_dev(device_handle);
110+
advertising = 1;
111+
return Qnil;
112+
}
113+
114+
VALUE method_stop_advertising()
115+
{
116+
int device_id = hci_get_route(NULL);
117+
int device_handle = hci_open_dev(device_id);
118+
hci_le_set_advertise_enable(device_handle, 0x00, 1000);
119+
hci_close_dev(device_handle);
120+
advertising = 0;
121+
return Qnil;
122+
}
123+
124+
125+
#endif // linux

ext/bluez/bluez.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#ifdef linux
2+
#include "ruby.h"
3+
#include <stdio.h>
4+
#include <errno.h>
5+
#include <unistd.h>
6+
#include <sys/ioctl.h>
7+
#include <sys/socket.h>
8+
#include <bluetooth/bluetooth.h>
9+
#include <bluetooth/hci.h>
10+
#include <bluetooth/hci_lib.h>
11+
12+
VALUE bluez_module = Qnil;
13+
14+
void Init_bluez();
15+
VALUE method_device_up(VALUE self, VALUE device_id);
16+
VALUE method_device_down(VALUE self, VALUE device_id);
17+
VALUE method_set_connectable(VALUE self, VALUE connectable);
18+
VALUE method_set_advertisement_bytes(VALUE self, VALUE bytes);
19+
VALUE method_start_advertising();
20+
VALUE method_stop_advertising();
21+
VALUE method_scan(int argc, VALUE *argv, VALUE klass);
22+
VALUE method_devices();
23+
void init_advertisement();
24+
25+
void Init_bluez()
26+
{
27+
VALUE scan_beacon_module = rb_const_get(rb_cObject, rb_intern("ScanBeacon"));
28+
bluez_module = rb_define_module_under(scan_beacon_module, "BlueZ");
29+
rb_define_singleton_method(bluez_module, "device_up", method_device_up, 1);
30+
rb_define_singleton_method(bluez_module, "device_down", method_device_down, 1);
31+
rb_define_singleton_method(bluez_module, "start_advertising", method_start_advertising, 0);
32+
rb_define_singleton_method(bluez_module, "stop_advertising", method_stop_advertising, 0);
33+
rb_define_singleton_method(bluez_module, "advertisement_bytes=", method_set_advertisement_bytes, 1);
34+
rb_define_singleton_method(bluez_module, "connectable=", method_set_connectable, 1);
35+
rb_define_singleton_method(bluez_module, "scan", method_scan, -1);
36+
rb_define_singleton_method(bluez_module, "devices", method_devices, 0);
37+
38+
// initialize the advertisement
39+
init_advertisement();
40+
}
41+
42+
43+
#endif // linux

ext/bluez/devices.c

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#ifdef linux
2+
#include "ruby.h"
3+
#include <stdio.h>
4+
#include <errno.h>
5+
#include <unistd.h>
6+
#include <sys/ioctl.h>
7+
#include <sys/socket.h>
8+
#include <bluetooth/bluetooth.h>
9+
#include <bluetooth/hci.h>
10+
#include <bluetooth/hci_lib.h>
11+
12+
#include "utils.h"
13+
14+
VALUE method_device_up(VALUE self, VALUE device_id)
15+
{
16+
VALUE success;
17+
int ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
18+
int hdev = NUM2INT(device_id);
19+
/* Start HCI device */
20+
if (ioctl(ctl, HCIDEVUP, hdev) < 0) {
21+
if (errno == EALREADY) {
22+
success = Qtrue;
23+
} else {
24+
rb_sys_fail("Can't init device");
25+
success = Qfalse;
26+
}
27+
} else {
28+
success = Qtrue;
29+
}
30+
close(ctl);
31+
return success;
32+
}
33+
34+
VALUE method_device_down(VALUE self, VALUE device_id)
35+
{
36+
VALUE success;
37+
int ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
38+
int hdev = NUM2INT(device_id);
39+
/* Stop HCI device */
40+
if (ioctl(ctl, HCIDEVDOWN, hdev) < 0) {
41+
rb_sys_fail("Can't init device");
42+
success = Qfalse;
43+
} else {
44+
success = Qtrue;
45+
}
46+
close(ctl);
47+
return success;
48+
}
49+
50+
VALUE di_to_hash(struct hci_dev_info *di)
51+
{
52+
VALUE hash = rb_hash_new();
53+
rb_hash_aset(hash, ID2SYM(rb_intern("device_id")), INT2FIX(di->dev_id));
54+
rb_hash_aset(hash, ID2SYM(rb_intern("name")), rb_str_new2(di->name));
55+
rb_hash_aset(hash, ID2SYM(rb_intern("addr")), ba2value(&di->bdaddr));
56+
if (hci_test_bit(HCI_UP, &di->flags)) {
57+
rb_hash_aset(hash, ID2SYM(rb_intern("up")), Qtrue);
58+
} else {
59+
rb_hash_aset(hash, ID2SYM(rb_intern("up")), Qfalse);
60+
}
61+
return hash;
62+
}
63+
64+
VALUE method_devices()
65+
{
66+
struct hci_dev_list_req *dl;
67+
struct hci_dev_req *dr;
68+
struct hci_dev_info di;
69+
int i;
70+
71+
if (!(dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t)))) {
72+
rb_raise(rb_eException, "Can't allocate memory");
73+
return Qnil;
74+
}
75+
dl->dev_num = HCI_MAX_DEV;
76+
dr = dl->dev_req;
77+
78+
int ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
79+
if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) {
80+
rb_raise(rb_eException, "Can't get device list");
81+
return Qnil;
82+
}
83+
84+
VALUE devices = rb_ary_new();
85+
86+
for (i = 0; i< dl->dev_num; i++) {
87+
di.dev_id = (dr+i)->dev_id;
88+
if (ioctl(ctl, HCIGETDEVINFO, (void *) &di) < 0)
89+
continue;
90+
if (hci_test_bit(HCI_RAW, &di.flags) &&
91+
!bacmp(&di.bdaddr, BDADDR_ANY)) {
92+
int dd = hci_open_dev(di.dev_id);
93+
hci_read_bd_addr(dd, &di.bdaddr, 1000);
94+
hci_close_dev(dd);
95+
}
96+
rb_ary_push(devices, di_to_hash(&di));
97+
}
98+
return devices;
99+
}
100+
101+
#endif // linux

ext/bluez/extconf.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Loads mkmf which is used to make makefiles for Ruby extensions
2+
require 'mkmf'
3+
4+
# Give it a name
5+
extension_name = 'scan_beacon/bluez'
6+
7+
# The destination
8+
dir_config(extension_name)
9+
10+
if RUBY_PLATFORM =~ /linux/
11+
abort 'could not find bluetooth library (libbluetooth-dev)' unless have_library("bluetooth")
12+
end
13+
14+
create_makefile(extension_name)

0 commit comments

Comments
 (0)