├── .gitignore ├── LICENSE ├── README.md └── ibeacon /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Donald Burr 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of linux-ibeacon nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | linux-ibeacon 2 | ============= 3 | 4 | This is a Python script that creates an Apple® [iBeacon®][IBEACON]-compatible 5 | [Bluetooth LE beacon][BTBEACONS] using a computer running Linux and a Bluetooth LE adapter. 6 | 7 | What You Need 8 | ------------- 9 | 10 | You need a computer capable of running Linux. It can be a desktop or notebook PC, or any 11 | of the various single-board computers that are popular nowadays such as the [Raspberry Pi][PI] 12 | or [Arduino YUN][YUN]. It must have Python 2.6 or 2.7 installed. This script does not need 13 | any special Python libraries or modules, just the ones that come standard with Python. 14 | 15 | Your version of Linux must be compatible with the new [Bluetooth 4.0 Low Energy (LE)][BLE] standard. 16 | Currently this requires version 3.5 or greater of the Linux kernel. You will 17 | also need version 5.0 or greater of [BlueZ][BLUEZ], the Linux Bluetooth stack and associated 18 | tools. 19 | 20 | On most Linux distributions, BlueZ can be easily installed from your distribution's package 21 | manager. E.g., for Debian and Debian derivatives (Ubuntu, etc.): 22 | 23 | `sudo apt-get install bluetooth bluez-utils blueman` 24 | 25 | Your computer must also have a Bluetooth adapter (either built-in or USB) that is compatible with 26 | the [Bluetooth 4.0 LE][BLE] standard. To test whether your adapter is LE-compatible, issue the 27 | following command: 28 | 29 | `sudo hcitool lescan` 30 | 31 | If you see either nothing, or a list of MAC addresses (`aa:bb:cc:dd:ee:ff`) then your adapter 32 | supports Bluetooth LE. If, on the other hand, you see any error messages in the output, then 33 | your adapter does not support LE. (This command will continuously scan for devices, so to exit 34 | it press `Control-C.) 35 | 36 | If you don't have a LE-capable adapter, the [Plugable USB Bluetooth 4.0 Low Energy Micro Adapter][USB-BT-LE] 37 | is an inexpensive, low-profile USB Bluetooth adapter that is known to work well with Linux. 38 | 39 | How to use it 40 | ------------- 41 | 42 | Usage: sudo ibeacon [-u|--uuid=UUID or `random' (default=Beacon Toolkit app)] 43 | [-M|--major=major (0-65535, default=0)] 44 | [-m|--minor=minor (0-65535, default=0)] 45 | [-p|--power=power (0-255, default=200)] 46 | [-d|--device=BLE device to use (default=hci0)] 47 | [-z|--down] 48 | [-v|--verbose] 49 | [-n|--simulate (implies -v)] 50 | [-h|--help] 51 | 52 | This script must be run with `root` privileges in order to configure Bluetooth adapters. It is most convenient to run it using `sudo.` 53 | 54 | By default, the script creates an iBeacon whose UUID matches that which is used by the [Beacon Toolkit iOS app][BEACON-APP-IOS], 55 | with major and minor both set to `0`. These can be changed using the `-u`, `-M` and `-m` flags respectively. When specifying 56 | the UUID, you can specify an explicit UUID, or by specifying `random` the script will randomly generate a UUID. 57 | 58 | UUID, major and minor may also be specified by setting the `IBEACON_UUID,` `IBEACON_MAJOR` and `IBEACON_MINOR` environment 59 | variables, respectively. If a value(s) is specified both in the environment as well as a command line option, the command 60 | line option takes precedence. 61 | 62 | To test, you will need a [device compatible with Bluetooth LE][BLE-DEVICES]. In the Apple universe, that means the iPhone 4S 63 | or later, iPad 3rd gen or later (including Mini and Air), and the iPod touch. For Android, you'll have to 64 | [check the list][BLE-DEVICES] (most phones made within the last 2 years or so should be BLE-compatible.) Then download 65 | either [Beacon Toolkit][BEACON-APP-IOS] (for iOS) or [iBeacon Scanner][BEACON-APP-ANDROID] (for Android.) Fire up the app 66 | and begin scanning. Your newly-created iBeacon should appear in the list. If not, check to make sure that you 67 | specified the correct UUID, major and minor numbers. (For iOS devices, if you used a non-default UUID, you will have to 68 | enter it in the Beacon Toolkit app's settings screen.) 69 | 70 | License 71 | ------- 72 | 73 | This script is licensed under the [MIT license][MITLICENSE]. 74 | 75 | [IBEACON]: https://developer.apple.com/ibeacon/ "iBeacon info page" 76 | [BTBEACONS]: http://www.infoworld.com/article/2608498/mobile-apps/what-you-need-to-know-about-using-bluetooth-beacons.html "Bluetooth Beacons" 77 | [PI]: http://www.amazon.com/dp/B00LPESRUK/?tag=otakunocast-20 "Raspberry Pi" 78 | [YUN]: http://www.amazon.com/dp/B00F6YJK3S/?tag=otakunocast-20 "Arduino YUN" 79 | [BLE]: http://en.wikipedia.org/wiki/Bluetooth_low_energy "Bluetooth LE" 80 | [USB-BT-LE]: http://www.amazon.com/dp/B009ZIILLI/?tag=otakunocast-20 "Plugable USB Bluetooth 4.0 Low Energy Micro Adapter" 81 | [BLUEZ]: http://www.bluez.org "BlueZ - Linux Bluetooth stack" 82 | [MITLICENSE]: http://opensource.org/licenses/MIT "MIT License" 83 | [BLE-DEVICES]: http://www.bluetooth.com/Pages/Bluetooth-Smart-Devices-List.aspx "Bluetooth LE compatible devices" 84 | [BEACON-APP-IOS]: https://itunes.apple.com/us/app/beacon-toolkit/id728479775?mt=8 "Beacon Toolkit iOS App" 85 | [BEACON-APP-ANDROID]: https://play.google.com/store/apps/details?id=de.flurp.beaconscanner.app "iBeacon Scanner Android App" 86 | -------------------------------------------------------------------------------- /ibeacon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | Usage: sudo ibeacon [-u|--uuid=UUID or `random' (default=Beacon Toolkit app)] 5 | [-M|--major=major (0-65535, default=0)] 6 | [-m|--minor=minor (0-65535, default=0)] 7 | [-p|--power=power (0-255, default=200)] 8 | [-d|--device=BLE device to use (default=hci0)] 9 | [-z|--down] 10 | [-v|--verbose] 11 | [-n|--simulate (implies -v)] 12 | [-h|--help] 13 | 14 | The default UUID matches that which the "Beacon Toolkit" app uses. 15 | https://itunes.apple.com/us/app/beacon-toolkit/id728479775 16 | 17 | UUID, major, minor and power may also be specified by setting the environment 18 | variables `IBEACON_UUID,' `IBEACON_MAJOR,' `IBEACON_MINOR' and `IBEACON_POWER.' 19 | Options set with command line switches always override defaults or environment. 20 | 21 | NOTE: for environment variables to work, the following must be present 22 | in your `sudoers' file: 23 | Defaults env_keep += "IBEACON_UUID IBEACON_MAJOR IBEACON_MINOR IBEACON_POWER" 24 | """ 25 | 26 | import os 27 | import subprocess 28 | import sys 29 | import re 30 | import getopt 31 | import uuid 32 | 33 | # option flags 34 | simulate = False 35 | verbose = False 36 | 37 | # prints usage information 38 | class Usage(Exception): 39 | def __init__(self, msg): 40 | self.msg = msg 41 | 42 | # return a random UUID 43 | def get_random_uuid(): 44 | return uuid.uuid4().hex 45 | 46 | # convert an integer into a hex value of a given number of digits 47 | def hexify(i, digits=2): 48 | format_string = "0%dx" % digits 49 | return format(i, format_string).upper() 50 | 51 | # swap the byte order of a 16-bit hex value 52 | # NOT USED, leaving this here just in case, turns out major/minor ids 53 | # are big-endian (i.e. no swap required) 54 | def endian_swap(hex): 55 | hexbyte1 = hex[0] + hex[1] 56 | hexbyte2 = hex[2] + hex[3] 57 | newhex = hexbyte2 + hexbyte1 58 | return newhex 59 | 60 | # split a hex string into 8-bit/2-hex-character groupings separated by spaces 61 | def hexsplit(string): 62 | return ' '.join([string[i:i+2] for i in range(0, len(string), 2)]) 63 | 64 | # process a command string - print it if verbose is on, 65 | # and if not simulating, then actually run the command 66 | def process_command(c): 67 | global verbose 68 | if verbose: 69 | print ">>> %s" % c 70 | if not simulate: 71 | os.system(c) 72 | 73 | # check to see if we are the superuser - returns 1 if yes, 0 if no 74 | def check_for_sudo(): 75 | if 'SUDO_UID' in os.environ.keys(): 76 | return 1 77 | else: 78 | print "Error: this script requires superuser privileges. Please re-run with `sudo.'" 79 | return 0 80 | 81 | # check to see if the hci device is valid 82 | # kind of a cheaty way of doing this, we just grep the output of 83 | # `hcitool list' to make sure the passed-in device string is present 84 | def is_valid_device(device): 85 | return not os.system("hciconfig list 2>/dev/null | grep -q ^%s:" % device) 86 | 87 | ############################################################################### 88 | 89 | def main(argv=None): 90 | 91 | # option flags 92 | global verbose 93 | global simulate 94 | 95 | # default uuid, this is the uuid that the "Beacon Toolkit" iOS app uses 96 | # https://itunes.apple.com/us/app/beacon-toolkit/id728479775 97 | # can be overriden with environment variable 98 | uuid = os.getenv("IBEACON_UUID", "E20A39F473F54BC4A12F17D1AD07A961") 99 | 100 | # major and minor ids, can be overriden with environment variable 101 | major = int(os.getenv("IBEACON_MAJOR", "0")) 102 | minor = int(os.getenv("IBEACON_MINOR", "0")) 103 | 104 | # default to the first available bluetooth device 105 | device = "hci0" 106 | 107 | # default power level 108 | power = int(os.getenv("IBEACON_POWER", "200")) 109 | 110 | # regexp to test for a valid UUID 111 | # here the - separators are optional 112 | 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) 113 | 114 | # grab command line arguments 115 | if argv is None: 116 | argv = sys.argv 117 | 118 | # parse command line options 119 | try: 120 | try: 121 | opts, args = getopt.getopt(argv[1:], "hu:M:m:p:vd:nz", ["help", "uuid=", "major=", "minor=", "power=", "verbose", "device=", "simulate", "down"]) 122 | 123 | except getopt.error, msg: 124 | raise Usage(msg) 125 | 126 | for o, a in opts: 127 | if o in ("-h", "--help"): 128 | print __doc__ 129 | return 0 130 | elif o in ("-n", "--simulate"): 131 | simulate = True 132 | verbose = True 133 | elif o in ("-u", "--uuid"): 134 | uuid = a 135 | if uuid == "random": 136 | uuid = get_random_uuid() 137 | elif o in ("-M", "--major"): 138 | major = int(a) 139 | elif o in ("-m", "--minor"): 140 | minor = int(a) 141 | elif o in ("-p", "--power"): 142 | power = int(a) 143 | elif o in ("-v", "--verbose"): 144 | verbose = True 145 | elif o in ( "-d", "--device"): 146 | temp_device = str(a) 147 | # devices can be specified as "X" or "hciX" 148 | if not temp_device.startswith("hci"): 149 | device = "hci%s" % temp_device 150 | else: 151 | device = temp_device 152 | elif o in ( "-z", "--down"): 153 | if check_for_sudo(): 154 | if is_valid_device(device): 155 | print "Downing iBeacon on %s" % device 156 | process_command("hciconfig %s noleadv" % device) 157 | process_command("hciconfig %s piscan" % device) 158 | process_command("hciconfig %s down" % device) 159 | return 0 160 | else: 161 | print "Error: no such device: %s (try `hciconfig list')" % device 162 | return 1 163 | else: 164 | return 1 165 | 166 | # test for valid UUID 167 | if not valid_uuid_match.match(uuid): 168 | print "Error: `%s' is an invalid UUID." % uuid 169 | return 1 170 | 171 | # strip out - symbols from uuid and turn it into uppercase 172 | uuid = uuid.replace("-","") 173 | uuid = uuid.upper() 174 | 175 | # bounds check major/minor ids 176 | if major < 0 or major > 65535: 177 | print "Error: major id is out of bounds (0-65535)" 178 | return 1 179 | if minor < 0 or minor > 65535: 180 | print "Error: minor id is out of bounds (0-65535)" 181 | return 1 182 | 183 | # bail if we're not running as superuser (don't care if we are simulating) 184 | if not simulate and not check_for_sudo(): 185 | return 1 186 | 187 | # split the uuid into 8 bit (=2 hex digit) chunks 188 | split_uuid = hexsplit(uuid) 189 | 190 | # convert major/minor id into hex 191 | major_hex = hexify(major, 4) 192 | minor_hex = hexify(minor, 4) 193 | 194 | # create split versions of these (for the hcitool command) 195 | split_major_hex = hexsplit(major_hex) 196 | split_minor_hex = hexsplit(minor_hex) 197 | 198 | # convert power into hex 199 | power_hex = hexify(power, 2) 200 | 201 | # make sure we are using a valid hci device 202 | if not simulate and not is_valid_device(device): 203 | print "Error: no such device: %s (try `hciconfig list')" % device 204 | return 1 205 | 206 | # print status info 207 | print "Advertising on %s with:" % device 208 | print " uuid: 0x%s" % uuid 209 | print "major/minor: %d/%d (0x%s/0x%s)" % (major, minor, major_hex, minor_hex) 210 | print " power: %d (0x%s)" % (power, power_hex) 211 | 212 | # first bring up bluetooth 213 | process_command("hciconfig %s up" % device) 214 | # now turn on LE advertising 215 | process_command("hciconfig %s leadv" % device) 216 | # now turn off scanning 217 | process_command("hciconfig %s noscan" % device) 218 | # set up the beacon 219 | # pipe stdout to /dev/null to get rid of the ugly "here's what I did" 220 | # message from hcitool 221 | 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)) 222 | 223 | except Usage, err: 224 | print >>sys.stderr, err.msg 225 | print >>sys.stderr, "for help use --help" 226 | return 2 227 | 228 | ############################################################################### 229 | 230 | if __name__ == "__main__": 231 | sys.exit(main()) 232 | --------------------------------------------------------------------------------