odrive 0.6.9.dev0__py37-none-win_amd64.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.
- odrive/__init__.py +142 -0
- odrive/api_client.py +54 -0
- odrive/config.py +1296 -0
- odrive/crypto.py +369 -0
- odrive/data/brakeRs.json +18 -0
- odrive/data/drvs.json +82 -0
- odrive/data/encoders.json +86 -0
- odrive/data/motors.json +128 -0
- odrive/data/odrive-micro-x1.json +48 -0
- odrive/data/odrive-micro-x3.json +40 -0
- odrive/data/odrive-micro-x4.json +40 -0
- odrive/data/odrive-s1-x4.json +52 -0
- odrive/data/odrive-v4.4.json +58 -0
- odrive/data/schema-brakeR.json +6 -0
- odrive/data/schema-drv.json +6 -0
- odrive/data/schema-encoders.json +6 -0
- odrive/data/schema-motors.json +6 -0
- odrive/data/schema-odrive.json +5 -0
- odrive/data/schema.json +244 -0
- odrive/database.py +184 -0
- odrive/dfu.py +195 -0
- odrive/dfuse/DfuDevice.py +297 -0
- odrive/dfuse/__init__.py +1 -0
- odrive/enums.py +415 -0
- odrive/firmware.py +102 -0
- odrive/hw_version.py +67 -0
- odrive/legacy.py +73 -0
- odrive/legacy_config.py +123 -0
- odrive/legacy_dfu.py +442 -0
- odrive/lib/libodrive-windows-x64.dll +0 -0
- odrive/libodrive.py +325 -0
- odrive/pyfibre/fibre/__init__.py +4 -0
- odrive/pyfibre/fibre/libfibre-windows-amd64.dll +0 -0
- odrive/pyfibre/fibre/libfibre.py +1078 -0
- odrive/pyfibre/fibre/libwinpthread-1.dll +0 -0
- odrive/pyfibre/fibre/shell.py +162 -0
- odrive/pyfibre/fibre/utils.py +133 -0
- odrive/release_api.py +184 -0
- odrive/rich_text.py +134 -0
- odrive/shell.py +144 -0
- odrive/utils.py +881 -0
- odrive/version.py +1 -0
- odrive-0.6.9.dev0.data/scripts/odrive_demo.py +50 -0
- odrive-0.6.9.dev0.data/scripts/odrivetool +263 -0
- odrive-0.6.9.dev0.data/scripts/odrivetool.bat +2 -0
- odrive-0.6.9.dev0.dist-info/METADATA +22 -0
- odrive-0.6.9.dev0.dist-info/RECORD +49 -0
- odrive-0.6.9.dev0.dist-info/WHEEL +5 -0
- odrive-0.6.9.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1078 @@
|
|
|
1
|
+
#!/bin/python
|
|
2
|
+
|
|
3
|
+
from ctypes import *
|
|
4
|
+
import asyncio
|
|
5
|
+
import os
|
|
6
|
+
from itertools import count, takewhile
|
|
7
|
+
import struct
|
|
8
|
+
from types import MethodType
|
|
9
|
+
import concurrent
|
|
10
|
+
import enum
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
import platform
|
|
14
|
+
from .utils import Logger
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
# Enable this for better tracebacks in some cases
|
|
18
|
+
#import tracemalloc
|
|
19
|
+
#tracemalloc.start(10)
|
|
20
|
+
|
|
21
|
+
lib_names = {
|
|
22
|
+
('Linux', 'x86_64'): 'libfibre-linux-amd64.so',
|
|
23
|
+
('Linux', 'armv7l'): 'libfibre-linux-armhf.so',
|
|
24
|
+
('Linux', 'aarch64'): 'libfibre-linux-aarch64.so',
|
|
25
|
+
('Windows', 'AMD64'): 'libfibre-windows-amd64.dll',
|
|
26
|
+
('Darwin', 'x86_64'): 'libfibre-macos-x86.dylib',
|
|
27
|
+
('Darwin', 'arm64'): 'libfibre-macos-arm.dylib',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
system_desc = (platform.system(), platform.machine())
|
|
31
|
+
|
|
32
|
+
if system_desc == ('Linux', 'aarch64') and sys.maxsize <= 2**32:
|
|
33
|
+
system_desc = ('Linux', 'armv7l') # Python running in 32-bit mode on 64-bit OS
|
|
34
|
+
|
|
35
|
+
script_dir = os.path.dirname(os.path.realpath(__file__))
|
|
36
|
+
fibre_cpp_paths = [
|
|
37
|
+
os.path.join(os.path.dirname(os.path.dirname(script_dir)), "cpp"),
|
|
38
|
+
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(script_dir)))), "Firmware", "fibre-cpp")
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
def get_first(lst, predicate, default):
|
|
42
|
+
for item in lst:
|
|
43
|
+
if predicate(item):
|
|
44
|
+
return item
|
|
45
|
+
return default
|
|
46
|
+
|
|
47
|
+
if not system_desc in lib_names:
|
|
48
|
+
raise ModuleNotFoundError(("libfibre is not supported on your platform ({} {}). "
|
|
49
|
+
"Go to https://github.com/samuelsadok/fibre-cpp/tree/devel for "
|
|
50
|
+
"instructions on how to compile libfibre. Once you have compiled it, "
|
|
51
|
+
"add it to this folder.").format(*system_desc))
|
|
52
|
+
|
|
53
|
+
lib_name = lib_names[system_desc]
|
|
54
|
+
search_paths = fibre_cpp_paths + [script_dir]
|
|
55
|
+
|
|
56
|
+
lib_path = get_first(
|
|
57
|
+
(os.path.join(p, lib_name) for p in search_paths),
|
|
58
|
+
os.path.isfile, None)
|
|
59
|
+
|
|
60
|
+
if lib_path is None:
|
|
61
|
+
raise ModuleNotFoundError("{} was not found in {}".format(lib_name, search_paths))
|
|
62
|
+
|
|
63
|
+
if os.path.getsize(lib_path) < 1000:
|
|
64
|
+
raise ModuleNotFoundError("{} is too small. Did you forget to init git lfs? Try this:\n"
|
|
65
|
+
" 1. Install git lfs (https://git-lfs.github.com/)\n"
|
|
66
|
+
" 2. Run `cd {}`\n"
|
|
67
|
+
" 3. Run `git lfs install`\n"
|
|
68
|
+
" 4. Run `git lfs pull`".format(lib_path, os.path.dirname(lib_path)))
|
|
69
|
+
|
|
70
|
+
if os.name == 'nt':
|
|
71
|
+
dll_dir = os.path.dirname(lib_path)
|
|
72
|
+
try:
|
|
73
|
+
# New way in python 3.8+
|
|
74
|
+
os.add_dll_directory(dll_dir)
|
|
75
|
+
except:
|
|
76
|
+
os.environ['PATH'] = dll_dir + os.pathsep + os.environ['PATH']
|
|
77
|
+
lib = windll.LoadLibrary(lib_path)
|
|
78
|
+
else:
|
|
79
|
+
lib = cdll.LoadLibrary(lib_path)
|
|
80
|
+
|
|
81
|
+
# libfibre definitions --------------------------------------------------------#
|
|
82
|
+
|
|
83
|
+
PostSignature = CFUNCTYPE(c_int, CFUNCTYPE(None, c_void_p), POINTER(c_int))
|
|
84
|
+
RegisterEventSignature = CFUNCTYPE(c_int, c_int, c_uint32, CFUNCTYPE(None, c_void_p, c_int), POINTER(c_int))
|
|
85
|
+
DeregisterEventSignature = CFUNCTYPE(c_int, c_int)
|
|
86
|
+
CallLaterSignature = CFUNCTYPE(c_void_p, c_float, CFUNCTYPE(None, c_void_p), POINTER(c_int))
|
|
87
|
+
CancelTimerSignature = CFUNCTYPE(c_int, c_void_p)
|
|
88
|
+
|
|
89
|
+
OnFoundObjectSignature = CFUNCTYPE(None, c_void_p, c_void_p, c_void_p)
|
|
90
|
+
OnLostObjectSignature = CFUNCTYPE(None, c_void_p, c_void_p)
|
|
91
|
+
OnStoppedSignature = CFUNCTYPE(None, c_void_p, c_int)
|
|
92
|
+
|
|
93
|
+
OnAttributeAddedSignature = CFUNCTYPE(None, c_void_p, c_void_p, c_void_p, c_size_t, c_void_p, c_void_p, c_size_t)
|
|
94
|
+
OnAttributeRemovedSignature = CFUNCTYPE(None, c_void_p, c_void_p)
|
|
95
|
+
OnFunctionAddedSignature = CFUNCTYPE(None, c_void_p, c_void_p, c_void_p, c_size_t, POINTER(c_char_p), POINTER(c_char_p), POINTER(c_char_p), POINTER(c_char_p))
|
|
96
|
+
OnFunctionRemovedSignature = CFUNCTYPE(None, c_void_p, c_void_p)
|
|
97
|
+
|
|
98
|
+
OnCallCompletedSignature = CFUNCTYPE(c_int, c_void_p, c_int, c_void_p, c_void_p, POINTER(c_void_p), POINTER(c_size_t), POINTER(c_void_p), POINTER(c_size_t))
|
|
99
|
+
OnTxCompletedSignature = CFUNCTYPE(None, c_void_p, c_void_p, c_int, c_void_p)
|
|
100
|
+
OnRxCompletedSignature = CFUNCTYPE(None, c_void_p, c_void_p, c_int, c_void_p)
|
|
101
|
+
|
|
102
|
+
kFibreOk = 0
|
|
103
|
+
kFibreBusy = 1
|
|
104
|
+
kFibreCancelled = 2
|
|
105
|
+
kFibreClosed = 3
|
|
106
|
+
kFibreInvalidArgument = 4
|
|
107
|
+
kFibreInternalError = 5
|
|
108
|
+
kFibreProtocolError = 6
|
|
109
|
+
kFibreHostUnreachable = 7
|
|
110
|
+
|
|
111
|
+
class LibFibreVersion(Structure):
|
|
112
|
+
_fields_ = [
|
|
113
|
+
("major", c_uint16),
|
|
114
|
+
("minor", c_uint16),
|
|
115
|
+
("patch", c_uint16),
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
def __repr__(self):
|
|
119
|
+
return "{}.{}.{}".format(self.major, self.minor, self.patch)
|
|
120
|
+
|
|
121
|
+
class LibFibreEventLoop(Structure):
|
|
122
|
+
_fields_ = [
|
|
123
|
+
("post", PostSignature),
|
|
124
|
+
("register_event", RegisterEventSignature),
|
|
125
|
+
("deregister_event", DeregisterEventSignature),
|
|
126
|
+
("call_later", CallLaterSignature),
|
|
127
|
+
("cancel_timer", CancelTimerSignature),
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
libfibre_get_version = lib.libfibre_get_version
|
|
131
|
+
libfibre_get_version.argtypes = []
|
|
132
|
+
libfibre_get_version.restype = POINTER(LibFibreVersion)
|
|
133
|
+
|
|
134
|
+
version = libfibre_get_version().contents
|
|
135
|
+
if (version.major, version.minor) != (0, 1):
|
|
136
|
+
raise Exception("Incompatible libfibre version: {}".format(version))
|
|
137
|
+
|
|
138
|
+
libfibre_open = lib.libfibre_open
|
|
139
|
+
libfibre_open.argtypes = [LibFibreEventLoop]
|
|
140
|
+
libfibre_open.restype = c_void_p
|
|
141
|
+
|
|
142
|
+
libfibre_close = lib.libfibre_close
|
|
143
|
+
libfibre_close.argtypes = [c_void_p]
|
|
144
|
+
libfibre_close.restype = None
|
|
145
|
+
|
|
146
|
+
libfibre_open_domain = lib.libfibre_open_domain
|
|
147
|
+
libfibre_open_domain.argtypes = [c_void_p, c_char_p, c_size_t]
|
|
148
|
+
libfibre_open_domain.restype = c_void_p
|
|
149
|
+
|
|
150
|
+
libfibre_close_domain = lib.libfibre_close_domain
|
|
151
|
+
libfibre_close_domain.argtypes = [c_void_p]
|
|
152
|
+
libfibre_close_domain.restype = None
|
|
153
|
+
|
|
154
|
+
libfibre_start_discovery = lib.libfibre_start_discovery
|
|
155
|
+
libfibre_start_discovery.argtypes = [c_void_p, c_void_p, OnFoundObjectSignature, OnLostObjectSignature, OnStoppedSignature, c_void_p]
|
|
156
|
+
libfibre_start_discovery.restype = None
|
|
157
|
+
|
|
158
|
+
libfibre_stop_discovery = lib.libfibre_stop_discovery
|
|
159
|
+
libfibre_stop_discovery.argtypes = [c_void_p]
|
|
160
|
+
libfibre_stop_discovery.restype = None
|
|
161
|
+
|
|
162
|
+
libfibre_subscribe_to_interface = lib.libfibre_subscribe_to_interface
|
|
163
|
+
libfibre_subscribe_to_interface.argtypes = [c_void_p, OnAttributeAddedSignature, OnAttributeRemovedSignature, OnFunctionAddedSignature, OnFunctionRemovedSignature, c_void_p]
|
|
164
|
+
libfibre_subscribe_to_interface.restype = None
|
|
165
|
+
|
|
166
|
+
libfibre_get_attribute = lib.libfibre_get_attribute
|
|
167
|
+
libfibre_get_attribute.argtypes = [c_void_p, c_void_p, POINTER(c_void_p)]
|
|
168
|
+
libfibre_get_attribute.restype = c_int
|
|
169
|
+
|
|
170
|
+
libfibre_call = lib.libfibre_call
|
|
171
|
+
libfibre_call.argtypes = [c_void_p, POINTER(c_void_p), c_int, c_void_p, c_size_t, c_void_p, c_size_t, POINTER(c_void_p), POINTER(c_void_p), OnCallCompletedSignature, c_void_p]
|
|
172
|
+
libfibre_call.restype = c_int
|
|
173
|
+
|
|
174
|
+
libfibre_start_tx = lib.libfibre_start_tx
|
|
175
|
+
libfibre_start_tx.argtypes = [c_void_p, c_char_p, c_size_t, OnTxCompletedSignature, c_void_p]
|
|
176
|
+
libfibre_start_tx.restype = None
|
|
177
|
+
|
|
178
|
+
libfibre_cancel_tx = lib.libfibre_cancel_tx
|
|
179
|
+
libfibre_cancel_tx.argtypes = [c_void_p]
|
|
180
|
+
libfibre_cancel_tx.restype = None
|
|
181
|
+
|
|
182
|
+
libfibre_start_rx = lib.libfibre_start_rx
|
|
183
|
+
libfibre_start_rx.argtypes = [c_void_p, c_char_p, c_size_t, OnRxCompletedSignature, c_void_p]
|
|
184
|
+
libfibre_start_rx.restype = None
|
|
185
|
+
|
|
186
|
+
libfibre_cancel_rx = lib.libfibre_cancel_rx
|
|
187
|
+
libfibre_cancel_rx.argtypes = [c_void_p]
|
|
188
|
+
libfibre_cancel_rx.restype = None
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# libfibre wrapper ------------------------------------------------------------#
|
|
192
|
+
|
|
193
|
+
class ObjectLostError(Exception):
|
|
194
|
+
def __init__(self):
|
|
195
|
+
super(Exception, self).__init__("the object disappeared")
|
|
196
|
+
|
|
197
|
+
def _get_exception(status):
|
|
198
|
+
if status == kFibreOk:
|
|
199
|
+
return None
|
|
200
|
+
elif status == kFibreCancelled:
|
|
201
|
+
return asyncio.CancelledError()
|
|
202
|
+
elif status == kFibreClosed:
|
|
203
|
+
return EOFError()
|
|
204
|
+
elif status == kFibreInvalidArgument:
|
|
205
|
+
return ArgumentError()
|
|
206
|
+
elif status == kFibreInternalError:
|
|
207
|
+
return Exception("internal libfibre error")
|
|
208
|
+
elif status == kFibreProtocolError:
|
|
209
|
+
return Exception("peer misbehaving")
|
|
210
|
+
elif status == kFibreHostUnreachable:
|
|
211
|
+
return ObjectLostError()
|
|
212
|
+
else:
|
|
213
|
+
return Exception("unknown libfibre error {}".format(status))
|
|
214
|
+
|
|
215
|
+
class StructCodec():
|
|
216
|
+
"""
|
|
217
|
+
Generic serializer/deserializer based on struct pack
|
|
218
|
+
"""
|
|
219
|
+
def __init__(self, struct_format, target_type):
|
|
220
|
+
self._struct_format = struct_format
|
|
221
|
+
self._target_type = target_type
|
|
222
|
+
def get_length(self):
|
|
223
|
+
return struct.calcsize(self._struct_format)
|
|
224
|
+
def serialize(self, libfibre, value):
|
|
225
|
+
if self._target_type == int and isinstance(value, enum.Enum):
|
|
226
|
+
value = value.value
|
|
227
|
+
value = self._target_type(value)
|
|
228
|
+
return struct.pack(self._struct_format, value)
|
|
229
|
+
def deserialize(self, libfibre, buffer):
|
|
230
|
+
value = struct.unpack(self._struct_format, buffer)
|
|
231
|
+
value = value[0] if len(value) == 1 else value
|
|
232
|
+
return self._target_type(value)
|
|
233
|
+
|
|
234
|
+
class ObjectPtrCodec():
|
|
235
|
+
"""
|
|
236
|
+
Serializer/deserializer for an object reference
|
|
237
|
+
|
|
238
|
+
libfibre transcodes object references internally from/to something that can
|
|
239
|
+
be sent over the wire and understood by the remote instance.
|
|
240
|
+
"""
|
|
241
|
+
def get_length(self):
|
|
242
|
+
return struct.calcsize("P")
|
|
243
|
+
def serialize(self, libfibre, value):
|
|
244
|
+
if value is None:
|
|
245
|
+
return struct.pack("P", 0)
|
|
246
|
+
elif isinstance(value, RemoteObject):
|
|
247
|
+
assert(value._obj_handle) # Cannot serialize reference to a lost object
|
|
248
|
+
return struct.pack("P", value._obj_handle)
|
|
249
|
+
else:
|
|
250
|
+
raise TypeError("Expected value of type RemoteObject or None but got '{}'. An example for a RemoteObject is this expression: odrv0.axis0.controller._input_pos_property".format(type(value).__name__))
|
|
251
|
+
def deserialize(self, libfibre, buffer):
|
|
252
|
+
handle = struct.unpack("P", buffer)[0]
|
|
253
|
+
|
|
254
|
+
# TODO: this is broken: A function can return an object ref before it is known to PyFibre.
|
|
255
|
+
return None if handle == 0 else libfibre._objects[handle]
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
codecs = {
|
|
259
|
+
'int8': StructCodec("<b", int),
|
|
260
|
+
'uint8': StructCodec("<B", int),
|
|
261
|
+
'int16': StructCodec("<h", int),
|
|
262
|
+
'uint16': StructCodec("<H", int),
|
|
263
|
+
'int32': StructCodec("<i", int),
|
|
264
|
+
'uint32': StructCodec("<I", int),
|
|
265
|
+
'int64': StructCodec("<q", int),
|
|
266
|
+
'uint64': StructCodec("<Q", int),
|
|
267
|
+
'bool': StructCodec("<?", bool),
|
|
268
|
+
'float': StructCodec("<f", float),
|
|
269
|
+
'object_ref': ObjectPtrCodec()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
def decode_arg_list(arg_names, codec_names):
|
|
273
|
+
for i in count(0):
|
|
274
|
+
if arg_names[i] is None or codec_names[i] is None:
|
|
275
|
+
break
|
|
276
|
+
arg_name = arg_names[i].decode('utf-8')
|
|
277
|
+
codec_name = codec_names[i].decode('utf-8')
|
|
278
|
+
if not codec_name in codecs:
|
|
279
|
+
raise Exception("unsupported codec {}".format(codec_name))
|
|
280
|
+
yield arg_name, codec_name, codecs[codec_name]
|
|
281
|
+
|
|
282
|
+
def insert_with_new_id(dictionary, val):
|
|
283
|
+
key = next(x for x in count(1) if x not in set(dictionary.keys()))
|
|
284
|
+
dictionary[key] = val
|
|
285
|
+
return key
|
|
286
|
+
|
|
287
|
+
# Runs a function on a foreign event loop and blocks until the function is done.
|
|
288
|
+
def run_coroutine_threadsafe(loop, func):
|
|
289
|
+
future = concurrent.futures.Future()
|
|
290
|
+
async def func_async():
|
|
291
|
+
try:
|
|
292
|
+
result = func()
|
|
293
|
+
if hasattr(result, '__await__'):
|
|
294
|
+
result = await result
|
|
295
|
+
future.set_result(result)
|
|
296
|
+
except Exception as ex:
|
|
297
|
+
future.set_exception(ex)
|
|
298
|
+
loop.call_soon_threadsafe(asyncio.ensure_future, func_async())
|
|
299
|
+
return future.result()
|
|
300
|
+
|
|
301
|
+
class TxStream():
|
|
302
|
+
"""Python wrapper for libfibre's LibFibreTxStream interface"""
|
|
303
|
+
|
|
304
|
+
def __init__(self, libfibre, tx_stream_handle):
|
|
305
|
+
self._libfibre = libfibre
|
|
306
|
+
self._tx_stream_handle = tx_stream_handle
|
|
307
|
+
self._future = None
|
|
308
|
+
self._tx_buf = None
|
|
309
|
+
self._c_on_tx_completed = OnTxCompletedSignature(self._on_tx_completed)
|
|
310
|
+
self.is_closed = False
|
|
311
|
+
|
|
312
|
+
def _on_tx_completed(self, ctx, tx_stream, status, tx_end):
|
|
313
|
+
tx_start = cast(self._tx_buf, c_void_p).value
|
|
314
|
+
|
|
315
|
+
n_written = tx_end - tx_start
|
|
316
|
+
assert(n_written <= len(self._tx_buf))
|
|
317
|
+
future = self._future
|
|
318
|
+
self._future = None
|
|
319
|
+
self._tx_buf = None
|
|
320
|
+
|
|
321
|
+
if status == kFibreClosed:
|
|
322
|
+
self.is_closed = True
|
|
323
|
+
|
|
324
|
+
if status == kFibreOk or status == kFibreClosed:
|
|
325
|
+
future.set_result(n_written)
|
|
326
|
+
else:
|
|
327
|
+
future.set_exception(_get_exception(status))
|
|
328
|
+
|
|
329
|
+
def write(self, data):
|
|
330
|
+
"""
|
|
331
|
+
Writes the provided data to the stream. Not all bytes are guaranteed to
|
|
332
|
+
be written. The caller should check the return value to determine the
|
|
333
|
+
actual number of bytes written.
|
|
334
|
+
|
|
335
|
+
If a non-empty buffer is provided, this function will either write at
|
|
336
|
+
least one byte to the output, set is_closed to True or throw an
|
|
337
|
+
Exception (through the future).
|
|
338
|
+
|
|
339
|
+
Currently only one write call may be active at a time (this may change
|
|
340
|
+
in the future).
|
|
341
|
+
|
|
342
|
+
Returns: A future that completes with the number of bytes actually
|
|
343
|
+
written or an Exception.
|
|
344
|
+
"""
|
|
345
|
+
assert(self._future is None)
|
|
346
|
+
self._future = future = self._libfibre.loop.create_future()
|
|
347
|
+
self._tx_buf = data # Retain a reference to the buffer to prevent it from being garbage collected
|
|
348
|
+
|
|
349
|
+
libfibre_start_tx(self._tx_stream_handle,
|
|
350
|
+
cast(self._tx_buf, c_char_p), len(self._tx_buf),
|
|
351
|
+
self._c_on_tx_completed, None)
|
|
352
|
+
|
|
353
|
+
return future
|
|
354
|
+
|
|
355
|
+
async def write_all(self, data):
|
|
356
|
+
"""
|
|
357
|
+
Writes all of the provided data to the stream or completes with an
|
|
358
|
+
Exception.
|
|
359
|
+
|
|
360
|
+
If an empty buffer is provided, the underlying stream's write function
|
|
361
|
+
is still called at least once.
|
|
362
|
+
|
|
363
|
+
Returns: A future that either completes with an empty result or with
|
|
364
|
+
an Exception.
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
while True:
|
|
368
|
+
n_written = await self.write(data)
|
|
369
|
+
data = data[n_written:]
|
|
370
|
+
if len(data) == 0:
|
|
371
|
+
break
|
|
372
|
+
elif self.is_closed:
|
|
373
|
+
raise EOFError("the TX stream was closed but there are still {} bytes left to send".format(len(data)))
|
|
374
|
+
assert(n_written > 0) # Ensure progress
|
|
375
|
+
|
|
376
|
+
class RxStream():
|
|
377
|
+
"""Python wrapper for libfibre's LibFibreRxStream interface"""
|
|
378
|
+
|
|
379
|
+
def __init__(self, libfibre, rx_stream_handle):
|
|
380
|
+
self._libfibre = libfibre
|
|
381
|
+
self._rx_stream_handle = rx_stream_handle
|
|
382
|
+
self._future = None
|
|
383
|
+
self._rx_buf = None
|
|
384
|
+
self._c_on_rx_completed = OnRxCompletedSignature(self._on_rx_completed)
|
|
385
|
+
self.is_closed = False
|
|
386
|
+
|
|
387
|
+
def _on_rx_completed(self, ctx, rx_stream, status, rx_end):
|
|
388
|
+
rx_start = cast(self._rx_buf, c_void_p).value
|
|
389
|
+
|
|
390
|
+
n_read = rx_end - rx_start
|
|
391
|
+
assert(n_read <= len(self._rx_buf))
|
|
392
|
+
data = self._rx_buf[:n_read]
|
|
393
|
+
future = self._future
|
|
394
|
+
self._future = None
|
|
395
|
+
self._rx_buf = None
|
|
396
|
+
|
|
397
|
+
if status == kFibreClosed:
|
|
398
|
+
self.is_closed = True
|
|
399
|
+
|
|
400
|
+
if status == kFibreOk or status == kFibreClosed:
|
|
401
|
+
future.set_result(data)
|
|
402
|
+
else:
|
|
403
|
+
future.set_exception(_get_exception(status))
|
|
404
|
+
|
|
405
|
+
def read(self, n_read):
|
|
406
|
+
"""
|
|
407
|
+
Reads up to the specified number of bytes from the stream.
|
|
408
|
+
|
|
409
|
+
If more than zero bytes are requested, this function will either read at
|
|
410
|
+
least one byte, set is_closed to True or throw an Exception (through the
|
|
411
|
+
future).
|
|
412
|
+
|
|
413
|
+
Currently only one write call may be active at a time (this may change
|
|
414
|
+
in the future).
|
|
415
|
+
|
|
416
|
+
Returns: A future that either completes with a buffer containing the
|
|
417
|
+
bytes that were read or completes with an Exception.
|
|
418
|
+
"""
|
|
419
|
+
assert(self._future is None)
|
|
420
|
+
self._future = future = self._libfibre.loop.create_future()
|
|
421
|
+
self._rx_buf = bytes(n_read)
|
|
422
|
+
|
|
423
|
+
libfibre_start_rx(self._rx_stream_handle,
|
|
424
|
+
cast(self._rx_buf, c_char_p), len(self._rx_buf),
|
|
425
|
+
self._c_on_rx_completed, None)
|
|
426
|
+
|
|
427
|
+
return future
|
|
428
|
+
|
|
429
|
+
async def read_all(self, n_read):
|
|
430
|
+
"""
|
|
431
|
+
Reads the specified number of bytes from the stream or throws an
|
|
432
|
+
Exception.
|
|
433
|
+
|
|
434
|
+
If zero bytes are requested, the underlying stream's read function
|
|
435
|
+
is still called at least once.
|
|
436
|
+
|
|
437
|
+
Returns: A future that either completes with a buffer of size n_read or
|
|
438
|
+
an Exception.
|
|
439
|
+
"""
|
|
440
|
+
|
|
441
|
+
data = bytes()
|
|
442
|
+
while True:
|
|
443
|
+
chunk = await self.read(n_read - len(data))
|
|
444
|
+
data += chunk
|
|
445
|
+
if n_read == len(data):
|
|
446
|
+
break
|
|
447
|
+
elif self.is_closed:
|
|
448
|
+
raise EOFError()
|
|
449
|
+
assert(len(chunk) > 0) # Ensure progress
|
|
450
|
+
return data
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
class Call(object):
|
|
454
|
+
"""
|
|
455
|
+
This call behaves as you would expect an async generator to behave. This is
|
|
456
|
+
used to provide compatibility down to Python 3.5.
|
|
457
|
+
"""
|
|
458
|
+
def __init__(self, func):
|
|
459
|
+
self._func = func
|
|
460
|
+
self._call_handle = c_void_p(0)
|
|
461
|
+
self._is_started = False
|
|
462
|
+
self._should_close = False
|
|
463
|
+
self._is_closed = False
|
|
464
|
+
self._tx_buf = None
|
|
465
|
+
|
|
466
|
+
def __aiter__(self):
|
|
467
|
+
return self
|
|
468
|
+
|
|
469
|
+
async def asend(self, val):
|
|
470
|
+
assert(self._is_started == (not val is None))
|
|
471
|
+
if not val is None:
|
|
472
|
+
self._tx_buf, self._rx_len, self._should_close = val
|
|
473
|
+
return await self.__anext__()
|
|
474
|
+
|
|
475
|
+
async def __anext__(self):
|
|
476
|
+
if not self._is_started:
|
|
477
|
+
self._is_started = True
|
|
478
|
+
return None # This immitates the weird starting behavior of Python 3.6+ async generators iterators
|
|
479
|
+
|
|
480
|
+
if self._is_closed:
|
|
481
|
+
raise StopAsyncIteration
|
|
482
|
+
|
|
483
|
+
tx_end = c_void_p(0)
|
|
484
|
+
rx_end = c_void_p(0)
|
|
485
|
+
|
|
486
|
+
rx_buf = bytes(self._rx_len)
|
|
487
|
+
|
|
488
|
+
call_id = insert_with_new_id(self._func._libfibre._calls, self)
|
|
489
|
+
|
|
490
|
+
status = libfibre_call(self._func._func_handle, byref(self._call_handle),
|
|
491
|
+
kFibreClosed if self._should_close else kFibreOk,
|
|
492
|
+
cast(self._tx_buf, c_char_p), len(self._tx_buf),
|
|
493
|
+
cast(rx_buf, c_char_p), len(rx_buf),
|
|
494
|
+
byref(tx_end), byref(rx_end), self._func._libfibre.c_on_call_completed, call_id)
|
|
495
|
+
|
|
496
|
+
if status == kFibreBusy:
|
|
497
|
+
self.ag_await = self._func._libfibre.loop.create_future()
|
|
498
|
+
status, tx_end, rx_end = await self.ag_await
|
|
499
|
+
self.ag_await = None
|
|
500
|
+
|
|
501
|
+
if status != kFibreOk and status != kFibreClosed:
|
|
502
|
+
raise _get_exception(status)
|
|
503
|
+
|
|
504
|
+
n_written = tx_end - cast(self._tx_buf, c_void_p).value
|
|
505
|
+
self._tx_buf = self._tx_buf[n_written:]
|
|
506
|
+
n_read = rx_end - cast(rx_buf, c_void_p).value
|
|
507
|
+
rx_buf = rx_buf[:n_read]
|
|
508
|
+
|
|
509
|
+
if status != kFibreOk:
|
|
510
|
+
self._is_closed = True
|
|
511
|
+
return self._tx_buf, rx_buf, self._is_closed
|
|
512
|
+
|
|
513
|
+
async def cancel():
|
|
514
|
+
# TODO: this doesn't follow the official Python async generator protocol. Should implement aclose() instead.
|
|
515
|
+
status = libfibre_call(self._func._func_handle, byref(self._call_handle), kFibreOk,
|
|
516
|
+
0, 0, 0, 0, 0, 0, self._func._libfibre.c_on_call_completed, call_id)
|
|
517
|
+
|
|
518
|
+
#async def aclose(self):
|
|
519
|
+
# assert(self._is_started and not self._is_closed)
|
|
520
|
+
# return self._tx_buf, rx_buf, self._is_closed
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
class RemoteFunction(object):
|
|
524
|
+
"""
|
|
525
|
+
Represents a callable function that maps to a function call on a remote object.
|
|
526
|
+
"""
|
|
527
|
+
def __init__(self, libfibre, func_handle, inputs, outputs):
|
|
528
|
+
self._libfibre = libfibre
|
|
529
|
+
self._func_handle = func_handle
|
|
530
|
+
self._inputs = inputs
|
|
531
|
+
self._outputs = outputs
|
|
532
|
+
self._rx_size = sum(codec.get_length() for _, _, codec in self._outputs)
|
|
533
|
+
|
|
534
|
+
async def async_call(self, args, cancellation_token):
|
|
535
|
+
#print("making call on " + hex(args[0]._obj_handle))
|
|
536
|
+
tx_buf = bytes()
|
|
537
|
+
for i, arg in enumerate(self._inputs):
|
|
538
|
+
tx_buf += arg[2].serialize(self._libfibre, args[i])
|
|
539
|
+
rx_buf = bytes()
|
|
540
|
+
|
|
541
|
+
agen = Call(self)
|
|
542
|
+
|
|
543
|
+
if not cancellation_token is None:
|
|
544
|
+
cancellation_token.add_done_callback(agen.cancel)
|
|
545
|
+
|
|
546
|
+
try:
|
|
547
|
+
assert(await agen.asend(None) is None)
|
|
548
|
+
|
|
549
|
+
is_closed = False
|
|
550
|
+
while not is_closed:
|
|
551
|
+
tx_buf, rx_chunk, is_closed = await agen.asend((tx_buf, self._rx_size - len(rx_buf), True))
|
|
552
|
+
rx_buf += rx_chunk
|
|
553
|
+
|
|
554
|
+
finally:
|
|
555
|
+
if not cancellation_token is None:
|
|
556
|
+
cancellation_token.remove_done_callback(agen.cancel)
|
|
557
|
+
|
|
558
|
+
assert(len(rx_buf) == self._rx_size)
|
|
559
|
+
|
|
560
|
+
outputs = []
|
|
561
|
+
for arg in self._outputs:
|
|
562
|
+
arg_length = arg[2].get_length()
|
|
563
|
+
outputs.append(arg[2].deserialize(self._libfibre, rx_buf[:arg_length]))
|
|
564
|
+
rx_buf = rx_buf[arg_length:]
|
|
565
|
+
|
|
566
|
+
if len(outputs) == 0:
|
|
567
|
+
return
|
|
568
|
+
elif len(outputs) == 1:
|
|
569
|
+
return outputs[0]
|
|
570
|
+
else:
|
|
571
|
+
return tuple(outputs)
|
|
572
|
+
|
|
573
|
+
def __call__(self, *args, cancellation_token = None):
|
|
574
|
+
"""
|
|
575
|
+
Starts invoking the remote function. The first argument is usually a
|
|
576
|
+
remote object.
|
|
577
|
+
If this function is called from the Fibre thread then it is nonblocking
|
|
578
|
+
and returns an asyncio.Future. If it is called from another thread then
|
|
579
|
+
it blocks until the function completes and returns the result(s) of the
|
|
580
|
+
invokation.
|
|
581
|
+
"""
|
|
582
|
+
|
|
583
|
+
if threading.current_thread() != libfibre_thread:
|
|
584
|
+
return run_coroutine_threadsafe(self._libfibre.loop, lambda: self.__call__(*args))
|
|
585
|
+
|
|
586
|
+
if (len(self._inputs) != len(args)):
|
|
587
|
+
raise TypeError("expected {} arguments but have {}".format(len(self._inputs), len(args)))
|
|
588
|
+
|
|
589
|
+
coro = self.async_call(args, cancellation_token)
|
|
590
|
+
return asyncio.ensure_future(coro, loop=self._libfibre.loop)
|
|
591
|
+
|
|
592
|
+
def __get__(self, instance, owner):
|
|
593
|
+
return MethodType(self, instance) if instance else self
|
|
594
|
+
|
|
595
|
+
def _dump(self, name):
|
|
596
|
+
print_arglist = lambda arglist: ", ".join("{}: {}".format(arg_name, codec_name) for arg_name, codec_name, codec in arglist)
|
|
597
|
+
return "{}({}){}".format(name,
|
|
598
|
+
print_arglist(self._inputs),
|
|
599
|
+
"" if len(self._outputs) == 0 else
|
|
600
|
+
" -> " + print_arglist(self._outputs) if len(self._outputs) == 1 else
|
|
601
|
+
" -> (" + print_arglist(self._outputs) + ")")
|
|
602
|
+
|
|
603
|
+
class RemoteAttribute(object):
|
|
604
|
+
def __init__(self, libfibre, attr_handle, intf_handle, intf_name, magic_getter, magic_setter):
|
|
605
|
+
self._libfibre = libfibre
|
|
606
|
+
self._attr_handle = attr_handle
|
|
607
|
+
self._intf_handle = intf_handle
|
|
608
|
+
self._intf_name = intf_name
|
|
609
|
+
self._magic_getter = magic_getter
|
|
610
|
+
self._magic_setter = magic_setter
|
|
611
|
+
|
|
612
|
+
def _get_obj(self, instance):
|
|
613
|
+
assert(not instance._obj_handle is None)
|
|
614
|
+
|
|
615
|
+
obj_handle = c_void_p(0)
|
|
616
|
+
status = libfibre_get_attribute(instance._obj_handle, self._attr_handle, byref(obj_handle))
|
|
617
|
+
if status != kFibreOk:
|
|
618
|
+
raise _get_exception(status)
|
|
619
|
+
|
|
620
|
+
obj = self._libfibre._load_py_obj(obj_handle.value, self._intf_handle)
|
|
621
|
+
if obj in instance._children:
|
|
622
|
+
self._libfibre._release_py_obj(obj_handle.value)
|
|
623
|
+
else:
|
|
624
|
+
# the object will be released when the parent is released
|
|
625
|
+
instance._children.add(obj)
|
|
626
|
+
|
|
627
|
+
return obj
|
|
628
|
+
|
|
629
|
+
def __get__(self, instance, owner):
|
|
630
|
+
if not instance:
|
|
631
|
+
return self
|
|
632
|
+
|
|
633
|
+
if self._magic_getter:
|
|
634
|
+
if threading.current_thread() == libfibre_thread:
|
|
635
|
+
# read() behaves asynchronously when run on the fibre thread
|
|
636
|
+
# which means it returns an awaitable which _must_ be awaited
|
|
637
|
+
# (otherwise it's a bug). However hasattr(...) internally calls
|
|
638
|
+
# __get__ and does not await the result. Thus the safest thing
|
|
639
|
+
# is to just disallow __get__ from run as an async method.
|
|
640
|
+
raise Exception("Cannot use magic getter on Fibre thread. Use _[prop_name]_propery.read() instead.")
|
|
641
|
+
return self._get_obj(instance).read()
|
|
642
|
+
else:
|
|
643
|
+
return self._get_obj(instance)
|
|
644
|
+
|
|
645
|
+
def __set__(self, instance, val):
|
|
646
|
+
if self._magic_setter:
|
|
647
|
+
return self._get_obj(instance).exchange(val)
|
|
648
|
+
else:
|
|
649
|
+
raise Exception("this attribute cannot be written to")
|
|
650
|
+
|
|
651
|
+
class EmptyInterface():
|
|
652
|
+
def __str__(self):
|
|
653
|
+
return "[lost object]"
|
|
654
|
+
def __repr__(self):
|
|
655
|
+
return self.__str__()
|
|
656
|
+
|
|
657
|
+
class RemoteObject(object):
|
|
658
|
+
"""
|
|
659
|
+
Base class for interfaces of remote objects.
|
|
660
|
+
"""
|
|
661
|
+
__sealed__ = False
|
|
662
|
+
|
|
663
|
+
def __init__(self, libfibre, obj_handle):
|
|
664
|
+
self.__class__._refcount += 1
|
|
665
|
+
self._refcount = 0
|
|
666
|
+
self._children = set()
|
|
667
|
+
|
|
668
|
+
self._libfibre = libfibre
|
|
669
|
+
self._obj_handle = obj_handle
|
|
670
|
+
self._on_lost = concurrent.futures.Future() # TODO: maybe we can do this with conc
|
|
671
|
+
|
|
672
|
+
# Ensure that assignments to undefined attributes raise an exception
|
|
673
|
+
self.__sealed__ = True
|
|
674
|
+
|
|
675
|
+
def __setattr__(self, key, value):
|
|
676
|
+
if self.__sealed__ and not key in dir(self) and not hasattr(self, key):
|
|
677
|
+
raise AttributeError("Attribute {} not found".format(key))
|
|
678
|
+
object.__setattr__(self, key, value)
|
|
679
|
+
|
|
680
|
+
#def __del__(self):
|
|
681
|
+
# print("unref")
|
|
682
|
+
# libfibre_unref_obj(self._obj_handle)
|
|
683
|
+
|
|
684
|
+
def _dump(self, indent, depth):
|
|
685
|
+
if self._obj_handle is None:
|
|
686
|
+
return "[object lost]"
|
|
687
|
+
|
|
688
|
+
try:
|
|
689
|
+
if depth <= 0:
|
|
690
|
+
return "..."
|
|
691
|
+
lines = []
|
|
692
|
+
for key in dir(self.__class__):
|
|
693
|
+
if key.startswith('_'):
|
|
694
|
+
continue
|
|
695
|
+
class_member = getattr(self.__class__, key)
|
|
696
|
+
if isinstance(class_member, RemoteFunction):
|
|
697
|
+
lines.append(indent + class_member._dump(key))
|
|
698
|
+
elif isinstance(class_member, RemoteAttribute):
|
|
699
|
+
val = getattr(self, key)
|
|
700
|
+
if isinstance(val, RemoteObject) and not class_member._magic_getter:
|
|
701
|
+
lines.append(indent + key + (": " if depth == 1 else ":\n") + val._dump(indent + " ", depth - 1))
|
|
702
|
+
else:
|
|
703
|
+
if isinstance(val, RemoteObject) and class_member._magic_getter:
|
|
704
|
+
val_str = get_user_name(val)
|
|
705
|
+
else:
|
|
706
|
+
val_str = str(val)
|
|
707
|
+
property_type = str(class_member._get_obj(self).__class__.read._outputs[0][1])
|
|
708
|
+
lines.append(indent + key + ": " + val_str + " (" + property_type + ")")
|
|
709
|
+
else:
|
|
710
|
+
lines.append(indent + key + ": " + str(type(val)))
|
|
711
|
+
except:
|
|
712
|
+
return "[failed to dump object]"
|
|
713
|
+
|
|
714
|
+
return "\n".join(lines)
|
|
715
|
+
|
|
716
|
+
def __str__(self):
|
|
717
|
+
return self._dump("", depth=2)
|
|
718
|
+
|
|
719
|
+
def __repr__(self):
|
|
720
|
+
return self.__str__()
|
|
721
|
+
|
|
722
|
+
def _destroy(self):
|
|
723
|
+
libfibre = self._libfibre
|
|
724
|
+
on_lost = self._on_lost
|
|
725
|
+
children = self._children
|
|
726
|
+
|
|
727
|
+
self._libfibre = None
|
|
728
|
+
self._obj_handle = None
|
|
729
|
+
self._on_lost = None
|
|
730
|
+
self._children = set()
|
|
731
|
+
|
|
732
|
+
for child in children:
|
|
733
|
+
libfibre._release_py_obj(child._obj_handle)
|
|
734
|
+
|
|
735
|
+
self.__class__._refcount -= 1
|
|
736
|
+
if self.__class__._refcount == 0:
|
|
737
|
+
libfibre.interfaces.pop(self.__class__._handle)
|
|
738
|
+
|
|
739
|
+
self.__class__ = EmptyInterface # ensure that this object has no more attributes
|
|
740
|
+
on_lost.set_result(True)
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
class LibFibre():
|
|
744
|
+
def __init__(self, loop):
|
|
745
|
+
self.loop = loop
|
|
746
|
+
|
|
747
|
+
# We must keep a reference to these function objects so they don't get
|
|
748
|
+
# garbage collected.
|
|
749
|
+
self.c_post = PostSignature(self._post)
|
|
750
|
+
self.c_register_event = RegisterEventSignature(self._register_event)
|
|
751
|
+
self.c_deregister_event = DeregisterEventSignature(self._deregister_event)
|
|
752
|
+
self.c_call_later = CallLaterSignature(self._call_later)
|
|
753
|
+
self.c_cancel_timer = CancelTimerSignature(self._cancel_timer)
|
|
754
|
+
self.c_on_found_object = OnFoundObjectSignature(self._on_found_object)
|
|
755
|
+
self.c_on_lost_object = OnLostObjectSignature(self._on_lost_object)
|
|
756
|
+
self.c_on_discovery_stopped = OnStoppedSignature(self._on_discovery_stopped)
|
|
757
|
+
self.c_on_attribute_added = OnAttributeAddedSignature(self._on_attribute_added)
|
|
758
|
+
self.c_on_attribute_removed = OnAttributeRemovedSignature(self._on_attribute_removed)
|
|
759
|
+
self.c_on_function_added = OnFunctionAddedSignature(self._on_function_added)
|
|
760
|
+
self.c_on_function_removed = OnFunctionRemovedSignature(self._on_function_removed)
|
|
761
|
+
self.c_on_call_completed = OnCallCompletedSignature(self._on_call_completed)
|
|
762
|
+
|
|
763
|
+
self.timer_map = {}
|
|
764
|
+
self.eventfd_map = {}
|
|
765
|
+
self.interfaces = {} # key: libfibre handle, value: python class
|
|
766
|
+
self.discovery_processes = {} # key: ID, value: python dict
|
|
767
|
+
self._objects = {} # key: libfibre handle, value: python class
|
|
768
|
+
self._calls = {} # key: libfibre handle, value: Call object
|
|
769
|
+
|
|
770
|
+
event_loop = LibFibreEventLoop()
|
|
771
|
+
event_loop.post = self.c_post
|
|
772
|
+
event_loop.register_event = self.c_register_event
|
|
773
|
+
event_loop.deregister_event = self.c_deregister_event
|
|
774
|
+
event_loop.call_later = self.c_call_later
|
|
775
|
+
event_loop.cancel_timer = self.c_cancel_timer
|
|
776
|
+
|
|
777
|
+
self.ctx = c_void_p(libfibre_open(event_loop))
|
|
778
|
+
assert(self.ctx)
|
|
779
|
+
|
|
780
|
+
def _post(self, callback, ctx):
|
|
781
|
+
self.loop.call_soon_threadsafe(callback, ctx)
|
|
782
|
+
return 0
|
|
783
|
+
|
|
784
|
+
def _register_event(self, event_fd, events, callback, ctx):
|
|
785
|
+
self.eventfd_map[event_fd] = events
|
|
786
|
+
if (events & 1):
|
|
787
|
+
self.loop.add_reader(event_fd, lambda x: callback(x, 1), ctx)
|
|
788
|
+
if (events & 4):
|
|
789
|
+
self.loop.add_writer(event_fd, lambda x: callback(x, 4), ctx)
|
|
790
|
+
if (events & 0xfffffffa):
|
|
791
|
+
raise Exception("unsupported event mask " + str(events))
|
|
792
|
+
return 0
|
|
793
|
+
|
|
794
|
+
def _deregister_event(self, event_fd):
|
|
795
|
+
events = self.eventfd_map.pop(event_fd)
|
|
796
|
+
if (events & 1):
|
|
797
|
+
self.loop.remove_reader(event_fd)
|
|
798
|
+
if (events & 4):
|
|
799
|
+
self.loop.remove_writer(event_fd)
|
|
800
|
+
return 0
|
|
801
|
+
|
|
802
|
+
def _call_later(self, delay, callback, ctx):
|
|
803
|
+
def cb():
|
|
804
|
+
self.timer_map.pop(timer_id)
|
|
805
|
+
callback(ctx)
|
|
806
|
+
timer_id = insert_with_new_id(self.timer_map, self.loop.call_later(delay, cb))
|
|
807
|
+
return timer_id
|
|
808
|
+
|
|
809
|
+
def _cancel_timer(self, timer_id):
|
|
810
|
+
self.timer_map.pop(timer_id).cancel()
|
|
811
|
+
return 0
|
|
812
|
+
|
|
813
|
+
def _load_py_intf(self, name, intf_handle):
|
|
814
|
+
"""
|
|
815
|
+
Creates a new python type for the specified libfibre interface handle or
|
|
816
|
+
returns the existing python type if one was already create before.
|
|
817
|
+
|
|
818
|
+
Behind the scenes the python type will react to future events coming
|
|
819
|
+
from libfibre, such as functions/attributes being added/removed.
|
|
820
|
+
"""
|
|
821
|
+
if intf_handle in self.interfaces:
|
|
822
|
+
return self.interfaces[intf_handle]
|
|
823
|
+
else:
|
|
824
|
+
if name is None:
|
|
825
|
+
name = "anonymous_interface_" + str(intf_handle)
|
|
826
|
+
py_intf = self.interfaces[intf_handle] = type(name, (RemoteObject,), {'_handle': intf_handle, '_refcount': 0})
|
|
827
|
+
#exit(1)
|
|
828
|
+
libfibre_subscribe_to_interface(intf_handle, self.c_on_attribute_added, self.c_on_attribute_removed, self.c_on_function_added, self.c_on_function_removed, intf_handle)
|
|
829
|
+
return py_intf
|
|
830
|
+
|
|
831
|
+
def _load_py_obj(self, obj_handle, intf_handle):
|
|
832
|
+
if not obj_handle in self._objects:
|
|
833
|
+
name = None # TODO: load from libfibre
|
|
834
|
+
py_intf = self._load_py_intf(name, intf_handle)
|
|
835
|
+
py_obj = py_intf(self, obj_handle)
|
|
836
|
+
self._objects[obj_handle] = py_obj
|
|
837
|
+
else:
|
|
838
|
+
py_obj = self._objects[obj_handle]
|
|
839
|
+
|
|
840
|
+
# Note: this refcount does not count the python references to the object
|
|
841
|
+
# but rather mirrors the libfibre-internal refcount of the object. This
|
|
842
|
+
# is so that we can destroy the Python object when libfibre releases it.
|
|
843
|
+
py_obj._refcount += 1
|
|
844
|
+
return py_obj
|
|
845
|
+
|
|
846
|
+
def _release_py_obj(self, obj_handle):
|
|
847
|
+
py_obj = self._objects[obj_handle]
|
|
848
|
+
py_obj._refcount -= 1
|
|
849
|
+
if py_obj._refcount <= 0:
|
|
850
|
+
self._objects.pop(obj_handle)
|
|
851
|
+
py_obj._destroy()
|
|
852
|
+
|
|
853
|
+
def _on_found_object(self, ctx, obj, intf):
|
|
854
|
+
py_obj = self._load_py_obj(obj, intf)
|
|
855
|
+
discovery = self.discovery_processes[ctx]
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
# TODO: this is a hack because ObjectPtrCodec is broken
|
|
859
|
+
def load(subobj):
|
|
860
|
+
for key in dir(subobj):
|
|
861
|
+
if not key.startswith('_'):
|
|
862
|
+
attr = getattr(subobj.__class__, key)
|
|
863
|
+
if isinstance(attr, RemoteAttribute):
|
|
864
|
+
load(attr._get_obj(subobj))
|
|
865
|
+
|
|
866
|
+
load(py_obj)
|
|
867
|
+
|
|
868
|
+
discovery._unannounced.append(py_obj)
|
|
869
|
+
old_future = discovery._future
|
|
870
|
+
discovery._future = self.loop.create_future()
|
|
871
|
+
old_future.set_result(None)
|
|
872
|
+
|
|
873
|
+
def _on_lost_object(self, ctx, obj):
|
|
874
|
+
assert(obj)
|
|
875
|
+
self._release_py_obj(obj)
|
|
876
|
+
|
|
877
|
+
def _on_discovery_stopped(self, ctx, result):
|
|
878
|
+
print("discovery stopped")
|
|
879
|
+
|
|
880
|
+
def _on_attribute_added(self, ctx, attr, name, name_length, subintf, subintf_name, subintf_name_length):
|
|
881
|
+
name = string_at(name, name_length).decode('utf-8')
|
|
882
|
+
subintf_name = None if subintf_name is None else string_at(subintf_name, subintf_name_length).decode('utf-8')
|
|
883
|
+
intf = self.interfaces[ctx]
|
|
884
|
+
|
|
885
|
+
magic_getter = not subintf_name is None and subintf_name.startswith("fibre.Property<") and subintf_name.endswith(">")
|
|
886
|
+
magic_setter = not subintf_name is None and subintf_name.startswith("fibre.Property<readwrite ") and subintf_name.endswith(">")
|
|
887
|
+
|
|
888
|
+
setattr(intf, name, RemoteAttribute(self, attr, subintf, subintf_name, magic_getter, magic_setter))
|
|
889
|
+
if magic_getter or magic_setter:
|
|
890
|
+
setattr(intf, "_" + name + "_property", RemoteAttribute(self, attr, subintf, subintf_name, False, False))
|
|
891
|
+
|
|
892
|
+
def _on_attribute_removed(self, ctx, attr):
|
|
893
|
+
print("attribute removed") # TODO
|
|
894
|
+
|
|
895
|
+
def _on_function_added(self, ctx, func, name, name_length, input_names, input_codecs, output_names, output_codecs):
|
|
896
|
+
name = string_at(name, name_length).decode('utf-8')
|
|
897
|
+
inputs = list(decode_arg_list(input_names, input_codecs))
|
|
898
|
+
outputs = list(decode_arg_list(output_names, output_codecs))
|
|
899
|
+
intf = self.interfaces[ctx]
|
|
900
|
+
setattr(intf, name, RemoteFunction(self, func, inputs, outputs))
|
|
901
|
+
|
|
902
|
+
def _on_function_removed(self, ctx, func):
|
|
903
|
+
print("function removed") # TODO
|
|
904
|
+
|
|
905
|
+
def _on_call_completed(self, ctx, status, tx_end, rx_end, tx_buf, tx_len, rx_buf, rx_len):
|
|
906
|
+
call = self._calls.pop(ctx)
|
|
907
|
+
|
|
908
|
+
call.ag_await.set_result((status, tx_end, rx_end))
|
|
909
|
+
|
|
910
|
+
return kFibreBusy
|
|
911
|
+
|
|
912
|
+
class Discovery():
|
|
913
|
+
"""
|
|
914
|
+
All public members of this class are thread-safe.
|
|
915
|
+
"""
|
|
916
|
+
|
|
917
|
+
def __init__(self, domain):
|
|
918
|
+
self._domain = domain
|
|
919
|
+
self._id = 0
|
|
920
|
+
self._discovery_handle = c_void_p(0)
|
|
921
|
+
self._unannounced = []
|
|
922
|
+
self._future = domain._libfibre.loop.create_future()
|
|
923
|
+
|
|
924
|
+
async def _next(self):
|
|
925
|
+
if len(self._unannounced) == 0:
|
|
926
|
+
await self._future
|
|
927
|
+
return self._unannounced.pop(0)
|
|
928
|
+
|
|
929
|
+
def _stop(self):
|
|
930
|
+
self._domain._libfibre.discovery_processes.pop(self._id)
|
|
931
|
+
libfibre_stop_discovery(self._discovery_handle)
|
|
932
|
+
self._future.set_exception(asyncio.CancelledError())
|
|
933
|
+
|
|
934
|
+
def stop(self):
|
|
935
|
+
if threading.current_thread() == libfibre_thread:
|
|
936
|
+
self._stop()
|
|
937
|
+
else:
|
|
938
|
+
run_coroutine_threadsafe(self._domain._libfibre.loop, self._stop)
|
|
939
|
+
|
|
940
|
+
class _Domain():
|
|
941
|
+
"""
|
|
942
|
+
All public members of this class are thread-safe.
|
|
943
|
+
"""
|
|
944
|
+
|
|
945
|
+
def __init__(self, libfibre, handle):
|
|
946
|
+
self._libfibre = libfibre
|
|
947
|
+
self._domain_handle = handle
|
|
948
|
+
|
|
949
|
+
def _close(self):
|
|
950
|
+
libfibre_close_domain(self._domain_handle)
|
|
951
|
+
self._domain_handle = None
|
|
952
|
+
#decrement_lib_refcount()
|
|
953
|
+
|
|
954
|
+
def _start_discovery(self):
|
|
955
|
+
discovery = Discovery(self)
|
|
956
|
+
discovery._id = insert_with_new_id(self._libfibre.discovery_processes, discovery)
|
|
957
|
+
libfibre_start_discovery(self._domain_handle, byref(discovery._discovery_handle), self._libfibre.c_on_found_object, self._libfibre.c_on_lost_object, self._libfibre.c_on_discovery_stopped, discovery._id)
|
|
958
|
+
return discovery
|
|
959
|
+
|
|
960
|
+
async def _discover_one(self):
|
|
961
|
+
discovery = self._start_discovery()
|
|
962
|
+
obj = await discovery._next()
|
|
963
|
+
discovery._stop()
|
|
964
|
+
return obj
|
|
965
|
+
|
|
966
|
+
def discover_one(self):
|
|
967
|
+
"""
|
|
968
|
+
Blocks until exactly one object is discovered.
|
|
969
|
+
"""
|
|
970
|
+
return run_coroutine_threadsafe(self._libfibre.loop, self._discover_one)
|
|
971
|
+
|
|
972
|
+
def run_discovery(self, callback):
|
|
973
|
+
"""
|
|
974
|
+
Invokes `callback` for every object that is discovered. The callback is
|
|
975
|
+
invoked on the libfibre thread and can be an asynchronous function.
|
|
976
|
+
Returns a `Discovery` object on which `stop()` can be called to
|
|
977
|
+
terminate the discovery.
|
|
978
|
+
"""
|
|
979
|
+
discovery = run_coroutine_threadsafe(self._libfibre.loop, self._start_discovery)
|
|
980
|
+
async def loop():
|
|
981
|
+
while True:
|
|
982
|
+
obj = await discovery._next()
|
|
983
|
+
await callback(obj)
|
|
984
|
+
self._libfibre.loop.call_soon_threadsafe(lambda: asyncio.ensure_future(loop()))
|
|
985
|
+
return discovery
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
class Domain():
|
|
989
|
+
def __init__(self, path):
|
|
990
|
+
increment_lib_refcount()
|
|
991
|
+
self._opened_domain = run_coroutine_threadsafe(libfibre.loop, lambda: Domain._open(path))
|
|
992
|
+
|
|
993
|
+
def _open(path):
|
|
994
|
+
assert(libfibre_thread == threading.current_thread())
|
|
995
|
+
buf = path.encode('ascii')
|
|
996
|
+
domain_handle = libfibre_open_domain(libfibre.ctx, buf, len(buf))
|
|
997
|
+
return _Domain(libfibre, domain_handle)
|
|
998
|
+
|
|
999
|
+
def __enter__(self):
|
|
1000
|
+
return self._opened_domain
|
|
1001
|
+
|
|
1002
|
+
def __exit__(self, type, value, traceback):
|
|
1003
|
+
run_coroutine_threadsafe(self._opened_domain._libfibre.loop, self._opened_domain._close)
|
|
1004
|
+
self._opened_domain = None
|
|
1005
|
+
decrement_lib_refcount()
|
|
1006
|
+
|
|
1007
|
+
libfibre = None
|
|
1008
|
+
|
|
1009
|
+
def _run_event_loop():
|
|
1010
|
+
global libfibre
|
|
1011
|
+
global terminate_libfibre
|
|
1012
|
+
|
|
1013
|
+
loop = asyncio.new_event_loop()
|
|
1014
|
+
asyncio.set_event_loop(loop)
|
|
1015
|
+
|
|
1016
|
+
terminate_libfibre = loop.create_future()
|
|
1017
|
+
libfibre = LibFibre(loop)
|
|
1018
|
+
|
|
1019
|
+
libfibre.loop.run_until_complete(terminate_libfibre)
|
|
1020
|
+
|
|
1021
|
+
libfibre_close(libfibre.ctx)
|
|
1022
|
+
|
|
1023
|
+
# Detach all objects that still exist
|
|
1024
|
+
# TODO: the proper way would be either of these
|
|
1025
|
+
# - provide a libfibre function to destroy an object on-demand which we'd
|
|
1026
|
+
# call before libfibre_close().
|
|
1027
|
+
# - have libfibre_close() report the destruction of all objects
|
|
1028
|
+
|
|
1029
|
+
while len(libfibre._objects):
|
|
1030
|
+
libfibre._objects.pop(list(libfibre._objects.keys())[0])._destroy()
|
|
1031
|
+
assert(len(libfibre.interfaces) == 0)
|
|
1032
|
+
|
|
1033
|
+
libfibre = None
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
lock = threading.Lock()
|
|
1037
|
+
libfibre_refcount = 0
|
|
1038
|
+
libfibre_thread = None
|
|
1039
|
+
|
|
1040
|
+
def increment_lib_refcount():
|
|
1041
|
+
global libfibre_refcount
|
|
1042
|
+
global libfibre_thread
|
|
1043
|
+
|
|
1044
|
+
with lock:
|
|
1045
|
+
libfibre_refcount += 1
|
|
1046
|
+
#print("inc refcount to {}".format(libfibre_refcount))
|
|
1047
|
+
|
|
1048
|
+
if libfibre_refcount == 1:
|
|
1049
|
+
libfibre_thread = threading.Thread(target = _run_event_loop)
|
|
1050
|
+
libfibre_thread.start()
|
|
1051
|
+
|
|
1052
|
+
while libfibre is None:
|
|
1053
|
+
time.sleep(0.1)
|
|
1054
|
+
|
|
1055
|
+
def decrement_lib_refcount():
|
|
1056
|
+
global libfibre_refcount
|
|
1057
|
+
global libfibre_thread
|
|
1058
|
+
|
|
1059
|
+
with lock:
|
|
1060
|
+
#print("dec refcount from {}".format(libfibre_refcount))
|
|
1061
|
+
libfibre_refcount -= 1
|
|
1062
|
+
|
|
1063
|
+
if libfibre_refcount == 0:
|
|
1064
|
+
libfibre.loop.call_soon_threadsafe(lambda: terminate_libfibre.set_result(True))
|
|
1065
|
+
|
|
1066
|
+
# It's unlikely that releasing fibre from a fibre callback is ok. If
|
|
1067
|
+
# there is a valid scenario for this then we can remove the assert.
|
|
1068
|
+
assert(libfibre_thread != threading.current_thread())
|
|
1069
|
+
|
|
1070
|
+
libfibre_thread.join()
|
|
1071
|
+
libfibre_thread = None
|
|
1072
|
+
|
|
1073
|
+
def get_user_name(obj):
|
|
1074
|
+
"""
|
|
1075
|
+
Can be overridden by the application to return the user-facing name of an
|
|
1076
|
+
object.
|
|
1077
|
+
"""
|
|
1078
|
+
return "[anonymous object]"
|