libpebble2 0.0.29__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. libpebble2/__init__.py +8 -0
  2. libpebble2/communication/__init__.py +389 -0
  3. libpebble2/communication/transports/__init__.py +64 -0
  4. libpebble2/communication/transports/pulse.py +108 -0
  5. libpebble2/communication/transports/qemu/__init__.py +113 -0
  6. libpebble2/communication/transports/qemu/protocol.py +165 -0
  7. libpebble2/communication/transports/serial.py +58 -0
  8. libpebble2/communication/transports/websocket/__init__.py +74 -0
  9. libpebble2/communication/transports/websocket/protocol.py +160 -0
  10. libpebble2/events/__init__.py +93 -0
  11. libpebble2/events/mixin.py +43 -0
  12. libpebble2/events/threaded.py +84 -0
  13. libpebble2/exceptions.py +70 -0
  14. libpebble2/protocol/__init__.py +16 -0
  15. libpebble2/protocol/appglance.py +66 -0
  16. libpebble2/protocol/appmessage.py +88 -0
  17. libpebble2/protocol/apps.py +84 -0
  18. libpebble2/protocol/audio.py +33 -0
  19. libpebble2/protocol/base/__init__.py +234 -0
  20. libpebble2/protocol/base/types.py +635 -0
  21. libpebble2/protocol/blobdb.py +74 -0
  22. libpebble2/protocol/data_logging.py +86 -0
  23. libpebble2/protocol/legacy2.py +157 -0
  24. libpebble2/protocol/logs.py +69 -0
  25. libpebble2/protocol/meta.py +21 -0
  26. libpebble2/protocol/music.py +109 -0
  27. libpebble2/protocol/phone.py +80 -0
  28. libpebble2/protocol/screenshots.py +38 -0
  29. libpebble2/protocol/system.py +241 -0
  30. libpebble2/protocol/timeline.py +94 -0
  31. libpebble2/protocol/transfers.py +152 -0
  32. libpebble2/protocol/voice.py +160 -0
  33. libpebble2/services/__init__.py +1 -0
  34. libpebble2/services/appglances.py +41 -0
  35. libpebble2/services/appmessage.py +211 -0
  36. libpebble2/services/blobdb.py +164 -0
  37. libpebble2/services/data_logging.py +141 -0
  38. libpebble2/services/getbytes.py +85 -0
  39. libpebble2/services/install.py +161 -0
  40. libpebble2/services/notifications.py +97 -0
  41. libpebble2/services/putbytes.py +105 -0
  42. libpebble2/services/screenshot.py +93 -0
  43. libpebble2/services/voice.py +180 -0
  44. libpebble2/util/__init__.py +1 -0
  45. libpebble2/util/bundle.py +203 -0
  46. libpebble2/util/hardware.py +64 -0
  47. libpebble2/util/stm32_crc.py +43 -0
  48. libpebble2/version.py +4 -0
  49. libpebble2-0.0.29.dist-info/METADATA +69 -0
  50. libpebble2-0.0.29.dist-info/RECORD +54 -0
  51. libpebble2-0.0.29.dist-info/WHEEL +5 -0
  52. libpebble2-0.0.29.dist-info/licenses/LICENSE +21 -0
  53. libpebble2-0.0.29.dist-info/top_level.txt +1 -0
  54. libpebble2-0.0.29.dist-info/zip-safe +1 -0
libpebble2/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ __author__ = 'katharine'
2
+ from .version import __version__, __version_info__
3
+
4
+ import logging
5
+
6
+ from .exceptions import *
7
+
8
+ logging.getLogger('libpebble2').addHandler(logging.NullHandler())
@@ -0,0 +1,389 @@
1
+ from __future__ import absolute_import
2
+
3
+ __author__ = 'katharine'
4
+
5
+ from binascii import hexlify
6
+ from collections import namedtuple
7
+ from enum import Enum
8
+ import logging
9
+ import struct
10
+ import threading
11
+
12
+ from .transports import BaseTransport, MessageTargetWatch
13
+ from libpebble2.events.threaded import ThreadedEventHandler
14
+ from libpebble2.exceptions import PacketDecodeError, ConnectionError, IncompleteMessage
15
+ from libpebble2.protocol.base import PebblePacket, PacketType
16
+ from libpebble2.protocol.system import (PhoneAppVersion, AppVersionResponse, WatchVersion, WatchVersionRequest,
17
+ WatchVersionResponse, WatchModel, ModelRequest, Model)
18
+ from libpebble2.util.hardware import PebbleHardware
19
+
20
+ logger = logging.getLogger("libpebble2.communication")
21
+
22
+ _EventType = Enum('_EventType', ('Watch', 'Transport'))
23
+
24
+ FirmwareVersion = namedtuple('FirmwareVersion', ('major', 'minor', 'patch', 'suffix'))
25
+ """
26
+ Represents a firmware version, in the format ``major.minor.patch-suffix``.
27
+ """
28
+
29
+
30
+ class PebbleConnection(object):
31
+ """
32
+ PebbleConnection represents the connection to a pebble; all interaction with a pebble goes through it.
33
+
34
+ :param transport: The underlying transport layer to communicate with the Pebble.
35
+ :type transport: BaseTransport
36
+ :param log_packet_level: If not None, the log level at which to log decoded messages sent and received.
37
+ :type log_packet_level: int
38
+ :param log_protocol_level: int If not None, the log level at which to log raw messages sent and received.
39
+ :type log_protocol_level: int
40
+ """
41
+ def __init__(self, transport, log_protocol_level=None, log_packet_level=None):
42
+ assert isinstance(transport, BaseTransport)
43
+ self.transport = transport
44
+ self.pending_bytes = b''
45
+ self.event_handler = ThreadedEventHandler()
46
+ self._register_internal_handlers()
47
+ self._watch_info = None
48
+ self._watch_model = None
49
+ self.log_protocol_level = log_protocol_level
50
+ self.log_packet_level = log_packet_level
51
+
52
+ def connect(self):
53
+ """
54
+ Synchronously initialises a connection to the Pebble. Once it returns, a valid connection will be open.
55
+ """
56
+ self.transport.connect()
57
+
58
+ @property
59
+ def connected(self):
60
+ """
61
+ :return: ``True`` if currently connected to a Pebble; otherwise ``False``.
62
+ """
63
+ return self.transport.connected
64
+
65
+ def pump_reader(self):
66
+ """
67
+ Synchronously reads one message from the watch, blocking until a message is available.
68
+ All events caused by the message read will be processed before this method returns.
69
+
70
+ .. note::
71
+ You usually don't need to invoke this method manually; instead, see :meth:`run_sync` and :meth:`run_async`.
72
+ """
73
+ origin, message = self.transport.read_packet()
74
+ if isinstance(origin, MessageTargetWatch):
75
+ self._handle_watch_message(message)
76
+ else:
77
+ self._broadcast_transport_message(origin, message)
78
+
79
+ def run_sync(self):
80
+ """
81
+ Runs the message loop until the Pebble disconnects. This method will block until the watch disconnects or
82
+ a fatal error occurs.
83
+
84
+ For alternatives that don't block forever, see :meth:`pump_reader` and :meth:`run_async`.
85
+ """
86
+ while self.connected:
87
+ try:
88
+ self.pump_reader()
89
+ except PacketDecodeError as e:
90
+ logger.warning("Packet decode failed: %s", e)
91
+ except ConnectionError:
92
+ break
93
+
94
+ def run_async(self):
95
+ """
96
+ Spawns a new thread that runs the message loop until the Pebble disconnects.
97
+ ``run_async`` will call :meth:`fetch_watch_info` on your behalf, and block until it receives a response.
98
+ """
99
+ thread = threading.Thread(target=self.run_sync)
100
+ thread.daemon = True
101
+ thread.name = "PebbleConnection"
102
+ thread.start()
103
+ self.fetch_watch_info()
104
+
105
+ def _handle_watch_message(self, message):
106
+ """
107
+ Processes a binary message received from the watch and broadcasts the relevant events.
108
+
109
+ :param message: A raw message from the watch, without any transport framing.
110
+ :type message: bytes
111
+ """
112
+ if self.log_protocol_level is not None:
113
+ logger.log(self.log_protocol_level, "<- %s", hexlify(message).decode())
114
+ message = self.pending_bytes + message
115
+
116
+ while len(message) >= 4:
117
+ try:
118
+ packet, length = PebblePacket.parse_message(message)
119
+ except IncompleteMessage:
120
+ self.pending_bytes = message
121
+ break
122
+ except:
123
+ # At this point we've failed to deconstruct the message via normal means, but we don't want to end
124
+ # up permanently desynced (because we wiped a partial message), nor do we want to get stuck (because
125
+ # we didn't wipe anything). We therefore parse the packet length manually and skip ahead that far.
126
+ # If the expected length is 0, we wipe everything to ensure forward motion (but we are quite probably
127
+ # screwed).
128
+ expected_length, = struct.unpack('!H', message[:2])
129
+ if expected_length == 0:
130
+ self.pending_bytes = b''
131
+ else:
132
+ self.pending_bytes = message[expected_length + 4:]
133
+ raise
134
+
135
+ self.event_handler.broadcast_event("raw_inbound", message[:length])
136
+ if self.log_packet_level is not None:
137
+ logger.log(self.log_packet_level, "<- %s", packet)
138
+ message = message[length:]
139
+ self.event_handler.broadcast_event((_EventType.Watch, type(packet)), packet)
140
+ if length == 0:
141
+ break
142
+ self.pending_bytes = message
143
+
144
+ def _broadcast_transport_message(self, origin, message):
145
+ """
146
+ Broadcasts an event originating from a transport that does not represent a message from the Pebble.
147
+
148
+ :param origin: The type of transport responsible for the message.
149
+ :type origin: .MessageTarget
150
+ :param message: The message from the transport
151
+ """
152
+ self.event_handler.broadcast_event((_EventType.Transport, type(origin), type(message)), message)
153
+
154
+ def register_transport_endpoint(self, origin, message_type, handler):
155
+ """
156
+ Register a handler for a message received from a transport that does not indicate a message from the connected
157
+ Pebble.
158
+
159
+ :param origin: The type of :class:`.MessageTarget` that triggers the message
160
+ :param message_type: The class of the message that is expected.
161
+ :param handler: A callback to be called when a message is received.
162
+ :type handler: callable
163
+ :return: A handle that can be passed to :meth:`unregister_endpoint` to remove the handler.
164
+ """
165
+ return self.event_handler.register_handler((_EventType.Transport, origin, message_type), handler)
166
+
167
+ def register_endpoint(self, endpoint, handler):
168
+ """
169
+ Register a handler for a message received from the Pebble.
170
+
171
+ :param endpoint: The type of :class:`.PebblePacket` that is being listened for.
172
+ :type endpoint: .PacketType
173
+ :param handler: A callback to be called when a message is received.
174
+ :type handler: callable
175
+ :return: A handle that can be passed to :meth:`unregister_endpoint` to remove the handler.
176
+ """
177
+ return self.event_handler.register_handler((_EventType.Watch, endpoint), handler)
178
+
179
+ def register_raw_outbound_handler(self, handler):
180
+ """
181
+ Register a handler for all outgoing messages to be sent to the Pebble. Transport framing is not included.
182
+
183
+ :param handler: A callback to be called when any message is received.
184
+ :type handler: callable
185
+ :return: A handle that can be passed to :meth:`unregister_endpoint` to remove the handler.
186
+ """
187
+ return self.event_handler.register_handler("raw_outbound", handler)
188
+
189
+ def register_raw_inbound_handler(self, handler):
190
+ """
191
+ Register a handler for all outgoing messages received from the Pebble. Transport framing is not included.
192
+ In most cases you should not need to use this; consider using :meth:`register_endpoint` instead.
193
+
194
+ :param handler: A callback to be called when any message is received.
195
+ :type handler: callable
196
+ :return: A handle that can be passed to :meth:`unregister_endpoint` to remove the handler.
197
+ """
198
+ return self.event_handler.register_handler("raw_inbound", handler)
199
+
200
+ def unregister_endpoint(self, handle):
201
+ """
202
+ Removes a handler registered by :meth:`register_transport_endpoint`, :meth:`register_endpoint`,
203
+ :meth:`register_raw_outbound_handler` or :meth:`register_raw_inbound_handler`.
204
+
205
+ :param handle: A handle returned by the register call to be undone.
206
+ """
207
+ return self.event_handler.unregister_handler(handle)
208
+
209
+ def read_from_endpoint(self, endpoint, timeout=15):
210
+ """
211
+ Blocking read from an endpoint. Will block until a message is received, or it times out. Also see
212
+ :meth:`get_endpoint_queue` if you are considering calling this in a loop.
213
+
214
+ .. warning::
215
+ Avoid calling this method from an endpoint callback; doing so is likely to lead to deadlock.
216
+
217
+ .. note::
218
+ If you're reading a response to a message you just sent, :meth:`send_and_read` might be more appropriate.
219
+
220
+ :param endpoint: The endpoint to read from.
221
+ :type endpoint: .PacketType
222
+ :param timeout: The maximum time to wait before raising :exc:`.TimeoutError`.
223
+ :return: The message read from the endpoint; of the same type as passed to ``endpoint``.
224
+ """
225
+ return self.event_handler.wait_for_event((_EventType.Watch, endpoint), timeout=timeout)
226
+
227
+ def get_endpoint_queue(self, endpoint):
228
+ """
229
+ Returns a :class:`.BaseEventQueue` from which messages to the given ``endpoint`` can be read.
230
+
231
+ This is useful if you need to make sure that you receive all messages to an endpoint, without risking
232
+ dropping some due to time in between :meth:`read_from_endpoint` calls.
233
+
234
+ :param endpoint: The endpoint to read from
235
+ :type endpoint: .PacketType
236
+ :return:
237
+ """
238
+ return self.event_handler.queue_events((_EventType.Watch, endpoint))
239
+
240
+ def read_transport_message(self, origin, message_type, timeout=15):
241
+ """
242
+ Blocking read of a transport message that does not indicate a message from the Pebble.
243
+ Will block until a message is received, or it times out.
244
+
245
+ .. warning::
246
+ Avoid calling this method from an endpoint callback; doing so is likely to lead to deadlock.
247
+
248
+ :param origin: The type of :class:`.MessageTarget` that triggers the message.
249
+ :param message_type: The class of the message to read from the transport.
250
+ :param timeout: The maximum time to wait before raising :exc:`.TimeoutError`.
251
+ :return: The object read from the transport; of the same type as passed to ``message_type``.
252
+ """
253
+ return self.event_handler.wait_for_event((_EventType.Transport, origin, message_type), timeout=timeout)
254
+
255
+ def send_packet(self, packet):
256
+ """
257
+ Sends a message to the Pebble.
258
+
259
+ :param packet: The message to send.
260
+ :type packet: .PebblePacket
261
+ """
262
+ if self.log_packet_level:
263
+ logger.log(self.log_packet_level, "-> %s", packet)
264
+ serialised = packet.serialise_packet()
265
+ self.event_handler.broadcast_event("raw_outbound", serialised)
266
+ self.send_raw(serialised)
267
+
268
+ def send_and_read(self, packet, endpoint, timeout=15):
269
+ """
270
+ Sends a packet, then returns the next response received from that endpoint. This method sets up a listener
271
+ before it actually sends the message, avoiding a potential race.
272
+
273
+ .. warning::
274
+ Avoid calling this method from an endpoint callback; doing so is likely to lead to deadlock.
275
+
276
+ :param packet: The message to send.
277
+ :type packet: .PebblePacket
278
+ :param endpoint: The endpoint to read from
279
+ :type endpoint: .PacketType
280
+ :param timeout: The maximum time to wait before raising :exc:`.TimeoutError`.
281
+ :return: The message read from the endpoint; of the same type as passed to ``endpoint``.
282
+ """
283
+ queue = self.get_endpoint_queue(endpoint)
284
+ self.send_packet(packet)
285
+ try:
286
+ return queue.get(timeout=timeout)
287
+ finally:
288
+ queue.close()
289
+
290
+ def send_raw(self, message):
291
+ """
292
+ Sends a raw binary message to the Pebble. No processing will be applied, but any transport framing should be
293
+ omitted.
294
+
295
+ :param message: The message to send to the pebble.
296
+ :type message: bytes
297
+ """
298
+ if self.log_protocol_level:
299
+ logger.log(self.log_protocol_level, "-> %s", hexlify(message).decode())
300
+ self.transport.send_packet(message)
301
+
302
+ def _register_internal_handlers(self):
303
+ if self.transport.must_initialise:
304
+ self.register_endpoint(PhoneAppVersion, self._app_version_response)
305
+
306
+ def _app_version_response(self, packet):
307
+ packet = PhoneAppVersion(message=AppVersionResponse(
308
+ protocol_version=0xFFFFFFFF,
309
+ session_caps=0x80000000,
310
+ platform_flags=50,
311
+ response_version=2,
312
+ major_version=3,
313
+ minor_version=0,
314
+ bugfix_version=0,
315
+ protocol_caps=0xFFFFFFFFFFFFFFFF
316
+ ))
317
+ self.send_packet(packet)
318
+
319
+ def fetch_watch_info(self):
320
+ """
321
+ This method should be called before accessing :attr:`watch_info`, :attr:`firmware_version`
322
+ or :attr:`watch_platform`. Blocks until it has fetched the required information.
323
+ """
324
+ self._watch_info = self.send_and_read(WatchVersion(data=WatchVersionRequest()), WatchVersion).data
325
+
326
+ @property
327
+ def watch_info(self):
328
+ """
329
+ Returns information on the connected Pebble, including its firmware version, language, capabilities, etc.
330
+
331
+ .. note:
332
+ This is a blocking call if :meth:`fetch_watch_info` has not yet been called, which could lead to deadlock
333
+ if called in an endpoint callback.
334
+
335
+ :rtype: .WatchVersionResponse
336
+ """
337
+ if self._watch_info is None:
338
+ self.fetch_watch_info()
339
+ return self._watch_info
340
+
341
+ @property
342
+ def firmware_version(self):
343
+ """
344
+ Provides information on the connected Pebble, including its firmware version, language, capabilities, etc.
345
+
346
+ .. note:
347
+ This is a blocking call if :meth:`fetch_watch_info` has not yet been called, which could lead to deadlock
348
+ if called in an endpoint callback.
349
+
350
+ :rtype: .WatchVersionResponse
351
+ """
352
+ version = self.watch_info.running.version_tag[1:]
353
+ parts = version.split('-', 1)
354
+ points = [int(x) for x in parts[0].split('.')]
355
+ while len(points) < 3:
356
+ points.append(0)
357
+ if len(parts) == 2:
358
+ suffix = parts[1]
359
+ else:
360
+ suffix = ''
361
+ return FirmwareVersion(*(points + [suffix]))
362
+
363
+ @property
364
+ def watch_model(self):
365
+ """
366
+
367
+ :return: The model of the watch.
368
+ :rtype: ~libpebble2.protocol.system.Model
369
+ """
370
+ if self._watch_model is None:
371
+ info_bytes = self.send_and_read(WatchModel(data=ModelRequest()), WatchModel).data.data
372
+ if len(info_bytes) == 4:
373
+ self._watch_model, = struct.unpack('>I', info_bytes)
374
+ else:
375
+ self._watch_model = Model.Unknown
376
+ return self._watch_model
377
+
378
+ @property
379
+ def watch_platform(self):
380
+ """
381
+ A string naming the platform of the watch ('aplite', 'basalt', 'chalk', or 'unknown').
382
+
383
+ .. note:
384
+ This is a blocking call if :meth:`fetch_watch_info` has not yet been called, which could lead to deadlock
385
+ if called in an endpoint callback.
386
+
387
+ :rtype: str
388
+ """
389
+ return PebbleHardware.hardware_platform(self.watch_info.running.hardware_platform)
@@ -0,0 +1,64 @@
1
+ __author__ = 'katharine'
2
+
3
+ from six import with_metaclass
4
+
5
+ from abc import ABCMeta, abstractmethod, abstractproperty
6
+
7
+
8
+ class MessageTarget(object):
9
+ def __repr__(self):
10
+ return type(self).__name__ + "()"
11
+
12
+
13
+ class MessageTargetWatch(MessageTarget):
14
+ pass
15
+
16
+
17
+ class BaseTransport(with_metaclass(ABCMeta)):
18
+ @abstractproperty
19
+ def must_initialise(self):
20
+ """
21
+ :return: ``True`` if libpebble2 is responsible for negotiating the connection; otherwise ``False``.
22
+ """
23
+ pass
24
+
25
+ @abstractproperty
26
+ def connected(self):
27
+ """
28
+ :return: ``True`` if the transport is currently connected; otherwise ``False``.
29
+ """
30
+ pass
31
+
32
+ @abstractmethod
33
+ def connect(self):
34
+ """
35
+ Synchronously connect to the Pebble. Once this method returns, libpebble2 should be able to safely send
36
+ messages to the connected Pebble.
37
+
38
+ Ordinarily, this method should only be called by :class:`.PebbleConnection`.
39
+ """
40
+ pass
41
+
42
+ @abstractmethod
43
+ def read_packet(self):
44
+ """
45
+ Synchronously read a message. This message could be from the Pebble(in which case it will be a
46
+ :class:`PebblePacket`), or it could be from the transport, in which case the result is transport-defined.
47
+ The origin of the result is indicated by the returned :class:`MessageTarget`.
48
+
49
+ :return: (:class:`MessageTarget`, :class:`libpebble2.protocol.base.PebblePacket`)
50
+ """
51
+ pass
52
+
53
+ @abstractmethod
54
+ def send_packet(self, message, target=MessageTargetWatch()):
55
+ """
56
+ Send a message. This message could be to the Pebble (in which case it must be a :class:`PebblePacket`), or
57
+ to the transport (in which case the message type is transport-defined).
58
+
59
+ :param message: Message to send.
60
+ :type message: PebblePacket
61
+ :param target: Target for the message
62
+ :type target: MessageTarget
63
+ """
64
+ pass
@@ -0,0 +1,108 @@
1
+ from __future__ import absolute_import
2
+ __author__ = 'Liam McLoughlin'
3
+
4
+ import time
5
+ import struct
6
+
7
+ try:
8
+ from pebble import pulse2
9
+ except ImportError:
10
+ pass
11
+
12
+ from . import BaseTransport, MessageTargetWatch
13
+ from libpebble2.exceptions import ConnectionError, PebbleError
14
+
15
+
16
+ class PULSETransport(BaseTransport):
17
+ """
18
+ Represents a direct connection to a physical/virtual Pebble uses the PULSEv2 interface.
19
+ This transport expects to be given a PULSE2 Link object.
20
+
21
+ :param connection: A PULSE2 Link object to tunnel Pebble Protocol over.
22
+ :type link: pulse2.link.Link
23
+ """
24
+ must_initialise = True
25
+
26
+ PPOPULSE_PORT = 0x3e22
27
+
28
+ OPCODE_PROTOCOL_DATA = 0x1
29
+ OPCODE_PROTOCOL_OPEN = 0x2
30
+ OPCODE_PROTOCOL_CLOSE = 0x3
31
+
32
+ def __init__(self, link):
33
+ self.link = link
34
+ self.connection = None
35
+ self.buffer = b''
36
+
37
+ @staticmethod
38
+ def _chunks(list_items, chunk_length):
39
+ for i in range(0, len(list_items), chunk_length):
40
+ yield list_items[i:i+chunk_length]
41
+
42
+ def connect(self):
43
+ self.connection = self.link.open_socket('reliable', self.PPOPULSE_PORT)
44
+ if not self.connection:
45
+ raise ConnectionError('Failed to open PPoPULSE socket')
46
+
47
+ self._send_with_opcode(self.OPCODE_PROTOCOL_OPEN)
48
+ start_time = time.time()
49
+ while time.time() < start_time + 10.0:
50
+ opcode, _ = self._recv_with_opcode()
51
+ if opcode == self.OPCODE_PROTOCOL_OPEN:
52
+ break
53
+ else:
54
+ raise ConnectionError('Timeout waiting for PPoPULSE open ACK')
55
+
56
+ def disconnect(self):
57
+ if self.connected:
58
+ try:
59
+ self._send_with_opcode(self.OPCODE_PROTOCOL_CLOSE)
60
+ except pulse2.exceptions.SocketClosed:
61
+ pass
62
+ self.connection.close()
63
+ self.connection = None
64
+
65
+ @property
66
+ def connected(self):
67
+ return self.connection is not None
68
+
69
+ def read_packet(self):
70
+ while self.connected:
71
+ if len(self.buffer) >= 2:
72
+ length, = struct.unpack('!H', self.buffer[:2])
73
+ length += 4
74
+
75
+ if len(self.buffer) >= length:
76
+ msg_data = self.buffer[:length]
77
+ self.buffer = self.buffer[length:]
78
+
79
+ return MessageTargetWatch(), msg_data
80
+
81
+ opcode, data = self._recv_with_opcode()
82
+ if opcode == self.OPCODE_PROTOCOL_DATA:
83
+ self.buffer += data
84
+
85
+ def send_packet(self, message, target=MessageTargetWatch()):
86
+ assert isinstance(target, MessageTargetWatch)
87
+ for chunk in self._chunks(message, self.connection.mtu - 1):
88
+ self._send_with_opcode(self.OPCODE_PROTOCOL_DATA, chunk)
89
+
90
+ def _recv_with_opcode(self):
91
+ try:
92
+ packet = self.connection.receive(block=True)
93
+ except (AttributeError, pulse2.exceptions.SocketClosed):
94
+ self.connection = None
95
+ raise ConnectionError('PULSE transport closed')
96
+
97
+ assert len(packet) >= 1
98
+ opcode = packet[0]
99
+ data = packet[1:]
100
+ return opcode, data
101
+
102
+ def _send_with_opcode(self, opcode, body=None):
103
+ assert self.connected
104
+
105
+ data = bytes([opcode])
106
+ if body:
107
+ data += body
108
+ self.connection.send(data)
@@ -0,0 +1,113 @@
1
+ from __future__ import absolute_import
2
+ __author__ = 'katharine'
3
+
4
+ import socket
5
+
6
+ from .. import BaseTransport, MessageTarget, MessageTargetWatch
7
+ from .protocol import QemuPacket, QemuInboundPacket, QemuSPP, QemuRawPacket, HEADER_SIGNATURE, FOOTER_SIGNATURE
8
+ from libpebble2.exceptions import ConnectionError
9
+ from libpebble2.protocol.base.types import PacketDecodeError
10
+
11
+
12
+ class MessageTargetQemu(MessageTarget):
13
+ """
14
+ Indicates that a message is directed at QEMU, rather than the firmware running on it. If ``raw`` is ``True``,
15
+ the message should be a binary message (without framing), with QEMU protocol indicated by ``protocol``.
16
+ Otherwise, the message should be a :class:`.PebblePacket` from :mod:`.protocol`.
17
+
18
+ :param protocol: The protocol to send to, if sending a ``raw`` message
19
+ :type protocol: :any:`int`
20
+ :param raw: If ``True``, the message is pre-serialised and will be sent as-is after adding framing.
21
+ :type raw: :any:`bool`
22
+ """
23
+ def __init__(self, protocol=None, raw=False):
24
+ self.protocol = protocol
25
+ self.raw = raw
26
+
27
+
28
+ class QemuTransport(BaseTransport):
29
+ """
30
+ Represents a connection to a `Pebble QEMU <https://github.com/pebble/qemu>`_ instance.
31
+
32
+ :param host: The host on which the QEMU instance is running.
33
+ :type host: str
34
+ :param port: The port on which the QEMU instance has exposed its Pebble QEMU Protocol port.
35
+ :type port: int
36
+ """
37
+ #: Number of bytes read from the socket at a time.
38
+ BUFFER_SIZE = 2048
39
+ must_initialise = True
40
+
41
+ def __init__(self, host='127.0.0.1', port=12344, socket = None):
42
+ self.host = host
43
+ self.port = port
44
+ self.socket = socket
45
+ self.assembled_data = b''
46
+ self._connected = socket is not None
47
+
48
+ def connect(self):
49
+ try:
50
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
51
+ self.socket.connect((self.host, self.port))
52
+ except socket.error as e:
53
+ raise ConnectionError(str(e))
54
+ self._connected = True
55
+
56
+ @property
57
+ def connected(self):
58
+ return self.socket is not None and self._connected
59
+
60
+ def read_packet(self):
61
+ while True:
62
+ if len(self.assembled_data) > 0:
63
+ try:
64
+ packet, length = QemuInboundPacket.parse(self.assembled_data)
65
+ except PacketDecodeError:
66
+ pass
67
+ else:
68
+ self.assembled_data = self.assembled_data[length:]
69
+ if packet.signature == HEADER_SIGNATURE and packet.footer == FOOTER_SIGNATURE:
70
+ if isinstance(packet.data, QemuSPP):
71
+ return MessageTargetWatch(), packet.data.payload
72
+ else:
73
+ return MessageTargetQemu(packet.protocol), packet.data
74
+ else:
75
+ raise PacketDecodeError("QemuTransport: signature mismatch ({:x} = {:x}, {:x} = {:x})".format(
76
+ packet.signature,
77
+ HEADER_SIGNATURE,
78
+ packet.footer,
79
+ FOOTER_SIGNATURE,
80
+ ))
81
+ try:
82
+ received = self.socket.recv(self.BUFFER_SIZE)
83
+ if len(received) == 0:
84
+ self._connected = False
85
+ raise ConnectionError("Disconnected.")
86
+ self.assembled_data += received
87
+ except socket.error:
88
+ self._connected = False
89
+ raise ConnectionError("Disconnected.")
90
+
91
+ def send_packet(self, message, target=MessageTargetWatch()):
92
+ try:
93
+ if isinstance(target, MessageTargetWatch):
94
+ start_idx = 0
95
+ bytes_left = len(message)
96
+ while bytes_left:
97
+ bytes_to_send = bytes_left
98
+ if bytes_to_send > self.BUFFER_SIZE:
99
+ bytes_to_send = self.BUFFER_SIZE
100
+ chunk = message[start_idx:start_idx+bytes_to_send]
101
+ self.socket.send(QemuPacket(data=QemuSPP(payload=chunk)).serialise())
102
+ bytes_left -= bytes_to_send
103
+ start_idx += bytes_to_send
104
+ elif isinstance(target, MessageTargetQemu):
105
+ if not target.raw:
106
+ self.socket.send(QemuPacket(data=message).serialise())
107
+ else:
108
+ self.socket.send(QemuRawPacket(protocol=target.protocol, data=message).serialise())
109
+ else:
110
+ assert False
111
+ except socket.error as e:
112
+ self._connected = False
113
+ raise ConnectionError(str(e))