motor-python 0.0.10__tar.gz → 0.0.11__tar.gz
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.
- {motor_python-0.0.10 → motor_python-0.0.11}/PKG-INFO +1 -1
- {motor_python-0.0.10 → motor_python-0.0.11}/pyproject.toml +1 -1
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/__init__.py +1 -1
- motor_python-0.0.11/src/motor_python/can_utils.py +275 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/definitions.py +12 -0
- motor_python-0.0.10/src/motor_python/can_utils.py +0 -88
- {motor_python-0.0.10 → motor_python-0.0.11}/.gitignore +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/LICENSE +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/README.md +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/scripts/README.md +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/__main__.py +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/base_motor.py +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/can_protocol.py +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/cube_mars_motor.py +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/cube_mars_motor_can.py +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/examples.py +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/examples_can.py +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/motor_control_using_pid.py +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/motor_manager.py +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/motor_status_parser.py +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/pid_controller.py +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/second_order_low_pass_filter.py +0 -0
- {motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: motor_python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.11
|
|
4
4
|
Summary: CubeMars motor module for Aries exosuits.
|
|
5
5
|
Project-URL: homepage, https://github.com/TUM-Aries-Lab/motor-module
|
|
6
6
|
Author-email: Tsmorz <tony.smoragiewicz@tum.de>, Hannes Nguyen <hannes.nguyen@tum.de>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "motor_python"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.11"
|
|
4
4
|
description = "CubeMars motor module for Aries exosuits."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "Tsmorz", email = "tony.smoragiewicz@tum.de"},{ name = "Hannes Nguyen", email = "hannes.nguyen@tum.de" }]
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""CAN Network Utilities for Linux (Jetson).
|
|
2
|
+
|
|
3
|
+
Three operations on the interface, in increasing order of violence:
|
|
4
|
+
|
|
5
|
+
* :func:`get_can_state` reads the controller's error state and counters;
|
|
6
|
+
* :func:`ensure_can_interface` raises the link if it is down, and leaves it
|
|
7
|
+
strictly alone if it is already up at the right bitrate;
|
|
8
|
+
* :func:`reset_can_interface` reloads the kernel module, which is the only
|
|
9
|
+
thing that clears latched error counters on the Orin's mttcan controller.
|
|
10
|
+
|
|
11
|
+
Reach for the lightest one that answers the problem. Reconfiguring a working
|
|
12
|
+
bus is not free: taking the link down and up resets the controller and can
|
|
13
|
+
drop a motor into BUS-OFF.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import shutil
|
|
17
|
+
import subprocess
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
from loguru import logger
|
|
21
|
+
|
|
22
|
+
from motor_python.definitions import CAN_DEFAULTS
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_can_state(interface: str = "can0") -> dict:
|
|
26
|
+
"""Parse CAN controller state from the kernel via `ip` command.
|
|
27
|
+
|
|
28
|
+
:param interface: CAN interface name (e.g. 'can0').
|
|
29
|
+
:return: Dict with keys 'state', 'tx_err', 'rx_err'.
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
result = subprocess.run(
|
|
33
|
+
["ip", "-details", "-statistics", "link", "show", interface],
|
|
34
|
+
capture_output=True,
|
|
35
|
+
text=True,
|
|
36
|
+
timeout=5,
|
|
37
|
+
)
|
|
38
|
+
output = result.stdout
|
|
39
|
+
state = "UNKNOWN"
|
|
40
|
+
tx_err = rx_err = 0
|
|
41
|
+
for line in output.splitlines():
|
|
42
|
+
if "state" in line and "berr-counter" in line:
|
|
43
|
+
for part in line.split():
|
|
44
|
+
if part in (
|
|
45
|
+
"ERROR-ACTIVE",
|
|
46
|
+
"ERROR-PASSIVE",
|
|
47
|
+
"ERROR-WARNING",
|
|
48
|
+
"BUS-OFF",
|
|
49
|
+
):
|
|
50
|
+
state = part
|
|
51
|
+
if "tx" in line:
|
|
52
|
+
idx = line.index("tx")
|
|
53
|
+
tx_err = int(line[idx:].split()[1])
|
|
54
|
+
if "rx" in line:
|
|
55
|
+
idx = line.index("rx")
|
|
56
|
+
rx_err = int(line[idx:].split()[1].rstrip(")"))
|
|
57
|
+
return {"state": state, "tx_err": tx_err, "rx_err": rx_err}
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.debug(f"Could not read CAN state: {e}")
|
|
60
|
+
return {"state": "UNKNOWN", "tx_err": 0, "rx_err": 0}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def reset_can_interface(interface: str = "can0", bitrate: int = 1000000) -> bool:
|
|
64
|
+
"""Fully reset the CAN interface by reloading the mttcan kernel module.
|
|
65
|
+
|
|
66
|
+
``ip link set down/up`` does NOT reset TX/RX error counters on the
|
|
67
|
+
Jetson Orin Nano mttcan controller and causes a CAN bus disruption
|
|
68
|
+
that can push the motor into BUS-OFF (completely silent). This method
|
|
69
|
+
instead:
|
|
70
|
+
|
|
71
|
+
1. Takes the interface down.
|
|
72
|
+
2. Unloads the mttcan module — the bus is silent during unload, giving
|
|
73
|
+
the motor's CAN controller time to see the 128×11 recessive bits
|
|
74
|
+
required by the CAN spec to recover from BUS-OFF.
|
|
75
|
+
3. Reloads the module and brings the interface back up.
|
|
76
|
+
|
|
77
|
+
:param interface: CAN interface name (e.g. 'can0').
|
|
78
|
+
:param bitrate: Desired bitrate (e.g. 1000000).
|
|
79
|
+
:return: True if successful, False if the commands fail (e.g. no sudo).
|
|
80
|
+
"""
|
|
81
|
+
logger.warning(
|
|
82
|
+
f"Performing full kernel-level reset of {interface} to clear error states..."
|
|
83
|
+
)
|
|
84
|
+
cmds = [
|
|
85
|
+
f"sudo ip link set {interface} down",
|
|
86
|
+
"sudo rmmod mttcan",
|
|
87
|
+
"sleep 0.5", # Let bus stay recessive so nodes can recover
|
|
88
|
+
"sudo modprobe mttcan",
|
|
89
|
+
# Keep runtime reset behavior aligned with setup_can.sh.
|
|
90
|
+
f"sudo ip link set {interface} up type can bitrate {bitrate} berr-reporting on restart-ms 100",
|
|
91
|
+
f"sudo ip link set {interface} txqueuelen 1000",
|
|
92
|
+
]
|
|
93
|
+
try:
|
|
94
|
+
for cmd in cmds:
|
|
95
|
+
if cmd == "sleep 0.5":
|
|
96
|
+
time.sleep(0.5)
|
|
97
|
+
else:
|
|
98
|
+
subprocess.run(cmd.split(), check=True)
|
|
99
|
+
time.sleep(0.1)
|
|
100
|
+
logger.success(f"CAN interface {interface} reset successfully.")
|
|
101
|
+
return True
|
|
102
|
+
except subprocess.CalledProcessError as e:
|
|
103
|
+
logger.error(f"Failed to reset CAN interface: {e}")
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _run(command: list[str], timeout: float) -> subprocess.CompletedProcess | None:
|
|
108
|
+
"""Run a command, returning None if it could not be run at all.
|
|
109
|
+
|
|
110
|
+
:param command: Argument vector.
|
|
111
|
+
:param timeout: Seconds to wait before giving up.
|
|
112
|
+
:return: The finished process, or None if it could not run.
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
115
|
+
# check=False: a non-zero exit is information here, not an exception.
|
|
116
|
+
# The caller reads returncode and reports it with the interface's name.
|
|
117
|
+
return subprocess.run(
|
|
118
|
+
command, capture_output=True, text=True, timeout=timeout, check=False
|
|
119
|
+
)
|
|
120
|
+
except (OSError, subprocess.SubprocessError) as err:
|
|
121
|
+
logger.debug(f"Could not run {' '.join(command)}: '{err}'.")
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def read_can_interface(
|
|
126
|
+
interface: str = CAN_DEFAULTS.interface,
|
|
127
|
+
) -> tuple[bool, bool, int | None]:
|
|
128
|
+
"""Report what the kernel currently thinks of a CAN interface.
|
|
129
|
+
|
|
130
|
+
:param interface: CAN interface name (e.g. 'can0').
|
|
131
|
+
:return: ``(exists, is_up, bitrate)``; bitrate is None when unreadable.
|
|
132
|
+
:rtype: tuple[bool, bool, int | None]
|
|
133
|
+
"""
|
|
134
|
+
result = _run(
|
|
135
|
+
["ip", "-details", "link", "show", interface],
|
|
136
|
+
CAN_DEFAULTS.can_command_timeout_s,
|
|
137
|
+
)
|
|
138
|
+
if result is None or result.returncode != 0:
|
|
139
|
+
return False, False, None
|
|
140
|
+
|
|
141
|
+
output = result.stdout
|
|
142
|
+
# The flags live in angle brackets on the first line. "UP" also appears in
|
|
143
|
+
# "state UP" further along, but the flag is the authoritative one -- an
|
|
144
|
+
# interface can carry the UP flag while its state reads UNKNOWN, which is
|
|
145
|
+
# normal for CAN.
|
|
146
|
+
first_line = output.splitlines()[0] if output else ""
|
|
147
|
+
is_up = "UP" in first_line.split("<")[-1].split(">")[0].split(",")
|
|
148
|
+
|
|
149
|
+
bitrate = None
|
|
150
|
+
fields = output.split()
|
|
151
|
+
if "bitrate" in fields:
|
|
152
|
+
try:
|
|
153
|
+
bitrate = int(fields[fields.index("bitrate") + 1])
|
|
154
|
+
except (IndexError, ValueError):
|
|
155
|
+
bitrate = None
|
|
156
|
+
return True, is_up, bitrate
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def ensure_can_interface(
|
|
160
|
+
interface: str = CAN_DEFAULTS.interface,
|
|
161
|
+
bitrate: int = CAN_DEFAULTS.bitrate,
|
|
162
|
+
) -> bool:
|
|
163
|
+
"""Make sure a CAN interface is up at the given bitrate, raising it if not.
|
|
164
|
+
|
|
165
|
+
Callers otherwise have to remember ``sudo ./setup_can.sh`` after every power
|
|
166
|
+
cycle, and forgetting it presents as a motor that will not answer -- which
|
|
167
|
+
sends you to the wiring for a fault that is one command away.
|
|
168
|
+
|
|
169
|
+
**Idempotent**: an interface already up at the right bitrate is left
|
|
170
|
+
untouched. That is the point of the check rather than a shortcut through
|
|
171
|
+
it, because bringing a working link down and up resets the controller and
|
|
172
|
+
can drop a motor into BUS-OFF.
|
|
173
|
+
|
|
174
|
+
This does not reload the ``mttcan`` module; see
|
|
175
|
+
:func:`reset_can_interface` for that, which is the right tool once error
|
|
176
|
+
counters have latched and the wrong one to use unasked at startup.
|
|
177
|
+
|
|
178
|
+
:param interface: CAN interface name (e.g. 'can0').
|
|
179
|
+
:param bitrate: Desired bitrate in bits/sec.
|
|
180
|
+
:return: True if the interface is usable afterwards.
|
|
181
|
+
:rtype: bool
|
|
182
|
+
"""
|
|
183
|
+
if shutil.which("ip") is None:
|
|
184
|
+
logger.debug(
|
|
185
|
+
"No 'ip' command, so this is not a Linux host. Skipping CAN setup."
|
|
186
|
+
)
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
exists, is_up, current_bitrate = read_can_interface(interface)
|
|
190
|
+
if not exists:
|
|
191
|
+
logger.error(
|
|
192
|
+
f"No CAN interface '{interface}'. The mttcan kernel module is "
|
|
193
|
+
f"probably not loaded; run setup_can.sh once, which modprobes it."
|
|
194
|
+
)
|
|
195
|
+
return False
|
|
196
|
+
|
|
197
|
+
if is_up and current_bitrate == bitrate:
|
|
198
|
+
logger.info(
|
|
199
|
+
f"CAN interface '{interface}' is already up at {bitrate} bps. "
|
|
200
|
+
f"Leaving it alone."
|
|
201
|
+
)
|
|
202
|
+
return True
|
|
203
|
+
|
|
204
|
+
reason = "down" if not is_up else f"at {current_bitrate} bps rather than {bitrate}"
|
|
205
|
+
logger.warning(f"CAN interface '{interface}' is {reason}. Bringing it up.")
|
|
206
|
+
return _bring_up_can_interface(interface, bitrate)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _bring_up_can_interface(interface: str, bitrate: int) -> bool:
|
|
210
|
+
"""Configure and raise the interface.
|
|
211
|
+
|
|
212
|
+
``sudo -n`` throughout: a control process must not stop at a password
|
|
213
|
+
prompt, so a host without passwordless sudo fails immediately and says so
|
|
214
|
+
rather than hanging until something times out.
|
|
215
|
+
|
|
216
|
+
:param interface: CAN interface name.
|
|
217
|
+
:param bitrate: Desired bitrate in bits/sec.
|
|
218
|
+
:return: True if the interface came up at the requested bitrate.
|
|
219
|
+
:rtype: bool
|
|
220
|
+
"""
|
|
221
|
+
timeout = CAN_DEFAULTS.can_command_timeout_s
|
|
222
|
+
commands = [
|
|
223
|
+
# Down first: "up type can bitrate ..." is rejected on an interface that
|
|
224
|
+
# is already up, which is the case when only the bitrate is wrong.
|
|
225
|
+
["sudo", "-n", "ip", "link", "set", interface, "down"],
|
|
226
|
+
[
|
|
227
|
+
"sudo",
|
|
228
|
+
"-n",
|
|
229
|
+
"ip",
|
|
230
|
+
"link",
|
|
231
|
+
"set",
|
|
232
|
+
interface,
|
|
233
|
+
"up",
|
|
234
|
+
"type",
|
|
235
|
+
"can",
|
|
236
|
+
"bitrate",
|
|
237
|
+
str(bitrate),
|
|
238
|
+
"berr-reporting",
|
|
239
|
+
"on",
|
|
240
|
+
"restart-ms",
|
|
241
|
+
str(CAN_DEFAULTS.can_restart_ms),
|
|
242
|
+
],
|
|
243
|
+
[
|
|
244
|
+
"sudo",
|
|
245
|
+
"-n",
|
|
246
|
+
"ip",
|
|
247
|
+
"link",
|
|
248
|
+
"set",
|
|
249
|
+
interface,
|
|
250
|
+
"txqueuelen",
|
|
251
|
+
str(CAN_DEFAULTS.can_tx_queue_length),
|
|
252
|
+
],
|
|
253
|
+
]
|
|
254
|
+
|
|
255
|
+
for command in commands:
|
|
256
|
+
result = _run(command, timeout)
|
|
257
|
+
if result is None or result.returncode != 0:
|
|
258
|
+
detail = (result.stderr or result.stdout).strip() if result else "not run"
|
|
259
|
+
logger.error(
|
|
260
|
+
f"Could not bring up '{interface}': '{detail}'. Run "
|
|
261
|
+
f"scripts/allow_can_bringup_without_password.sh once to fix this "
|
|
262
|
+
f"permanently, or setup_can.sh with sudo for just this boot."
|
|
263
|
+
)
|
|
264
|
+
return False
|
|
265
|
+
|
|
266
|
+
_, is_up, current_bitrate = read_can_interface(interface)
|
|
267
|
+
if not (is_up and current_bitrate == bitrate):
|
|
268
|
+
logger.error(
|
|
269
|
+
f"'{interface}' did not come up as asked: up={is_up}, "
|
|
270
|
+
f"bitrate={current_bitrate}."
|
|
271
|
+
)
|
|
272
|
+
return False
|
|
273
|
+
|
|
274
|
+
logger.success(f"CAN interface '{interface}' up at {bitrate} bps.")
|
|
275
|
+
return True
|
|
@@ -98,6 +98,18 @@ class CANDefaults:
|
|
|
98
98
|
retry_backoff: float = 0.01 # Base backoff time for retries (seconds)
|
|
99
99
|
can_reset_pause: float = 0.1 # Small Pause after CAN bus reset (seconds)
|
|
100
100
|
|
|
101
|
+
# Link settings used when raising the interface, matching setup_can.sh.
|
|
102
|
+
# berr-reporting and restart-ms are what let the controller restart itself
|
|
103
|
+
# out of BUS-OFF -- a single unacknowledged frame is enough to cause it, and
|
|
104
|
+
# without these the interface goes permanently silent instead of recovering.
|
|
105
|
+
can_restart_ms: int = 100
|
|
106
|
+
# The kernel default of 10 is too shallow for a 100 Hz control loop driving
|
|
107
|
+
# two motors.
|
|
108
|
+
can_tx_queue_length: int = 1000
|
|
109
|
+
# Per `ip` invocation. Generous for a command that returns immediately, and
|
|
110
|
+
# short enough that a host which cannot run it does not stall startup.
|
|
111
|
+
can_command_timeout_s: float = 5.0
|
|
112
|
+
|
|
101
113
|
|
|
102
114
|
@dataclass(frozen=True)
|
|
103
115
|
class MITModeLimits:
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
"""CAN Network Utilities for Linux (Jetson)."""
|
|
2
|
-
|
|
3
|
-
import subprocess
|
|
4
|
-
import time
|
|
5
|
-
|
|
6
|
-
from loguru import logger
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def get_can_state(interface: str = "can0") -> dict:
|
|
10
|
-
"""Parse CAN controller state from the kernel via `ip` command.
|
|
11
|
-
|
|
12
|
-
:param interface: CAN interface name (e.g. 'can0').
|
|
13
|
-
:return: Dict with keys 'state', 'tx_err', 'rx_err'.
|
|
14
|
-
"""
|
|
15
|
-
try:
|
|
16
|
-
result = subprocess.run(
|
|
17
|
-
["ip", "-details", "-statistics", "link", "show", interface],
|
|
18
|
-
capture_output=True,
|
|
19
|
-
text=True,
|
|
20
|
-
timeout=5,
|
|
21
|
-
)
|
|
22
|
-
output = result.stdout
|
|
23
|
-
state = "UNKNOWN"
|
|
24
|
-
tx_err = rx_err = 0
|
|
25
|
-
for line in output.splitlines():
|
|
26
|
-
if "state" in line and "berr-counter" in line:
|
|
27
|
-
for part in line.split():
|
|
28
|
-
if part in (
|
|
29
|
-
"ERROR-ACTIVE",
|
|
30
|
-
"ERROR-PASSIVE",
|
|
31
|
-
"ERROR-WARNING",
|
|
32
|
-
"BUS-OFF",
|
|
33
|
-
):
|
|
34
|
-
state = part
|
|
35
|
-
if "tx" in line:
|
|
36
|
-
idx = line.index("tx")
|
|
37
|
-
tx_err = int(line[idx:].split()[1])
|
|
38
|
-
if "rx" in line:
|
|
39
|
-
idx = line.index("rx")
|
|
40
|
-
rx_err = int(line[idx:].split()[1].rstrip(")"))
|
|
41
|
-
return {"state": state, "tx_err": tx_err, "rx_err": rx_err}
|
|
42
|
-
except Exception as e:
|
|
43
|
-
logger.debug(f"Could not read CAN state: {e}")
|
|
44
|
-
return {"state": "UNKNOWN", "tx_err": 0, "rx_err": 0}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def reset_can_interface(interface: str = "can0", bitrate: int = 1000000) -> bool:
|
|
48
|
-
"""Fully reset the CAN interface by reloading the mttcan kernel module.
|
|
49
|
-
|
|
50
|
-
``ip link set down/up`` does NOT reset TX/RX error counters on the
|
|
51
|
-
Jetson Orin Nano mttcan controller and causes a CAN bus disruption
|
|
52
|
-
that can push the motor into BUS-OFF (completely silent). This method
|
|
53
|
-
instead:
|
|
54
|
-
|
|
55
|
-
1. Takes the interface down.
|
|
56
|
-
2. Unloads the mttcan module — the bus is silent during unload, giving
|
|
57
|
-
the motor's CAN controller time to see the 128×11 recessive bits
|
|
58
|
-
required by the CAN spec to recover from BUS-OFF.
|
|
59
|
-
3. Reloads the module and brings the interface back up.
|
|
60
|
-
|
|
61
|
-
:param interface: CAN interface name (e.g. 'can0').
|
|
62
|
-
:param bitrate: Desired bitrate (e.g. 1000000).
|
|
63
|
-
:return: True if successful, False if the commands fail (e.g. no sudo).
|
|
64
|
-
"""
|
|
65
|
-
logger.warning(
|
|
66
|
-
f"Performing full kernel-level reset of {interface} to clear error states..."
|
|
67
|
-
)
|
|
68
|
-
cmds = [
|
|
69
|
-
f"sudo ip link set {interface} down",
|
|
70
|
-
"sudo rmmod mttcan",
|
|
71
|
-
"sleep 0.5", # Let bus stay recessive so nodes can recover
|
|
72
|
-
"sudo modprobe mttcan",
|
|
73
|
-
# Keep runtime reset behavior aligned with setup_can.sh.
|
|
74
|
-
f"sudo ip link set {interface} up type can bitrate {bitrate} berr-reporting on restart-ms 100",
|
|
75
|
-
f"sudo ip link set {interface} txqueuelen 1000",
|
|
76
|
-
]
|
|
77
|
-
try:
|
|
78
|
-
for cmd in cmds:
|
|
79
|
-
if cmd == "sleep 0.5":
|
|
80
|
-
time.sleep(0.5)
|
|
81
|
-
else:
|
|
82
|
-
subprocess.run(cmd.split(), check=True)
|
|
83
|
-
time.sleep(0.1)
|
|
84
|
-
logger.success(f"CAN interface {interface} reset successfully.")
|
|
85
|
-
return True
|
|
86
|
-
except subprocess.CalledProcessError as e:
|
|
87
|
-
logger.error(f"Failed to reset CAN interface: {e}")
|
|
88
|
-
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{motor_python-0.0.10 → motor_python-0.0.11}/src/motor_python/second_order_low_pass_filter.py
RENAMED
|
File without changes
|
|
File without changes
|