moteus 0.3.56__py3-none-any.whl → 0.3.58__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 CHANGED
@@ -19,6 +19,7 @@ class Command():
19
19
  reply_required = False
20
20
  data = b''
21
21
  can_prefix = 0x0000 # a 13 bit CAN prefix
22
+ expected_reply_size = 0
22
23
 
23
24
  # If True, then the following parameters are used directly instead
24
25
  # of being calculated from destination and source (i.e. for
moteus/moteus.py CHANGED
@@ -606,7 +606,7 @@ class Controller:
606
606
  self._can_prefix = can_prefix
607
607
 
608
608
  # Pre-compute our query string.
609
- self._query_data = self._make_query_data()
609
+ self._query_data, self._default_query_reply_size = self._make_query_data()
610
610
 
611
611
  def _get_transport(self):
612
612
  if self.transport:
@@ -618,6 +618,8 @@ class Controller:
618
618
  return self.transport
619
619
 
620
620
  def _make_query_data(self):
621
+ expected_reply_size = 0
622
+
621
623
  buf = io.BytesIO()
622
624
  writer = Writer(buf)
623
625
  qr = self.query_resolution
@@ -633,6 +635,8 @@ class Controller:
633
635
  for i in range(c1.size()):
634
636
  c1.maybe_write()
635
637
 
638
+ expected_reply_size += c1.reply_size
639
+
636
640
  c2 = mp.WriteCombiner(writer, 0x10, int(Register.MOTOR_TEMPERATURE), [
637
641
  qr.motor_temperature,
638
642
  qr.trajectory_complete,
@@ -644,6 +648,8 @@ class Controller:
644
648
  for i in range(c2.size()):
645
649
  c2.maybe_write()
646
650
 
651
+ expected_reply_size += c2.reply_size
652
+
647
653
  c3 = mp.WriteCombiner(writer, 0x10, int(Register.AUX1_GPIO_STATUS), [
648
654
  qr.aux1_gpio,
649
655
  qr.aux2_gpio,
@@ -651,6 +657,8 @@ class Controller:
651
657
  for i in range(c3.size()):
652
658
  c3.maybe_write()
653
659
 
660
+ expected_reply_size += c3.reply_size
661
+
654
662
  if len(qr._extra):
655
663
  min_val = int(min(qr._extra.keys()))
656
664
  max_val = int(max(qr._extra.keys()))
@@ -660,8 +668,9 @@ class Controller:
660
668
  for i in range(min_val, max_val +1)])
661
669
  for _ in range(c4.size()):
662
670
  c4.maybe_write()
671
+ expected_reply_size += c4.reply_size
663
672
 
664
- return buf.getvalue()
673
+ return buf.getvalue(), expected_reply_size
665
674
 
666
675
  def _make_command(self, *, query, source=0):
667
676
  result = cmd.Command()
@@ -671,6 +680,7 @@ class Controller:
671
680
  result.reply_required = query
672
681
  result.parse = self._parser
673
682
  result.can_prefix = self._can_prefix
683
+ result.expected_reply_size = self._default_query_reply_size if query else 0
674
684
 
675
685
  return result
676
686
 
@@ -702,6 +712,7 @@ class Controller:
702
712
  c.maybe_write()
703
713
 
704
714
  result.data = buf.getvalue()
715
+ result.expected_reply_size = c.reply_size
705
716
  return result
706
717
 
707
718
  async def custom_query(self, *args, **kwargs):
@@ -1157,6 +1168,7 @@ class Controller:
1157
1168
  result.parse = make_diagnostic_parser(self.id, channel)
1158
1169
 
1159
1170
  result.data = data_buf.getvalue()
1171
+ result.expected_reply_size = 3 + max_length
1160
1172
  return result
1161
1173
 
1162
1174
  async def diagnostic_read(self, *args, **kwargs):
moteus/moteus_tool.py CHANGED
@@ -801,11 +801,9 @@ class Stream:
801
801
  input_V, winding_resistance)
802
802
  await self.check_for_fault()
803
803
 
804
- # We use a larger voltage for inductance measurement to get a
805
- # more accurate value. Since we switch back and forth at a
806
- # high rate, this doesn't actually use all that much power no
807
- # matter what we choose.
808
- inductance = await self.calibrate_inductance(2.0 * resistance_cal_voltage)
804
+ # Determine our inductance.
805
+ inductance = await self.calibrate_inductance(
806
+ resistance_cal_voltage, winding_resistance)
809
807
  await self.check_for_fault()
810
808
 
811
809
  kp, ki, torque_bw_hz = None, None, None
@@ -820,7 +818,8 @@ class Stream:
820
818
  await self.check_for_fault()
821
819
 
822
820
  enc_kp, enc_ki, enc_bw_hz = await self.set_encoder_filter(
823
- torque_bw_hz, control_rate_hz=control_rate_hz)
821
+ torque_bw_hz, inductance,
822
+ control_rate_hz=control_rate_hz)
824
823
  await self.check_for_fault()
825
824
 
826
825
  v_per_hz = await self.calibrate_kv_rating(
@@ -1119,12 +1118,25 @@ class Stream:
1119
1118
 
1120
1119
  return winding_resistance
1121
1120
 
1122
- async def calibrate_inductance(self, cal_voltage):
1121
+ async def calibrate_inductance(self, cal_voltage, winding_resistance):
1123
1122
  print("Calculating motor inductance")
1124
1123
 
1125
1124
  try:
1125
+ # High winding resistance motors typically have a much
1126
+ # larger inductance, and therefore need a longer
1127
+ # inductance period. We still need to keep this low for
1128
+ # low resistance/inductance motors, otherwise we can have
1129
+ # excessive peak currents during the measurement process.
1130
+
1131
+ # For phase resistances of 0.2 ohm or less, stick with 8
1132
+ # cycles, which for a 15kHz pwm rate would equal ~0.6ms of
1133
+ # on time. Increase that as phase resistance increases,
1134
+ # until it maxes at 32/2ms around 0.8 ohms of phase
1135
+ # resistance.
1136
+ ind_period = max(8, min(32, int(winding_resistance / 0.2)))
1137
+
1126
1138
  await asyncio.wait_for(
1127
- self.command(f"d ind {cal_voltage} 4"), 0.25)
1139
+ self.command(f"d ind {cal_voltage} {ind_period}"), 0.25)
1128
1140
  except moteus.CommandError as e:
1129
1141
  # It is possible this is an old firmware that does not
1130
1142
  # support inductance measurement.
@@ -1152,7 +1164,7 @@ class Stream:
1152
1164
  print(f"Calculated inductance: {inductance}H")
1153
1165
  return inductance
1154
1166
 
1155
- async def set_encoder_filter(self, torque_bw_hz, control_rate_hz = None):
1167
+ async def set_encoder_filter(self, torque_bw_hz, inductance, control_rate_hz = None):
1156
1168
  # Check to see if our firmware supports encoder filtering.
1157
1169
  motor_position_style = await self.is_config_supported("motor_position.sources.0.pll_filter_hz")
1158
1170
  servo_style = await self.is_config_supported("servo.encoder_filter.enabled")
@@ -1162,18 +1174,17 @@ class Stream:
1162
1174
  if self.args.encoder_bw_hz:
1163
1175
  desired_encoder_bw_hz = self.args.encoder_bw_hz
1164
1176
  else:
1165
- if self.args.cal_hall:
1166
- # Hall effect configurations require a low encoder BW
1167
- # if the hall effects are also used for position and
1168
- # velocity control. Since that is one of the most
1169
- # common ways of using hall effects, we by default cap
1170
- # the bw at 20Hz and use a lower one if the torque bw
1171
- # would otherwise have been lower.
1172
- desired_encoder_bw_hz = min(20, 2 * torque_bw_hz)
1173
- else:
1174
- # We default to an encoder bandwidth of 50Hz, or 2x the
1175
- # torque bw, whichever is larger.
1176
- desired_encoder_bw_hz = max(50, 2 * torque_bw_hz)
1177
+ # Don't let the encoder bandwidth be less than 10Hz by default.
1178
+ desired_encoder_bw_hz = max(10, 2 * torque_bw_hz)
1179
+
1180
+ # However, we limit the maximum encoder bandwidth for high
1181
+ # inductance motors, because they shouldn't be able to
1182
+ # accelerate that fast anyways.
1183
+ if inductance:
1184
+ # This is just a random constant that seems to work
1185
+ # well in practice.
1186
+ desired_encoder_bw_hz = min(
1187
+ desired_encoder_bw_hz, 2e-2 / inductance)
1177
1188
 
1178
1189
  # And our bandwidth with the filter can be no larger than
1179
1190
  # 1/10th the control rate.
@@ -1192,7 +1203,8 @@ class Stream:
1192
1203
  await self.command(f"conf set servo.encoder_filter.kp {kp}")
1193
1204
  await self.command(f"conf set servo.encoder_filter.ki {ki}")
1194
1205
  elif motor_position_style:
1195
- await self.command(f"conf set motor_position.sources.0.pll_filter_hz {encoder_bw_hz}")
1206
+ commutation_source = await self.read_config_int("motor_position.commutation_source")
1207
+ await self.command(f"conf set motor_position.sources.{commutation_source}.pll_filter_hz {encoder_bw_hz}")
1196
1208
  else:
1197
1209
  assert False
1198
1210
  return kp, ki, encoder_bw_hz
moteus/multiplex.py CHANGED
@@ -244,21 +244,30 @@ class WriteFrame:
244
244
 
245
245
  def __init__(self, buf):
246
246
  self._buf = buf
247
+ self._size = 0
248
+
249
+ def size(self):
250
+ return self._size
247
251
 
248
252
  def write(self, value, resolution):
249
253
  self._buf.write(TYPES[resolution].pack(value))
254
+ self._size += TYPES[resolution].size
250
255
 
251
256
  def write_int8(self, value):
252
257
  self._buf.write(TYPES[INT8].pack(value))
258
+ self._size += 1
253
259
 
254
260
  def write_int16(self, value):
255
261
  self._buf.write(TYPES[INT16].pack(value))
262
+ self._size += 2
256
263
 
257
264
  def write_int32(self, value):
258
265
  self._buf.write(TYPES[INT32].pack(value))
266
+ self._size += 4
259
267
 
260
268
  def write_f32(self, value):
261
269
  self._buf.write(TYPES[F32].pack(value))
270
+ self._size += 4
262
271
 
263
272
  def write_varuint(self, value):
264
273
  while True:
@@ -266,6 +275,7 @@ class WriteFrame:
266
275
  value >>= 7
267
276
  this_byte |= 0x80 if value else 0x00
268
277
  self._buf.write(bytes([this_byte]))
278
+ self._size += 1
269
279
 
270
280
  if value == 0:
271
281
  break
@@ -286,6 +296,7 @@ class WriteCombiner:
286
296
  self.base_command = base_command
287
297
  self.start_register = start_register
288
298
  self.resolutions = resolutions
299
+ self.reply_size = 0
289
300
  self._offset = 0
290
301
  self._current_resolution = -1
291
302
 
@@ -320,6 +331,8 @@ class WriteCombiner:
320
331
 
321
332
  write_command = [0x00, 0x04, 0x08, 0x0c][new_resolution] | self.base_command
322
333
 
334
+ start_size = self.writer.size()
335
+
323
336
  if count <= 3:
324
337
  # Use the shorthand formulation.
325
338
  self.writer.write_int8(write_command + count)
@@ -329,4 +342,10 @@ class WriteCombiner:
329
342
  self.writer.write_int8(count)
330
343
 
331
344
  self.writer.write_varuint(self.start_register + this_offset)
345
+
346
+ end_size = self.writer.size()
347
+
348
+ self.reply_size += end_size - start_size
349
+ self.reply_size += count * resolution_size(new_resolution)
350
+
332
351
  return True
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.56"
15
+ VERSION="0.3.58"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: moteus
3
- Version: 0.3.56
3
+ Version: 0.3.58
4
4
  Summary: moteus brushless controller library and tools
5
5
  Home-page: https://github.com/mjbots/moteus
6
6
  Author: mjbots Robotic Systems
@@ -2,22 +2,22 @@ moteus/__init__.py,sha256=1Prmih65X7Qz7WY8AUiSwUWXz3Dev8ggQS9xohL75M0,721
2
2
  moteus/aioserial.py,sha256=lOBFPXManRBMsjMc1zuyw-NRt6CNVMvnj_mQqIZLpyI,1061
3
3
  moteus/aiostream.py,sha256=SLDXwdBMHIWVTAmeUGG6ke7sntfNVxd9CqapzfniJOo,3678
4
4
  moteus/calibrate_encoder.py,sha256=kY6mV1WkgCrD9z2YpRoqTTK_BUrRfop4_YJym9AQI-I,12403
5
- moteus/command.py,sha256=YEtDNXX-TlkUSElsGVQoSYbGqcboneNEejZo06v8ZsI,1128
5
+ moteus/command.py,sha256=IXb0ToAg74fm8FYKPiOdjlzxosqdafLhqADffCvw5OY,1156
6
6
  moteus/export.py,sha256=dI8QjdqrcI3pi5fKfP25PwLvsIlVJ695x0ta0PVMKx8,1628
7
7
  moteus/fdcanusb.py,sha256=AMwqdvIRz4X9yA4qz8REuyFBfs3OcLcVqbGX28sEx4s,6729
8
- moteus/moteus.py,sha256=W-ADEI1uAHF0j5A3s1CINTUYpwjEgO-xPRQS15C9Ewg,43410
9
- moteus/moteus_tool.py,sha256=WPpXvLwD9bJaWZY8LVygzFrmT7diN0HXMVZ_nUSfp9k,66231
10
- moteus/multiplex.py,sha256=0Hs63pFZpl1y7ciTcwEeg8F2JDjlhrkb2FcfUbdox-g,9637
8
+ moteus/moteus.py,sha256=J7nzalhDTUvJq3h1FNjFnYLkrSVnL_IGDU1sw2PNsmU,43869
9
+ moteus/moteus_tool.py,sha256=L-7IBs9YHcbd2nc8SPwCEinHvzp09iVb_JeQCUtNM2U,66800
10
+ moteus/multiplex.py,sha256=EBOhAE-DHkS_AXtqUl2YEs745evvLMPIMeXQSYxF8zk,10102
11
11
  moteus/posix_aioserial.py,sha256=3JFiY5p4dtC2ztg6N5SOffnNk9lNcjie02yjD3UlJWo,2971
12
12
  moteus/pythoncan.py,sha256=lawewmkd9zQuE-Z1LurmpFD2iSWATei65SZb42um_Vg,3309
13
13
  moteus/reader.py,sha256=jGADQTmONSBMQ25I5AqXELLqil2TEha1KjraPdOsf78,11932
14
14
  moteus/regression.py,sha256=wpPlxAZ9nC9kfv0oX1K9W2AZNnBLbY8htAJz60SxIb8,1183
15
15
  moteus/router.py,sha256=k4Tf6hWtHSgzznmdnj4NySe84-y9feYRxGz0yTrJtoc,2043
16
16
  moteus/transport.py,sha256=3asI2A87eQDImLP74LNLtETaShQRiD9RuCjlxNY6AlE,1003
17
- moteus/version.py,sha256=6IePxRuf-dHbF45uc1W5kTJba8U8rF7AWW44ngo7bEY,609
17
+ moteus/version.py,sha256=zpTmrEA7ncnYPk73r5YQJOdYXTB_LBw60As5aQhXRA0,609
18
18
  moteus/win32_aioserial.py,sha256=SZsnoBWE0Uwo4ZZF8ALB1WNPRY9NiaCOBz6VfvVcnxA,1841
19
- moteus-0.3.56.dist-info/METADATA,sha256=rztYCXjyhVOwKH0uVEQf7Xp2zuD6C0kexA4CXIMVUgo,3417
20
- moteus-0.3.56.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
21
- moteus-0.3.56.dist-info/entry_points.txt,sha256=indCsEML1fmtWJU1WiV-d7UmmTaAMhyBLEc1iiKnexQ,57
22
- moteus-0.3.56.dist-info/top_level.txt,sha256=aZzmI_yecTaDrdSp29pTJuowaSQ9dlIZheQpshGg4YQ,7
23
- moteus-0.3.56.dist-info/RECORD,,
19
+ moteus-0.3.58.dist-info/METADATA,sha256=WCPxHMe0doKijEQK9-XAaN5FjXUxTLVdsFn1lRvP4wE,3417
20
+ moteus-0.3.58.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
21
+ moteus-0.3.58.dist-info/entry_points.txt,sha256=indCsEML1fmtWJU1WiV-d7UmmTaAMhyBLEc1iiKnexQ,57
22
+ moteus-0.3.58.dist-info/top_level.txt,sha256=aZzmI_yecTaDrdSp29pTJuowaSQ9dlIZheQpshGg4YQ,7
23
+ moteus-0.3.58.dist-info/RECORD,,