Skip to content

Instantly share code, notes, and snippets.

@timawesomeness
Created January 11, 2018 02:25
Show Gist options
  • Save timawesomeness/8e9eac54603aa54c320d7daff25cdd09 to your computer and use it in GitHub Desktop.
Save timawesomeness/8e9eac54603aa54c320d7daff25cdd09 to your computer and use it in GitHub Desktop.
#!/usr/bin/python3
#################################################################
# Simple script to generate an Eddystone URL beacon as Bluetooth data readable by btmgmt
#
# To use: ./geneddystonebeacon.py [-p POWER] URL where POWER is an optional integer from -100 to 20 (per the Bluetooth spec) and
# URL is a URL including http(s):// to be broadcast by the beacon. Be aware that Physical Web (specifically Google Nearby)
# will not show the beacon unless you use https.
#
# To broadcast a beacon with that string: (as root)
# btmgmt add-adv -d INSERT-STRING-HERE 1
# To stop broadcasting: (again, as root)
# btmgmt rm-adv 1
#################################################################
import binascii, sys, argparse, re, struct
MAX_LENGTH = 31
BLE_HEADER = "020106" # 02 = length, 01 = flags, 06 = discovery flag
EDDYSTONE_SERVICE_UUID = "0303AAFE" # 03 length, 03 = Complete List of 16-bit Service Class UUIDs, AAFE = reversed eddystone UUID
EDDYSTONE_DATA_UUID_URL = "16AAFE10" # 16 = service data, AAFE = reversed eddystone UUID, 10 = eddystone URL frame type
parser = argparse.ArgumentParser(description="Convert a URL (and optional TX power) into BLE beacon data compatible with Google's Eddystone format.")
parser.add_argument("url", metavar="URL", type=str, nargs=1, help="A URL, including http(s)://, with a max length of 18 characters.")
parser.add_argument("-p", "--power", type=int, help="Calibrated TX power at 0m (defaults to 0)", default=0, choices=range(-100, 21), metavar="POWER")
args = parser.parse_args()
if not re.fullmatch(r"https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)", args.url[0]):
print("Not a valid url with http(s)://")
sys.exit(1)
url_prefix = ""
url = ""
if args.url[0].startswith("http://www."):
url_prefix = "00"
url = args.url[0].split("http://www.", 2)[1]
elif args.url[0].startswith("https://www."):
url_prefix = "01"
url = args.url[0].split("https://www.", 2)[1]
elif args.url[0].startswith("http://"):
url_prefix = "02"
url = args.url[0].split("http://", 2)[1]
elif args.url[0].startswith("https://"):
url_prefix = "03"
url = args.url[0].split("https://", 2)[1]
url = url_prefix + binascii.hexlify(url.encode("ascii")).decode("ascii")
# because I hate everything
url = url.replace("2e636f6d2f", "00").replace("2e6f72672f", "01").replace("2e6564752f", "02").replace("2e6e65742f", "03").replace("2e696e666f2f", "04").replace("2e62697a2f", "05").replace("2e676f762f", "06")
url = url.replace("2e636f6d", "07").replace("2e6f7267", "08").replace("2e656475", "09").replace("2e6e6574", "0a").replace("2e696e666f", "0b").replace("2e62697a", "0c").replace("2e676f76", "0d")
power = bytearray(struct.pack("!b", args.power)).hex() # stupid hacky way of converting int to hex the way bluetooth wants it
eddystone_length = int((len(EDDYSTONE_DATA_UUID_URL) + len(power) + len(url)) / 2)
full_ble_data = BLE_HEADER + EDDYSTONE_SERVICE_UUID + "{:02x}".format(eddystone_length) + EDDYSTONE_DATA_UUID_URL + power + url
if int(len(full_ble_data) / 2) > MAX_LENGTH:
print("URL is too long.")
sys.exit(1)
print(full_ble_data.upper())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment