moteus 0.3.68__py3-none-any.whl → 0.3.70__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/moteus.py CHANGED
@@ -162,6 +162,7 @@ class Register(enum.IntEnum):
162
162
  Q_CURRENT = 0x004
163
163
  D_CURRENT = 0x005
164
164
  ABS_POSITION = 0x006
165
+ POWER = 0x007
165
166
  MOTOR_TEMPERATURE = 0x00a
166
167
  TRAJECTORY_COMPLETE = 0x00b
167
168
  REZERO_STATE = 0x00c
@@ -199,6 +200,7 @@ class Register(enum.IntEnum):
199
200
  COMMAND_VELOCITY_LIMIT = 0x028
200
201
  COMMAND_ACCEL_LIMIT = 0x029
201
202
  COMMAND_FIXED_VOLTAGE_OVERRIDE = 0x02a
203
+ COMMAND_ILIMIT_SCALE = 0x02b
202
204
 
203
205
  POSITION_KP = 0x030
204
206
  POSITION_KI = 0x031
@@ -220,6 +222,7 @@ class Register(enum.IntEnum):
220
222
  COMMAND_WITHIN_KD_SCALE = 0x044
221
223
  COMMAND_WITHIN_MAX_TORQUE = 0x045
222
224
  COMMAND_WITHIN_TIMEOUT = 0x046
225
+ COMMAND_WITHIN_ILIMIT_SCALE = 0x047
223
226
 
224
227
  ENCODER_0_POSITION = 0x050
225
228
  ENCODER_0_VELOCITY = 0x051
@@ -299,6 +302,7 @@ class QueryResolution:
299
302
  q_current = mp.IGNORE
300
303
  d_current = mp.IGNORE
301
304
  abs_position = mp.IGNORE
305
+ power = mp.IGNORE
302
306
  motor_temperature = mp.IGNORE
303
307
  trajectory_complete = mp.IGNORE
304
308
  rezero_state = mp.IGNORE
@@ -329,6 +333,7 @@ class PositionResolution:
329
333
  velocity_limit = mp.F32
330
334
  accel_limit = mp.F32
331
335
  fixed_voltage_override = mp.F32
336
+ ilimit_scale = mp.F32
332
337
 
333
338
 
334
339
  class VFOCResolution:
@@ -371,6 +376,9 @@ class Parser(mp.RegisterParser):
371
376
  def read_current(self, resolution):
372
377
  return self.read_mapped(resolution, 1.0, 0.1, 0.001)
373
378
 
379
+ def read_power(self, resolution):
380
+ return self.read_mapped(resolution, 10.0, 0.05, 0.0001)
381
+
374
382
  def ignore(self, resolution):
375
383
  self._offset += mp.resolution_size(resolution)
376
384
 
@@ -407,6 +415,9 @@ class Writer(mp.WriteFrame):
407
415
  def write_current(self, value, resolution):
408
416
  self.write_mapped(value, 1.0, 0.1, 0.001, resolution)
409
417
 
418
+ def write_power(self, value, resolution):
419
+ self.write_mapped(value, 10.0, 0.05, 0.0001, resolution)
420
+
410
421
 
411
422
  def parse_register(parser, register, resolution):
412
423
  if register == Register.MODE:
@@ -423,6 +434,8 @@ def parse_register(parser, register, resolution):
423
434
  return parser.read_current(resolution)
424
435
  elif register == Register.ABS_POSITION:
425
436
  return parser.read_position(resolution)
437
+ elif register == Register.POWER:
438
+ return parser.read_power(resolution)
426
439
  elif register == Register.TRAJECTORY_COMPLETE:
427
440
  return parser.read_int(resolution)
428
441
  elif register == Register.HOME_STATE or register == Register.REZERO_STATE:
@@ -642,6 +655,7 @@ class Controller:
642
655
  qr.q_current,
643
656
  qr.d_current,
644
657
  qr.abs_position,
658
+ qr.power,
645
659
  ])
646
660
  for i in range(c1.size()):
647
661
  c1.maybe_write()
@@ -888,6 +902,7 @@ class Controller:
888
902
  velocity_limit=None,
889
903
  accel_limit=None,
890
904
  fixed_voltage_override=None,
905
+ ilimit_scale=None,
891
906
  query=False,
892
907
  query_override=None):
893
908
  """Return a moteus.Command structure with data necessary to send a
@@ -909,6 +924,7 @@ class Controller:
909
924
  pr.velocity_limit if velocity_limit is not None else mp.IGNORE,
910
925
  pr.accel_limit if accel_limit is not None else mp.IGNORE,
911
926
  pr.fixed_voltage_override if fixed_voltage_override is not None else mp.IGNORE,
927
+ pr.ilimit_scale if ilimit_scale is not None else mp.IGNORE
912
928
  ]
913
929
 
914
930
  data_buf = io.BytesIO()
@@ -943,6 +959,8 @@ class Controller:
943
959
  writer.write_accel(accel_limit, pr.accel_limit)
944
960
  if combiner.maybe_write():
945
961
  writer.write_voltage(fixed_voltage_override, pr.fixed_voltage_override)
962
+ if combiner.maybe_write():
963
+ writer.write_voltage(ilimit_scale, pr.ilimit_scale)
946
964
 
947
965
  self._format_query(query, query_override, data_buf, result)
948
966
 
@@ -1098,6 +1116,7 @@ class Controller:
1098
1116
  maximum_torque=None,
1099
1117
  stop_position=None,
1100
1118
  watchdog_timeout=None,
1119
+ ilimit_scale=None,
1101
1120
  query=False,
1102
1121
  query_override=None):
1103
1122
  """Return a moteus.Command structure with data necessary to send a
@@ -1115,6 +1134,7 @@ class Controller:
1115
1134
  pr.kd_scale if kd_scale is not None else mp.IGNORE,
1116
1135
  pr.maximum_torque if maximum_torque is not None else mp.IGNORE,
1117
1136
  pr.watchdog_timeout if watchdog_timeout is not None else mp.IGNORE,
1137
+ pr.ilimit_scale if ilimit_scale is not None else mp.IGNORE,
1118
1138
  ]
1119
1139
 
1120
1140
  data_buf = io.BytesIO()
@@ -1142,6 +1162,8 @@ class Controller:
1142
1162
  writer.write_torque(maximum_torque, pr.maximum_torque)
1143
1163
  if combiner.maybe_write():
1144
1164
  writer.write_time(watchdog_timeout, pr.watchdog_timeout)
1165
+ if combiner.maybe_write():
1166
+ writer.write_pwm(ilimit_scale, pr.ilimit_scale)
1145
1167
 
1146
1168
  self._format_query(query, query_override, data_buf, result)
1147
1169
 
moteus/moteus_tool.py CHANGED
@@ -54,7 +54,7 @@ class FirmwareUpgrade:
54
54
  self.old = old
55
55
  self.new = new
56
56
 
57
- SUPPORTED_ABI_VERSION = 0x0106
57
+ SUPPORTED_ABI_VERSION = 0x0107
58
58
 
59
59
  if new > SUPPORTED_ABI_VERSION:
60
60
  raise RuntimeError(f"\nmoteus_tool needs to be upgraded to support this firmware\n\n (likely 'python -m pip install --upgrade moteus')\n\nThe provided firmare is ABI version 0x{new:04x} but this moteus_tool only supports up to 0x{SUPPORTED_ABI_VERSION:04x}")
@@ -63,6 +63,13 @@ class FirmwareUpgrade:
63
63
  lines = old_config.split(b'\n')
64
64
  items = dict([line.split(b' ') for line in lines if b' ' in line])
65
65
 
66
+ if self.new <= 0x0106 and self.old >= 0x0107:
67
+ # motor_position.output.sign was broken in older versions.
68
+ if int(items[b'motor_position.output.sign']) != 1:
69
+ print("WARNING: motor_position.output.sign==-1 is broken in order versions, disabling")
70
+ items[b'motor_position.output.sign'] = '1'
71
+ pass
72
+
66
73
  if self.new <= 0x0105 and self.old >= 0x0106:
67
74
  # Downgrade the I2C polling rate.
68
75
  for aux in [1, 2]:
@@ -279,6 +286,13 @@ class FirmwareUpgrade:
279
286
  items[poll_rate_us_key] = str(int(poll_ms) * 1000).encode('utf8')
280
287
  print("Upgrading I2C rates for version 0x0106")
281
288
 
289
+ if self.new >= 0x0107 and self.old <= 0x0106:
290
+ if int(items.get(b'motor_position.output.sign', 1)) == -1:
291
+ print("Upgrading from motor_position.output.sign == -1, " +
292
+ "this was broken before, behavior will change")
293
+
294
+ # No actual configuration updating is required here.
295
+ pass
282
296
 
283
297
  lines = [key + b' ' + value for key, value in items.items()]
284
298
  return b'\n'.join(lines)
@@ -305,6 +319,10 @@ def _round_nearest_4v(input_V):
305
319
  return round(input_V / 4) * 4
306
320
 
307
321
 
322
+ def _average(x):
323
+ return sum(x) / len(x)
324
+
325
+
308
326
  def expand_targets(targets):
309
327
  result = set()
310
328
 
@@ -794,6 +812,8 @@ class Stream:
794
812
  return cal_voltage
795
813
 
796
814
  async def do_calibrate(self):
815
+ self.firmware = await self.read_data("firmware")
816
+
797
817
  # Determine what our calibration parameters are.
798
818
  self.calculate_calibration_parameters()
799
819
 
@@ -1239,6 +1259,16 @@ class Stream:
1239
1259
  desired_encoder_bw_hz = min(
1240
1260
  desired_encoder_bw_hz, 2e-2 / inductance)
1241
1261
 
1262
+ # Also, limit the bandwidth for halls based on the number
1263
+ # of poles and the estimated calibration speed.
1264
+ if hall_output:
1265
+ max_pole_bandwidth_hz = (
1266
+ 0.5 * self.args.cal_motor_poles *
1267
+ self.args.cal_motor_speed)
1268
+ desired_encoder_bw_hz = min(
1269
+ desired_encoder_bw_hz, max_pole_bandwidth_hz)
1270
+
1271
+
1242
1272
  # And our bandwidth with the filter can be no larger than
1243
1273
  # 1/30th the control rate.
1244
1274
  encoder_bw_hz = min(control_rate_hz / 30, desired_encoder_bw_hz)
@@ -1308,32 +1338,42 @@ class Stream:
1308
1338
 
1309
1339
  start_time = time.time()
1310
1340
 
1341
+ SAMPLE_PERIOD_S = 0.02
1342
+ AVERAGE_PERIOD_S = 0.10
1343
+
1344
+ AVERAGE_COUNT = int(AVERAGE_PERIOD_S / SAMPLE_PERIOD_S)
1345
+
1311
1346
  def sign(x):
1312
1347
  return 1 if x >= 0 else -1
1313
1348
 
1314
- old_change = None
1315
- old_vel = None
1349
+ velocity_samples = []
1350
+
1316
1351
  while True:
1317
1352
  data = await self.read_servo_stats()
1318
- velocity = data.velocity
1353
+ velocity_samples.append(data.velocity)
1354
+
1355
+ if len(velocity_samples) > (3 * AVERAGE_COUNT):
1356
+ del velocity_samples[0]
1319
1357
 
1320
- # As a fallback, timeout after 1s of waiting.
1358
+ recent_average = _average(velocity_samples[-AVERAGE_COUNT:])
1359
+
1360
+ # As a fallback, timeout after a fixed amount of waiting.
1321
1361
  if (time.time() - start_time) > 2.0:
1322
- return velocity
1362
+ return recent_average
1323
1363
 
1324
- if abs(velocity) < 0.2:
1325
- return velocity
1364
+ if (len(velocity_samples) >= AVERAGE_COUNT and
1365
+ abs(recent_average) < 0.2):
1366
+ return recent_average
1326
1367
 
1327
- if old_vel is not None:
1328
- change = velocity - old_vel
1329
- if old_change is not None:
1330
- if sign(old_change) != sign(change):
1331
- return velocity
1332
- old_change = change
1368
+ if len(velocity_samples) == 3 * AVERAGE_COUNT:
1369
+ average_1 = _average(velocity_samples[:AVERAGE_COUNT])
1370
+ average_2 = _average(velocity_samples[AVERAGE_COUNT:2*AVERAGE_COUNT])
1371
+ average_3 = recent_average
1333
1372
 
1334
- old_vel = velocity
1373
+ if sign(average_3 - average_2) != sign(average_2 - average_1):
1374
+ return recent_average
1335
1375
 
1336
- await asyncio.sleep(0.1)
1376
+ await asyncio.sleep(SAMPLE_PERIOD_S)
1337
1377
 
1338
1378
  return velocity
1339
1379
 
@@ -1393,8 +1433,10 @@ class Stream:
1393
1433
  geared_v_per_hz = 1.0 / _calculate_slope(voltages, speed_hzs)
1394
1434
 
1395
1435
  v_per_hz = (geared_v_per_hz *
1396
- unwrapped_position_scale *
1397
- motor_output_sign)
1436
+ unwrapped_position_scale)
1437
+ if self.firmware.version <= 0x0106:
1438
+ v_per_hz *= motor_output_sign
1439
+
1398
1440
  print(f"v_per_hz (pre-gearbox)={v_per_hz}")
1399
1441
 
1400
1442
  await self.command(f"conf set servopos.position_min {original_position_min}")
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.68"
15
+ VERSION="0.3.70"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: moteus
3
- Version: 0.3.68
3
+ Version: 0.3.70
4
4
  Summary: moteus brushless controller library and tools
5
5
  Home-page: https://github.com/mjbots/moteus
6
6
  Author: mjbots Robotic Systems
@@ -110,10 +110,10 @@ pr.kp_scale = moteus.F32
110
110
  pr.kd_scale = moteus.F32
111
111
 
112
112
  qr = moteus.QueryResolution()
113
- qr.mode = mp.INT8
114
- qr.position = mp.F32
115
- qr.velocity = mp.F32
116
- qr.torque = mp.F32
113
+ qr.mode = moteus.INT8
114
+ qr.position = moteus.F32
115
+ qr.velocity = moteus.F32
116
+ qr.torque = moteus.F32
117
117
 
118
118
  c = moteus.Controller(position_resolution=pr, query_resolution=qr)
119
119
  ```
@@ -5,8 +5,8 @@ moteus/calibrate_encoder.py,sha256=R3pRgGqrDCcmKQqFE7Fr4p8TSie179iqNfBwEJS3pL0,1
5
5
  moteus/command.py,sha256=UkOsbtkso6Oyex8CfbpAKpBNriik519ymxL86EZGkRs,1169
6
6
  moteus/export.py,sha256=vRIfldaqz1eHtWo3993SvatATtu73TZbejL0hzQe8YE,1646
7
7
  moteus/fdcanusb.py,sha256=7PrQiCTROY96gdT2zSZYU1bOCriw-I7H6NspaZpiEx4,7431
8
- moteus/moteus.py,sha256=2x5i-4BpTwS3Uo9FWyWesR--acIO8TJb0wCQHbuKuxQ,48131
9
- moteus/moteus_tool.py,sha256=PbQjd4t4gtLHs7vRrjMpajOoKVezAefDBuKR5CAvHiw,69211
8
+ moteus/moteus.py,sha256=gARNQ2_irh_1zp5YBTXmZK5U7SziBna8R140e_Wyfxk,48998
9
+ moteus/moteus_tool.py,sha256=dawmuZ9SnmZ9rDpKMnnGxeZkadrTaMGPEfs13X7nLP0,70999
10
10
  moteus/multiplex.py,sha256=LF6MuelzYHqqsCJuCB9YeEyUA03eBaTYRwAVotX3qm8,10120
11
11
  moteus/posix_aioserial.py,sha256=2oDrw8TBEwuEQjY41g9rHeuFeffcPHqMwNS3nf5NVq8,3137
12
12
  moteus/pythoncan.py,sha256=ofotOrDuaFhTLvaokaO3EJK6quVc75Bq-ue70lDMtXI,4071
@@ -14,10 +14,10 @@ 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=8rx1YUinZCom0pzbiJ3IcLDXrj5FEja3M79tr75nJJM,627
17
+ moteus/version.py,sha256=lm6BoZJ_qZ6gZJ22LrCQaLKZWXRuR2fV_to4JsV9Uvs,627
18
18
  moteus/win32_aioserial.py,sha256=culdl-vYxBKD5n2s5LkIMGyUaHyCcEc8BL5-DWEaxX8,2025
19
- moteus-0.3.68.dist-info/METADATA,sha256=1fNB-NxqLjIjL2hgDA2FD0aq3BLZnJrE7LgknXIMRfo,3372
20
- moteus-0.3.68.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
21
- moteus-0.3.68.dist-info/entry_points.txt,sha256=accRcwir_K8wCf7i3qHb5R6CPh5SiSgd5a1A92ibb9E,56
22
- moteus-0.3.68.dist-info/top_level.txt,sha256=aZzmI_yecTaDrdSp29pTJuowaSQ9dlIZheQpshGg4YQ,7
23
- moteus-0.3.68.dist-info/RECORD,,
19
+ moteus-0.3.70.dist-info/METADATA,sha256=GgWzsYPbYst5VXP3tSE15HJuvpZgVAIAdIBpOySqHAc,3388
20
+ moteus-0.3.70.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
21
+ moteus-0.3.70.dist-info/entry_points.txt,sha256=accRcwir_K8wCf7i3qHb5R6CPh5SiSgd5a1A92ibb9E,56
22
+ moteus-0.3.70.dist-info/top_level.txt,sha256=aZzmI_yecTaDrdSp29pTJuowaSQ9dlIZheQpshGg4YQ,7
23
+ moteus-0.3.70.dist-info/RECORD,,