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 +63 -94
- moteus/version.py +1 -1
- {moteus-0.3.89.dist-info → moteus-0.3.90.dist-info}/METADATA +1 -1
- {moteus-0.3.89.dist-info → moteus-0.3.90.dist-info}/RECORD +7 -7
- {moteus-0.3.89.dist-info → moteus-0.3.90.dist-info}/WHEEL +0 -0
- {moteus-0.3.89.dist-info → moteus-0.3.90.dist-info}/entry_points.txt +0 -0
- {moteus-0.3.89.dist-info → moteus-0.3.90.dist-info}/top_level.txt +0 -0
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 =
|
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
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
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
|
-
|
1781
|
-
|
1782
|
-
|
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
|
-
|
1785
|
-
|
1786
|
-
|
1787
|
-
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
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
|
-
#
|
1796
|
-
encoder_bw_hz = min(control_rate_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
|
-
|
1803
|
-
|
1804
|
-
|
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
|
-
|
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=
|
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
@@ -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=
|
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=
|
17
|
+
moteus/version.py,sha256=p-7him8WSagVK_VPWEM3hijP6IQe1euIvhNF2hqFEaM,627
|
18
18
|
moteus/win32_aioserial.py,sha256=culdl-vYxBKD5n2s5LkIMGyUaHyCcEc8BL5-DWEaxX8,2025
|
19
|
-
moteus-0.3.
|
20
|
-
moteus-0.3.
|
21
|
-
moteus-0.3.
|
22
|
-
moteus-0.3.
|
23
|
-
moteus-0.3.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|