moteus 0.3.89__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_tool.py CHANGED
@@ -84,7 +84,7 @@ def stddev(data):
84
84
  mean = sum(data) / len(data)
85
85
  return math.sqrt(sum((x - mean) ** 2 for x in data) / len(data))
86
86
 
87
- SUPPORTED_ABI_VERSION = 0x010b
87
+ SUPPORTED_ABI_VERSION = 0x010c
88
88
 
89
89
  # Old firmwares used a slightly incorrect definition of Kv/v_per_hz
90
90
  # that didn't match with vendors or oscilloscope tests.
@@ -111,6 +111,15 @@ class FirmwareUpgrade:
111
111
  lines = old_config.split(b'\n')
112
112
  items = dict([line.split(b' ') for line in lines if b' ' in line])
113
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
+
114
123
  if self.new <= 0x010a and self.old >= 0x010b:
115
124
  flux_brake_margin_voltage = float(items.pop(b'servo.flux_brake_margin_voltage'))
116
125
  max_voltage = float(items[b'servo.max_voltage'])
@@ -572,6 +581,16 @@ class FirmwareUpgrade:
572
581
  print(f"Upgraded servo.motor_derate_temperature to servo.motor_temperature_margin={motor_temperature_margin}")
573
582
  items[b'servo.motor_temperature_margin'] = str(motor_temperature_margin).encode('utf8')
574
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
+
575
594
  lines = [key + b' ' + value for key, value in items.items()]
576
595
  return b'\n'.join(lines)
577
596
 
@@ -1018,18 +1037,19 @@ class Stream:
1018
1037
  cmd = f"w {final_address:x} {'ff' * remaining_to_flush}"
1019
1038
  result = await self.command(cmd)
1020
1039
 
1021
- verify_ctx = FlashContext(elfs)
1022
- while True:
1023
- expected_block = verify_ctx.get_next_block()
1024
- cmd = f"r {expected_block.address:x} {len(expected_block.data):x}"
1025
- result = await self.command(cmd, allow_any_response=True)
1026
- # Emit progress first, to make it easier to see where
1027
- # things go wrong.
1028
- self._emit_flash_progress(verify_ctx, "verifying")
1029
- _verify_blocks(expected_block, result)
1030
- done = verify_ctx.advance_block()
1031
- if done:
1032
- 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
1033
1053
 
1034
1054
  async def read_servo_stats(self):
1035
1055
  servo_stats = await self.read_data("servo_stats")
@@ -1777,31 +1797,36 @@ class Stream:
1777
1797
  if output_type == 4: # kHall
1778
1798
  hall_output = True
1779
1799
 
1780
- if inductance and hall_output:
1781
- desired_encoder_bw_hz = min(
1782
- 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)
1783
1807
 
1784
- # Also, limit the bandwidth for halls based on the number
1785
- # of poles and the estimated calibration speed.
1786
- if hall_output:
1787
- max_pole_bandwidth_hz = (
1788
- 0.5 * self.args.cal_motor_poles *
1789
- self.args.cal_motor_speed)
1790
- desired_encoder_bw_hz = min(
1791
- 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)
1792
1816
 
1793
1817
 
1794
1818
  # And our bandwidth with the filter can be no larger than
1795
- # 1/30th the control rate.
1796
- 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)
1797
1821
 
1798
1822
  if encoder_bw_hz != desired_encoder_bw_hz:
1799
1823
  print(f"Warning: using lower encoder bandwidth than "+
1800
1824
  f"requested: {encoder_bw_hz:.1f}Hz")
1801
1825
 
1802
- w_3db = encoder_bw_hz * 2 * math.pi
1803
- kp = 2 * w_3db
1804
- 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
1805
1830
 
1806
1831
  if servo_style:
1807
1832
  await self.command(f"conf set servo.encoder_filter.enabled 1")
@@ -1809,7 +1834,12 @@ class Stream:
1809
1834
  await self.command(f"conf set servo.encoder_filter.ki {ki}")
1810
1835
  elif motor_position_style:
1811
1836
  commutation_source = await self.read_config_int("motor_position.commutation_source")
1812
- 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}")
1813
1843
  else:
1814
1844
  assert False
1815
1845
  return kp, ki, encoder_bw_hz
@@ -2066,65 +2096,6 @@ class Stream:
2066
2096
  await asyncio.sleep(0.2)
2067
2097
 
2068
2098
 
2069
- async def do_restore_calibration(self, filename):
2070
- report = json.load(open(filename, "r"))
2071
-
2072
- # Verify that the serial number matches.
2073
- device_info = await self.get_device_info()
2074
- if device_info['serial_number'] != report['device_info']['serial_number']:
2075
- raise RuntimeError(
2076
- f"Serial number in calibration ({report['serial_number']}) " +
2077
- f"does not match device ({device_info['serial_number']})")
2078
-
2079
- cal_result = report['calibration']
2080
-
2081
- await self.command(
2082
- f"conf set motor.poles {cal_result['poles']}")
2083
- if await self.is_config_supported("motor_position.sources.0.sign"):
2084
- await self.command(f"conf set motor_position.sources.0.sign {-1 if cal_result['invert'] else 1}")
2085
- else:
2086
- await self.command(
2087
- f"conf set motor.invert {1 if cal_result['invert'] else 0}")
2088
- if await self.is_config_supported("motor.phase_invert"):
2089
- phase_invert = cal_result.get('phase_invert', False)
2090
- await self.command(
2091
- f"conf set motor.phase_invert {1 if phase_invert else 0}")
2092
- for index, offset in enumerate(cal_result['offset']):
2093
- await self.command(f"conf set motor.offset.{index} {offset}")
2094
-
2095
- await self.command(f"conf set motor.resistance_ohm {report['winding_resistance']}")
2096
- if await self.is_config_supported("motor.v_per_hz"):
2097
- await self.command(f"conf set motor.v_per_hz {report['v_per_hz']}")
2098
- elif await self.is_config_supported("motor.Kv"):
2099
- await self.command(f"conf set motor.Kv {report['kv']}")
2100
-
2101
- pid_dq_kp = report.get('pid_dq_kp', None)
2102
- if pid_dq_kp is not None:
2103
- await self.command(f"conf set servo.pid_dq.kp {pid_dq_kp}")
2104
-
2105
- pid_dq_ki = report.get('pid_dq_ki', None)
2106
- if pid_dq_ki is not None:
2107
- await self.command(f"conf set servo.pid_dq.ki {pid_dq_ki}")
2108
-
2109
- enc_kp = report.get('encoder_filter_kp', None)
2110
- enc_ki = report.get('encoder_filter_ki', None)
2111
- enc_hz = report.get('encoder_filter_bw_hz', None)
2112
- if (enc_hz and
2113
- await self.is_config_supported(
2114
- "motor_position.sources.0.pll_filter_hz")):
2115
- await self.command(
2116
- f"conf set motor_position.sources.0.pll_filter_hz {enc_hz}")
2117
- elif await self.is_config_supported("servo.encoder_filter.kp"):
2118
- if enc_kp:
2119
- await self.command(f"conf set servo.encoder_filter.kp {enc_kp}")
2120
- if enc_ki:
2121
- await self.command(f"conf set servo.encoder_filter.ki {enc_ki}")
2122
-
2123
- await self.command("conf write")
2124
-
2125
- print("Calibration restored")
2126
-
2127
-
2128
2099
  class Runner:
2129
2100
  def __init__(self, args):
2130
2101
  self.args = args
@@ -2222,8 +2193,6 @@ class Runner:
2222
2193
  await stream.do_flash(self.args.flash)
2223
2194
  elif self.args.calibrate:
2224
2195
  await stream.do_calibrate()
2225
- elif self.args.restore_cal:
2226
- await stream.do_restore_calibration(self.args.restore_cal)
2227
2196
  else:
2228
2197
  raise RuntimeError("No action specified")
2229
2198
 
@@ -2260,6 +2229,8 @@ async def async_main():
2260
2229
  help='write the given configuration')
2261
2230
  group.add_argument('--flash', metavar='FILE',
2262
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')
2263
2234
 
2264
2235
  parser.add_argument('--no-restore-config', action='store_true',
2265
2236
  help='do not restore config after flash')
@@ -2269,8 +2240,6 @@ async def async_main():
2269
2240
  group.add_argument('--calibrate', action='store_true',
2270
2241
  help='calibrate the motor, requires full freedom of motion')
2271
2242
 
2272
- group.add_argument('--restore-cal', metavar='FILE', type=str,
2273
- help='restore calibration from logged data')
2274
2243
  group.add_argument('--zero-offset', action='store_true',
2275
2244
  help='set the motor\'s position offset')
2276
2245
  group.add_argument('--set-offset', metavar='O',
@@ -2288,7 +2257,7 @@ async def async_main():
2288
2257
  help='calibrate a motor with hall commutation sensors')
2289
2258
 
2290
2259
  parser.add_argument('--cal-bw-hz', metavar='HZ', type=float,
2291
- default=100.0,
2260
+ default=200.0,
2292
2261
  help='configure current loop bandwidth in Hz')
2293
2262
  parser.add_argument('--encoder-bw-hz', metavar='HZ', type=float,
2294
2263
  default=None,
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.89"
15
+ VERSION="0.3.90"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: moteus
3
- Version: 0.3.89
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
@@ -6,7 +6,7 @@ moteus/command.py,sha256=UkOsbtkso6Oyex8CfbpAKpBNriik519ymxL86EZGkRs,1169
6
6
  moteus/export.py,sha256=XitBUuf4MDRIneXQSUptizIhZi2BdHyFO2Vo_2d2CFI,1742
7
7
  moteus/fdcanusb.py,sha256=SOAvUlleI6bKwQiApo7nYAaqBM4JoNPn4PHdPqsgsQQ,7707
8
8
  moteus/moteus.py,sha256=r-aFtIFaj-0s438bKBc0feOo-G8v4G6jlWk4_siQshE,53322
9
- moteus/moteus_tool.py,sha256=2uiQyOvdn-Yd2mt0pTsZ2fzJIVb2kJeeyfT2taIiIX0,98391
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=awY4LvZ9OK5lPoCwGam-n3a3hS_9hqodOOLIH3OHQCw,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.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,,
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,,