moteus 0.3.87__py3-none-any.whl → 0.3.89__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/fdcanusb.py CHANGED
@@ -136,17 +136,34 @@ class Fdcanusb:
136
136
  # individually.
137
137
  return [await self._do_command(x) for x in commands]
138
138
 
139
+ def _parse_message(self, line):
140
+ fields = line.split(b" ")
141
+ message = CanMessage()
142
+ message.data = _dehexify(fields[2])
143
+ message.arbitration_id = int(fields[1], 16)
144
+
145
+ flags = fields[3] if len(fields) > 3 else ''
146
+ if b'E' in flags:
147
+ message.is_extended_id = True
148
+ if b'B' in flags:
149
+ message.bitrate_switch = True
150
+ if b'F' in flags:
151
+ message.is_fd = True
152
+
153
+ return message
154
+
139
155
  async def _do_command(self, command):
140
156
  await self.write(command)
141
157
  reply_required = command.reply_required
142
158
 
143
159
  # Get the OK response.
144
- ok_response = await self._readline(self._serial)
145
- if not ok_response.startswith(b"OK"):
146
- raise RuntimeError("fdcanusb lost synchronization, got: " +
147
- ok_response.decode('latin1'))
160
+ while True:
161
+ ok_response = await self._readline(self._serial)
162
+ if ok_response.startswith(b"OK"):
163
+ break
164
+ # Ignore spurious responses until we get an OK.
148
165
 
149
- if reply_required:
166
+ while reply_required:
150
167
  line = await self._readline(self._serial)
151
168
 
152
169
  if not line.startswith(b"rcv"):
@@ -157,16 +174,15 @@ class Fdcanusb:
157
174
  self._debug_log.write(f'{time.time()} < '.encode('latin1') +
158
175
  line.rstrip() + b'\n')
159
176
 
160
- fields = line.split(b" ")
161
- message = CanMessage()
162
- message.data = _dehexify(fields[2])
163
- message.arbitration_id = int(fields[1], 16)
177
+ message = self._parse_message(line)
178
+
179
+ moteus_id = (message.arbitration_id >> 8) & 0x7f
164
180
 
165
- # We are assuming that only one device is responding at a
166
- # time, thus we don't even need to look at the
167
- # source/destination or CAN prefix.
181
+ if command.raw or moteus_id == command.destination:
182
+ return command.parse(message)
168
183
 
169
- return command.parse(message)
184
+ # We are not raw and the message wasn't from the device we
185
+ # were writing to, so just loop and try some more.
170
186
 
171
187
  async def write(self, command):
172
188
  # This merely sends a command and doesn't even wait for an OK
@@ -201,10 +217,7 @@ class Fdcanusb:
201
217
  self._debug_log.write(f'{time.time()} < '.encode('latin1') +
202
218
  line.rstrip() + b'\n')
203
219
 
204
- fields = line.split(b" ")
205
- message = CanMessage()
206
- message.data = _dehexify(fields[2])
207
- message.arbitration_id = int(fields[1], 16)
220
+ message = self._parse_message(line)
208
221
  return message
209
222
 
210
223
  def _round_up_dlc(self, size):
moteus/moteus.py CHANGED
@@ -263,7 +263,11 @@ class Register(enum.IntEnum):
263
263
  AUX2_PWM4 = 0x07e,
264
264
  AUX2_PWM5 = 0x07f,
265
265
 
266
+ MODEL_NUMBER = 0x0100
267
+ FIRMWARE_VERSION = 0x101
266
268
  REGISTER_MAP_VERSION = 0x102
269
+ MULTIPLEX_ID = 0x110
270
+
267
271
  SERIAL_NUMBER = 0x120
268
272
  SERIAL_NUMBER1 = 0x120
269
273
  SERIAL_NUMBER2 = 0x121
@@ -578,7 +582,7 @@ class Result:
578
582
  def make_parser(id):
579
583
  def parse(message):
580
584
  result = Result()
581
- result.id = id
585
+ result.id = (message.arbitration_id >> 8) & 0x7f
582
586
  result.values = parse_reply(message.data)
583
587
 
584
588
  # We store these things just for reference, so that our
@@ -630,6 +634,11 @@ def make_diagnostic_parser(id, channel):
630
634
  return parse
631
635
 
632
636
 
637
+ class FaultError(RuntimeError):
638
+ def __init__(self, mode, code):
639
+ super(FaultError, self).__init__(f"Fault mode={mode} code={code}")
640
+
641
+
633
642
  class Controller:
634
643
  """Operates a single moteus controller across some communication
635
644
  medium.
@@ -1019,6 +1028,9 @@ class Controller:
1019
1028
  reports that the trajectory has been completed.
1020
1029
 
1021
1030
  If the controller is unresponsive, this method will never return.
1031
+
1032
+ If the controller reports a fault or position mode timeout, a
1033
+ FaultError exception will be raised.
1022
1034
  """
1023
1035
 
1024
1036
  if query_override is None:
@@ -1026,6 +1038,10 @@ class Controller:
1026
1038
  else:
1027
1039
  query_override = copy.deepcopy(query_override)
1028
1040
 
1041
+ if query_override.mode == mp.IGNORE:
1042
+ query_override.mode = mp.INT8
1043
+ if query_override.fault == mp.IGNORE:
1044
+ query_override.fault = mp.INT8
1029
1045
  query_override.trajectory_complete = mp.INT8
1030
1046
 
1031
1047
  count = 2
@@ -1041,6 +1057,12 @@ class Controller:
1041
1057
  result.values[Register.TRAJECTORY_COMPLETE]):
1042
1058
  return result
1043
1059
 
1060
+ current_mode = result.values.get(Register.MODE, Mode.STOPPED)
1061
+ fault_code = result.values.get(Register.FAULT, 0)
1062
+
1063
+ if current_mode == Mode.FAULT or current_mode == Mode.TIMEOUT:
1064
+ raise FaultError(current_mode, fault_code)
1065
+
1044
1066
  await asyncio.sleep(period_s)
1045
1067
 
1046
1068
  def make_vfoc(self,
moteus/moteus_tool.py CHANGED
@@ -35,6 +35,12 @@ from . import moteus
35
35
  from . import aiostream
36
36
  from . import regression
37
37
  from . import calibrate_encoder as ce
38
+ try:
39
+ from . import version
40
+ except ImportError:
41
+ class Version:
42
+ VERSION = 'dev'
43
+ version = Version()
38
44
 
39
45
  MAX_FLASH_BLOCK_SIZE = 32
40
46
 
@@ -1256,6 +1262,7 @@ class Stream:
1256
1262
  'motor_position_output_sign' : motor_output_sign,
1257
1263
  'abi_version' : self.firmware.version,
1258
1264
  'voltage_mode_control' : voltage_mode_control,
1265
+ 'py_version' : version.VERSION,
1259
1266
  }
1260
1267
 
1261
1268
  log_filename = f"moteus-cal-{device_info['serial_number']}-{now.strftime('%Y%m%dT%H%M%S.%f')}.log"
@@ -1618,13 +1625,15 @@ class Stream:
1618
1625
  # non-linear, corrupting the result.
1619
1626
 
1620
1627
  # What we'll do is take the very last result, and the last
1621
- # result that is less than 70% of the current of the last
1628
+ # result that is less than X% of the current of the last
1622
1629
  # result.
1623
1630
 
1624
1631
  last_result = results[-1]
1625
1632
 
1626
- less_than = [x for x in results if x[1] < 0.60 * last_result[1]][-1]
1627
-
1633
+ less_than_X = [x for x in results if x[1] < 0.60 * last_result[1]]
1634
+ if len(less_than_X) == 0:
1635
+ raise RuntimeError(f"Could not detect resistance, is motor connected? Peak current only {last_result[1]:.3f}A w/ {last_result[0]:.3f}V applied.")
1636
+ less_than = less_than_X[-1]
1628
1637
 
1629
1638
  resistance = ((last_result[0] - less_than[0]) /
1630
1639
  (last_result[1] - less_than[1]))
@@ -2236,6 +2245,7 @@ async def async_main():
2236
2245
 
2237
2246
  group = parser.add_mutually_exclusive_group()
2238
2247
 
2248
+ group.add_argument('--version', action='store_true')
2239
2249
  group.add_argument('-s', '--stop', action='store_true',
2240
2250
  help='command the servos to stop')
2241
2251
  group.add_argument('-i', '--info', action='store_true',
@@ -2351,6 +2361,10 @@ async def async_main():
2351
2361
 
2352
2362
  args = parser.parse_args()
2353
2363
 
2364
+ if args.version:
2365
+ print(f"moteus_tool version '{version.VERSION}'")
2366
+ sys.exit(0)
2367
+
2354
2368
  with Runner(args) as runner:
2355
2369
  await runner.start()
2356
2370
 
moteus/pythoncan.py CHANGED
@@ -95,16 +95,16 @@ class PythonCan:
95
95
  async def _do_command(self, command):
96
96
  await self.write(command)
97
97
 
98
- if not command.reply_required:
99
- return None
98
+ while command.reply_required:
99
+ reply = await self.read()
100
100
 
101
- reply = await self._reader.get_message()
101
+ moteus_id = (reply.arbitration_id >> 8) & 0x7f
102
102
 
103
- # We're assuming only one device will respond, so the source,
104
- # destination, and CAN prefix should all match without
105
- # checking.
103
+ if command.raw or command.destination == moteus_id:
104
+ return command.parse(reply)
106
105
 
107
- return command.parse(reply)
106
+ # We did not get a response from the device we were hoping
107
+ # for, so just keep waiting.
108
108
 
109
109
  async def write(self, command):
110
110
  reply_required = command.reply_required
@@ -125,7 +125,12 @@ class PythonCan:
125
125
 
126
126
  async def read(self):
127
127
  self._maybe_setup()
128
- return await self._reader.get_message()
128
+ while True:
129
+ frame = await self._reader.get_message()
130
+ if not frame.is_error_frame:
131
+ return frame
132
+ # Just ignore error frames entirely and keep reading until
133
+ # we get a good one.
129
134
 
130
135
  def _round_up_dlc(self, size):
131
136
  if size <= 8:
moteus/version.py CHANGED
@@ -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.87"
15
+ VERSION="0.3.89"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: moteus
3
- Version: 0.3.87
3
+ Version: 0.3.89
4
4
  Summary: moteus brushless controller library and tools
5
5
  Home-page: https://github.com/mjbots/moteus
6
6
  Author: mjbots Robotic Systems
@@ -13,7 +13,6 @@ Classifier: Programming Language :: Python :: 3
13
13
  Requires-Python: >=3.7, <4
14
14
  Description-Content-Type: text/markdown
15
15
  Requires-Dist: importlib-metadata >=3.6
16
- Requires-Dist: numpy <2
17
16
  Requires-Dist: pyelftools >=0.26
18
17
  Requires-Dist: pyserial >=3.5
19
18
  Requires-Dist: python-can >=3.3
@@ -4,20 +4,20 @@ moteus/aiostream.py,sha256=YAkVF6QWsA49vqO-GgXEohDghqm_-nnajJzhO_Q9qNQ,3696
4
4
  moteus/calibrate_encoder.py,sha256=Ami5e-LFw4RLoLseKcZx9QfS1PjQZJUwygvNZfPqd04,15494
5
5
  moteus/command.py,sha256=UkOsbtkso6Oyex8CfbpAKpBNriik519ymxL86EZGkRs,1169
6
6
  moteus/export.py,sha256=XitBUuf4MDRIneXQSUptizIhZi2BdHyFO2Vo_2d2CFI,1742
7
- moteus/fdcanusb.py,sha256=7PrQiCTROY96gdT2zSZYU1bOCriw-I7H6NspaZpiEx4,7431
8
- moteus/moteus.py,sha256=vImSRBn6VEmoijD6hvrSRVxuv1_xVaEJU3F_3Wi6GiE,52498
9
- moteus/moteus_tool.py,sha256=i_XvLfALdd3UitmXdO63tj0iGwCCkaSsBZSyrkgYS9Y,97847
7
+ moteus/fdcanusb.py,sha256=SOAvUlleI6bKwQiApo7nYAaqBM4JoNPn4PHdPqsgsQQ,7707
8
+ moteus/moteus.py,sha256=r-aFtIFaj-0s438bKBc0feOo-G8v4G6jlWk4_siQshE,53322
9
+ moteus/moteus_tool.py,sha256=2uiQyOvdn-Yd2mt0pTsZ2fzJIVb2kJeeyfT2taIiIX0,98391
10
10
  moteus/multiplex.py,sha256=2tdNX5JSh21TOjN6N9LKribLQtVYyyYbXjzwXB64sfA,12119
11
11
  moteus/posix_aioserial.py,sha256=2oDrw8TBEwuEQjY41g9rHeuFeffcPHqMwNS3nf5NVq8,3137
12
- moteus/pythoncan.py,sha256=M5Qba3aCzO_GyXcIsLJxjw3NrPgUk4CUnHOQIhQeIb0,4786
12
+ moteus/pythoncan.py,sha256=j7Gv9tugQqTZbanm1lQGIoTvfmeS2kAxigB0n1a50lo,5039
13
13
  moteus/reader.py,sha256=9i1-h4aGd4syfqtWJcpg70Bl-bmunkGU4FmXmOLyRt8,12121
14
14
  moteus/regression.py,sha256=M5gjDBYJQ64iBXIrvBhMkD8TYhtlnQ85x8U4py0niGA,1196
15
15
  moteus/router.py,sha256=501W5GZ12rFoc1lmcH3S7IYsoc-Q_-FJ4B3i37RzE3Q,2061
16
16
  moteus/transport.py,sha256=WhkW2G9i25lkOlO55eI5_oXmU0PhDmxTeJ75Sg_7nTI,1021
17
- moteus/version.py,sha256=0H-9-BkLhMrpt7WebRlIy2uP-2RmS0Y-oPCO-T9QBJY,627
17
+ moteus/version.py,sha256=awY4LvZ9OK5lPoCwGam-n3a3hS_9hqodOOLIH3OHQCw,627
18
18
  moteus/win32_aioserial.py,sha256=culdl-vYxBKD5n2s5LkIMGyUaHyCcEc8BL5-DWEaxX8,2025
19
- moteus-0.3.87.dist-info/METADATA,sha256=JIFUMH7iwTghDJf4fdupl4uwLj2vPCKCFHsp8yj3FSc,3441
20
- moteus-0.3.87.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
21
- moteus-0.3.87.dist-info/entry_points.txt,sha256=accRcwir_K8wCf7i3qHb5R6CPh5SiSgd5a1A92ibb9E,56
22
- moteus-0.3.87.dist-info/top_level.txt,sha256=aZzmI_yecTaDrdSp29pTJuowaSQ9dlIZheQpshGg4YQ,7
23
- moteus-0.3.87.dist-info/RECORD,,
19
+ moteus-0.3.89.dist-info/METADATA,sha256=CYM1GbGI8SfVCd58_tRUgHAXwiL-CCOJWSlUjoxqiYY,3417
20
+ moteus-0.3.89.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
21
+ moteus-0.3.89.dist-info/entry_points.txt,sha256=accRcwir_K8wCf7i3qHb5R6CPh5SiSgd5a1A92ibb9E,56
22
+ moteus-0.3.89.dist-info/top_level.txt,sha256=aZzmI_yecTaDrdSp29pTJuowaSQ9dlIZheQpshGg4YQ,7
23
+ moteus-0.3.89.dist-info/RECORD,,