moteus 0.3.91__py3-none-any.whl → 0.3.93__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.
- moteus/command.py +4 -0
- moteus/moteus.py +73 -1
- moteus/moteus_tool.py +3 -17
- moteus/transport.py +72 -10
- moteus/version.py +1 -1
- {moteus-0.3.91.dist-info → moteus-0.3.93.dist-info}/METADATA +1 -1
- {moteus-0.3.91.dist-info → moteus-0.3.93.dist-info}/RECORD +10 -10
- {moteus-0.3.91.dist-info → moteus-0.3.93.dist-info}/WHEEL +0 -0
- {moteus-0.3.91.dist-info → moteus-0.3.93.dist-info}/entry_points.txt +0 -0
- {moteus-0.3.91.dist-info → moteus-0.3.93.dist-info}/top_level.txt +0 -0
moteus/command.py
CHANGED
@@ -24,6 +24,10 @@ class Command():
|
|
24
24
|
can_prefix = 0x0000 # a 13 bit CAN prefix
|
25
25
|
expected_reply_size = 0
|
26
26
|
|
27
|
+
# An optional function object which when passed a Frame returns
|
28
|
+
# True if the frame matches what is expected for this command.
|
29
|
+
reply_filter = None
|
30
|
+
|
27
31
|
# If True, then the following parameters are used directly instead
|
28
32
|
# of being calculated from destination and source (i.e. for
|
29
33
|
# non-moteus devices).
|
moteus/moteus.py
CHANGED
@@ -14,6 +14,7 @@
|
|
14
14
|
|
15
15
|
import asyncio
|
16
16
|
import argparse
|
17
|
+
import collections.abc
|
17
18
|
import copy
|
18
19
|
import enum
|
19
20
|
import io
|
@@ -30,6 +31,32 @@ from .transport_factory import TRANSPORT_FACTORIES, get_singleton_transport, mak
|
|
30
31
|
import moteus.reader
|
31
32
|
|
32
33
|
|
34
|
+
def namedtuple_to_dict(obj):
|
35
|
+
'''Convert a namedtuple recursively into a nested dictionary.
|
36
|
+
|
37
|
+
This function handles namedtuples, dictionaries, and sequences,
|
38
|
+
converting them to nested dictionaries and lists suitable for
|
39
|
+
JSON serialization.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
obj: The object to convert (typically a namedtuple)
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
dict, list, or primitive type representation of the input
|
46
|
+
'''
|
47
|
+
|
48
|
+
if isinstance(obj, tuple) and hasattr(obj, "_fields"):
|
49
|
+
return {field: namedtuple_to_dict(getattr(obj, field)) for field in obj._fields}
|
50
|
+
|
51
|
+
if isinstance(obj, collections.abc.Mapping):
|
52
|
+
return {k: namedtuple_to_dict(v) for k, v in obj.items()}
|
53
|
+
|
54
|
+
if isinstance(obj, collections.abc.Sequence) and not isinstance(obj, (str, bytes, bytearray)):
|
55
|
+
return [namedtuple_to_dict(x) for x in obj]
|
56
|
+
|
57
|
+
return obj
|
58
|
+
|
59
|
+
|
33
60
|
def _merge_resolutions(a, b):
|
34
61
|
if a == mp.IGNORE:
|
35
62
|
return b
|
@@ -271,14 +298,27 @@ class Controller:
|
|
271
298
|
return buf.getvalue(), expected_reply_size
|
272
299
|
|
273
300
|
def _format_query(self, query, query_override, data_buf, result):
|
301
|
+
def expect_reply(frame):
|
302
|
+
# For a reply to these requests, the first byte should be
|
303
|
+
# one of the reply or error frames.
|
304
|
+
if len(frame.data) < 1:
|
305
|
+
return False
|
306
|
+
|
307
|
+
if frame.data[0] & 0xf0 == 0x20 or frame.data[0] == 0x31:
|
308
|
+
return True
|
309
|
+
|
310
|
+
return False
|
311
|
+
|
274
312
|
if query_override is not None:
|
275
313
|
query_data, expected_reply_size = \
|
276
314
|
self._make_query_data(query_override)
|
277
315
|
data_buf.write(query_data)
|
278
316
|
result.expected_reply_size = expected_reply_size
|
317
|
+
result.reply_filter = expect_reply
|
279
318
|
elif query:
|
280
319
|
data_buf.write(self._query_data)
|
281
320
|
result.expected_reply_size = self._default_query_reply_size
|
321
|
+
result.reply_filter = expect_reply
|
282
322
|
|
283
323
|
def _make_command(self, *, query, query_override=None):
|
284
324
|
result = cmd.Command()
|
@@ -311,7 +351,18 @@ class Controller:
|
|
311
351
|
else:
|
312
352
|
result.data = self._query_data
|
313
353
|
result.expected_reply_size = self._default_query_reply_size
|
314
|
-
|
354
|
+
|
355
|
+
def expect_reply(frame):
|
356
|
+
if len(frame.data) < 1:
|
357
|
+
return False
|
358
|
+
|
359
|
+
if frame.data[0] & 0xf0 == 0x20 or frame.data[0] == 0x31:
|
360
|
+
return True
|
361
|
+
|
362
|
+
return False
|
363
|
+
|
364
|
+
result.reply_filter = expect_reply
|
365
|
+
return result
|
315
366
|
|
316
367
|
async def query(self, **kwargs):
|
317
368
|
return await self.execute(self.make_query(**kwargs))
|
@@ -336,6 +387,17 @@ class Controller:
|
|
336
387
|
|
337
388
|
result.data = data_buf.getvalue()
|
338
389
|
result.expected_reply_size = c.reply_size
|
390
|
+
|
391
|
+
def expect_reply(frame):
|
392
|
+
if len(frame.data) < 1:
|
393
|
+
return False
|
394
|
+
|
395
|
+
if frame.data[0] & 0xf0 == 0x20 or frame.data[0] == 0x31:
|
396
|
+
return True
|
397
|
+
|
398
|
+
return False
|
399
|
+
|
400
|
+
result.reply_filter = expect_reply
|
339
401
|
return result
|
340
402
|
|
341
403
|
async def custom_query(self, *args, **kwargs):
|
@@ -906,6 +968,7 @@ class Controller:
|
|
906
968
|
data_buf.write(data)
|
907
969
|
|
908
970
|
result.data = data_buf.getvalue()
|
971
|
+
|
909
972
|
return result
|
910
973
|
|
911
974
|
async def send_diagnostic_write(self, *args, **kwargs):
|
@@ -923,6 +986,15 @@ class Controller:
|
|
923
986
|
|
924
987
|
result.data = data_buf.getvalue()
|
925
988
|
result.expected_reply_size = 3 + max_length
|
989
|
+
|
990
|
+
def expect_diagnostic_response(frame):
|
991
|
+
if len(frame.data) < 3:
|
992
|
+
return False
|
993
|
+
|
994
|
+
return frame.data[0] == 0x41
|
995
|
+
|
996
|
+
result.reply_filter = expect_diagnostic_response
|
997
|
+
|
926
998
|
return result
|
927
999
|
|
928
1000
|
async def diagnostic_read(self, *args, **kwargs):
|
moteus/moteus_tool.py
CHANGED
@@ -37,6 +37,7 @@ from . import moteus
|
|
37
37
|
from . import aiostream
|
38
38
|
from . import regression
|
39
39
|
from . import calibrate_encoder as ce
|
40
|
+
from .moteus import namedtuple_to_dict
|
40
41
|
|
41
42
|
from .device_info import DeviceAddress
|
42
43
|
|
@@ -63,21 +64,6 @@ CURRENT_QUALITY_MIN = 20
|
|
63
64
|
VOLTAGE_MODE_QUALITY_MIN = 40
|
64
65
|
|
65
66
|
|
66
|
-
def _deep_asdict(obj):
|
67
|
-
'''Convert a namedtuple recursively into a nested dictionary'''
|
68
|
-
|
69
|
-
if isinstance(obj, tuple) and hasattr(obj, "_fields"):
|
70
|
-
return {field: _deep_asdict(getattr(obj, field)) for field in obj._fields}
|
71
|
-
|
72
|
-
if isinstance(obj, collections.abc.Mapping):
|
73
|
-
return {k: _deep_asdict(v) for k, v in obj.items()}
|
74
|
-
|
75
|
-
if isinstance(obj, collections.abc.Sequence) and not isinstance(obj, (str, bytes, bytearray)):
|
76
|
-
return [_deep_asdict(x) for x in obj]
|
77
|
-
|
78
|
-
return obj
|
79
|
-
|
80
|
-
|
81
67
|
def _wrap_neg_pi_to_pi(value):
|
82
68
|
while value > math.pi:
|
83
69
|
value -= 2.0 * math.pi
|
@@ -1014,7 +1000,7 @@ class Stream:
|
|
1014
1000
|
|
1015
1001
|
async def do_read(self, channel):
|
1016
1002
|
result = await self.read_data(channel)
|
1017
|
-
print(json.dumps(
|
1003
|
+
print(json.dumps(namedtuple_to_dict(result), indent=2))
|
1018
1004
|
|
1019
1005
|
async def do_flash(self, elffile):
|
1020
1006
|
elf = _read_elf(elffile, [".text", ".ARM.extab", ".ARM.exidx",
|
@@ -1177,7 +1163,7 @@ class Stream:
|
|
1177
1163
|
print()
|
1178
1164
|
print("*** FAILED: Gate driver fault (code=33) during calibration: ")
|
1179
1165
|
drv8323 = await self.read_data("drv8323")
|
1180
|
-
print(json.dumps(
|
1166
|
+
print(json.dumps(namedtuple_to_dict(drv8323), indent=2))
|
1181
1167
|
sys.exit(1)
|
1182
1168
|
else:
|
1183
1169
|
raise
|
moteus/transport.py
CHANGED
@@ -321,16 +321,36 @@ class Transport:
|
|
321
321
|
# responses.
|
322
322
|
return lambda f: True
|
323
323
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
324
|
+
dest_id = self._make_canid(command.destination)
|
325
|
+
source_id = command.source
|
326
|
+
reply_filter = getattr(command, 'reply_filter', None)
|
327
|
+
prefix = getattr(command, 'can_prefix', 0)
|
328
|
+
|
329
|
+
def predicate(f,
|
330
|
+
dest_id=dest_id,
|
331
|
+
source_id=source_id,
|
332
|
+
prefix=prefix):
|
333
|
+
if ((f.arbitration_id >> 16) & 0x1fff) != prefix:
|
334
|
+
return False
|
335
|
+
|
336
|
+
if ((f.arbitration_id >> 8) & 0x7f != dest_id and
|
337
|
+
dest_id != 0x7f):
|
338
|
+
return False
|
339
|
+
|
340
|
+
if f.arbitration_id & 0x7f != source_id:
|
341
|
+
return False
|
342
|
+
|
343
|
+
if reply_filter and not reply_filter(f):
|
344
|
+
return False
|
345
|
+
|
346
|
+
return True
|
347
|
+
|
348
|
+
return predicate
|
329
349
|
|
330
350
|
async def _cycle_batch(self, commands, request_attitude,
|
331
351
|
read_unsolicited, force_can_check):
|
332
352
|
# Group requests by device. This is a map from device to:
|
333
|
-
#
|
353
|
+
# [TransportDevice.Request, ...]
|
334
354
|
device_requests = collections.defaultdict(list)
|
335
355
|
|
336
356
|
for i, command in enumerate(commands):
|
@@ -396,10 +416,52 @@ class Transport:
|
|
396
416
|
tasks = []
|
397
417
|
device_list = []
|
398
418
|
for device, request_list in device_requests.items():
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
419
|
+
# We can send at most one broadcast frame that needs
|
420
|
+
# replies per transaction.
|
421
|
+
broadcast_with_reply = lambda request: (
|
422
|
+
(request.frame.arbitration_id & 0x7f) == 0x7f and
|
423
|
+
request.frame_filter is not None)
|
424
|
+
|
425
|
+
broadcast_requests = [x for x in request_list
|
426
|
+
if broadcast_with_reply(x)]
|
427
|
+
non_broadcast_requests = [x for x in request_list
|
428
|
+
if not broadcast_with_reply(x)]
|
429
|
+
|
430
|
+
async def run_in_sequence(d=device,
|
431
|
+
br=broadcast_requests,
|
432
|
+
nbr=non_broadcast_requests):
|
433
|
+
|
434
|
+
this_request_attitude = request_attitude
|
435
|
+
this_force_can_check = force_can_check
|
436
|
+
|
437
|
+
for b in br:
|
438
|
+
await d.transaction(
|
439
|
+
[b],
|
440
|
+
request_attitude=this_request_attitude,
|
441
|
+
force_can_check=this_force_can_check)
|
442
|
+
|
443
|
+
this_request_attitude = False
|
444
|
+
this_force_can_check = None
|
445
|
+
|
446
|
+
if nbr:
|
447
|
+
await d.transaction(
|
448
|
+
nbr,
|
449
|
+
request_attitude=this_request_attitude,
|
450
|
+
force_can_check=this_force_can_check)
|
451
|
+
|
452
|
+
if (len(broadcast_requests) + (1 if non_broadcast_requests else 0)) == 1:
|
453
|
+
# We can use a single transaction, either because we
|
454
|
+
# have a single broadcast request and no other
|
455
|
+
# requests, or no broadcast requests.
|
456
|
+
tasks.append(device.transaction(
|
457
|
+
request_list,
|
458
|
+
request_attitude=request_attitude,
|
459
|
+
force_can_check=force_can_check))
|
460
|
+
else:
|
461
|
+
# We have to do multiple transactions to complete
|
462
|
+
# this.
|
463
|
+
tasks.append(run_in_sequence())
|
464
|
+
|
403
465
|
device_list.append(device)
|
404
466
|
|
405
467
|
# Wait for all transports to complete.
|
moteus/version.py
CHANGED
@@ -2,13 +2,13 @@ moteus/__init__.py,sha256=DxasQre5HmK3BS3I35K8GEUvFZF3_OE_hjVzQy-seIE,739
|
|
2
2
|
moteus/aioserial.py,sha256=GeWuvsZKCRrfBN33JZFjtBXPr-0sKpQv9shRn2ulcDA,1079
|
3
3
|
moteus/aiostream.py,sha256=YAkVF6QWsA49vqO-GgXEohDghqm_-nnajJzhO_Q9qNQ,3696
|
4
4
|
moteus/calibrate_encoder.py,sha256=Ami5e-LFw4RLoLseKcZx9QfS1PjQZJUwygvNZfPqd04,15494
|
5
|
-
moteus/command.py,sha256=
|
5
|
+
moteus/command.py,sha256=f4r2NeFFKZ9ypS47cMKf325sr_eqS4OHfAYM4YsaXvo,1462
|
6
6
|
moteus/device_info.py,sha256=TBYQtfXDfIlyg17HXnUOKj13JZNc6iWvt0ZiZm0MlXA,2292
|
7
7
|
moteus/export.py,sha256=9cR_Y3JypZ756JYNKeYRamOwwzNmt5Ign8yJ7BRoBuk,2124
|
8
8
|
moteus/fdcanusb.py,sha256=96WQjwjTgHEPeIAV0A0AsW0_n3hbyUEOl2ZuWsaiOBE,1058
|
9
9
|
moteus/fdcanusb_device.py,sha256=rYJdncT9C7ikavNqiFpjb_9n8fsLbsd5CGpbdm_fSyw,11456
|
10
|
-
moteus/moteus.py,sha256=
|
11
|
-
moteus/moteus_tool.py,sha256=
|
10
|
+
moteus/moteus.py,sha256=vyAdT6YT3-9i9erJ-QUrR1bY2-ETgkhRzehDil99pQc,42870
|
11
|
+
moteus/moteus_tool.py,sha256=7LSxPc4gwJTAkOuBVhMm0arQGwP4336S71wBZBTE-Jg,99267
|
12
12
|
moteus/multiplex.py,sha256=2tdNX5JSh21TOjN6N9LKribLQtVYyyYbXjzwXB64sfA,12119
|
13
13
|
moteus/posix_aioserial.py,sha256=2oDrw8TBEwuEQjY41g9rHeuFeffcPHqMwNS3nf5NVq8,3137
|
14
14
|
moteus/protocol.py,sha256=zzoKukC6p5MemConDFdUKyk3QuSyMM-tgzXDITFPYss,13407
|
@@ -16,14 +16,14 @@ moteus/pythoncan.py,sha256=7VMCPHvFasy9V507kKVV5jUyzZXFjvhsbqT5hMYI-jA,864
|
|
16
16
|
moteus/pythoncan_device.py,sha256=e-NGnMX4-tFhQY8PShNJiA8GLwf_iKU_BiJsTekM6B8,7710
|
17
17
|
moteus/reader.py,sha256=9i1-h4aGd4syfqtWJcpg70Bl-bmunkGU4FmXmOLyRt8,12121
|
18
18
|
moteus/regression.py,sha256=M5gjDBYJQ64iBXIrvBhMkD8TYhtlnQ85x8U4py0niGA,1196
|
19
|
-
moteus/transport.py,sha256=
|
19
|
+
moteus/transport.py,sha256=jVrT47O5nv_TpZeNr2GUZd7oOz397zIJJnBQvXT82gY,28125
|
20
20
|
moteus/transport_device.py,sha256=QDSqFiGuLQVvMo26a6CmnMXMzrSovdji6yzHSr_cDGA,5820
|
21
21
|
moteus/transport_factory.py,sha256=vuLUXWl62DcGrXfoQTRKQvx4EAegx22lU8A-18JgJqg,5889
|
22
22
|
moteus/transport_wrapper.py,sha256=7oOBFSvD7Zk5BiWjX_YcGSTlMIZC4GmL88uzVYVQBbE,1733
|
23
|
-
moteus/version.py,sha256=
|
23
|
+
moteus/version.py,sha256=O719mKnILALctfBBRT6sn77cBNmjrF5gGIFv54dJvXo,627
|
24
24
|
moteus/win32_aioserial.py,sha256=culdl-vYxBKD5n2s5LkIMGyUaHyCcEc8BL5-DWEaxX8,2025
|
25
|
-
moteus-0.3.
|
26
|
-
moteus-0.3.
|
27
|
-
moteus-0.3.
|
28
|
-
moteus-0.3.
|
29
|
-
moteus-0.3.
|
25
|
+
moteus-0.3.93.dist-info/METADATA,sha256=_4swFTQeCCEDlqeUTJjwaootPp2_vVLwD1TfDkRdleE,3417
|
26
|
+
moteus-0.3.93.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
27
|
+
moteus-0.3.93.dist-info/entry_points.txt,sha256=accRcwir_K8wCf7i3qHb5R6CPh5SiSgd5a1A92ibb9E,56
|
28
|
+
moteus-0.3.93.dist-info/top_level.txt,sha256=aZzmI_yecTaDrdSp29pTJuowaSQ9dlIZheQpshGg4YQ,7
|
29
|
+
moteus-0.3.93.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|