moteus 0.3.88__py3-none-any.whl → 0.3.90__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
@@ -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
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
 
@@ -78,7 +84,7 @@ def stddev(data):
78
84
  mean = sum(data) / len(data)
79
85
  return math.sqrt(sum((x - mean) ** 2 for x in data) / len(data))
80
86
 
81
- SUPPORTED_ABI_VERSION = 0x010b
87
+ SUPPORTED_ABI_VERSION = 0x010c
82
88
 
83
89
  # Old firmwares used a slightly incorrect definition of Kv/v_per_hz
84
90
  # that didn't match with vendors or oscilloscope tests.
@@ -105,6 +111,15 @@ class FirmwareUpgrade:
105
111
  lines = old_config.split(b'\n')
106
112
  items = dict([line.split(b' ') for line in lines if b' ' in line])
107
113
 
114
+ if self.new <= 0x010b and self.old >= 0x010c:
115
+ # Update all pll_filter_hz parameters.
116
+ for mpsource in range(0, 3):
117
+ key = f'motor_position.sources.{mpsource}.pll_filter_hz'.encode('utf8')
118
+ pll_filter_hz = float(items.get(key, 150.0))
119
+ natural_hz = pll_filter_hz / 2.48
120
+ items[key] = str(natural_hz).encode('utf8')
121
+ print(f"Downgraded motor_position.sources.{mpsource}.pll_filter_hz from {pll_filter_hz} to {natural_hz}")
122
+
108
123
  if self.new <= 0x010a and self.old >= 0x010b:
109
124
  flux_brake_margin_voltage = float(items.pop(b'servo.flux_brake_margin_voltage'))
110
125
  max_voltage = float(items[b'servo.max_voltage'])
@@ -566,6 +581,16 @@ class FirmwareUpgrade:
566
581
  print(f"Upgraded servo.motor_derate_temperature to servo.motor_temperature_margin={motor_temperature_margin}")
567
582
  items[b'servo.motor_temperature_margin'] = str(motor_temperature_margin).encode('utf8')
568
583
 
584
+ if self.new >= 0x010c and self.old <= 0x010b:
585
+ # Update all pll_filter_hz parameters.
586
+ for mpsource in range(0, 3):
587
+ key = f'motor_position.sources.{mpsource}.pll_filter_hz'.encode('utf8')
588
+ natural_hz = float(items.get(key, 400.0))
589
+ pll_filter_hz = natural_hz * 2.48
590
+ items[key] = str(pll_filter_hz).encode('utf8')
591
+ print(f"Upgraded motor_position.sources.{mpsource}.pll_filter_hz from {natural_hz} to {pll_filter_hz}")
592
+
593
+
569
594
  lines = [key + b' ' + value for key, value in items.items()]
570
595
  return b'\n'.join(lines)
571
596
 
@@ -1012,18 +1037,19 @@ class Stream:
1012
1037
  cmd = f"w {final_address:x} {'ff' * remaining_to_flush}"
1013
1038
  result = await self.command(cmd)
1014
1039
 
1015
- verify_ctx = FlashContext(elfs)
1016
- while True:
1017
- expected_block = verify_ctx.get_next_block()
1018
- cmd = f"r {expected_block.address:x} {len(expected_block.data):x}"
1019
- result = await self.command(cmd, allow_any_response=True)
1020
- # Emit progress first, to make it easier to see where
1021
- # things go wrong.
1022
- self._emit_flash_progress(verify_ctx, "verifying")
1023
- _verify_blocks(expected_block, result)
1024
- done = verify_ctx.advance_block()
1025
- if done:
1026
- break
1040
+ if not self.args.no_verify:
1041
+ verify_ctx = FlashContext(elfs)
1042
+ while True:
1043
+ expected_block = verify_ctx.get_next_block()
1044
+ cmd = f"r {expected_block.address:x} {len(expected_block.data):x}"
1045
+ result = await self.command(cmd, allow_any_response=True)
1046
+ # Emit progress first, to make it easier to see where
1047
+ # things go wrong.
1048
+ self._emit_flash_progress(verify_ctx, "verifying")
1049
+ _verify_blocks(expected_block, result)
1050
+ done = verify_ctx.advance_block()
1051
+ if done:
1052
+ break
1027
1053
 
1028
1054
  async def read_servo_stats(self):
1029
1055
  servo_stats = await self.read_data("servo_stats")
@@ -1256,6 +1282,7 @@ class Stream:
1256
1282
  'motor_position_output_sign' : motor_output_sign,
1257
1283
  'abi_version' : self.firmware.version,
1258
1284
  'voltage_mode_control' : voltage_mode_control,
1285
+ 'py_version' : version.VERSION,
1259
1286
  }
1260
1287
 
1261
1288
  log_filename = f"moteus-cal-{device_info['serial_number']}-{now.strftime('%Y%m%dT%H%M%S.%f')}.log"
@@ -1770,31 +1797,36 @@ class Stream:
1770
1797
  if output_type == 4: # kHall
1771
1798
  hall_output = True
1772
1799
 
1773
- if inductance and hall_output:
1774
- desired_encoder_bw_hz = min(
1775
- desired_encoder_bw_hz, 2e-2 / inductance)
1800
+ # If we are calibrating a device with older firmware, we
1801
+ # artifically limit the bandwidth for hall commutation
1802
+ # sensors.
1803
+ if self.firmware.version <= 0x010b:
1804
+ if inductance and hall_output:
1805
+ desired_encoder_bw_hz = min(
1806
+ desired_encoder_bw_hz, 2e-2 / inductance)
1776
1807
 
1777
- # Also, limit the bandwidth for halls based on the number
1778
- # of poles and the estimated calibration speed.
1779
- if hall_output:
1780
- max_pole_bandwidth_hz = (
1781
- 0.5 * self.args.cal_motor_poles *
1782
- self.args.cal_motor_speed)
1783
- desired_encoder_bw_hz = min(
1784
- desired_encoder_bw_hz, max_pole_bandwidth_hz)
1808
+ # Also, limit the bandwidth for halls based on the number
1809
+ # of poles and the estimated calibration speed.
1810
+ if hall_output:
1811
+ max_pole_bandwidth_hz = (
1812
+ 0.5 * self.args.cal_motor_poles *
1813
+ self.args.cal_motor_speed)
1814
+ desired_encoder_bw_hz = min(
1815
+ desired_encoder_bw_hz, max_pole_bandwidth_hz)
1785
1816
 
1786
1817
 
1787
1818
  # And our bandwidth with the filter can be no larger than
1788
- # 1/30th the control rate.
1789
- encoder_bw_hz = min(control_rate_hz / 30, desired_encoder_bw_hz)
1819
+ # a fixed fraction of the control rate.
1820
+ encoder_bw_hz = min(control_rate_hz / 10, desired_encoder_bw_hz)
1790
1821
 
1791
1822
  if encoder_bw_hz != desired_encoder_bw_hz:
1792
1823
  print(f"Warning: using lower encoder bandwidth than "+
1793
1824
  f"requested: {encoder_bw_hz:.1f}Hz")
1794
1825
 
1795
- w_3db = encoder_bw_hz * 2 * math.pi
1796
- kp = 2 * w_3db
1797
- ki = w_3db * w_3db
1826
+ encoder_natural_frequency_hz = encoder_bw_hz / 2.48
1827
+ w_n = encoder_natural_frequency_hz * 2 * math.pi # natural frequency for zeta=1.0
1828
+ kp = 2 * w_n
1829
+ ki = w_n * w_n
1798
1830
 
1799
1831
  if servo_style:
1800
1832
  await self.command(f"conf set servo.encoder_filter.enabled 1")
@@ -1802,7 +1834,12 @@ class Stream:
1802
1834
  await self.command(f"conf set servo.encoder_filter.ki {ki}")
1803
1835
  elif motor_position_style:
1804
1836
  commutation_source = await self.read_config_int("motor_position.commutation_source")
1805
- await self.command(f"conf set motor_position.sources.{commutation_source}.pll_filter_hz {encoder_bw_hz}")
1837
+ output_hz = encoder_bw_hz if self.firmware.version >= 0x010c else encoder_natural_frequency_hz
1838
+ await self.command(f"conf set motor_position.sources.{commutation_source}.pll_filter_hz {output_hz}")
1839
+
1840
+ output_source = await self.read_config_int("motor_position.output.source")
1841
+ if output_source != commutation_source:
1842
+ await self.command(f"conf set motor_position.sources.{output_source}.pll_filter_hz {output_hz}")
1806
1843
  else:
1807
1844
  assert False
1808
1845
  return kp, ki, encoder_bw_hz
@@ -2059,65 +2096,6 @@ class Stream:
2059
2096
  await asyncio.sleep(0.2)
2060
2097
 
2061
2098
 
2062
- async def do_restore_calibration(self, filename):
2063
- report = json.load(open(filename, "r"))
2064
-
2065
- # Verify that the serial number matches.
2066
- device_info = await self.get_device_info()
2067
- if device_info['serial_number'] != report['device_info']['serial_number']:
2068
- raise RuntimeError(
2069
- f"Serial number in calibration ({report['serial_number']}) " +
2070
- f"does not match device ({device_info['serial_number']})")
2071
-
2072
- cal_result = report['calibration']
2073
-
2074
- await self.command(
2075
- f"conf set motor.poles {cal_result['poles']}")
2076
- if await self.is_config_supported("motor_position.sources.0.sign"):
2077
- await self.command(f"conf set motor_position.sources.0.sign {-1 if cal_result['invert'] else 1}")
2078
- else:
2079
- await self.command(
2080
- f"conf set motor.invert {1 if cal_result['invert'] else 0}")
2081
- if await self.is_config_supported("motor.phase_invert"):
2082
- phase_invert = cal_result.get('phase_invert', False)
2083
- await self.command(
2084
- f"conf set motor.phase_invert {1 if phase_invert else 0}")
2085
- for index, offset in enumerate(cal_result['offset']):
2086
- await self.command(f"conf set motor.offset.{index} {offset}")
2087
-
2088
- await self.command(f"conf set motor.resistance_ohm {report['winding_resistance']}")
2089
- if await self.is_config_supported("motor.v_per_hz"):
2090
- await self.command(f"conf set motor.v_per_hz {report['v_per_hz']}")
2091
- elif await self.is_config_supported("motor.Kv"):
2092
- await self.command(f"conf set motor.Kv {report['kv']}")
2093
-
2094
- pid_dq_kp = report.get('pid_dq_kp', None)
2095
- if pid_dq_kp is not None:
2096
- await self.command(f"conf set servo.pid_dq.kp {pid_dq_kp}")
2097
-
2098
- pid_dq_ki = report.get('pid_dq_ki', None)
2099
- if pid_dq_ki is not None:
2100
- await self.command(f"conf set servo.pid_dq.ki {pid_dq_ki}")
2101
-
2102
- enc_kp = report.get('encoder_filter_kp', None)
2103
- enc_ki = report.get('encoder_filter_ki', None)
2104
- enc_hz = report.get('encoder_filter_bw_hz', None)
2105
- if (enc_hz and
2106
- await self.is_config_supported(
2107
- "motor_position.sources.0.pll_filter_hz")):
2108
- await self.command(
2109
- f"conf set motor_position.sources.0.pll_filter_hz {enc_hz}")
2110
- elif await self.is_config_supported("servo.encoder_filter.kp"):
2111
- if enc_kp:
2112
- await self.command(f"conf set servo.encoder_filter.kp {enc_kp}")
2113
- if enc_ki:
2114
- await self.command(f"conf set servo.encoder_filter.ki {enc_ki}")
2115
-
2116
- await self.command("conf write")
2117
-
2118
- print("Calibration restored")
2119
-
2120
-
2121
2099
  class Runner:
2122
2100
  def __init__(self, args):
2123
2101
  self.args = args
@@ -2215,8 +2193,6 @@ class Runner:
2215
2193
  await stream.do_flash(self.args.flash)
2216
2194
  elif self.args.calibrate:
2217
2195
  await stream.do_calibrate()
2218
- elif self.args.restore_cal:
2219
- await stream.do_restore_calibration(self.args.restore_cal)
2220
2196
  else:
2221
2197
  raise RuntimeError("No action specified")
2222
2198
 
@@ -2238,6 +2214,7 @@ async def async_main():
2238
2214
 
2239
2215
  group = parser.add_mutually_exclusive_group()
2240
2216
 
2217
+ group.add_argument('--version', action='store_true')
2241
2218
  group.add_argument('-s', '--stop', action='store_true',
2242
2219
  help='command the servos to stop')
2243
2220
  group.add_argument('-i', '--info', action='store_true',
@@ -2252,6 +2229,8 @@ async def async_main():
2252
2229
  help='write the given configuration')
2253
2230
  group.add_argument('--flash', metavar='FILE',
2254
2231
  help='write the given elf file to flash')
2232
+ parser.add_argument('--no-verify', action='store_true',
2233
+ help='do not verify after flashing')
2255
2234
 
2256
2235
  parser.add_argument('--no-restore-config', action='store_true',
2257
2236
  help='do not restore config after flash')
@@ -2261,8 +2240,6 @@ async def async_main():
2261
2240
  group.add_argument('--calibrate', action='store_true',
2262
2241
  help='calibrate the motor, requires full freedom of motion')
2263
2242
 
2264
- group.add_argument('--restore-cal', metavar='FILE', type=str,
2265
- help='restore calibration from logged data')
2266
2243
  group.add_argument('--zero-offset', action='store_true',
2267
2244
  help='set the motor\'s position offset')
2268
2245
  group.add_argument('--set-offset', metavar='O',
@@ -2280,7 +2257,7 @@ async def async_main():
2280
2257
  help='calibrate a motor with hall commutation sensors')
2281
2258
 
2282
2259
  parser.add_argument('--cal-bw-hz', metavar='HZ', type=float,
2283
- default=100.0,
2260
+ default=200.0,
2284
2261
  help='configure current loop bandwidth in Hz')
2285
2262
  parser.add_argument('--encoder-bw-hz', metavar='HZ', type=float,
2286
2263
  default=None,
@@ -2353,6 +2330,10 @@ async def async_main():
2353
2330
 
2354
2331
  args = parser.parse_args()
2355
2332
 
2333
+ if args.version:
2334
+ print(f"moteus_tool version '{version.VERSION}'")
2335
+ sys.exit(0)
2336
+
2356
2337
  with Runner(args) as runner:
2357
2338
  await runner.start()
2358
2339
 
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.88"
15
+ VERSION="0.3.90"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: moteus
3
- Version: 0.3.88
3
+ Version: 0.3.90
4
4
  Summary: moteus brushless controller library and tools
5
5
  Home-page: https://github.com/mjbots/moteus
6
6
  Author: mjbots Robotic Systems
@@ -5,8 +5,8 @@ moteus/calibrate_encoder.py,sha256=Ami5e-LFw4RLoLseKcZx9QfS1PjQZJUwygvNZfPqd04,1
5
5
  moteus/command.py,sha256=UkOsbtkso6Oyex8CfbpAKpBNriik519ymxL86EZGkRs,1169
6
6
  moteus/export.py,sha256=XitBUuf4MDRIneXQSUptizIhZi2BdHyFO2Vo_2d2CFI,1742
7
7
  moteus/fdcanusb.py,sha256=SOAvUlleI6bKwQiApo7nYAaqBM4JoNPn4PHdPqsgsQQ,7707
8
- moteus/moteus.py,sha256=QH1y5W-KQ0hKoGDPv7lLkWTIS7YBS5pNO3gbYKoF5vw,53241
9
- moteus/moteus_tool.py,sha256=FmV-opl8PyhOHY9z_B86dyLuwbMwtkMRsH2CCNOf1Fo,98072
8
+ moteus/moteus.py,sha256=r-aFtIFaj-0s438bKBc0feOo-G8v4G6jlWk4_siQshE,53322
9
+ moteus/moteus_tool.py,sha256=YSij2l0HhldsdRYnehgs2Xp5sPf4QUSlRUfzseA3YyM,97403
10
10
  moteus/multiplex.py,sha256=2tdNX5JSh21TOjN6N9LKribLQtVYyyYbXjzwXB64sfA,12119
11
11
  moteus/posix_aioserial.py,sha256=2oDrw8TBEwuEQjY41g9rHeuFeffcPHqMwNS3nf5NVq8,3137
12
12
  moteus/pythoncan.py,sha256=j7Gv9tugQqTZbanm1lQGIoTvfmeS2kAxigB0n1a50lo,5039
@@ -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=bYmrQeWLHbeSIrqSgHNqnNU3m7WMEJx3w6Szx9slMuo,627
17
+ moteus/version.py,sha256=p-7him8WSagVK_VPWEM3hijP6IQe1euIvhNF2hqFEaM,627
18
18
  moteus/win32_aioserial.py,sha256=culdl-vYxBKD5n2s5LkIMGyUaHyCcEc8BL5-DWEaxX8,2025
19
- moteus-0.3.88.dist-info/METADATA,sha256=9dBQGXpZ4BTL8pA26Cvh2aD5WlHdgRj-3klygfDOvMA,3417
20
- moteus-0.3.88.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
21
- moteus-0.3.88.dist-info/entry_points.txt,sha256=accRcwir_K8wCf7i3qHb5R6CPh5SiSgd5a1A92ibb9E,56
22
- moteus-0.3.88.dist-info/top_level.txt,sha256=aZzmI_yecTaDrdSp29pTJuowaSQ9dlIZheQpshGg4YQ,7
23
- moteus-0.3.88.dist-info/RECORD,,
19
+ moteus-0.3.90.dist-info/METADATA,sha256=Iv4mDWBnQZtzCS5UBySeI3AAtVwIKl-NMX9xwke-NjU,3417
20
+ moteus-0.3.90.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
21
+ moteus-0.3.90.dist-info/entry_points.txt,sha256=accRcwir_K8wCf7i3qHb5R6CPh5SiSgd5a1A92ibb9E,56
22
+ moteus-0.3.90.dist-info/top_level.txt,sha256=aZzmI_yecTaDrdSp29pTJuowaSQ9dlIZheQpshGg4YQ,7
23
+ moteus-0.3.90.dist-info/RECORD,,