Skip to content

Commit

Permalink
Added support for Eddystone-UID (#4)
Browse files Browse the repository at this point in the history
* Add support for Eddystone-uid

PyBeacon now supports the creation of Eddystone-uid Beacons, via the -i option.

* Add support for scanning eddystone UIDs

* Enforced

* Add default value for uid advertising

* Update Readme

* Minor Refactorings

* Address review comments

* Add mutually exclusive param groups, to prevent providing both -i <UID> , -u <URL> (hcitool can't advertise both).

Default behavior still remains the same (e.g running Pybeacon with no args advertises the default URL)
  • Loading branch information
mikemyl authored and PrabhanshuAttri committed Jan 15, 2017
1 parent 5fb141f commit cb0d567
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 22 deletions.
93 changes: 71 additions & 22 deletions PyBeacon/PyBeacon.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import argparse
from . import __version__
from pprint import pprint
from enum import Enum

application_name = 'PyBeacon'
version = __version__ + 'beta'
Expand All @@ -36,7 +37,10 @@
DEVNULL = open(os.devnull, 'wb')

# The default url
url = "https://goo.gl/SkcDTN"
defaultUrl = "https://goo.gl/SkcDTN"

# The default uid
defaultUid = "01234567890123456789012345678901"

schemes = [
"http://www.",
Expand All @@ -45,15 +49,25 @@
"https://",
]


class Eddystone(Enum):
uid = 0x00
url = 0x10
tlm = 0x20


extensions = [
".com/", ".org/", ".edu/", ".net/", ".info/", ".biz/", ".gov/",
".com", ".org", ".edu", ".net", ".info", ".biz", ".gov",
]

parser = argparse.ArgumentParser(prog=application_name, description= __doc__)
group = parser.add_mutually_exclusive_group()

parser.add_argument("-u", "--url", nargs='?', const=url, type=str,
default=url, help='URL to advertise.')
group.add_argument("-u", "--url", nargs='?', const=defaultUrl, type=str,
default=defaultUrl, help='URL to advertise.')
group.add_argument("-i", "--uid", nargs='?', type=str,
const=defaultUid, help='UID to advertise.')
parser.add_argument('-s','--scan', action='store_true',
help='Scan for URLs.')
parser.add_argument('-t','--terminate', action='store_true',
Expand Down Expand Up @@ -103,13 +117,38 @@ def encodeurl(url):
return data


def encodeMessage(url):
encodedurl = encodeurl(url)
encodedurlLength = len(encodedurl)
def encodeUid(uid):
if not uidIsValid(uid):
raise ValueError("Invalid uid. Please specify a valid 16-byte (e.g 32 hex digits) hex string")
ret = []
for i in range(0, len(uid), 2):
ret.append(int(uid[i:i+2], 16))
ret.append(0x00)
ret.append(0x00)
return ret


def uidIsValid(uid):
if len(uid) == 32:
try:
int(uid, 16)
return True
except ValueError:
return False
else:
return False


verboseOutput("Encoded url length: " + str(encodedurlLength))
def encodeMessage(data, beacon_type=Eddystone.url):
if beacon_type == Eddystone.url:
payload = encodeurl(data)
elif beacon_type == Eddystone.uid:
payload = encodeUid(data)
encodedmessageLength = len(payload)

if encodedurlLength > 18:
verboseOutput("Encoded message length: " + str(encodedmessageLength))

if encodedmessageLength > 18:
raise Exception("Encoded url too long (max 18 bytes)")

message = [
Expand All @@ -122,16 +161,16 @@ def encodeMessage(url):
0xaa, # 16-bit Eddystone UUID
0xfe, # 16-bit Eddystone UUID

5 + len(encodedurl), # Service Data length
5 + len(payload), # Service Data length
0x16, # Service Data data type value
0xaa, # 16-bit Eddystone UUID
0xfe, # 16-bit Eddystone UUID

0x10, # Eddystone-url frame type
beacon_type.value, # Eddystone-url frame type
0xed, # txpower
]

message += encodedurl
message += payload

return message

Expand Down Expand Up @@ -198,13 +237,22 @@ def onUrlFound(url):
"""

url = resolveUrl(url)
sys.stdout.write("\n/* Eddystone-URL */\n")
sys.stdout.write(url)
sys.stdout.write("\n")
sys.stdout.flush()


foundPackets = set()


def onUidFound(bytearray):
print("\n/* Eddystone-UID */")
namespace = ("".join(format(x, '02x') for x in bytearray[0:10]))
instance = ("".join(format(x, '02x') for x in bytearray[10:16]))
print("Namspace: {}\nInstance: {}\n".format(namespace, instance))


def onPacketFound(packet):
"""
Called by the scan function for each beacon packets found.
Expand All @@ -224,12 +272,11 @@ def onPacketFound(packet):
frameType = data[25]

# Eddystone-URL
if frameType == 0x10:
verboseOutput("Eddystone-URL")
if frameType == Eddystone.url.value:
onUrlFound(decodeUrl(data[27:22 + serviceDataLength]))
elif frameType == 0x00:
verboseOutput("Eddystone-UID")
elif frameType == 0x20:
elif frameType == Eddystone.uid.value:
onUidFound(data[27:22 + serviceDataLength])
elif frameType == Eddystone.tlm.value:
verboseOutput("Eddystone-TLM")
else:
verboseOutput("Unknown Eddystone frame type: {}".format(frameType))
Expand Down Expand Up @@ -259,7 +306,7 @@ def scan(duration = None):
lescan = subprocess.Popen(
["sudo", "-n", "hcitool", "lescan", "--duplicates"],
stdout = DEVNULL)

dump = subprocess.Popen(
["sudo", "-n", "hcidump", "--raw"],
stdout = subprocess.PIPE)
Expand Down Expand Up @@ -288,9 +335,9 @@ def scan(duration = None):
subprocess.call(["sudo", "-n", "kill", str(lescan.pid), "-s", "SIGINT"])


def advertise(url):
print("Advertising: " + url)
message = encodeMessage(url)
def advertise(ad, beacon_type=Eddystone.url):
print("Advertising: {} : {}".format(beacon_type.name, ad))
message = encodeMessage(ad, beacon_type)

# Prepend the length of the whole message
message.insert(0, len(message))
Expand Down Expand Up @@ -324,7 +371,7 @@ def stopAdvertising():
def showVersion():
print(application_name + " " + version)

def main():
def main():
if args.version:
showVersion()
else:
Expand All @@ -335,8 +382,10 @@ def main():
scan(3)
elif args.scan:
scan()
elif args.uid:
advertise(args.uid, Eddystone.uid)
else:
advertise(args.url)

if __name__ == "__main__":
main()
main()
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Python script for scanning and advertising urls over [Eddystone-URL](https://git
optional arguments:
-h, --help show this help message and exit
-u [URL], --url [URL] URL to advertise.
-i [UID], --uid [UID] UID to advertise.
-s, --scan Scan for URLs.
-t, --terminate Stop advertising URL.
-o, --one Scan one URL only.
Expand Down

0 comments on commit cb0d567

Please sign in to comment.