moteus 0.3.91__tar.gz → 0.3.93__tar.gz

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 (34) hide show
  1. {moteus-0.3.91 → moteus-0.3.93}/PKG-INFO +1 -1
  2. {moteus-0.3.91 → moteus-0.3.93}/moteus/command.py +4 -0
  3. {moteus-0.3.91 → moteus-0.3.93}/moteus/moteus.py +73 -1
  4. {moteus-0.3.91 → moteus-0.3.93}/moteus/moteus_tool.py +3 -17
  5. {moteus-0.3.91 → moteus-0.3.93}/moteus/transport.py +72 -10
  6. {moteus-0.3.91 → moteus-0.3.93}/moteus/version.py +1 -1
  7. {moteus-0.3.91 → moteus-0.3.93}/moteus.egg-info/PKG-INFO +1 -1
  8. {moteus-0.3.91 → moteus-0.3.93}/setup.py +1 -1
  9. {moteus-0.3.91 → moteus-0.3.93}/README.md +0 -0
  10. {moteus-0.3.91 → moteus-0.3.93}/moteus/__init__.py +0 -0
  11. {moteus-0.3.91 → moteus-0.3.93}/moteus/aioserial.py +0 -0
  12. {moteus-0.3.91 → moteus-0.3.93}/moteus/aiostream.py +0 -0
  13. {moteus-0.3.91 → moteus-0.3.93}/moteus/calibrate_encoder.py +0 -0
  14. {moteus-0.3.91 → moteus-0.3.93}/moteus/device_info.py +0 -0
  15. {moteus-0.3.91 → moteus-0.3.93}/moteus/export.py +0 -0
  16. {moteus-0.3.91 → moteus-0.3.93}/moteus/fdcanusb.py +0 -0
  17. {moteus-0.3.91 → moteus-0.3.93}/moteus/fdcanusb_device.py +0 -0
  18. {moteus-0.3.91 → moteus-0.3.93}/moteus/multiplex.py +0 -0
  19. {moteus-0.3.91 → moteus-0.3.93}/moteus/posix_aioserial.py +0 -0
  20. {moteus-0.3.91 → moteus-0.3.93}/moteus/protocol.py +0 -0
  21. {moteus-0.3.91 → moteus-0.3.93}/moteus/pythoncan.py +0 -0
  22. {moteus-0.3.91 → moteus-0.3.93}/moteus/pythoncan_device.py +0 -0
  23. {moteus-0.3.91 → moteus-0.3.93}/moteus/reader.py +0 -0
  24. {moteus-0.3.91 → moteus-0.3.93}/moteus/regression.py +0 -0
  25. {moteus-0.3.91 → moteus-0.3.93}/moteus/transport_device.py +0 -0
  26. {moteus-0.3.91 → moteus-0.3.93}/moteus/transport_factory.py +0 -0
  27. {moteus-0.3.91 → moteus-0.3.93}/moteus/transport_wrapper.py +0 -0
  28. {moteus-0.3.91 → moteus-0.3.93}/moteus/win32_aioserial.py +0 -0
  29. {moteus-0.3.91 → moteus-0.3.93}/moteus.egg-info/SOURCES.txt +0 -0
  30. {moteus-0.3.91 → moteus-0.3.93}/moteus.egg-info/dependency_links.txt +0 -0
  31. {moteus-0.3.91 → moteus-0.3.93}/moteus.egg-info/entry_points.txt +0 -0
  32. {moteus-0.3.91 → moteus-0.3.93}/moteus.egg-info/requires.txt +0 -0
  33. {moteus-0.3.91 → moteus-0.3.93}/moteus.egg-info/top_level.txt +0 -0
  34. {moteus-0.3.91 → moteus-0.3.93}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: moteus
3
- Version: 0.3.91
3
+ Version: 0.3.93
4
4
  Summary: moteus brushless controller library and tools
5
5
  Home-page: https://github.com/mjbots/moteus
6
6
  Author: mjbots Robotic Systems
@@ -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).
@@ -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
- return result;
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):
@@ -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(_deep_asdict(result), indent=2))
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(_deep_asdict(drv8323), indent=2))
1166
+ print(json.dumps(namedtuple_to_dict(drv8323), indent=2))
1181
1167
  sys.exit(1)
1182
1168
  else:
1183
1169
  raise
@@ -321,16 +321,36 @@ class Transport:
321
321
  # responses.
322
322
  return lambda f: True
323
323
 
324
- # Filter by both the source and destination device IDs.
325
- return lambda f: (
326
- (((f.arbitration_id >> 8) & 0x7f) == self._make_canid(command.destination) or
327
- self._make_canid(command.destination) == 0x7f) and
328
- (f.arbitration_id & 0x7f) == command.source)
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
- # (TransportDevice.Request)
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
- tasks.append(device.transaction(
400
- request_list,
401
- request_attitude=request_attitude,
402
- force_can_check=force_can_check))
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.
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- VERSION="0.3.91"
15
+ VERSION="0.3.93"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: moteus
3
- Version: 0.3.91
3
+ Version: 0.3.93
4
4
  Summary: moteus brushless controller library and tools
5
5
  Home-page: https://github.com/mjbots/moteus
6
6
  Author: mjbots Robotic Systems
@@ -25,7 +25,7 @@ long_description = (here / 'README.md').read_text(encoding='utf-8')
25
25
 
26
26
  setuptools.setup(
27
27
  name = 'moteus',
28
- version = "0.3.91",
28
+ version = "0.3.93",
29
29
  description = 'moteus brushless controller library and tools',
30
30
  long_description = long_description,
31
31
  long_description_content_type = 'text/markdown',
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes