moteus 0.3.87__py3-none-any.whl → 0.3.89__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/fdcanusb.py +30 -17
- moteus/moteus.py +23 -1
- moteus/moteus_tool.py +17 -3
- moteus/pythoncan.py +13 -8
- moteus/version.py +1 -1
- {moteus-0.3.87.dist-info → moteus-0.3.89.dist-info}/METADATA +1 -2
- {moteus-0.3.87.dist-info → moteus-0.3.89.dist-info}/RECORD +10 -10
- {moteus-0.3.87.dist-info → moteus-0.3.89.dist-info}/WHEEL +0 -0
- {moteus-0.3.87.dist-info → moteus-0.3.89.dist-info}/entry_points.txt +0 -0
- {moteus-0.3.87.dist-info → moteus-0.3.89.dist-info}/top_level.txt +0 -0
moteus/fdcanusb.py
CHANGED
@@ -136,17 +136,34 @@ class Fdcanusb:
|
|
136
136
|
# individually.
|
137
137
|
return [await self._do_command(x) for x in commands]
|
138
138
|
|
139
|
+
def _parse_message(self, line):
|
140
|
+
fields = line.split(b" ")
|
141
|
+
message = CanMessage()
|
142
|
+
message.data = _dehexify(fields[2])
|
143
|
+
message.arbitration_id = int(fields[1], 16)
|
144
|
+
|
145
|
+
flags = fields[3] if len(fields) > 3 else ''
|
146
|
+
if b'E' in flags:
|
147
|
+
message.is_extended_id = True
|
148
|
+
if b'B' in flags:
|
149
|
+
message.bitrate_switch = True
|
150
|
+
if b'F' in flags:
|
151
|
+
message.is_fd = True
|
152
|
+
|
153
|
+
return message
|
154
|
+
|
139
155
|
async def _do_command(self, command):
|
140
156
|
await self.write(command)
|
141
157
|
reply_required = command.reply_required
|
142
158
|
|
143
159
|
# Get the OK response.
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
160
|
+
while True:
|
161
|
+
ok_response = await self._readline(self._serial)
|
162
|
+
if ok_response.startswith(b"OK"):
|
163
|
+
break
|
164
|
+
# Ignore spurious responses until we get an OK.
|
148
165
|
|
149
|
-
|
166
|
+
while reply_required:
|
150
167
|
line = await self._readline(self._serial)
|
151
168
|
|
152
169
|
if not line.startswith(b"rcv"):
|
@@ -157,16 +174,15 @@ class Fdcanusb:
|
|
157
174
|
self._debug_log.write(f'{time.time()} < '.encode('latin1') +
|
158
175
|
line.rstrip() + b'\n')
|
159
176
|
|
160
|
-
|
161
|
-
|
162
|
-
message.
|
163
|
-
message.arbitration_id = int(fields[1], 16)
|
177
|
+
message = self._parse_message(line)
|
178
|
+
|
179
|
+
moteus_id = (message.arbitration_id >> 8) & 0x7f
|
164
180
|
|
165
|
-
|
166
|
-
|
167
|
-
# source/destination or CAN prefix.
|
181
|
+
if command.raw or moteus_id == command.destination:
|
182
|
+
return command.parse(message)
|
168
183
|
|
169
|
-
|
184
|
+
# We are not raw and the message wasn't from the device we
|
185
|
+
# were writing to, so just loop and try some more.
|
170
186
|
|
171
187
|
async def write(self, command):
|
172
188
|
# This merely sends a command and doesn't even wait for an OK
|
@@ -201,10 +217,7 @@ class Fdcanusb:
|
|
201
217
|
self._debug_log.write(f'{time.time()} < '.encode('latin1') +
|
202
218
|
line.rstrip() + b'\n')
|
203
219
|
|
204
|
-
|
205
|
-
message = CanMessage()
|
206
|
-
message.data = _dehexify(fields[2])
|
207
|
-
message.arbitration_id = int(fields[1], 16)
|
220
|
+
message = self._parse_message(line)
|
208
221
|
return message
|
209
222
|
|
210
223
|
def _round_up_dlc(self, size):
|
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
|
@@ -578,7 +582,7 @@ class Result:
|
|
578
582
|
def make_parser(id):
|
579
583
|
def parse(message):
|
580
584
|
result = Result()
|
581
|
-
result.id =
|
585
|
+
result.id = (message.arbitration_id >> 8) & 0x7f
|
582
586
|
result.values = parse_reply(message.data)
|
583
587
|
|
584
588
|
# We store these things just for reference, so that our
|
@@ -630,6 +634,11 @@ def make_diagnostic_parser(id, channel):
|
|
630
634
|
return parse
|
631
635
|
|
632
636
|
|
637
|
+
class FaultError(RuntimeError):
|
638
|
+
def __init__(self, mode, code):
|
639
|
+
super(FaultError, self).__init__(f"Fault mode={mode} code={code}")
|
640
|
+
|
641
|
+
|
633
642
|
class Controller:
|
634
643
|
"""Operates a single moteus controller across some communication
|
635
644
|
medium.
|
@@ -1019,6 +1028,9 @@ class Controller:
|
|
1019
1028
|
reports that the trajectory has been completed.
|
1020
1029
|
|
1021
1030
|
If the controller is unresponsive, this method will never return.
|
1031
|
+
|
1032
|
+
If the controller reports a fault or position mode timeout, a
|
1033
|
+
FaultError exception will be raised.
|
1022
1034
|
"""
|
1023
1035
|
|
1024
1036
|
if query_override is None:
|
@@ -1026,6 +1038,10 @@ class Controller:
|
|
1026
1038
|
else:
|
1027
1039
|
query_override = copy.deepcopy(query_override)
|
1028
1040
|
|
1041
|
+
if query_override.mode == mp.IGNORE:
|
1042
|
+
query_override.mode = mp.INT8
|
1043
|
+
if query_override.fault == mp.IGNORE:
|
1044
|
+
query_override.fault = mp.INT8
|
1029
1045
|
query_override.trajectory_complete = mp.INT8
|
1030
1046
|
|
1031
1047
|
count = 2
|
@@ -1041,6 +1057,12 @@ class Controller:
|
|
1041
1057
|
result.values[Register.TRAJECTORY_COMPLETE]):
|
1042
1058
|
return result
|
1043
1059
|
|
1060
|
+
current_mode = result.values.get(Register.MODE, Mode.STOPPED)
|
1061
|
+
fault_code = result.values.get(Register.FAULT, 0)
|
1062
|
+
|
1063
|
+
if current_mode == Mode.FAULT or current_mode == Mode.TIMEOUT:
|
1064
|
+
raise FaultError(current_mode, fault_code)
|
1065
|
+
|
1044
1066
|
await asyncio.sleep(period_s)
|
1045
1067
|
|
1046
1068
|
def make_vfoc(self,
|
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
|
|
@@ -1256,6 +1262,7 @@ class Stream:
|
|
1256
1262
|
'motor_position_output_sign' : motor_output_sign,
|
1257
1263
|
'abi_version' : self.firmware.version,
|
1258
1264
|
'voltage_mode_control' : voltage_mode_control,
|
1265
|
+
'py_version' : version.VERSION,
|
1259
1266
|
}
|
1260
1267
|
|
1261
1268
|
log_filename = f"moteus-cal-{device_info['serial_number']}-{now.strftime('%Y%m%dT%H%M%S.%f')}.log"
|
@@ -1618,13 +1625,15 @@ class Stream:
|
|
1618
1625
|
# non-linear, corrupting the result.
|
1619
1626
|
|
1620
1627
|
# What we'll do is take the very last result, and the last
|
1621
|
-
# result that is less than
|
1628
|
+
# result that is less than X% of the current of the last
|
1622
1629
|
# result.
|
1623
1630
|
|
1624
1631
|
last_result = results[-1]
|
1625
1632
|
|
1626
|
-
|
1627
|
-
|
1633
|
+
less_than_X = [x for x in results if x[1] < 0.60 * last_result[1]]
|
1634
|
+
if len(less_than_X) == 0:
|
1635
|
+
raise RuntimeError(f"Could not detect resistance, is motor connected? Peak current only {last_result[1]:.3f}A w/ {last_result[0]:.3f}V applied.")
|
1636
|
+
less_than = less_than_X[-1]
|
1628
1637
|
|
1629
1638
|
resistance = ((last_result[0] - less_than[0]) /
|
1630
1639
|
(last_result[1] - less_than[1]))
|
@@ -2236,6 +2245,7 @@ async def async_main():
|
|
2236
2245
|
|
2237
2246
|
group = parser.add_mutually_exclusive_group()
|
2238
2247
|
|
2248
|
+
group.add_argument('--version', action='store_true')
|
2239
2249
|
group.add_argument('-s', '--stop', action='store_true',
|
2240
2250
|
help='command the servos to stop')
|
2241
2251
|
group.add_argument('-i', '--info', action='store_true',
|
@@ -2351,6 +2361,10 @@ async def async_main():
|
|
2351
2361
|
|
2352
2362
|
args = parser.parse_args()
|
2353
2363
|
|
2364
|
+
if args.version:
|
2365
|
+
print(f"moteus_tool version '{version.VERSION}'")
|
2366
|
+
sys.exit(0)
|
2367
|
+
|
2354
2368
|
with Runner(args) as runner:
|
2355
2369
|
await runner.start()
|
2356
2370
|
|
moteus/pythoncan.py
CHANGED
@@ -95,16 +95,16 @@ class PythonCan:
|
|
95
95
|
async def _do_command(self, command):
|
96
96
|
await self.write(command)
|
97
97
|
|
98
|
-
|
99
|
-
|
98
|
+
while command.reply_required:
|
99
|
+
reply = await self.read()
|
100
100
|
|
101
|
-
|
101
|
+
moteus_id = (reply.arbitration_id >> 8) & 0x7f
|
102
102
|
|
103
|
-
|
104
|
-
|
105
|
-
# checking.
|
103
|
+
if command.raw or command.destination == moteus_id:
|
104
|
+
return command.parse(reply)
|
106
105
|
|
107
|
-
|
106
|
+
# We did not get a response from the device we were hoping
|
107
|
+
# for, so just keep waiting.
|
108
108
|
|
109
109
|
async def write(self, command):
|
110
110
|
reply_required = command.reply_required
|
@@ -125,7 +125,12 @@ class PythonCan:
|
|
125
125
|
|
126
126
|
async def read(self):
|
127
127
|
self._maybe_setup()
|
128
|
-
|
128
|
+
while True:
|
129
|
+
frame = await self._reader.get_message()
|
130
|
+
if not frame.is_error_frame:
|
131
|
+
return frame
|
132
|
+
# Just ignore error frames entirely and keep reading until
|
133
|
+
# we get a good one.
|
129
134
|
|
130
135
|
def _round_up_dlc(self, size):
|
131
136
|
if size <= 8:
|
moteus/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: moteus
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.89
|
4
4
|
Summary: moteus brushless controller library and tools
|
5
5
|
Home-page: https://github.com/mjbots/moteus
|
6
6
|
Author: mjbots Robotic Systems
|
@@ -13,7 +13,6 @@ Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Requires-Python: >=3.7, <4
|
14
14
|
Description-Content-Type: text/markdown
|
15
15
|
Requires-Dist: importlib-metadata >=3.6
|
16
|
-
Requires-Dist: numpy <2
|
17
16
|
Requires-Dist: pyelftools >=0.26
|
18
17
|
Requires-Dist: pyserial >=3.5
|
19
18
|
Requires-Dist: python-can >=3.3
|
@@ -4,20 +4,20 @@ moteus/aiostream.py,sha256=YAkVF6QWsA49vqO-GgXEohDghqm_-nnajJzhO_Q9qNQ,3696
|
|
4
4
|
moteus/calibrate_encoder.py,sha256=Ami5e-LFw4RLoLseKcZx9QfS1PjQZJUwygvNZfPqd04,15494
|
5
5
|
moteus/command.py,sha256=UkOsbtkso6Oyex8CfbpAKpBNriik519ymxL86EZGkRs,1169
|
6
6
|
moteus/export.py,sha256=XitBUuf4MDRIneXQSUptizIhZi2BdHyFO2Vo_2d2CFI,1742
|
7
|
-
moteus/fdcanusb.py,sha256=
|
8
|
-
moteus/moteus.py,sha256=
|
9
|
-
moteus/moteus_tool.py,sha256=
|
7
|
+
moteus/fdcanusb.py,sha256=SOAvUlleI6bKwQiApo7nYAaqBM4JoNPn4PHdPqsgsQQ,7707
|
8
|
+
moteus/moteus.py,sha256=r-aFtIFaj-0s438bKBc0feOo-G8v4G6jlWk4_siQshE,53322
|
9
|
+
moteus/moteus_tool.py,sha256=2uiQyOvdn-Yd2mt0pTsZ2fzJIVb2kJeeyfT2taIiIX0,98391
|
10
10
|
moteus/multiplex.py,sha256=2tdNX5JSh21TOjN6N9LKribLQtVYyyYbXjzwXB64sfA,12119
|
11
11
|
moteus/posix_aioserial.py,sha256=2oDrw8TBEwuEQjY41g9rHeuFeffcPHqMwNS3nf5NVq8,3137
|
12
|
-
moteus/pythoncan.py,sha256=
|
12
|
+
moteus/pythoncan.py,sha256=j7Gv9tugQqTZbanm1lQGIoTvfmeS2kAxigB0n1a50lo,5039
|
13
13
|
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=awY4LvZ9OK5lPoCwGam-n3a3hS_9hqodOOLIH3OHQCw,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.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,,
|
File without changes
|
File without changes
|
File without changes
|