tinymovr 2.6.0__tar.gz → 3.2.0__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.
- {tinymovr-2.6.0 → tinymovr-3.2.0}/PKG-INFO +2 -2
- {tinymovr-2.6.0 → tinymovr-3.2.0}/pyproject.toml +1 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/setup.py +3 -3
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tests/test_amt22.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tests/test_as5047.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tests/test_board.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tests/test_dfu.py +1 -1
- tinymovr-3.2.0/tests/test_differential.py +93 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tests/test_export_import.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tests/test_hall.py +67 -4
- tinymovr-3.2.0/tests/test_homing.py +237 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tests/test_nvm.py +29 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tests/test_rates.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tests/test_simulation.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tests/test_traj_planner.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/bus_manager.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/bus_router.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/channel.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/cli.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/codec/codec.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/config/config.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/constants.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/device_discovery.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/dfu.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/gui/__init__.py +0 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/gui/gui.py +18 -4
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/gui/helpers.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/gui/widgets.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/gui/window.py +1 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/gui/worker.py +1 -1
- tinymovr-3.2.0/tinymovr/specs/tinymovr_3_0_x.yaml +642 -0
- tinymovr-3.2.0/tinymovr/specs/tinymovr_3_1_x.yaml +656 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr.egg-info/PKG-INFO +2 -2
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr.egg-info/SOURCES.txt +4 -1
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr.egg-info/entry_points.txt +1 -1
- tinymovr-2.6.0/tests/test_homing.py +0 -39
- {tinymovr-2.6.0 → tinymovr-3.2.0}/MANIFEST.in +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/README.md +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/setup.cfg +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tests/test_watchdog.py +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/__init__.py +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/codec/__init__.py +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/config/__init__.py +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/resources/icons/app_icon.png +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/resources/icons/call.png +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/resources/icons/call@2x.png +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/resources/icons/call_dark.png +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/resources/icons/call_dark@2x.png +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/resources/icons/empty.png +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/resources/icons/empty@2x.png +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/resources/icons/empty_dark.png +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/resources/icons/empty_dark@2x.png +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/specs/dfu_1_0_x.yaml +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/specs/dfu_1_1_x.yaml +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/specs/tinymovr_1_3_x.yaml +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/specs/tinymovr_1_4_x.yaml +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/specs/tinymovr_1_5_x.yaml +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/specs/tinymovr_1_6_x.yaml +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/specs/tinymovr_2_0_x.yaml +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/specs/tinymovr_2_1_x.yaml +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/specs/tinymovr_2_3_x.yaml +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/specs/tinymovr_2_4_x.yaml +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/specs/tinymovr_2_5_x.yaml +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr/specs/tinymovr_2_6_x.yaml +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr.egg-info/dependency_links.txt +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr.egg-info/requires.txt +0 -0
- {tinymovr-2.6.0 → tinymovr-3.2.0}/tinymovr.egg-info/top_level.txt +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tinymovr
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Tinymovr Studio
|
|
5
5
|
Home-page: https://github.com/yconst/Tinymovr
|
|
6
|
-
Author:
|
|
6
|
+
Author: MotionLayer P.C.
|
|
7
7
|
Author-email: info@tinymovr.com
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
@@ -9,6 +9,7 @@ markers = [
|
|
|
9
9
|
"sensor_amt22: marks tests that need the amt22 sensor",
|
|
10
10
|
"sensor_as5047: marks tests that need the as5047 sensor",
|
|
11
11
|
"sensor_hall: marks tests that need a hall effect sensored motor",
|
|
12
|
+
"differential: marks tests that need a differential (vernier) dual-sensor setup",
|
|
12
13
|
"eol: marks tests for end-of-line testing",
|
|
13
14
|
"dfu: marks tests for dfu testing",
|
|
14
15
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Tinymovr Setup Module
|
|
3
|
-
Copyright
|
|
3
|
+
Copyright 2020-2026 MotionLayer P.C.
|
|
4
4
|
|
|
5
5
|
Standard Python module for setting up the Tinymovr package
|
|
6
6
|
|
|
@@ -27,7 +27,7 @@ README = (HERE / "README.md").read_text()
|
|
|
27
27
|
setup(
|
|
28
28
|
name="tinymovr",
|
|
29
29
|
# version="", #version will be taken from git tag
|
|
30
|
-
author="
|
|
30
|
+
author="MotionLayer P.C.",
|
|
31
31
|
author_email="info@tinymovr.com",
|
|
32
32
|
description="Tinymovr Studio",
|
|
33
33
|
long_description=README,
|
|
@@ -66,7 +66,7 @@ setup(
|
|
|
66
66
|
entry_points={
|
|
67
67
|
"console_scripts": [
|
|
68
68
|
"tinymovr_cli=tinymovr.cli:spawn",
|
|
69
|
-
"tinymovr=tinymovr.gui:spawn",
|
|
69
|
+
"tinymovr=tinymovr.gui.gui:spawn",
|
|
70
70
|
"tinymovr_dfu=tinymovr.dfu:spawn"
|
|
71
71
|
]
|
|
72
72
|
},
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tinymovr Differential (Vernier) Mode Tests
|
|
3
|
+
Copyright 2020-2026 MotionLayer P.C.
|
|
4
|
+
|
|
5
|
+
Tests the differential transform-init mode, where the absolute multiturn shaft
|
|
6
|
+
position is recovered from two geared sensors (vernier/nonius) during
|
|
7
|
+
calibration and re-seeded on every reset.
|
|
8
|
+
|
|
9
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
10
|
+
the terms of the GNU General Public License as published by the Free Software
|
|
11
|
+
Foundation, either version 3 of the License, or (at your option) any later
|
|
12
|
+
version.
|
|
13
|
+
This program is distributed in the hope that it will be useful, but WITHOUT
|
|
14
|
+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
15
|
+
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
16
|
+
You should have received a copy of the GNU General Public License along with
|
|
17
|
+
this program. If not, see <http://www.gnu.org/licenses/>.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import time
|
|
21
|
+
|
|
22
|
+
from avlos.unit_field import get_registry
|
|
23
|
+
|
|
24
|
+
import unittest
|
|
25
|
+
import pytest
|
|
26
|
+
from tests import TMTestCase
|
|
27
|
+
|
|
28
|
+
ureg = get_registry()
|
|
29
|
+
tick = ureg.ticks
|
|
30
|
+
tsleep = 1.50
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TestDifferential(TMTestCase):
|
|
34
|
+
|
|
35
|
+
@pytest.mark.differential
|
|
36
|
+
def test_multiturn_position_retained_after_reset(self):
|
|
37
|
+
"""The differential transform must recover the same absolute multiturn
|
|
38
|
+
shaft position after a reset, with no offset and no full-turn slip.
|
|
39
|
+
"""
|
|
40
|
+
self.check_state(0)
|
|
41
|
+
self.erase_config()
|
|
42
|
+
time.sleep(0.2)
|
|
43
|
+
|
|
44
|
+
# Vernier setup: the onboard sensor is used for both commutation and
|
|
45
|
+
# position, and the geared secondary (MA7XX) on external SPI is read
|
|
46
|
+
# during calibration/seed.
|
|
47
|
+
self.configure_sensors(self.tm.sensors.setup.external_spi.type.MA7XX)
|
|
48
|
+
self.select_sensors(
|
|
49
|
+
self.tm.sensors.select.commutation_sensor.connection.ONBOARD,
|
|
50
|
+
self.tm.sensors.select.position_sensor.connection.ONBOARD,
|
|
51
|
+
)
|
|
52
|
+
self.tm.sensors.select.transform_init.mode = (
|
|
53
|
+
self.tm.sensors.select.transform_init.mode.DIFFERENTIAL
|
|
54
|
+
)
|
|
55
|
+
time.sleep(0.2)
|
|
56
|
+
|
|
57
|
+
self.try_calibrate()
|
|
58
|
+
|
|
59
|
+
self.tm.controller.velocity.p_gain = 1e-5
|
|
60
|
+
|
|
61
|
+
self.tm.controller.position_mode()
|
|
62
|
+
self.check_state(2)
|
|
63
|
+
|
|
64
|
+
# Stay within a single output-shaft turn (the vernier resolves only one
|
|
65
|
+
# turn). Sweep mid-turn, then near a full turn, and verify tracking.
|
|
66
|
+
for target in (4000 * tick, 8000 * tick):
|
|
67
|
+
self.tm.controller.position.setpoint = target
|
|
68
|
+
time.sleep(tsleep)
|
|
69
|
+
self.assertAlmostEqual(
|
|
70
|
+
self.tm.sensors.user_frame.position_estimate,
|
|
71
|
+
target,
|
|
72
|
+
delta=2000 * tick,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
pos_before = self.tm.sensors.user_frame.position_estimate
|
|
76
|
+
|
|
77
|
+
self.tm.controller.idle()
|
|
78
|
+
time.sleep(0.1)
|
|
79
|
+
self.save_config()
|
|
80
|
+
time.sleep(0.2)
|
|
81
|
+
|
|
82
|
+
# After reset the origin is re-seeded purely from the two sensors.
|
|
83
|
+
self.reset_and_wait()
|
|
84
|
+
|
|
85
|
+
pos_after = self.tm.sensors.user_frame.position_estimate
|
|
86
|
+
self.assertAlmostEqual(pos_after, pos_before, delta=500 * tick)
|
|
87
|
+
|
|
88
|
+
self.erase_config()
|
|
89
|
+
time.sleep(0.2)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if __name__ == "__main__":
|
|
93
|
+
unittest.main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Tinymovr Board Config Tests
|
|
3
|
-
Copyright
|
|
3
|
+
Copyright 2020-2026 MotionLayer P.C.
|
|
4
4
|
|
|
5
5
|
Tests saving and loading Tinymovr configuration to/from NVRAM.
|
|
6
6
|
|
|
@@ -28,7 +28,7 @@ ureg = get_registry()
|
|
|
28
28
|
A = ureg.ampere
|
|
29
29
|
tick = ureg.ticks
|
|
30
30
|
s = ureg.second
|
|
31
|
-
tsleep =
|
|
31
|
+
tsleep = 0.9
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
def set_pole_pairs(tm):
|
|
@@ -56,8 +56,8 @@ class TestHall(TMTestCase):
|
|
|
56
56
|
|
|
57
57
|
self.try_calibrate(precheck_callback=set_pole_pairs)
|
|
58
58
|
|
|
59
|
-
self.tm.controller.position.p_gain =
|
|
60
|
-
self.tm.controller.velocity.limit =
|
|
59
|
+
self.tm.controller.position.p_gain = 11
|
|
60
|
+
self.tm.controller.velocity.limit = 700000
|
|
61
61
|
self.tm.controller.velocity.p_gain = 1e-5
|
|
62
62
|
self.tm.controller.velocity.i_gain = 0
|
|
63
63
|
|
|
@@ -100,6 +100,69 @@ class TestHall(TMTestCase):
|
|
|
100
100
|
self.erase_config()
|
|
101
101
|
time.sleep(0.2)
|
|
102
102
|
|
|
103
|
+
@pytest.mark.sensor_hall
|
|
104
|
+
def test_b_velocity_control(self):
|
|
105
|
+
"""
|
|
106
|
+
Test velocity control with Hall sensors, ramping through
|
|
107
|
+
velocities in both directions.
|
|
108
|
+
"""
|
|
109
|
+
self.check_state(0)
|
|
110
|
+
self.erase_config()
|
|
111
|
+
time.sleep(0.2)
|
|
112
|
+
|
|
113
|
+
self.tm.sensors.select.position_sensor.connection = self.tm.sensors.select.position_sensor.connection.HALL
|
|
114
|
+
self.tm.sensors.select.commutation_sensor.connection = self.tm.sensors.select.commutation_sensor.connection.HALL
|
|
115
|
+
|
|
116
|
+
self.tm.sensors.select.position_sensor.bandwidth = 50
|
|
117
|
+
self.tm.sensors.select.commutation_sensor.bandwidth = 80
|
|
118
|
+
time.sleep(0.2)
|
|
119
|
+
|
|
120
|
+
self.try_calibrate(precheck_callback=set_pole_pairs)
|
|
121
|
+
|
|
122
|
+
self.tm.controller.velocity.limit = 700000
|
|
123
|
+
self.tm.controller.velocity.p_gain = 8e-6
|
|
124
|
+
self.tm.controller.velocity.i_gain = 0
|
|
125
|
+
|
|
126
|
+
self.tm.controller.velocity_mode()
|
|
127
|
+
self.check_state(2)
|
|
128
|
+
|
|
129
|
+
R = 10
|
|
130
|
+
multiplier = 60000 * tick / s
|
|
131
|
+
max_delta = 60000 * tick / s
|
|
132
|
+
|
|
133
|
+
velocity_pairs = []
|
|
134
|
+
|
|
135
|
+
for i in range(R):
|
|
136
|
+
target = i * multiplier
|
|
137
|
+
self.tm.controller.velocity.setpoint = target
|
|
138
|
+
time.sleep(tsleep)
|
|
139
|
+
velocity_pairs.append((target, self.tm.sensors.user_frame.velocity_estimate))
|
|
140
|
+
|
|
141
|
+
for i in range(R):
|
|
142
|
+
target = (R - i) * multiplier
|
|
143
|
+
self.tm.controller.velocity.setpoint = target
|
|
144
|
+
time.sleep(tsleep)
|
|
145
|
+
velocity_pairs.append((target, self.tm.sensors.user_frame.velocity_estimate))
|
|
146
|
+
|
|
147
|
+
for i in range(R):
|
|
148
|
+
target = -i * multiplier
|
|
149
|
+
self.tm.controller.velocity.setpoint = target
|
|
150
|
+
time.sleep(tsleep)
|
|
151
|
+
velocity_pairs.append((target, self.tm.sensors.user_frame.velocity_estimate))
|
|
152
|
+
|
|
153
|
+
for i in range(R):
|
|
154
|
+
target = (i - R) * multiplier
|
|
155
|
+
self.tm.controller.velocity.setpoint = target
|
|
156
|
+
time.sleep(tsleep)
|
|
157
|
+
velocity_pairs.append((target, self.tm.sensors.user_frame.velocity_estimate))
|
|
158
|
+
|
|
159
|
+
for target, estimate in velocity_pairs:
|
|
160
|
+
self.assertAlmostEqual(target, estimate, delta=max_delta)
|
|
161
|
+
|
|
162
|
+
self.tm.controller.idle()
|
|
163
|
+
self.erase_config()
|
|
164
|
+
time.sleep(0.2)
|
|
165
|
+
|
|
103
166
|
|
|
104
167
|
if __name__ == "__main__":
|
|
105
168
|
unittest.main()
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This unit test suite tests the Homing functionality.
|
|
3
|
+
"""
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from avlos.unit_field import get_registry
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
import pytest
|
|
10
|
+
from tests import TMTestCase
|
|
11
|
+
|
|
12
|
+
ureg = get_registry()
|
|
13
|
+
A = ureg.ampere
|
|
14
|
+
ticks = ureg.ticks
|
|
15
|
+
s = ureg.second
|
|
16
|
+
|
|
17
|
+
HOMING_WARNINGS_NONE = 0
|
|
18
|
+
HOMING_WARNINGS_TIMEOUT = 1
|
|
19
|
+
|
|
20
|
+
CONTROLLER_MODE_POSITION = 2
|
|
21
|
+
CONTROLLER_MODE_HOMING = 4
|
|
22
|
+
|
|
23
|
+
ITERATIONS_PER_DIRECTION = 2
|
|
24
|
+
HOMING_SPEED = 4000.0
|
|
25
|
+
FREE_TRAVEL_S = 1.5
|
|
26
|
+
STALL_DWELL_EXTRA_S = 0.3
|
|
27
|
+
RETRACT_SETTLE_S = 0.3
|
|
28
|
+
INTER_ITERATION_PAUSE_S = 0.3
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestHoming(TMTestCase):
|
|
32
|
+
def test_homing(self):
|
|
33
|
+
"""
|
|
34
|
+
Test Homing
|
|
35
|
+
"""
|
|
36
|
+
self.check_state(0)
|
|
37
|
+
self.try_calibrate()
|
|
38
|
+
|
|
39
|
+
self.tm.controller.position_mode()
|
|
40
|
+
self.check_state(2)
|
|
41
|
+
|
|
42
|
+
self.tm.homing.home()
|
|
43
|
+
time.sleep(self.tm.homing.max_homing_t.magnitude + 0.1)
|
|
44
|
+
|
|
45
|
+
self.assertEqual(self.tm.controller.mode, 2)
|
|
46
|
+
self.assertEqual(self.tm.homing.warnings, 1)
|
|
47
|
+
self.tm.controller.idle()
|
|
48
|
+
|
|
49
|
+
@pytest.mark.hitl_default
|
|
50
|
+
@pytest.mark.hitl_mini
|
|
51
|
+
def test_homing_retraction(self):
|
|
52
|
+
"""
|
|
53
|
+
Regression test for the homing retraction reference bug
|
|
54
|
+
(fix commit 86b19db).
|
|
55
|
+
|
|
56
|
+
Per-iteration flow: motor runs freely for FREE_TRAVEL_S, the current
|
|
57
|
+
limit is dropped to simulate an endstop, the planner detects the
|
|
58
|
+
stall and installs the user-frame offset, the current limit is
|
|
59
|
+
restored so the motor physically retracts to user_pos =
|
|
60
|
+
-direction*retract_dist, and a sensor-frame displacement assertion
|
|
61
|
+
verifies the physical motion matches expectation.
|
|
62
|
+
|
|
63
|
+
The bug only manifests when the user-frame offset is non-zero at the
|
|
64
|
+
start of homing (carried over from a previous run), so the test
|
|
65
|
+
runs ITERATIONS_PER_DIRECTION>=2 iterations per direction in both
|
|
66
|
+
+1 and -1 directions. See plan file for full frame analysis.
|
|
67
|
+
"""
|
|
68
|
+
self.check_state(0)
|
|
69
|
+
self.try_calibrate()
|
|
70
|
+
|
|
71
|
+
iq_limit_normal = self.tm.controller.current.Iq_limit.magnitude
|
|
72
|
+
homing_velocity_saved = self.tm.homing.velocity.magnitude
|
|
73
|
+
max_homing_t_saved = self.tm.homing.max_homing_t.magnitude
|
|
74
|
+
max_stall_t_saved = self.tm.homing.stall_detect.t.magnitude
|
|
75
|
+
retract_dist_saved = self.tm.homing.retract_dist.magnitude
|
|
76
|
+
stall_vel_saved = self.tm.homing.stall_detect.velocity.magnitude
|
|
77
|
+
stall_dpos_saved = self.tm.homing.stall_detect.delta_pos.magnitude
|
|
78
|
+
user_offset_saved = self.tm.sensors.user_frame.offset.magnitude
|
|
79
|
+
user_multiplier_saved = self.tm.sensors.user_frame.multiplier
|
|
80
|
+
|
|
81
|
+
iq_limit_stall = 0.1
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
self.assertAlmostEqual(
|
|
85
|
+
float(user_multiplier_saved),
|
|
86
|
+
1.0,
|
|
87
|
+
delta=1e-6,
|
|
88
|
+
msg=(
|
|
89
|
+
"Test math assumes user_frame.multiplier == 1.0 "
|
|
90
|
+
f"(actual: {user_multiplier_saved}). Set it to 1.0 "
|
|
91
|
+
"before running this test."
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self.tm.homing.max_homing_t = 6.0
|
|
96
|
+
self.tm.homing.stall_detect.t = 0.3
|
|
97
|
+
self.tm.homing.retract_dist = 1024
|
|
98
|
+
self.tm.homing.stall_detect.velocity = 500
|
|
99
|
+
self.tm.homing.stall_detect.delta_pos = 256
|
|
100
|
+
|
|
101
|
+
self.tm.controller.position_mode()
|
|
102
|
+
self.check_state(2)
|
|
103
|
+
|
|
104
|
+
for direction in (+1, -1):
|
|
105
|
+
for iteration in range(ITERATIONS_PER_DIRECTION):
|
|
106
|
+
self._run_homing_iteration(
|
|
107
|
+
direction=direction,
|
|
108
|
+
iteration=iteration,
|
|
109
|
+
iq_limit_normal=iq_limit_normal,
|
|
110
|
+
iq_limit_stall=iq_limit_stall,
|
|
111
|
+
)
|
|
112
|
+
time.sleep(INTER_ITERATION_PAUSE_S)
|
|
113
|
+
finally:
|
|
114
|
+
self.tm.controller.idle()
|
|
115
|
+
self.tm.controller.current.Iq_limit = iq_limit_normal
|
|
116
|
+
self.tm.homing.velocity = homing_velocity_saved
|
|
117
|
+
self.tm.homing.max_homing_t = max_homing_t_saved
|
|
118
|
+
self.tm.homing.stall_detect.t = max_stall_t_saved
|
|
119
|
+
self.tm.homing.retract_dist = retract_dist_saved
|
|
120
|
+
self.tm.homing.stall_detect.velocity = stall_vel_saved
|
|
121
|
+
self.tm.homing.stall_detect.delta_pos = stall_dpos_saved
|
|
122
|
+
self.tm.sensors.user_frame.offset = user_offset_saved
|
|
123
|
+
self.tm.sensors.user_frame.multiplier = user_multiplier_saved
|
|
124
|
+
|
|
125
|
+
def _run_homing_iteration(
|
|
126
|
+
self, direction, iteration, iq_limit_normal, iq_limit_stall
|
|
127
|
+
):
|
|
128
|
+
"""
|
|
129
|
+
Execute a single homing iteration in the given direction and verify
|
|
130
|
+
the resulting physical displacement in the sensor frame.
|
|
131
|
+
|
|
132
|
+
Returns nothing; raises AssertionError on failure.
|
|
133
|
+
"""
|
|
134
|
+
retract_dist = self.tm.homing.retract_dist.magnitude
|
|
135
|
+
max_homing_t = self.tm.homing.max_homing_t.magnitude
|
|
136
|
+
stall_detect_t = self.tm.homing.stall_detect.t.magnitude
|
|
137
|
+
|
|
138
|
+
sensor_pos_before = (
|
|
139
|
+
self.tm.sensors.select.position_sensor.position_estimate.magnitude
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
self.tm.homing.velocity = direction * HOMING_SPEED
|
|
143
|
+
self.tm.controller.current.Iq_limit = iq_limit_normal
|
|
144
|
+
time.sleep(0.05)
|
|
145
|
+
|
|
146
|
+
self.tm.homing.home()
|
|
147
|
+
|
|
148
|
+
time.sleep(FREE_TRAVEL_S)
|
|
149
|
+
|
|
150
|
+
mode_after_free = int(self.tm.controller.mode)
|
|
151
|
+
self.assertEqual(
|
|
152
|
+
mode_after_free,
|
|
153
|
+
CONTROLLER_MODE_HOMING,
|
|
154
|
+
msg=(
|
|
155
|
+
f"dir={direction:+d} iter={iteration}: planner exited HOMING "
|
|
156
|
+
f"during free-travel phase (mode={mode_after_free}). "
|
|
157
|
+
f"FREE_TRAVEL_S ({FREE_TRAVEL_S}s) may be too long for the "
|
|
158
|
+
"current stall_detect parameters, or the motor stalled "
|
|
159
|
+
"without the simulated low-Iq endstop."
|
|
160
|
+
),
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
self.tm.controller.current.Iq_limit = iq_limit_stall
|
|
164
|
+
time.sleep(stall_detect_t + STALL_DWELL_EXTRA_S)
|
|
165
|
+
|
|
166
|
+
self.tm.controller.current.Iq_limit = iq_limit_normal
|
|
167
|
+
|
|
168
|
+
timeout_s = max_homing_t + 2.0
|
|
169
|
+
deadline = time.monotonic() + timeout_s
|
|
170
|
+
while int(self.tm.controller.mode) == CONTROLLER_MODE_HOMING:
|
|
171
|
+
if time.monotonic() > deadline:
|
|
172
|
+
self.fail(
|
|
173
|
+
f"dir={direction:+d} iter={iteration}: homing did not "
|
|
174
|
+
f"exit within {timeout_s}s after stall induction"
|
|
175
|
+
)
|
|
176
|
+
time.sleep(0.05)
|
|
177
|
+
|
|
178
|
+
warnings = int(self.tm.homing.warnings)
|
|
179
|
+
self.assertEqual(
|
|
180
|
+
warnings,
|
|
181
|
+
HOMING_WARNINGS_NONE,
|
|
182
|
+
msg=(
|
|
183
|
+
f"dir={direction:+d} iter={iteration}: expected endstop "
|
|
184
|
+
f"detection (warnings=0) but got warnings={warnings}"
|
|
185
|
+
),
|
|
186
|
+
)
|
|
187
|
+
self.assertEqual(
|
|
188
|
+
int(self.tm.controller.mode), CONTROLLER_MODE_POSITION
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
time.sleep(RETRACT_SETTLE_S)
|
|
192
|
+
|
|
193
|
+
sensor_pos_after = (
|
|
194
|
+
self.tm.sensors.select.position_sensor.position_estimate.magnitude
|
|
195
|
+
)
|
|
196
|
+
sensor_displacement = sensor_pos_after - sensor_pos_before
|
|
197
|
+
expected_displacement = direction * (
|
|
198
|
+
FREE_TRAVEL_S * HOMING_SPEED - retract_dist
|
|
199
|
+
)
|
|
200
|
+
displacement_tolerance = 0.5 * FREE_TRAVEL_S * HOMING_SPEED
|
|
201
|
+
|
|
202
|
+
self.assertAlmostEqual(
|
|
203
|
+
sensor_displacement,
|
|
204
|
+
expected_displacement,
|
|
205
|
+
delta=displacement_tolerance,
|
|
206
|
+
msg=(
|
|
207
|
+
f"dir={direction:+d} iter={iteration}: sensor-frame "
|
|
208
|
+
f"displacement is {sensor_displacement:.1f} ticks, expected "
|
|
209
|
+
f"{expected_displacement:.1f} +-{displacement_tolerance:.1f}. "
|
|
210
|
+
"A large deviation here on iteration>=1 indicates the "
|
|
211
|
+
"user-frame offset was installed in the wrong reference "
|
|
212
|
+
"frame during homing (regression of fix 86b19db). The "
|
|
213
|
+
"magnitude of the deviation should be approximately equal "
|
|
214
|
+
"to the offset carried over from the previous homing."
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
user_pos = self.tm.sensors.user_frame.position_estimate.magnitude
|
|
219
|
+
expected_user_pos = -direction * retract_dist
|
|
220
|
+
user_pos_tolerance = 0.5 * retract_dist
|
|
221
|
+
self.assertAlmostEqual(
|
|
222
|
+
user_pos,
|
|
223
|
+
expected_user_pos,
|
|
224
|
+
delta=user_pos_tolerance,
|
|
225
|
+
msg=(
|
|
226
|
+
f"dir={direction:+d} iter={iteration}: user-frame position "
|
|
227
|
+
f"after homing is {user_pos:.1f} ticks, expected "
|
|
228
|
+
f"{expected_user_pos:.1f} +-{user_pos_tolerance:.1f}. This "
|
|
229
|
+
"is a self-consistency check on the planner's setpoint "
|
|
230
|
+
"tracking and does NOT detect the frame-offset bug; the "
|
|
231
|
+
"sensor-frame assertion above is the regression check."
|
|
232
|
+
),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
if __name__ == "__main__":
|
|
237
|
+
unittest.main(failfast=True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Tinymovr Board Config Tests
|
|
3
|
-
Copyright
|
|
3
|
+
Copyright 2020-2026 MotionLayer P.C.
|
|
4
4
|
|
|
5
5
|
Tests saving and loading Tinymovr configuration to/from NVRAM.
|
|
6
6
|
|
|
@@ -112,6 +112,34 @@ class TestNVM(TMTestCase):
|
|
|
112
112
|
self.erase_config()
|
|
113
113
|
time.sleep(0.2)
|
|
114
114
|
|
|
115
|
+
def test_c2_homing_parameter_persistence(self):
|
|
116
|
+
"""
|
|
117
|
+
Test persisting homing parameters across config saves.
|
|
118
|
+
WARNING: This will perform one NVRAM write and two erase cycles.
|
|
119
|
+
"""
|
|
120
|
+
self.check_state(0)
|
|
121
|
+
self.erase_config()
|
|
122
|
+
time.sleep(0.2)
|
|
123
|
+
|
|
124
|
+
self.tm.homing.velocity = 5000
|
|
125
|
+
self.tm.homing.max_homing_t = 10.0
|
|
126
|
+
self.tm.homing.retract_dist = 2000
|
|
127
|
+
self.tm.homing.stall_detect.velocity = 3000
|
|
128
|
+
self.tm.homing.stall_detect.delta_pos = 500
|
|
129
|
+
self.tm.homing.stall_detect.t = 2.0
|
|
130
|
+
self.save_config()
|
|
131
|
+
|
|
132
|
+
time.sleep(0.2)
|
|
133
|
+
self.reset_and_wait()
|
|
134
|
+
self.assertAlmostEqual(self.tm.homing.velocity, 5000 * tick / s, delta=1 * tick / s)
|
|
135
|
+
self.assertAlmostEqual(self.tm.homing.max_homing_t, 10.0 * s, delta=0.01 * s)
|
|
136
|
+
self.assertAlmostEqual(self.tm.homing.retract_dist, 2000 * tick, delta=1 * tick)
|
|
137
|
+
self.assertAlmostEqual(self.tm.homing.stall_detect.velocity, 3000 * tick / s, delta=1 * tick / s)
|
|
138
|
+
self.assertAlmostEqual(self.tm.homing.stall_detect.delta_pos, 500 * tick, delta=1 * tick)
|
|
139
|
+
self.assertAlmostEqual(self.tm.homing.stall_detect.t, 2.0 * s, delta=0.01 * s)
|
|
140
|
+
self.erase_config()
|
|
141
|
+
time.sleep(0.2)
|
|
142
|
+
|
|
115
143
|
def test_d_position_control_w_loaded_config(self):
|
|
116
144
|
"""
|
|
117
145
|
Test position control after saving and loading config.
|
|
@@ -26,7 +26,7 @@ from tinymovr.config import get_bus_config, configure_logging, add_spec
|
|
|
26
26
|
|
|
27
27
|
"""
|
|
28
28
|
Tinymovr CLI Module
|
|
29
|
-
Copyright
|
|
29
|
+
Copyright 2020-2026 MotionLayer P.C.
|
|
30
30
|
|
|
31
31
|
The Tinymovr Studio IPython-based command line interface
|
|
32
32
|
|