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 +4 -0
- moteus/moteus_tool.py +75 -94
- moteus/version.py +1 -1
- {moteus-0.3.88.dist-info → moteus-0.3.90.dist-info}/METADATA +1 -1
- {moteus-0.3.88.dist-info → moteus-0.3.90.dist-info}/RECORD +8 -8
- {moteus-0.3.88.dist-info → moteus-0.3.90.dist-info}/WHEEL +0 -0
- {moteus-0.3.88.dist-info → moteus-0.3.90.dist-info}/entry_points.txt +0 -0
- {moteus-0.3.88.dist-info → moteus-0.3.90.dist-info}/top_level.txt +0 -0
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 =
|
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
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
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
|
-
|
1774
|
-
|
1775
|
-
|
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
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
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
|
-
#
|
1789
|
-
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)
|
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
|
-
|
1796
|
-
|
1797
|
-
|
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
|
-
|
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=
|
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
@@ -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=
|
9
|
-
moteus/moteus_tool.py,sha256=
|
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=
|
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
|