Skip to content

Commit

Permalink
Merge branch 'master' of github.com:pebble/libpebble
Browse files Browse the repository at this point in the history
  • Loading branch information
Hexxeh committed Nov 26, 2013
2 parents b7f793d + b322bbf commit bdde8dd
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 34 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ nosetests.xml
.mr.developer.cfg
.project
.pydevproject
.idea
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ Interact with your Pebble from OSX, Ubuntu or Debian operating systems.

## Warning and Complications

* Supported OS's are `OSX 10.8`, `Ubuntu`, `Debian`
* OS's which can utilize a faster Bluetooth library, Lightblue-0.4, are `OSX 10.8` and `Ubuntu`
* Detailed Lightblue-0.4 installation instructions for earlier version of OSX (10.6) and other OS's can be found [here](http://lightblue.sourceforge.net/#downloads)
* Supported OSes are `OSX 10.8`, `Ubuntu`, `Debian`
* OSes which can utilize a faster Bluetooth library, Lightblue-0.4, are `OSX 10.8` and `Ubuntu`
* Detailed Lightblue-0.4 installation instructions for earlier version of OSX (10.6) and other OSes can be found [here](http://lightblue.sourceforge.net/#downloads)


##1. Install Dependencies

All supported OS's will require `python 2.7` to operate libpebble. It can be installed [here](http://www.python.org/download/releases/2.7/)
All supported OSes will require `python 2.7` to operate libpebble. It can be installed [here](http://www.python.org/download/releases/2.7/)
* `Pyserial`will also be required, is can be installed via [pip](https://pypi.python.org/pypi/pip)

###a. OSX Additional Dependencies

Expand Down Expand Up @@ -59,13 +60,17 @@ When using libpebble on OSX, it is recommended that `--lightblue` be utilized.

#####Using libpebble with --lightblue on OSX
* First install the OSX dependencies, general dependencies and lightblue
* From the `libpebble-dev` folder, execute the following: `./p.py --lightblue --pair get_time`
* From the `libpebble` folder, execute the following: `./p.py --lightblue --pair get_time`
* Note that if no `--pebble_id` is specified before the command, you are provided with a GUI selection tool.
* Note that if a MAC address is supplied, initialization time is reduced.
* For example: `./p.py --pebble_id 00:11:22:33:44:55:66 --lightblue get_time`
where `00:11:22:33:44:55:66` is the Pebble's MAC Address, viewable on the Pebble from `settings`-->`about`
* You can obtain your pebble's MAC address after a successful connection in the libpebble stdout debug logs
* The `--pebble_id` can also be the 4 letter friendly name of your pebble but this will require that the Pebble is broadcasting.
* It is also possible to set the PEBBLE_ID environment variable as well:

export PEBBLE_ID="00:11:22:33:44:55:66"
./p.py --lightblue get_time

#####Using libpebble without --lightblue on OSX (MAY CAUSE KERNEL PANICS)

Expand All @@ -79,7 +84,7 @@ _Automated pairing via `--pair` is not currently supported in Ubuntu_

* First install the Ubuntu dependencies, general dependencies and lightblue
* In Ubuntu's `Menu`-->`Settings`-->`Connectivity`-->`Bluetooth` dialog, pair with your Pebble
* From the `libpebble-dev` folder, execute the following: `./p.py --lightblue get_time`
* From the `libpebble` folder, execute the following: `./p.py --lightblue get_time`
* Note that if no `--pebble_id` is specified before the command, you are provided with a GUI selection tool.
* For example: `./p.py --pebble_id 00:11:22:33:44:55:66 --lightblue get_time`
* The `--pebble_id` can also be the 4 letter friendly name of your pebble but this require that the Pebble is broadcasting.
Expand Down Expand Up @@ -121,4 +126,4 @@ A basic REPL is available, it is best used with ipython:

`sudo ipython repl.py`

The variable pebble refers the watch connection. You can for example perform `pebble.get_time()` to get the time of the watch
The variable `pebble` refers to the watch connection. You can for example perform `pebble.get_time()` to get the time of the watch
41 changes: 40 additions & 1 deletion p.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ def cmd_load_fw(pebble, args):
def cmd_launch_app(pebble, args):
pebble.launcher_message(args.app_uuid, "RUNNING")

def cmd_app_msg_send_string(pebble, args):
pebble.app_message_send_string(args.app_uuid, args.key, args.tuple_string)

def cmd_app_msg_send_uint(pebble, args):
pebble.app_message_send_uint(args.app_uuid, args.key, args.tuple_uint)

def cmd_app_msg_send_int(pebble, args):
pebble.app_message_send_int(args.app_uuid, args.key, args.tuple_int)

def cmd_app_msg_send_bytes(pebble, args):
pebble.app_message_send_byte_array(args.app_uuid, args.key, args.tuple_bytes)

def cmd_remote(pebble, args):
def do_oscacript(command):
cmd = "osascript -e 'tell application \""+args.app_name+"\" to "+command+"'"
Expand Down Expand Up @@ -143,6 +155,30 @@ def main():
launch_parser.add_argument('app_uuid', metavar='UUID', type=str, help='a valid UUID in the form of: 54D3008F0E46462C995C0D0B4E01148C')
launch_parser.set_defaults(func=cmd_launch_app)

msg_send_string_parser = subparsers.add_parser('msg_send_string', help='sends a string via app message')
msg_send_string_parser.add_argument('app_uuid', metavar='UUID', type=str, help='a valid UUID in the form of: 54D3008F0E46462C995C0D0B4E01148C')
msg_send_string_parser.add_argument('key', type=int, help='a valid tuple key for the app')
msg_send_string_parser.add_argument('tuple_string', type=str, help='a string to send along')
msg_send_string_parser.set_defaults(func=cmd_app_msg_send_string)

msg_send_int_parser = subparsers.add_parser('msg_send_int', help='sends an int via app message')
msg_send_int_parser.add_argument('app_uuid', metavar='UUID', type=str, help='a valid UUID in the form of: 54D3008F0E46462C995C0D0B4E01148C')
msg_send_int_parser.add_argument('key', type=int, help='a valid tuple key for the app')
msg_send_int_parser.add_argument('tuple_int', type=int, help='an int to send along')
msg_send_int_parser.set_defaults(func=cmd_app_msg_send_int)

msg_send_uint_parser = subparsers.add_parser('msg_send_uint', help='sends a uint via app message')
msg_send_uint_parser.add_argument('app_uuid', metavar='UUID', type=str, help='a valid UUID in the form of: 54D3008F0E46462C995C0D0B4E01148C')
msg_send_uint_parser.add_argument('key', type=int, help='a valid tuple key for the app')
msg_send_uint_parser.add_argument('tuple_uint', type=int, help='a uint to send along')
msg_send_uint_parser.set_defaults(func=cmd_app_msg_send_uint)

msg_send_bytes_parser = subparsers.add_parser('msg_send_bytes', help='sends a byte array via app message')
msg_send_bytes_parser.add_argument('app_uuid', metavar='UUID', type=str, help='a valid UUID in the form of: 54D3008F0E46462C995C0D0B4E01148C')
msg_send_bytes_parser.add_argument('key', type=int, help='a valid tuple key for the app')
msg_send_bytes_parser.add_argument('tuple_bytes', type=str, help='a byte array to send along')
msg_send_bytes_parser.set_defaults(func=cmd_app_msg_send_bytes)

load_parser = subparsers.add_parser('load', help='load an app onto a connected watch')
load_parser.add_argument('--nolaunch', action="store_false", help='do not launch the application after install')
load_parser.add_argument('app_bundle', metavar='FILE', type=str, help='a compiled app bundle')
Expand Down Expand Up @@ -213,7 +249,10 @@ def main():
if attempts > MAX_ATTEMPTS:
raise 'Could not connect to Pebble'
try:
pebble = libpebble.Pebble(args.pebble_id, args.lightblue, args.pair)
pebble_id = args.pebble_id
if pebble_id is None and "PEBBLE_ID" in os.environ:
pebble_id = os.environ["PEBBLE_ID"]
pebble = libpebble.Pebble(pebble_id, args.lightblue, args.pair)
break
except:
time.sleep(5)
Expand Down
9 changes: 8 additions & 1 deletion pebble/LightBluePebble.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,14 @@ def autodetect(self):
if (rec_data is not None) and (len(rec_data) == 4):
# check the Stream Multiplexing Layer message and get the length of the data to read
size, endpoint = unpack("!HH", rec_data)
resp = self._bts.recv(size)
resp = ''
while len(resp) < size:
try:
resp += self._bts.recv(size-len(resp))
except (socket.timeout, socket.error):
# Exception raised from timing out on nonblocking
# TODO: Should probably have some kind of timeout here
pass
try:
self.rec_queue.put((endpoint, resp, rec_data))
except (IOError, EOFError):
Expand Down
125 changes: 100 additions & 25 deletions pebble/pebble.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
DEFAULT_PEBBLE_ID = None #Triggers autodetection on unix-like systems
DEBUG_PROTOCOL = False


class PebbleBundle(object):
MANIFEST_FILENAME = 'manifest.json'

Expand Down Expand Up @@ -76,7 +77,9 @@ def get_app_metadata(self):
if (self.header):
return self.header

app_bin = self.zip.open('pebble-app.bin').read()
app_manifest = self.get_manifest()['application']

app_bin = self.zip.open(app_manifest['name']).read()

header = app_bin[0:self.app_metadata_length_bytes]
values = self.app_metadata_struct.unpack(header)
Expand Down Expand Up @@ -180,12 +183,23 @@ class Pebble(object):
"LOG_DUMP": 2002,
"RESET": 2003,
"APP": 2004,
"APP_LOGS": 2006,
"NOTIFICATION": 3000,
"RESOURCE": 4000,
"APP_MANAGER": 6000,
"PUTBYTES": 48879
}

log_levels = {
0: "*",
1: "E",
50: "W",
100: "I",
200: "D",
250: "V"
}


@staticmethod
def AutodetectDevice():
if os.name != "posix": #i.e. Windows
Expand Down Expand Up @@ -221,6 +235,7 @@ def __init__(self, id = None, using_lightblue = True, pair_first = False):
self.endpoints["LAUNCHER"]: self._application_message_response,
self.endpoints["LOGS"]: self._log_response,
self.endpoints["PING"]: self._ping_response,
self.endpoints["APP_LOGS"]: self._app_log_response,
self.endpoints["APP_MANAGER"]: self._appbank_status_response
}

Expand Down Expand Up @@ -557,7 +572,7 @@ def launcher_message(self, app_uuid, key_value, uuid_is_string = True, async = F
""" send an appication message to launch or kill a specified application"""

launcher_keys = {
"RUN_STATE_KEY": b'\x00\x00\x00\x01'
"RUN_STATE_KEY": 1,
}

launcher_key_values = {
Expand Down Expand Up @@ -586,6 +601,56 @@ def launcher_message(self, app_uuid, key_value, uuid_is_string = True, async = F
if not async:
return EndpointSync(self, "LAUNCHER").get_data()

def app_message_send_tuple(self, app_uuid, key, tuple_datatype, tuple_data):

""" Send a Dictionary with a single tuple to the app corresponding to UUID """

app_uuid = app_uuid.decode('hex')
amsg = AppMessage()

app_message_tuple = amsg.build_tuple(key, tuple_datatype, tuple_data)
app_message_dict = amsg.build_dict(app_message_tuple)
packed_message = amsg.build_message(app_message_dict, "PUSH", app_uuid)
self._send_message("APPLICATION_MESSAGE", packed_message)

def app_message_send_string(self, app_uuid, key, string):

""" Send a Dictionary with a single tuple of type CSTRING to the app corresponding to UUID """

# NULL terminate and pack
string = string + '\0'
fmt = '<' + str(len(string)) + 's'
string = pack(fmt, string);

self.app_message_send_tuple(app_uuid, key, "CSTRING", string)

def app_message_send_uint(self, app_uuid, key, tuple_uint):

""" Send a Dictionary with a single tuple of type UINT to the app corresponding to UUID """

fmt = '<' + str(tuple_uint.bit_length() / 8 + 1) + 'B'
tuple_uint = pack(fmt, tuple_uint)

self.app_message_send_tuple(app_uuid, key, "UINT", tuple_uint)

def app_message_send_int(self, app_uuid, key, tuple_int):

""" Send a Dictionary with a single tuple of type INT to the app corresponding to UUID """

fmt = '<' + str(tuple_int.bit_length() / 8 + 1) + 'b'
tuple_int = pack(fmt, tuple_int)

self.app_message_send_tuple(app_uuid, key, "INT", tuple_int)

def app_message_send_byte_array(self, app_uuid, key, tuple_byte_array):

""" Send a Dictionary with a single tuple of type BYTE_ARRAY to the app corresponding to UUID """

# Already packed, fix endianness
tuple_byte_array = tuple_byte_array[::-1]

self.app_message_send_tuple(app_uuid, key, "BYTE_ARRAY", tuple_byte_array)

def system_message(self, command):

"""
Expand Down Expand Up @@ -654,24 +719,29 @@ def _system_message_response(self, endpoint, data):
def _log_response(self, endpoint, data):
if (len(data) < 8):
log.warn("Unable to decode log message (length %d is less than 8)" % len(data))
return;
return

timestamp, level, msgsize, linenumber = unpack("!Ibbh", data[:8])
timestamp, level, msgsize, linenumber = unpack("!IBBH", data[:8])
filename = data[8:24].decode('utf-8')
message = data[24:24+msgsize].decode('utf-8')

log_levels = {
0: "*",
1: "E",
50: "W",
100: "I",
200: "D",
250: "V"
}
str_level = self.log_levels[level] if level in self.log_levels else "?"

print timestamp, str_level, filename, linenumber, message

level = log_levels[level] if level in log_levels else "?"
def _app_log_response(self, endpoint, data):
if (len(data) < 8):
log.warn("Unable to decode log message (length %d is less than 8)" % len(data))
return

print timestamp, level, filename, linenumber, message
app_uuid = uuid.UUID(bytes=data[0:16])
timestamp, level, msgsize, linenumber = unpack("!IBBH", data[16:24])
filename = data[24:40].decode('utf-8')
message = data[40:40+msgsize].decode('utf-8')

str_level = self.log_levels[level] if level in self.log_levels else "?"

print timestamp, str_level, app_uuid, filename, linenumber, message

def _appbank_status_response(self, endpoint, data):
apps = {}
Expand All @@ -691,11 +761,17 @@ def _appbank_status_response(self, endpoint, data):
offset = 9
for i in xrange(apps_installed):
app = {}
app["id"], app["index"], app["name"], app["company"], app["flags"], app["version"] = \
unpack("!II32s32sIH", data[offset:offset+appinfo_size])
app["name"] = app["name"].replace("\x00", "")
app["company"] = app["company"].replace("\x00", "")
apps["apps"] += [app]
try:
app["id"], app["index"], app["name"], app["company"], app["flags"], app["version"] = \
unpack("!II32s32sIH", data[offset:offset+appinfo_size])
app["name"] = app["name"].replace("\x00", "")
app["company"] = app["company"].replace("\x00", "")
apps["apps"] += [app]
except:
if offset+appinfo_size > len(data):
log.warn("Couldn't load bank %d; remaining data = %s" % (i,repr(data[offset:])))
else:
raise
offset += appinfo_size

return apps
Expand Down Expand Up @@ -805,20 +881,19 @@ def build_tuple(self, key, data_type, data):
# available app message datatypes:
tuple_datatypes = {
"BYTE_ARRAY": b'\x00',
"CSTRIMG": b'\x01',
"CSTRING": b'\x01',
"UINT": b'\x02',
"INT": b'\x03'
}
# first build the message_tuple

# build the message_tuple
app_message_tuple = OrderedDict([
("KEY", key),
("KEY", pack('<L', key)),
("TYPE", tuple_datatypes[data_type]),
("LENGTH", pack('<H', len(data))),
("DATA", data)
])
# handle the little endians
app_message_tuple["KEY"] = app_message_tuple["KEY"][::-1]
app_message_tuple["DATA"] = app_message_tuple["DATA"][::-1]

return app_message_tuple

def build_dict(self, tuple_of_tuples):
Expand Down

0 comments on commit bdde8dd

Please sign in to comment.