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.
- libpebble2/__init__.py +8 -0
- libpebble2/communication/__init__.py +389 -0
- libpebble2/communication/transports/__init__.py +64 -0
- libpebble2/communication/transports/pulse.py +108 -0
- libpebble2/communication/transports/qemu/__init__.py +113 -0
- libpebble2/communication/transports/qemu/protocol.py +165 -0
- libpebble2/communication/transports/serial.py +58 -0
- libpebble2/communication/transports/websocket/__init__.py +74 -0
- libpebble2/communication/transports/websocket/protocol.py +160 -0
- libpebble2/events/__init__.py +93 -0
- libpebble2/events/mixin.py +43 -0
- libpebble2/events/threaded.py +84 -0
- libpebble2/exceptions.py +70 -0
- libpebble2/protocol/__init__.py +16 -0
- libpebble2/protocol/appglance.py +66 -0
- libpebble2/protocol/appmessage.py +88 -0
- libpebble2/protocol/apps.py +84 -0
- libpebble2/protocol/audio.py +33 -0
- libpebble2/protocol/base/__init__.py +234 -0
- libpebble2/protocol/base/types.py +635 -0
- libpebble2/protocol/blobdb.py +74 -0
- libpebble2/protocol/data_logging.py +86 -0
- libpebble2/protocol/legacy2.py +157 -0
- libpebble2/protocol/logs.py +69 -0
- libpebble2/protocol/meta.py +21 -0
- libpebble2/protocol/music.py +109 -0
- libpebble2/protocol/phone.py +80 -0
- libpebble2/protocol/screenshots.py +38 -0
- libpebble2/protocol/system.py +241 -0
- libpebble2/protocol/timeline.py +94 -0
- libpebble2/protocol/transfers.py +152 -0
- libpebble2/protocol/voice.py +160 -0
- libpebble2/services/__init__.py +1 -0
- libpebble2/services/appglances.py +41 -0
- libpebble2/services/appmessage.py +211 -0
- libpebble2/services/blobdb.py +164 -0
- libpebble2/services/data_logging.py +141 -0
- libpebble2/services/getbytes.py +85 -0
- libpebble2/services/install.py +161 -0
- libpebble2/services/notifications.py +97 -0
- libpebble2/services/putbytes.py +105 -0
- libpebble2/services/screenshot.py +93 -0
- libpebble2/services/voice.py +180 -0
- libpebble2/util/__init__.py +1 -0
- libpebble2/util/bundle.py +203 -0
- libpebble2/util/hardware.py +64 -0
- libpebble2/util/stm32_crc.py +43 -0
- libpebble2/version.py +4 -0
- libpebble2-0.0.29.dist-info/METADATA +69 -0
- libpebble2-0.0.29.dist-info/RECORD +54 -0
- libpebble2-0.0.29.dist-info/WHEEL +5 -0
- libpebble2-0.0.29.dist-info/licenses/LICENSE +21 -0
- libpebble2-0.0.29.dist-info/top_level.txt +1 -0
- libpebble2-0.0.29.dist-info/zip-safe +1 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
__author__ = 'katharine'
|
|
3
|
+
|
|
4
|
+
from enum import IntEnum
|
|
5
|
+
|
|
6
|
+
from libpebble2.protocol.base import PebblePacket
|
|
7
|
+
from libpebble2.protocol.base.types import *
|
|
8
|
+
|
|
9
|
+
HEADER_SIGNATURE = 0xFEED
|
|
10
|
+
FOOTER_SIGNATURE = 0xBEEF
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class QemuSPP(PebblePacket):
|
|
14
|
+
payload = BinaryArray()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class QemuTap(PebblePacket):
|
|
18
|
+
class Axis(IntEnum):
|
|
19
|
+
X = 0
|
|
20
|
+
Y = 1
|
|
21
|
+
Z = 2
|
|
22
|
+
|
|
23
|
+
axis = Uint8()
|
|
24
|
+
direction = Int8()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class QemuBluetoothConnection(PebblePacket):
|
|
28
|
+
connected = Boolean()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class QemuCompass(PebblePacket):
|
|
32
|
+
class Calibration(IntEnum):
|
|
33
|
+
Uncalibrated = 0
|
|
34
|
+
Refining = 1
|
|
35
|
+
Complete = 2
|
|
36
|
+
|
|
37
|
+
heading = Uint32()
|
|
38
|
+
calibrated = Uint8()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class QemuBattery(PebblePacket):
|
|
42
|
+
percent = Uint8()
|
|
43
|
+
charging = Boolean()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class QemuAccelSample(PebblePacket):
|
|
47
|
+
x = Int16()
|
|
48
|
+
y = Int16()
|
|
49
|
+
z = Int16()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class QemuAccel(PebblePacket):
|
|
53
|
+
count = Uint8()
|
|
54
|
+
samples = FixedList(QemuAccelSample, count=count)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class QemuAccelResponse(PebblePacket):
|
|
58
|
+
remaining_space = Uint16()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class QemuVibration(PebblePacket):
|
|
62
|
+
state = Optional(Boolean())
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class QemuButton(PebblePacket):
|
|
66
|
+
class Button(IntEnum):
|
|
67
|
+
Back = 1
|
|
68
|
+
Up = 2
|
|
69
|
+
Select = 4
|
|
70
|
+
Down = 8
|
|
71
|
+
|
|
72
|
+
state = Uint8()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class QemuTimeFormat(PebblePacket):
|
|
76
|
+
is_24_hour = Boolean()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class QemuTimelinePeek(PebblePacket):
|
|
80
|
+
enabled = Boolean()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class QemuContentSize(PebblePacket):
|
|
84
|
+
class ContentSize(IntEnum):
|
|
85
|
+
Small = 0
|
|
86
|
+
Medium = 1
|
|
87
|
+
Large = 2
|
|
88
|
+
ExtraLarge = 3
|
|
89
|
+
|
|
90
|
+
size = Uint8()
|
|
91
|
+
|
|
92
|
+
class QemuRebbleTestListRequest(PebblePacket):
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class QemuRebbleTestListResponse(PebblePacket):
|
|
97
|
+
id = Uint16()
|
|
98
|
+
is_last_test = Uint8()
|
|
99
|
+
name_len = Uint8()
|
|
100
|
+
name = BinaryArray(length = name_len)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class QemuRebbleTestRunRequest(PebblePacket):
|
|
104
|
+
id = Uint16()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class QemuRebbleTestComplete(PebblePacket):
|
|
108
|
+
passed = Uint8()
|
|
109
|
+
artifact = Uint32()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class QemuRebbleTestAlive(PebblePacket):
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class QemuRebbleTest(PebblePacket):
|
|
117
|
+
opcode = Uint16()
|
|
118
|
+
payload = Union(opcode, {
|
|
119
|
+
0x0000: QemuRebbleTestListRequest,
|
|
120
|
+
0x0001: QemuRebbleTestRunRequest,
|
|
121
|
+
0x8000: QemuRebbleTestListResponse,
|
|
122
|
+
0x8001: QemuRebbleTestComplete,
|
|
123
|
+
0xFFFF: QemuRebbleTestAlive
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class QemuPacket(PebblePacket):
|
|
128
|
+
signature = Uint16(default=HEADER_SIGNATURE)
|
|
129
|
+
protocol = Uint16()
|
|
130
|
+
length = Uint16()
|
|
131
|
+
data = Union(protocol, {
|
|
132
|
+
1: QemuSPP,
|
|
133
|
+
2: QemuTap,
|
|
134
|
+
3: QemuBluetoothConnection,
|
|
135
|
+
4: QemuCompass,
|
|
136
|
+
5: QemuBattery,
|
|
137
|
+
6: QemuAccel,
|
|
138
|
+
8: QemuButton,
|
|
139
|
+
9: QemuTimeFormat,
|
|
140
|
+
10: QemuTimelinePeek,
|
|
141
|
+
11: QemuContentSize,
|
|
142
|
+
100: QemuRebbleTest,
|
|
143
|
+
}, length=length)
|
|
144
|
+
footer = Uint16(default=FOOTER_SIGNATURE)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class QemuInboundPacket(PebblePacket):
|
|
148
|
+
signature = Uint16(default=HEADER_SIGNATURE)
|
|
149
|
+
protocol = Uint16()
|
|
150
|
+
length = Uint16()
|
|
151
|
+
data = Union(protocol, {
|
|
152
|
+
1: QemuSPP,
|
|
153
|
+
6: QemuAccelResponse,
|
|
154
|
+
7: QemuVibration,
|
|
155
|
+
100: QemuRebbleTest,
|
|
156
|
+
}, length=length)
|
|
157
|
+
footer = Uint16(default=FOOTER_SIGNATURE)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class QemuRawPacket(PebblePacket):
|
|
161
|
+
signature = Uint16(default=HEADER_SIGNATURE)
|
|
162
|
+
protocol = Uint16()
|
|
163
|
+
length = Uint16()
|
|
164
|
+
data = BinaryArray(length=length)
|
|
165
|
+
footer = Uint16(default=FOOTER_SIGNATURE)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
__author__ = 'katharine'
|
|
3
|
+
|
|
4
|
+
import errno
|
|
5
|
+
import serial
|
|
6
|
+
import struct
|
|
7
|
+
|
|
8
|
+
from . import BaseTransport, MessageTargetWatch
|
|
9
|
+
from libpebble2.exceptions import ConnectionError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SerialTransport(BaseTransport):
|
|
13
|
+
"""
|
|
14
|
+
Represents a direct connection to a physical Pebble paired to the computer via Bluetooth serial.
|
|
15
|
+
This transport expects to be given a device file over which it can communicate with the watch via Bluetooth.
|
|
16
|
+
|
|
17
|
+
.. warning::
|
|
18
|
+
Using this transport may cause occasional kernel panics on some versions of OS X.
|
|
19
|
+
|
|
20
|
+
:param device: The path to the device file (on OS X, often of the form ``/dev/cu.PebbleTimeXXXX-SerialPo`` or
|
|
21
|
+
``/dev/cu.PebbleXXXX-SerialPortSe``).
|
|
22
|
+
:type device: str
|
|
23
|
+
"""
|
|
24
|
+
must_initialise = True
|
|
25
|
+
|
|
26
|
+
def __init__(self, device):
|
|
27
|
+
self.device = device
|
|
28
|
+
self.connection = None
|
|
29
|
+
|
|
30
|
+
def connect(self):
|
|
31
|
+
try:
|
|
32
|
+
self.connection = serial.Serial(self.device, 115200)
|
|
33
|
+
except OSError as e:
|
|
34
|
+
if e.errno == errno.EBUSY:
|
|
35
|
+
raise ConnectionError("Could not connect to Pebble.")
|
|
36
|
+
else:
|
|
37
|
+
raise
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def connected(self):
|
|
41
|
+
return self.connection is not None and self.connection.isOpen()
|
|
42
|
+
|
|
43
|
+
def read_packet(self):
|
|
44
|
+
try:
|
|
45
|
+
data = self.connection.read(2)
|
|
46
|
+
except serial.SerialException:
|
|
47
|
+
self.connection.close()
|
|
48
|
+
raise ConnectionError("Disconnected from watch.")
|
|
49
|
+
if len(data) < 2:
|
|
50
|
+
raise ConnectionError("Got malformed packet.")
|
|
51
|
+
|
|
52
|
+
length, = struct.unpack('!H', data)
|
|
53
|
+
data += self.connection.read(length + 2)
|
|
54
|
+
return MessageTargetWatch(), data
|
|
55
|
+
|
|
56
|
+
def send_packet(self, message, target=MessageTargetWatch()):
|
|
57
|
+
assert isinstance(target, MessageTargetWatch)
|
|
58
|
+
self.connection.write(message)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
|
|
3
|
+
__author__ = 'katharine'
|
|
4
|
+
|
|
5
|
+
import socket
|
|
6
|
+
import struct
|
|
7
|
+
import websocket
|
|
8
|
+
|
|
9
|
+
from .. import BaseTransport, MessageTarget, MessageTargetWatch
|
|
10
|
+
from .protocol import WebSocketRelayToWatch, WebSocketRelayFromWatch, endpoints, from_watch
|
|
11
|
+
from libpebble2.exceptions import ConnectionError, PebbleError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MessageTargetPhone(MessageTarget):
|
|
15
|
+
"""
|
|
16
|
+
Indicates that the message is directed at a connected phone running the Pebble mobile app. For this purpose,
|
|
17
|
+
`pypkjs <https://github.com/pebble/pypkjs>`_ counts as a phone.
|
|
18
|
+
"""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WebsocketTransport(BaseTransport):
|
|
23
|
+
"""
|
|
24
|
+
Represents a connection via WebSocket to a phone running the Pebble mobile app, which is in turn connected
|
|
25
|
+
to a Pebble over Bluetooth.
|
|
26
|
+
|
|
27
|
+
:param url: The WebSocket URL to connect to, in standard format (e.g. ``ws://localhost:9000/``)
|
|
28
|
+
"""
|
|
29
|
+
must_initialise = False
|
|
30
|
+
|
|
31
|
+
def __init__(self, url):
|
|
32
|
+
self.url = url
|
|
33
|
+
""":type: str"""
|
|
34
|
+
self.ws = None
|
|
35
|
+
""":type: websocket.WebSocket"""
|
|
36
|
+
|
|
37
|
+
def connect(self):
|
|
38
|
+
try:
|
|
39
|
+
self.ws = websocket.create_connection(self.url)
|
|
40
|
+
except (websocket.WebSocketException, socket.error) as e:
|
|
41
|
+
raise ConnectionError(str(e))
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def connected(self):
|
|
45
|
+
return self.ws is not None and self.ws.connected
|
|
46
|
+
|
|
47
|
+
def send_packet(self, message, target=MessageTargetWatch()):
|
|
48
|
+
handlers = {
|
|
49
|
+
MessageTargetWatch: self._send_to_watch,
|
|
50
|
+
MessageTargetPhone: self._send_to_phone,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
handlers[type(target)](message)
|
|
54
|
+
|
|
55
|
+
def _send_to_watch(self, message):
|
|
56
|
+
self.send_packet(WebSocketRelayToWatch(payload=message), target=MessageTargetPhone())
|
|
57
|
+
|
|
58
|
+
def _send_to_phone(self, message):
|
|
59
|
+
message = struct.pack('B', endpoints[type(message)]) + message.serialise()
|
|
60
|
+
self.ws.send_binary(message)
|
|
61
|
+
|
|
62
|
+
def read_packet(self):
|
|
63
|
+
opcode, message = self.ws.recv_data()
|
|
64
|
+
if opcode == websocket.ABNF.OPCODE_BINARY:
|
|
65
|
+
endpoint, = struct.unpack_from('B', message, 0)
|
|
66
|
+
if from_watch.get(endpoint, None) == WebSocketRelayFromWatch:
|
|
67
|
+
return MessageTargetWatch(), message[1:]
|
|
68
|
+
else:
|
|
69
|
+
packet, length = from_watch[endpoint].parse(message[1:])
|
|
70
|
+
return MessageTargetPhone(), packet
|
|
71
|
+
elif opcode == websocket.ABNF.OPCODE_CLOSE:
|
|
72
|
+
raise ConnectionError("Connection gracefully closed by peer.")
|
|
73
|
+
else:
|
|
74
|
+
raise PebbleError("Got unexpected WebSocket opcode {}".format(opcode))
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
__author__ = 'katharine'
|
|
3
|
+
|
|
4
|
+
from six import iteritems
|
|
5
|
+
|
|
6
|
+
from enum import IntEnum
|
|
7
|
+
|
|
8
|
+
from libpebble2.protocol.base import PebblePacket
|
|
9
|
+
from libpebble2.protocol.base.types import *
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WebSocketRelayFromWatch(PebblePacket):
|
|
13
|
+
payload = BinaryArray()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WebSocketRelayToWatch(PebblePacket):
|
|
17
|
+
payload = BinaryArray()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WebSocketPhoneAppLog(PebblePacket):
|
|
21
|
+
payload = FixedString()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class WebSocketPhoneServerLog(PebblePacket):
|
|
25
|
+
payload = BinaryArray()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WebSocketInstallBundle(PebblePacket):
|
|
29
|
+
pbw = BinaryArray()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class WebSocketInstallStatus(PebblePacket):
|
|
33
|
+
class StatusCode(IntEnum):
|
|
34
|
+
Success = 0x00
|
|
35
|
+
Failed = 0x01
|
|
36
|
+
status = Uint32()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class WebSocketPhoneInfoRequest(PebblePacket):
|
|
40
|
+
version = Uint8(default=0x00)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class WebSocketInstallPhoneInfoResponse(PebblePacket):
|
|
44
|
+
payload = BinaryArray()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class WebSocketConnectionStatusUpdate(PebblePacket):
|
|
48
|
+
class StatusCode(IntEnum):
|
|
49
|
+
Connected = 0xff
|
|
50
|
+
Disconnected = 0x00
|
|
51
|
+
status = Uint8()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class WebSocketProxyConnectionStatusUpdate(PebblePacket):
|
|
55
|
+
class StatusCode(IntEnum):
|
|
56
|
+
Connected = 0xff
|
|
57
|
+
Disconnected = 0x00
|
|
58
|
+
status = Uint8()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class WebSocketProxyAuthenticationRequest(PebblePacket):
|
|
62
|
+
token = PascalString()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class WebSocketProxyAuthenticationResponse(PebblePacket):
|
|
66
|
+
class StatusCode(IntEnum):
|
|
67
|
+
Success = 0x00
|
|
68
|
+
Failed = 0x01
|
|
69
|
+
status = Uint8()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class AppConfigSetup(PebblePacket):
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class AppConfigResponse(PebblePacket):
|
|
77
|
+
length = Uint32()
|
|
78
|
+
data = FixedString(length=length)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class AppConfigCancelled(PebblePacket):
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class AppConfigURL(PebblePacket):
|
|
86
|
+
length = Uint32()
|
|
87
|
+
data = FixedString(length=length)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class WebSocketPhonesimAppConfig(PebblePacket):
|
|
91
|
+
command = Uint8()
|
|
92
|
+
config = Union(command, {
|
|
93
|
+
0x01: AppConfigSetup,
|
|
94
|
+
0x02: AppConfigResponse,
|
|
95
|
+
0x03: AppConfigCancelled,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class WebSocketPhonesimConfigResponse(PebblePacket):
|
|
100
|
+
command = Uint8()
|
|
101
|
+
|
|
102
|
+
config = Union(command, {
|
|
103
|
+
0x01: AppConfigURL
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class WebSocketRelayQemu(PebblePacket):
|
|
108
|
+
protocol = Uint8()
|
|
109
|
+
data = BinaryArray()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class InsertPin(PebblePacket):
|
|
113
|
+
json = FixedString()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class DeletePin(PebblePacket):
|
|
117
|
+
uuid = FixedString(36)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class WebSocketTimelinePin(PebblePacket):
|
|
121
|
+
command = Uint8()
|
|
122
|
+
data = Union(command, {
|
|
123
|
+
0x01: InsertPin,
|
|
124
|
+
0x02: DeletePin
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class WebSocketTimelineResponse(PebblePacket):
|
|
129
|
+
class Status(IntEnum):
|
|
130
|
+
Succeeded = 0x00
|
|
131
|
+
Failed = 0x01
|
|
132
|
+
|
|
133
|
+
status = Uint8(enum=Status)
|
|
134
|
+
|
|
135
|
+
to_watch = {
|
|
136
|
+
0x01: WebSocketRelayToWatch,
|
|
137
|
+
0x04: WebSocketInstallBundle,
|
|
138
|
+
0x06: WebSocketPhoneInfoRequest,
|
|
139
|
+
0x09: WebSocketProxyAuthenticationRequest,
|
|
140
|
+
0x0a: WebSocketPhonesimAppConfig,
|
|
141
|
+
0x0b: WebSocketRelayQemu,
|
|
142
|
+
0x0c: WebSocketTimelinePin
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
from_watch = {
|
|
146
|
+
0x00: WebSocketRelayFromWatch,
|
|
147
|
+
0x01: WebSocketRelayToWatch,
|
|
148
|
+
0x02: WebSocketPhoneAppLog,
|
|
149
|
+
0x03: WebSocketPhoneServerLog,
|
|
150
|
+
0x05: WebSocketInstallStatus,
|
|
151
|
+
0x06: WebSocketPhoneInfoRequest,
|
|
152
|
+
0x07: WebSocketConnectionStatusUpdate,
|
|
153
|
+
0x08: WebSocketProxyConnectionStatusUpdate,
|
|
154
|
+
0x09: WebSocketProxyAuthenticationResponse,
|
|
155
|
+
0x0a: WebSocketPhonesimConfigResponse,
|
|
156
|
+
0x0c: WebSocketTimelineResponse,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
endpoints = {v: k for k, v in iteritems(to_watch)}
|
|
160
|
+
endpoints.update({v: k for k, v in iteritems(from_watch)})
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
__author__ = 'katharine'
|
|
3
|
+
|
|
4
|
+
from six import with_metaclass
|
|
5
|
+
|
|
6
|
+
from abc import ABCMeta, abstractmethod
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseEventHandler(with_metaclass(ABCMeta)):
|
|
10
|
+
"""
|
|
11
|
+
An event handler, used throughout libpebble2 to indicate that something happened. These should ordinarily not need
|
|
12
|
+
to be directly invoked by a client of libpebble2.
|
|
13
|
+
"""
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def register_handler(self, event, handler):
|
|
16
|
+
"""
|
|
17
|
+
Register a handler for an event.
|
|
18
|
+
|
|
19
|
+
:param event: The event to be handled. This can be any object, as long as it's hashable.
|
|
20
|
+
:param handler: A callback function to be called when the event is triggered. The arguments are dependent
|
|
21
|
+
on the event.
|
|
22
|
+
:return: A handle that can be passed to :meth:`unregister_handler` to remove the registration.
|
|
23
|
+
"""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def unregister_handler(self, handle):
|
|
28
|
+
"""
|
|
29
|
+
Remove a handler for an event using a handle returned by :meth:`register_handler`.
|
|
30
|
+
|
|
31
|
+
:param handle: The handle for the registration to remove.
|
|
32
|
+
"""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def wait_for_event(self, event, timeout=10):
|
|
37
|
+
"""
|
|
38
|
+
A blocking wait for an event to be fired.
|
|
39
|
+
|
|
40
|
+
:param event: The event to wait on.
|
|
41
|
+
:param timeout: How long to wait before raising :exc:`.TimeoutError`
|
|
42
|
+
:return: The arguments that were passed to :meth:`broadcast_event`.
|
|
43
|
+
"""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def queue_events(self, event):
|
|
48
|
+
"""
|
|
49
|
+
Returns a :class:`BaseEventQueue` from which events can be read as they arrive, even if the arrive faster
|
|
50
|
+
than they are removed.
|
|
51
|
+
|
|
52
|
+
:param event: The events to add to the queue.
|
|
53
|
+
:return: An event queue.
|
|
54
|
+
:rtype: BaseEventQueue
|
|
55
|
+
"""
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def broadcast_event(self, event, *args):
|
|
60
|
+
"""
|
|
61
|
+
Broadcasts an event to all subscribers for that event, as added by :meth:`register_handler`
|
|
62
|
+
:meth:`wait_for_event` and :meth:`queue_events`. All arguments after `event` are passed on to the listeners.
|
|
63
|
+
|
|
64
|
+
:param event: The event to broadcast.
|
|
65
|
+
:param args: Any arguments to pass on.
|
|
66
|
+
"""
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class BaseEventQueue(with_metaclass(ABCMeta)):
|
|
71
|
+
@abstractmethod
|
|
72
|
+
def close(self):
|
|
73
|
+
"""
|
|
74
|
+
Stop adding events to this queue. It is illegal to call :meth:`get` or iterate over this queue after calling
|
|
75
|
+
:meth:`close`.
|
|
76
|
+
"""
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def get(self, timeout=10):
|
|
81
|
+
"""
|
|
82
|
+
Get the next event in the queue. Blocks until an item is available.
|
|
83
|
+
"""
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def __iter__(self):
|
|
88
|
+
"""
|
|
89
|
+
Iterate over events in the queue. Blocks if no more items are available.
|
|
90
|
+
"""
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
__author__ = 'katharine'
|
|
3
|
+
|
|
4
|
+
from libpebble2.events.threaded import ThreadedEventHandler
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EventSourceMixin(object):
|
|
8
|
+
"""
|
|
9
|
+
A convenient mixin to save on repeatedly exposing generic event handler functionality.
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.__handler = ThreadedEventHandler()
|
|
13
|
+
|
|
14
|
+
def register_handler(self, event, handler):
|
|
15
|
+
"""
|
|
16
|
+
Registers a handler to be triggered by an event
|
|
17
|
+
|
|
18
|
+
:param event: The event to handle
|
|
19
|
+
:param handler: The handler callable.
|
|
20
|
+
:return: A handle that can be used to unregister the handler.
|
|
21
|
+
"""
|
|
22
|
+
return self.__handler.register_handler(event, handler)
|
|
23
|
+
|
|
24
|
+
def unregister_handler(self, handle):
|
|
25
|
+
"""
|
|
26
|
+
Unregisters an event handler.
|
|
27
|
+
|
|
28
|
+
:param handle: The handle returned from :meth:`register_handler`
|
|
29
|
+
"""
|
|
30
|
+
self.__handler.unregister_handler(handle)
|
|
31
|
+
|
|
32
|
+
def wait_for_event(self, event, timeout=10):
|
|
33
|
+
"""
|
|
34
|
+
Block waiting for the given event. Returns the event params.
|
|
35
|
+
|
|
36
|
+
:param event: The event to handle.
|
|
37
|
+
:return: The event params.
|
|
38
|
+
:param timeout: The maximum time to wait before raising :exc:`.TimeoutError`.
|
|
39
|
+
"""
|
|
40
|
+
return self.__handler.wait_for_event(event, timeout=timeout)
|
|
41
|
+
|
|
42
|
+
def _broadcast_event(self, event, *args):
|
|
43
|
+
return self.__handler.broadcast_event(event, *args)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
|
|
3
|
+
__author__ = 'katharine'
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
from six.moves import queue
|
|
7
|
+
|
|
8
|
+
from . import BaseEventHandler, BaseEventQueue
|
|
9
|
+
from libpebble2.exceptions import TimeoutError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ThreadedEventHandler(BaseEventHandler):
|
|
13
|
+
"""
|
|
14
|
+
A threaded implementation of :class:`.BaseEventHandler`.
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self._handlers = {}
|
|
18
|
+
self._handle_map = {}
|
|
19
|
+
self._counter = 0
|
|
20
|
+
self._handler_lock = threading.RLock()
|
|
21
|
+
|
|
22
|
+
def register_handler(self, event, handler):
|
|
23
|
+
with self._handler_lock:
|
|
24
|
+
self._counter += 1
|
|
25
|
+
self._handlers.setdefault(event, {})[self._counter] = handler
|
|
26
|
+
self._handle_map[self._counter] = event
|
|
27
|
+
return self._counter
|
|
28
|
+
|
|
29
|
+
def unregister_handler(self, handle):
|
|
30
|
+
with self._handler_lock:
|
|
31
|
+
if handle not in self._handle_map:
|
|
32
|
+
return
|
|
33
|
+
del self._handlers[self._handle_map[handle]][handle]
|
|
34
|
+
del self._handle_map[handle]
|
|
35
|
+
|
|
36
|
+
def wait_for_event(self, event, timeout=10):
|
|
37
|
+
return _BlockingEventWait(self, event).wait(timeout=timeout)
|
|
38
|
+
|
|
39
|
+
def queue_events(self, event):
|
|
40
|
+
return _QueuedEventWait(self, event)
|
|
41
|
+
|
|
42
|
+
def broadcast_event(self, event, *args):
|
|
43
|
+
for handler in list(self._handlers.get(event, {}).values()):
|
|
44
|
+
handler(*args)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _BlockingEventWait(object):
|
|
48
|
+
def __init__(self, events, event):
|
|
49
|
+
self.block = threading.Event()
|
|
50
|
+
self.event_handler = events
|
|
51
|
+
self.result = None
|
|
52
|
+
self.handle = self.event_handler.register_handler(event, self.handle_result)
|
|
53
|
+
|
|
54
|
+
def handle_result(self, *args):
|
|
55
|
+
self.result, = args
|
|
56
|
+
self.event_handler.unregister_handler(self.handle)
|
|
57
|
+
self.block.set()
|
|
58
|
+
|
|
59
|
+
def wait(self, timeout=10):
|
|
60
|
+
if not self.block.wait(timeout=timeout):
|
|
61
|
+
raise TimeoutError()
|
|
62
|
+
return self.result
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class _QueuedEventWait(BaseEventQueue):
|
|
66
|
+
def __init__(self, events, event):
|
|
67
|
+
self.queue = queue.Queue()
|
|
68
|
+
self.event_handler = events
|
|
69
|
+
self.handle = self.event_handler.register_handler(event, self._handle_event)
|
|
70
|
+
|
|
71
|
+
def _handle_event(self, arg):
|
|
72
|
+
self.queue.put(arg)
|
|
73
|
+
|
|
74
|
+
def close(self):
|
|
75
|
+
self.event_handler.unregister_handler(self.handle)
|
|
76
|
+
|
|
77
|
+
def get(self, timeout=10):
|
|
78
|
+
try:
|
|
79
|
+
return self.queue.get(timeout=timeout)
|
|
80
|
+
except queue.Empty:
|
|
81
|
+
raise TimeoutError()
|
|
82
|
+
|
|
83
|
+
def __iter__(self):
|
|
84
|
+
yield self.get()
|