-
Notifications
You must be signed in to change notification settings - Fork 48
/
ibeacon
executable file
·231 lines (191 loc) · 7.32 KB
/
ibeacon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#!/usr/bin/python
"""
Usage: sudo ibeacon [-u|--uuid=UUID or `random' (default=Beacon Toolkit app)]
[-M|--major=major (0-65535, default=0)]
[-m|--minor=minor (0-65535, default=0)]
[-p|--power=power (0-255, default=200)]
[-d|--device=BLE device to use (default=hci0)]
[-z|--down]
[-v|--verbose]
[-n|--simulate (implies -v)]
[-h|--help]
The default UUID matches that which the "Beacon Toolkit" app uses.
https://itunes.apple.com/us/app/beacon-toolkit/id728479775
UUID, major, minor and power may also be specified by setting the environment
variables `IBEACON_UUID,' `IBEACON_MAJOR,' `IBEACON_MINOR' and `IBEACON_POWER.'
Options set with command line switches always override defaults or environment.
NOTE: for environment variables to work, the following must be present
in your `sudoers' file:
Defaults env_keep += "IBEACON_UUID IBEACON_MAJOR IBEACON_MINOR IBEACON_POWER"
"""
import os
import subprocess
import sys
import re
import getopt
import uuid
# option flags
simulate = False
verbose = False
# prints usage information
class Usage(Exception):
def __init__(self, msg):
self.msg = msg
# return a random UUID
def get_random_uuid():
return uuid.uuid4().hex
# convert an integer into a hex value of a given number of digits
def hexify(i, digits=2):
format_string = "0%dx" % digits
return format(i, format_string).upper()
# swap the byte order of a 16-bit hex value
# NOT USED, leaving this here just in case, turns out major/minor ids
# are big-endian (i.e. no swap required)
def endian_swap(hex):
hexbyte1 = hex[0] + hex[1]
hexbyte2 = hex[2] + hex[3]
newhex = hexbyte2 + hexbyte1
return newhex
# split a hex string into 8-bit/2-hex-character groupings separated by spaces
def hexsplit(string):
return ' '.join([string[i:i+2] for i in range(0, len(string), 2)])
# process a command string - print it if verbose is on,
# and if not simulating, then actually run the command
def process_command(c):
global verbose
if verbose:
print ">>> %s" % c
if not simulate:
os.system(c)
# check to see if we are the superuser - returns 1 if yes, 0 if no
def check_for_sudo():
if 'SUDO_UID' in os.environ.keys():
return 1
else:
print "Error: this script requires superuser privileges. Please re-run with `sudo.'"
return 0
# check to see if the hci device is valid
# kind of a cheaty way of doing this, we just grep the output of
# `hcitool list' to make sure the passed-in device string is present
def is_valid_device(device):
return not os.system("hciconfig list 2>/dev/null | grep -q ^%s:" % device)
###############################################################################
def main(argv=None):
# option flags
global verbose
global simulate
# default uuid, this is the uuid that the "Beacon Toolkit" iOS app uses
# https://itunes.apple.com/us/app/beacon-toolkit/id728479775
# can be overriden with environment variable
uuid = os.getenv("IBEACON_UUID", "E20A39F473F54BC4A12F17D1AD07A961")
# major and minor ids, can be overriden with environment variable
major = int(os.getenv("IBEACON_MAJOR", "0"))
minor = int(os.getenv("IBEACON_MINOR", "0"))
# default to the first available bluetooth device
device = "hci0"
# default power level
power = int(os.getenv("IBEACON_POWER", "200"))
# regexp to test for a valid UUID
# here the - separators are optional
valid_uuid_match = re.compile('^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$', re.I)
# grab command line arguments
if argv is None:
argv = sys.argv
# parse command line options
try:
try:
opts, args = getopt.getopt(argv[1:], "hu:M:m:p:vd:nz", ["help", "uuid=", "major=", "minor=", "power=", "verbose", "device=", "simulate", "down"])
except getopt.error, msg:
raise Usage(msg)
for o, a in opts:
if o in ("-h", "--help"):
print __doc__
return 0
elif o in ("-n", "--simulate"):
simulate = True
verbose = True
elif o in ("-u", "--uuid"):
uuid = a
if uuid == "random":
uuid = get_random_uuid()
elif o in ("-M", "--major"):
major = int(a)
elif o in ("-m", "--minor"):
minor = int(a)
elif o in ("-p", "--power"):
power = int(a)
elif o in ("-v", "--verbose"):
verbose = True
elif o in ( "-d", "--device"):
temp_device = str(a)
# devices can be specified as "X" or "hciX"
if not temp_device.startswith("hci"):
device = "hci%s" % temp_device
else:
device = temp_device
elif o in ( "-z", "--down"):
if check_for_sudo():
if is_valid_device(device):
print "Downing iBeacon on %s" % device
process_command("hciconfig %s noleadv" % device)
process_command("hciconfig %s piscan" % device)
process_command("hciconfig %s down" % device)
return 0
else:
print "Error: no such device: %s (try `hciconfig list')" % device
return 1
else:
return 1
# test for valid UUID
if not valid_uuid_match.match(uuid):
print "Error: `%s' is an invalid UUID." % uuid
return 1
# strip out - symbols from uuid and turn it into uppercase
uuid = uuid.replace("-","")
uuid = uuid.upper()
# bounds check major/minor ids
if major < 0 or major > 65535:
print "Error: major id is out of bounds (0-65535)"
return 1
if minor < 0 or minor > 65535:
print "Error: minor id is out of bounds (0-65535)"
return 1
# bail if we're not running as superuser (don't care if we are simulating)
if not simulate and not check_for_sudo():
return 1
# split the uuid into 8 bit (=2 hex digit) chunks
split_uuid = hexsplit(uuid)
# convert major/minor id into hex
major_hex = hexify(major, 4)
minor_hex = hexify(minor, 4)
# create split versions of these (for the hcitool command)
split_major_hex = hexsplit(major_hex)
split_minor_hex = hexsplit(minor_hex)
# convert power into hex
power_hex = hexify(power, 2)
# make sure we are using a valid hci device
if not simulate and not is_valid_device(device):
print "Error: no such device: %s (try `hciconfig list')" % device
return 1
# print status info
print "Advertising on %s with:" % device
print " uuid: 0x%s" % uuid
print "major/minor: %d/%d (0x%s/0x%s)" % (major, minor, major_hex, minor_hex)
print " power: %d (0x%s)" % (power, power_hex)
# first bring up bluetooth
process_command("hciconfig %s up" % device)
# now turn on LE advertising
process_command("hciconfig %s leadv" % device)
# now turn off scanning
process_command("hciconfig %s noscan" % device)
# set up the beacon
# pipe stdout to /dev/null to get rid of the ugly "here's what I did"
# message from hcitool
process_command("hcitool -i hci0 cmd 0x08 0x0008 1E 02 01 1A 1A FF 4C 00 02 15 %s %s %s %s 00 >/dev/null" % (split_uuid, split_major_hex, split_minor_hex, power_hex))
except Usage, err:
print >>sys.stderr, err.msg
print >>sys.stderr, "for help use --help"
return 2
###############################################################################
if __name__ == "__main__":
sys.exit(main())