tinymovr 2.3.1__tar.gz → 2.3.3__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.
Files changed (59) hide show
  1. {tinymovr-2.3.1/tinymovr.egg-info → tinymovr-2.3.3}/PKG-INFO +27 -2
  2. tinymovr-2.3.3/tests/test_amt22.py +159 -0
  3. tinymovr-2.3.3/tests/test_as5047.py +210 -0
  4. {tinymovr-2.3.1 → tinymovr-2.3.3}/tests/test_board.py +5 -0
  5. tinymovr-2.3.3/tests/test_hall.py +105 -0
  6. tinymovr-2.3.3/tests/test_nvm.py +250 -0
  7. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/bus_router.py +2 -1
  8. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/cli.py +2 -2
  9. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/gui/gui.py +2 -2
  10. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/gui/window.py +2 -2
  11. {tinymovr-2.3.1 → tinymovr-2.3.3/tinymovr.egg-info}/PKG-INFO +27 -2
  12. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr.egg-info/SOURCES.txt +4 -0
  13. {tinymovr-2.3.1 → tinymovr-2.3.3}/MANIFEST.in +0 -0
  14. {tinymovr-2.3.1 → tinymovr-2.3.3}/README.md +0 -0
  15. {tinymovr-2.3.1 → tinymovr-2.3.3}/pyproject.toml +0 -0
  16. {tinymovr-2.3.1 → tinymovr-2.3.3}/setup.cfg +0 -0
  17. {tinymovr-2.3.1 → tinymovr-2.3.3}/setup.py +0 -0
  18. {tinymovr-2.3.1 → tinymovr-2.3.3}/tests/test_dfu.py +0 -0
  19. {tinymovr-2.3.1 → tinymovr-2.3.3}/tests/test_homing.py +0 -0
  20. {tinymovr-2.3.1 → tinymovr-2.3.3}/tests/test_rates.py +0 -0
  21. {tinymovr-2.3.1 → tinymovr-2.3.3}/tests/test_simulation.py +0 -0
  22. {tinymovr-2.3.1 → tinymovr-2.3.3}/tests/test_traj_planner.py +0 -0
  23. {tinymovr-2.3.1 → tinymovr-2.3.3}/tests/test_watchdog.py +0 -0
  24. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/__init__.py +0 -0
  25. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/bus_manager.py +0 -0
  26. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/channel.py +0 -0
  27. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/codec/__init__.py +0 -0
  28. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/codec/codec.py +0 -0
  29. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/config/__init__.py +0 -0
  30. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/config/config.py +0 -0
  31. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/constants.py +0 -0
  32. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/device_discovery.py +0 -0
  33. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/dfu.py +0 -0
  34. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/gui/__init__.py +0 -0
  35. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/gui/helpers.py +0 -0
  36. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/gui/widgets.py +0 -0
  37. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/gui/worker.py +0 -0
  38. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/resources/icons/app_icon.png +0 -0
  39. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/resources/icons/call.png +0 -0
  40. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/resources/icons/call@2x.png +0 -0
  41. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/resources/icons/call_dark.png +0 -0
  42. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/resources/icons/call_dark@2x.png +0 -0
  43. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/resources/icons/empty.png +0 -0
  44. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/resources/icons/empty@2x.png +0 -0
  45. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/resources/icons/empty_dark.png +0 -0
  46. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/resources/icons/empty_dark@2x.png +0 -0
  47. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/specs/dfu_1_0_x.yaml +0 -0
  48. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/specs/dfu_1_1_x.yaml +0 -0
  49. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/specs/tinymovr_1_3_x.yaml +0 -0
  50. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/specs/tinymovr_1_4_x.yaml +0 -0
  51. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/specs/tinymovr_1_5_x.yaml +0 -0
  52. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/specs/tinymovr_1_6_x.yaml +0 -0
  53. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/specs/tinymovr_2_0_x.yaml +0 -0
  54. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/specs/tinymovr_2_1_x.yaml +0 -0
  55. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr/specs/tinymovr_2_3_x.yaml +0 -0
  56. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr.egg-info/dependency_links.txt +0 -0
  57. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr.egg-info/entry_points.txt +0 -0
  58. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr.egg-info/requires.txt +0 -0
  59. {tinymovr-2.3.1 → tinymovr-2.3.3}/tinymovr.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: tinymovr
3
- Version: 2.3.1
3
+ Version: 2.3.3
4
4
  Summary: Tinymovr Studio
5
5
  Home-page: https://github.com/yconst/Tinymovr
6
6
  Author: Yannis Chatzikonstantinou
@@ -10,7 +10,32 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (G
10
10
  Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.9
12
12
  Description-Content-Type: text/markdown
13
+ Requires-Dist: ipython
14
+ Requires-Dist: importlib_resources
15
+ Requires-Dist: python-can
16
+ Requires-Dist: python-can-canine
17
+ Requires-Dist: python-can-slcan_disco
18
+ Requires-Dist: avlos>=0.6.6
19
+ Requires-Dist: pyserial
20
+ Requires-Dist: pyusb
21
+ Requires-Dist: pyyaml
22
+ Requires-Dist: docopt
23
+ Requires-Dist: flatten-dict
24
+ Requires-Dist: pint
25
+ Requires-Dist: pretty_errors
13
26
  Provides-Extra: gui
27
+ Requires-Dist: pyside6; extra == "gui"
28
+ Requires-Dist: pyqtgraph>=0.13.3; extra == "gui"
29
+ Dynamic: author
30
+ Dynamic: author-email
31
+ Dynamic: classifier
32
+ Dynamic: description
33
+ Dynamic: description-content-type
34
+ Dynamic: home-page
35
+ Dynamic: provides-extra
36
+ Dynamic: requires-dist
37
+ Dynamic: requires-python
38
+ Dynamic: summary
14
39
 
15
40
  ![Studio Lint Test](https://github.com/yconst/tinymovr/workflows/Tinymovr%20Studio%20Lint%2FTest/badge.svg)
16
41
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
@@ -0,0 +1,159 @@
1
+ """
2
+ Tinymovr Board Config Tests
3
+ Copyright Ioannis Chatzikonstantinou 2020-2023
4
+
5
+ Tests saving and loading Tinymovr configuration to/from NVRAM.
6
+
7
+ This program is free software: you can redistribute it and/or modify it under
8
+ the terms of the GNU General Public License as published by the Free Software
9
+ Foundation, either version 3 of the License, or (at your option) any later
10
+ version.
11
+ This program is distributed in the hope that it will be useful, but WITHOUT
12
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14
+ You should have received a copy of the GNU General Public License along with
15
+ this program. If not, see <http://www.gnu.org/licenses/>.
16
+ """
17
+
18
+ import random
19
+ import time
20
+
21
+ from avlos.unit_field import get_registry
22
+
23
+ import unittest
24
+ import pytest
25
+ from tests import TMTestCase
26
+
27
+ ureg = get_registry()
28
+ A = ureg.ampere
29
+ tick = ureg.ticks
30
+ s = ureg.second
31
+ tsleep = 0.80
32
+
33
+
34
+ class TestAMT22(TMTestCase):
35
+
36
+ @pytest.mark.sensor_amt22
37
+ def test_a_position_control_before_after_save_and_load_config(self):
38
+ """
39
+ Test position control after saving and loading config.
40
+ WARNING: This will perform one NVRAM write and two erase cycles.
41
+ """
42
+ self.check_state(0)
43
+ self.tm.erase_config()
44
+ time.sleep(0.2)
45
+
46
+ self.configure_sensors(self.tm.sensors.setup.external_spi.type.AMT22)
47
+ self.select_sensors(self.tm.sensors.select.position_sensor.connection.ONBOARD, self.tm.sensors.select.position_sensor.connection.EXTERNAL_SPI)
48
+
49
+ self.try_calibrate()
50
+
51
+ self.tm.controller.position.p_gain = 9
52
+ self.tm.controller.velocity.p_gain = 3e-5
53
+ self.tm.controller.velocity.i_gain = 0
54
+ self.tm.controller.velocity.limit = 200000
55
+
56
+ self.tm.controller.position_mode()
57
+ self.check_state(2)
58
+
59
+ for _ in range(10):
60
+ new_pos = random.uniform(-1000, 1000)
61
+ self.tm.controller.position.setpoint = new_pos * tick
62
+ time.sleep(tsleep)
63
+ self.assertAlmostEqual(
64
+ self.tm.sensors.user_frame.position_estimate,
65
+ self.tm.controller.position.setpoint,
66
+ delta=200 * tick,
67
+ )
68
+
69
+ self.tm.controller.idle()
70
+
71
+ time.sleep(0.1)
72
+
73
+ self.tm.save_config()
74
+
75
+ time.sleep(0.2)
76
+
77
+ self.reset_and_wait()
78
+
79
+ self.tm.controller.position_mode()
80
+ self.check_state(2)
81
+
82
+ for _ in range(10):
83
+ new_pos = random.uniform(-1000, 1000)
84
+ self.tm.controller.position.setpoint = new_pos * tick
85
+ time.sleep(tsleep)
86
+ self.assertAlmostEqual(
87
+ self.tm.sensors.user_frame.position_estimate,
88
+ self.tm.controller.position.setpoint,
89
+ delta=200 * tick,
90
+ )
91
+
92
+ self.tm.erase_config()
93
+ time.sleep(0.2)
94
+
95
+ @pytest.mark.sensor_amt22
96
+ def test_b_position_control_following_sensor_change(self):
97
+ """
98
+ Test position control before and after changing sensor connection and type.
99
+ This test will alter the sensor configuration from an external SPI connection
100
+ to an onboard connection, and verify if the position control maintains accuracy.
101
+ """
102
+ self.check_state(0)
103
+ self.tm.erase_config()
104
+ time.sleep(0.2)
105
+
106
+ # Initially configure with external SPI sensor
107
+ self.configure_sensors(self.tm.sensors.setup.external_spi.type.AMT22)
108
+ self.select_sensors(self.tm.sensors.select.position_sensor.connection.ONBOARD, self.tm.sensors.select.position_sensor.connection.EXTERNAL_SPI)
109
+ self.try_calibrate()
110
+
111
+ # Set initial controller gains
112
+ self.tm.controller.position.p_gain = 9
113
+ self.tm.controller.velocity.p_gain = 3e-5
114
+ self.tm.controller.velocity.i_gain = 0
115
+ self.tm.controller.velocity.limit = 200000
116
+
117
+ # Start with external SPI sensor
118
+ self.tm.controller.position_mode()
119
+ self.check_state(2)
120
+
121
+ for _ in range(5):
122
+ new_pos = random.uniform(-1000, 1000)
123
+ self.tm.controller.position.setpoint = new_pos * tick
124
+ time.sleep(tsleep)
125
+ self.assertAlmostEqual(
126
+ self.tm.sensors.user_frame.position_estimate,
127
+ self.tm.controller.position.setpoint,
128
+ delta=200 * tick,
129
+ )
130
+
131
+ self.tm.controller.idle()
132
+ time.sleep(0.1)
133
+
134
+ # Change to onboard sensor
135
+ self.select_sensors(self.tm.sensors.select.position_sensor.connection.ONBOARD, self.tm.sensors.select.position_sensor.connection.ONBOARD)
136
+
137
+ # Re-calibrate with new sensor setup
138
+ self.try_calibrate()
139
+ time.sleep(0.1)
140
+
141
+ self.tm.controller.position_mode()
142
+ self.check_state(2)
143
+
144
+ for _ in range(5):
145
+ new_pos = random.uniform(-10000, 10000)
146
+ self.tm.controller.position.setpoint = new_pos * tick
147
+ time.sleep(tsleep)
148
+ self.assertAlmostEqual(
149
+ self.tm.sensors.user_frame.position_estimate,
150
+ self.tm.controller.position.setpoint,
151
+ delta=2000 * tick,
152
+ )
153
+
154
+ self.tm.erase_config()
155
+ time.sleep(0.2)
156
+
157
+
158
+ if __name__ == "__main__":
159
+ unittest.main()
@@ -0,0 +1,210 @@
1
+ """
2
+ Tinymovr Board Config Tests
3
+ Copyright Ioannis Chatzikonstantinou 2020-2023
4
+
5
+ Tests saving and loading Tinymovr configuration to/from NVRAM.
6
+
7
+ This program is free software: you can redistribute it and/or modify it under
8
+ the terms of the GNU General Public License as published by the Free Software
9
+ Foundation, either version 3 of the License, or (at your option) any later
10
+ version.
11
+ This program is distributed in the hope that it will be useful, but WITHOUT
12
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14
+ You should have received a copy of the GNU General Public License along with
15
+ this program. If not, see <http://www.gnu.org/licenses/>.
16
+ """
17
+
18
+ import random
19
+ import time
20
+
21
+ from avlos.unit_field import get_registry
22
+
23
+ import unittest
24
+ import pytest
25
+ from tests import TMTestCase
26
+
27
+ ureg = get_registry()
28
+ A = ureg.ampere
29
+ tick = ureg.ticks
30
+ s = ureg.second
31
+ tsleep = 0.50
32
+
33
+
34
+ class TestAS5047(TMTestCase):
35
+
36
+ @pytest.mark.sensor_as5047
37
+ def test_a_position_control_before_after_save_and_load_config(self):
38
+ """
39
+ Test position control after saving and loading config.
40
+ WARNING: This will perform one NVRAM write and two erase cycles.
41
+ """
42
+ self.check_state(0)
43
+ self.tm.erase_config()
44
+ time.sleep(0.2)
45
+
46
+ self.configure_sensors(self.tm.sensors.setup.external_spi.type.AS5047)
47
+ self.select_sensors(self.tm.sensors.select.position_sensor.connection.ONBOARD, self.tm.sensors.select.position_sensor.connection.EXTERNAL_SPI)
48
+
49
+ self.try_calibrate()
50
+
51
+ self.tm.controller.position.p_gain = 9
52
+ self.tm.controller.velocity.p_gain = 2e-5
53
+ self.tm.controller.velocity.i_gain = 0
54
+
55
+ self.tm.controller.position_mode()
56
+ self.check_state(2)
57
+
58
+ for _ in range(10):
59
+ new_pos = random.uniform(-20000, 20000)
60
+ self.tm.controller.position.setpoint = new_pos * tick
61
+ time.sleep(tsleep)
62
+ self.assertAlmostEqual(
63
+ self.tm.sensors.user_frame.position_estimate,
64
+ self.tm.controller.position.setpoint,
65
+ delta=2000 * tick,
66
+ )
67
+
68
+ self.tm.controller.idle()
69
+
70
+ time.sleep(0.1)
71
+
72
+ self.tm.save_config()
73
+
74
+ time.sleep(0.2)
75
+
76
+ self.reset_and_wait()
77
+
78
+ self.tm.controller.position_mode()
79
+ self.check_state(2)
80
+
81
+ for _ in range(10):
82
+ new_pos = random.uniform(-20000, 20000)
83
+ self.tm.controller.position.setpoint = new_pos * tick
84
+ time.sleep(tsleep)
85
+ self.assertAlmostEqual(
86
+ self.tm.sensors.user_frame.position_estimate,
87
+ self.tm.controller.position.setpoint,
88
+ delta=2000 * tick,
89
+ )
90
+
91
+ self.tm.erase_config()
92
+ time.sleep(0.2)
93
+
94
+ @pytest.mark.sensor_as5047
95
+ def test_b_position_control_following_sensor_change(self):
96
+ """
97
+ Test position control before and after changing sensor connection and type.
98
+ This test will alter the sensor configuration from an external SPI connection
99
+ to an onboard connection, and verify if the position control maintains accuracy.
100
+ """
101
+ self.check_state(0)
102
+ self.tm.erase_config()
103
+ time.sleep(0.2)
104
+
105
+ # Initially configure with external SPI sensor
106
+ self.configure_sensors(self.tm.sensors.setup.external_spi.type.AS5047)
107
+ self.select_sensors(self.tm.sensors.select.position_sensor.connection.ONBOARD, self.tm.sensors.select.position_sensor.connection.EXTERNAL_SPI)
108
+ self.try_calibrate()
109
+
110
+ # Set initial controller gains
111
+ self.tm.controller.position.p_gain = 9
112
+ self.tm.controller.velocity.p_gain = 3e-5
113
+ self.tm.controller.velocity.i_gain = 0
114
+ self.tm.controller.velocity.limit = 200000
115
+
116
+ # Start with external SPI sensor
117
+ self.tm.controller.position_mode()
118
+ self.check_state(2)
119
+
120
+ for _ in range(5):
121
+ new_pos = random.uniform(-20000, 20000)
122
+ self.tm.controller.position.setpoint = new_pos * tick
123
+ time.sleep(tsleep)
124
+ self.assertAlmostEqual(
125
+ self.tm.sensors.user_frame.position_estimate,
126
+ self.tm.controller.position.setpoint,
127
+ delta=2000 * tick,
128
+ )
129
+
130
+ self.tm.controller.idle()
131
+ time.sleep(0.1)
132
+
133
+ # Change to onboard sensor
134
+ self.select_sensors(self.tm.sensors.select.position_sensor.connection.ONBOARD, self.tm.sensors.select.position_sensor.connection.ONBOARD)
135
+
136
+ # Re-calibrate with new sensor setup
137
+ self.try_calibrate()
138
+ time.sleep(0.1)
139
+
140
+ self.tm.controller.position_mode()
141
+ self.check_state(2)
142
+
143
+ for _ in range(5):
144
+ new_pos = random.uniform(-20000, 20000)
145
+ self.tm.controller.position.setpoint = new_pos * tick
146
+ time.sleep(tsleep)
147
+ self.assertAlmostEqual(
148
+ self.tm.sensors.user_frame.position_estimate,
149
+ self.tm.controller.position.setpoint,
150
+ delta=2000 * tick,
151
+ )
152
+
153
+ self.tm.erase_config()
154
+ time.sleep(0.2)
155
+
156
+ @pytest.mark.sensor_as5047
157
+ def test_c_position_control_change_baud_rates(self):
158
+ """
159
+ Test position control with external sensor and
160
+ different baud rates.
161
+ """
162
+ self.check_state(0)
163
+ self.tm.erase_config()
164
+ time.sleep(0.2)
165
+
166
+ self.configure_sensors(self.tm.sensors.setup.external_spi.type.AS5047)
167
+ self.select_sensors(self.tm.sensors.select.position_sensor.connection.ONBOARD, self.tm.sensors.select.position_sensor.connection.EXTERNAL_SPI)
168
+
169
+ self.try_calibrate()
170
+
171
+ self.tm.controller.position.p_gain = 9
172
+ self.tm.controller.velocity.p_gain = 2e-5
173
+ self.tm.controller.velocity.i_gain = 0
174
+
175
+ self.tm.controller.position_mode()
176
+ self.check_state(2)
177
+
178
+ for _ in range(10):
179
+ new_pos = random.uniform(-20000, 20000)
180
+ self.tm.controller.position.setpoint = new_pos * tick
181
+ time.sleep(tsleep)
182
+ self.assertAlmostEqual(
183
+ self.tm.sensors.user_frame.position_estimate,
184
+ self.tm.controller.position.setpoint,
185
+ delta=2000 * tick,
186
+ )
187
+
188
+ self.tm.controller.idle()
189
+
190
+ self.tm.sensors.setup.external_spi.rate = "1_5Mbps"
191
+ time.sleep(0.1)
192
+
193
+ self.tm.controller.position_mode()
194
+ self.check_state(2)
195
+
196
+ for _ in range(10):
197
+ new_pos = random.uniform(-20000, 20000)
198
+ self.tm.controller.position.setpoint = new_pos * tick
199
+ time.sleep(tsleep)
200
+ self.assertAlmostEqual(
201
+ self.tm.sensors.user_frame.position_estimate,
202
+ self.tm.controller.position.setpoint,
203
+ delta=2000 * tick,
204
+ )
205
+
206
+ self.tm.controller.idle()
207
+
208
+
209
+ if __name__ == "__main__":
210
+ unittest.main()
@@ -98,6 +98,11 @@ class TestBoard(TMTestCase):
98
98
  self.check_state(0)
99
99
  self.tm.controller.current.Iq_limit = 5
100
100
  self.try_calibrate()
101
+ if hw_rev > 20:
102
+ self.tm.controller.position.p_gain = 5
103
+ self.tm.controller.velocity.p_gain = 2e-5
104
+ self.tm.controller.velocity.i_gain = 0
105
+
101
106
  self.tm.controller.position_mode()
102
107
  self.check_state(2)
103
108
 
@@ -0,0 +1,105 @@
1
+ """
2
+ Tinymovr Board Config Tests
3
+ Copyright Ioannis Chatzikonstantinou 2020-2023
4
+
5
+ Tests saving and loading Tinymovr configuration to/from NVRAM.
6
+
7
+ This program is free software: you can redistribute it and/or modify it under
8
+ the terms of the GNU General Public License as published by the Free Software
9
+ Foundation, either version 3 of the License, or (at your option) any later
10
+ version.
11
+ This program is distributed in the hope that it will be useful, but WITHOUT
12
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14
+ You should have received a copy of the GNU General Public License along with
15
+ this program. If not, see <http://www.gnu.org/licenses/>.
16
+ """
17
+
18
+ import random
19
+ import time
20
+
21
+ from avlos.unit_field import get_registry
22
+
23
+ import unittest
24
+ import pytest
25
+ from tests import TMTestCase
26
+
27
+ ureg = get_registry()
28
+ A = ureg.ampere
29
+ tick = ureg.ticks
30
+ s = ureg.second
31
+ tsleep = 1.00
32
+
33
+
34
+ def set_pole_pairs(tm):
35
+ tm.motor.pole_pairs = 15
36
+
37
+ class TestHall(TMTestCase):
38
+
39
+ @pytest.mark.sensor_hall
40
+ def test_a_position_control_w_loaded_config(self):
41
+ """
42
+ Test position control after saving and loading config.
43
+ WARNING: This will perform one NVRAM write and two erase cycles.
44
+ """
45
+ self.check_state(0)
46
+ self.tm.erase_config()
47
+ time.sleep(0.2)
48
+
49
+ self.tm.sensors.select.position_sensor.connection = self.tm.sensors.select.position_sensor.connection.HALL
50
+ self.tm.sensors.select.commutation_sensor.connection = self.tm.sensors.select.commutation_sensor.connection.HALL
51
+ # Commutation sensor remains internal
52
+
53
+ self.tm.sensors.select.position_sensor.bandwidth = 50;
54
+ self.tm.sensors.select.commutation_sensor.bandwidth = 80;
55
+ time.sleep(0.2)
56
+
57
+ self.try_calibrate(precheck_callback=set_pole_pairs)
58
+
59
+ self.tm.controller.position.p_gain = 15
60
+ self.tm.controller.velocity.limit = 400000
61
+ self.tm.controller.velocity.p_gain = 1e-5
62
+ self.tm.controller.velocity.i_gain = 0
63
+
64
+ self.tm.controller.position_mode()
65
+ self.check_state(2)
66
+
67
+ for _ in range(5):
68
+ new_pos = random.uniform(-200000, 200000)
69
+ self.tm.controller.position.setpoint = new_pos * tick
70
+ time.sleep(tsleep)
71
+ self.assertAlmostEqual(
72
+ self.tm.sensors.user_frame.position_estimate,
73
+ self.tm.controller.position.setpoint,
74
+ delta=30000 * tick,
75
+ )
76
+
77
+ self.tm.controller.idle()
78
+
79
+ time.sleep(0.1)
80
+
81
+ self.tm.save_config()
82
+
83
+ time.sleep(0.2)
84
+
85
+ self.reset_and_wait()
86
+
87
+ self.tm.controller.position_mode()
88
+ self.check_state(2)
89
+
90
+ for _ in range(10):
91
+ new_pos = random.uniform(-200000, 200000)
92
+ self.tm.controller.position.setpoint = new_pos * tick
93
+ time.sleep(tsleep)
94
+ self.assertAlmostEqual(
95
+ self.tm.sensors.user_frame.position_estimate,
96
+ self.tm.controller.position.setpoint,
97
+ delta=30000 * tick,
98
+ )
99
+
100
+ self.tm.erase_config()
101
+ time.sleep(0.2)
102
+
103
+
104
+ if __name__ == "__main__":
105
+ unittest.main()
@@ -0,0 +1,250 @@
1
+ """
2
+ Tinymovr Board Config Tests
3
+ Copyright Ioannis Chatzikonstantinou 2020-2023
4
+
5
+ Tests saving and loading Tinymovr configuration to/from NVRAM.
6
+
7
+ This program is free software: you can redistribute it and/or modify it under
8
+ the terms of the GNU General Public License as published by the Free Software
9
+ Foundation, either version 3 of the License, or (at your option) any later
10
+ version.
11
+ This program is distributed in the hope that it will be useful, but WITHOUT
12
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14
+ You should have received a copy of the GNU General Public License along with
15
+ this program. If not, see <http://www.gnu.org/licenses/>.
16
+ """
17
+
18
+ import random
19
+ import time
20
+
21
+ from avlos.unit_field import get_registry
22
+
23
+ import unittest
24
+ from tests import TMTestCase
25
+
26
+ ureg = get_registry()
27
+ A = ureg.ampere
28
+ tick = ureg.ticks
29
+ s = ureg.second
30
+ tsleep = 0.30
31
+
32
+
33
+ class TestNVM(TMTestCase):
34
+ def test_a_state_errors(self):
35
+ """
36
+ Test state transitions
37
+ WARNING: This may perform one NVRAM erase cycle.
38
+ """
39
+ if self.tm.motor.calibrated:
40
+ self.tm.erase_config()
41
+ time.sleep(0.2)
42
+
43
+ self.reset_and_wait()
44
+ self.check_state(0)
45
+ self.assertEqual(self.tm.motor.calibrated, False)
46
+ self.tm.controller.position_mode()
47
+ self.check_state(0)
48
+ self.reset_and_wait()
49
+ self.check_state(0)
50
+ self.assertEqual(self.tm.motor.calibrated, False)
51
+ self.try_calibrate()
52
+ self.assertEqual(self.tm.motor.calibrated, True)
53
+ self.check_state(0)
54
+ time.sleep(0.2)
55
+ self.tm.controller.position_mode()
56
+ self.check_state(2)
57
+ time.sleep(0.2)
58
+ self.tm.controller.calibrate()
59
+ self.check_state(0)
60
+
61
+ self.reset_and_wait()
62
+
63
+ def test_b_save_load_config(self):
64
+ """
65
+ Test erasing, saving and loading of config.
66
+ WARNING: This will perform one NVRAM write and two erase cycles.
67
+ """
68
+ self.check_state(0)
69
+ self.tm.erase_config()
70
+ time.sleep(0.2)
71
+ self.assertEqual(self.tm.motor.calibrated, False)
72
+ self.try_calibrate()
73
+ self.assertEqual(self.tm.motor.calibrated, True)
74
+ R = self.tm.motor.R
75
+ L = self.tm.motor.L
76
+ pole_pairs = self.tm.motor.pole_pairs
77
+ self.tm.save_config()
78
+ time.sleep(0.2)
79
+ self.reset_and_wait()
80
+ self.assertEqual(self.tm.motor.calibrated, True)
81
+ self.assertAlmostEqual(R, self.tm.motor.R)
82
+ self.assertAlmostEqual(L, self.tm.motor.L)
83
+ self.assertAlmostEqual(pole_pairs, self.tm.motor.pole_pairs)
84
+ self.tm.erase_config()
85
+ time.sleep(0.2)
86
+
87
+ def test_c_parameter_persistence(self):
88
+ """
89
+ Test persisting parameters across config saves.
90
+ WARNING: This will perform one NVRAM write and two erase cycles.
91
+ """
92
+ self.check_state(0)
93
+ self.tm.erase_config()
94
+ time.sleep(0.2)
95
+
96
+ self.tm.controller.position.p_gain = 30
97
+ self.tm.controller.velocity.p_gain = 3e-5
98
+ self.tm.controller.velocity.i_gain = 2e-2
99
+ self.tm.controller.velocity.deadband = 100
100
+ self.tm.controller.velocity.limit = 120000
101
+ self.tm.controller.current.Iq_limit = 18
102
+ self.tm.save_config()
103
+
104
+ time.sleep(0.2)
105
+ self.reset_and_wait()
106
+ self.assertAlmostEqual(self.tm.controller.position.p_gain, 30) # * 1 / s
107
+ self.assertAlmostEqual(self.tm.controller.velocity.p_gain, 3e-5) # * A * s / tick
108
+ self.assertAlmostEqual(self.tm.controller.velocity.i_gain, 2e-2) # * A * s / tick
109
+ self.assertAlmostEqual(self.tm.controller.velocity.deadband, 100 * tick)
110
+ self.assertAlmostEqual(self.tm.controller.velocity.limit, 120000 * tick / s)
111
+ self.assertAlmostEqual(self.tm.controller.current.Iq_limit, 18 * A)
112
+ self.tm.erase_config()
113
+ time.sleep(0.2)
114
+
115
+ def test_d_position_control_w_loaded_config(self):
116
+ """
117
+ Test position control after saving and loading config.
118
+ WARNING: This will perform one NVRAM write and two erase cycles.
119
+ """
120
+ self.check_state(0)
121
+ self.tm.erase_config()
122
+ time.sleep(0.2)
123
+
124
+ self.try_calibrate()
125
+
126
+ self.tm.controller.position_mode()
127
+ self.check_state(2)
128
+
129
+ for _ in range(15):
130
+ new_pos = random.uniform(-24000, 24000)
131
+ self.tm.controller.position.setpoint = new_pos * tick
132
+ time.sleep(tsleep)
133
+ self.assertAlmostEqual(
134
+ self.tm.sensors.user_frame.position_estimate,
135
+ self.tm.controller.position.setpoint,
136
+ delta=2000 * tick,
137
+ )
138
+
139
+ self.tm.controller.idle()
140
+
141
+ time.sleep(0.1)
142
+
143
+ self.tm.save_config()
144
+
145
+ time.sleep(0.2)
146
+
147
+ self.reset_and_wait()
148
+
149
+ self.tm.controller.position_mode()
150
+ self.check_state(2)
151
+
152
+ for _ in range(15):
153
+ new_pos = random.uniform(-24000, 24000)
154
+ self.tm.controller.position.setpoint = new_pos * tick
155
+ time.sleep(tsleep)
156
+ self.assertAlmostEqual(
157
+ self.tm.sensors.user_frame.position_estimate,
158
+ self.tm.controller.position.setpoint,
159
+ delta=2000 * tick,
160
+ )
161
+
162
+ self.tm.erase_config()
163
+ time.sleep(0.2)
164
+
165
+ def test_e_save_settings_and_reset(self):
166
+ """
167
+ Test case for issue reported by @Phosfor on Discord:
168
+ 1. Configure current limits etc.
169
+ 2. Calibrate the controller
170
+ 3. Make sure everything is working (i.e. enter current/velocity mode)
171
+ 4. Save the configuration
172
+ 5. Reset the tinymovr; all settings are still correct and the tinymovr reports that it is calibrated
173
+ 6. Try to enter current/velocity mode again; the tinymovr says it is in the correct mode and in the control state, but the motor is not turning
174
+ """
175
+ # Step 1: Configure current limits etc.
176
+ self.check_state(0)
177
+ self.tm.erase_config()
178
+ time.sleep(0.2)
179
+
180
+ self.tm.controller.current.Iq_limit = 15 * A
181
+ self.tm.controller.velocity.limit = 200000 * tick / s
182
+ self.tm.controller.velocity.i_gain = 1e-5
183
+ self.tm.controller.velocity.p_gain = 1.6e-4
184
+ self.tm.controller.velocity.deadband = 50 * tick
185
+ self.tm.traj_planner.max_vel = 110000 * tick / s
186
+ self.tm.traj_planner.max_accel = 500000 * tick / s
187
+ self.tm.traj_planner.max_decel = 500000 * tick / (s * s)
188
+
189
+ # Step 2: Calibrate the controller
190
+ self.try_calibrate()
191
+ self.assertEqual(self.tm.motor.calibrated, True)
192
+
193
+ # Step 3: Make sure everything is working (i.e. enter current/velocity mode)
194
+ self.tm.controller.velocity_mode()
195
+ self.check_state(2)
196
+
197
+ for _ in range(5):
198
+ new_velocity = random.uniform(-50000, 50000)
199
+ self.tm.controller.velocity.setpoint = new_velocity * tick / s
200
+ time.sleep(tsleep)
201
+ self.assertAlmostEqual(
202
+ self.tm.sensors.user_frame.velocity_estimate,
203
+ self.tm.controller.velocity.setpoint,
204
+ delta=5000 * tick / s,
205
+ )
206
+
207
+ self.tm.controller.idle()
208
+ time.sleep(0.1)
209
+
210
+ # Step 4: Save the configuration
211
+ self.tm.save_config()
212
+ time.sleep(0.2)
213
+
214
+ # Step 5: Reset the tinymovr; all settings are still correct and the tinymovr reports that it is calibrated
215
+ self.reset_and_wait()
216
+ self.assertEqual(self.tm.motor.calibrated, True)
217
+ self.assertAlmostEqual(self.tm.controller.current.Iq_limit, 15 * A)
218
+ self.assertAlmostEqual(self.tm.controller.velocity.limit, 200000 * tick / s)
219
+ self.assertAlmostEqual(self.tm.controller.velocity.i_gain, 1e-5)
220
+ self.assertAlmostEqual(self.tm.controller.velocity.p_gain, 1.6e-4)
221
+ self.assertAlmostEqual(self.tm.controller.velocity.deadband, 50 * tick)
222
+ self.assertAlmostEqual(self.tm.traj_planner.max_vel, 110000 * tick / s)
223
+ self.assertAlmostEqual(self.tm.traj_planner.max_accel, 500000 * tick / s)
224
+ self.assertAlmostEqual(self.tm.traj_planner.max_decel, 500000 * tick / (s * s))
225
+
226
+ # Step 6: Try to enter current/velocity mode again; the tinymovr says it is in the correct mode and in the control state, but the motor is not turning (and I don't hear any switching noise that I usually hear when the tinymovr is in the active state)
227
+ self.tm.controller.velocity_mode()
228
+ self.check_state(2)
229
+
230
+ for _ in range(5):
231
+ new_velocity = random.uniform(-50000, 50000)
232
+ self.tm.controller.velocity.setpoint = new_velocity * tick / s
233
+ time.sleep(tsleep)
234
+ # Here we expect the motor not to turn, so we check for lack of movement
235
+ self.assertAlmostEqual(
236
+ self.tm.sensors.user_frame.velocity_estimate,
237
+ self.tm.controller.velocity.setpoint,
238
+ delta=5000 * tick / s,
239
+ )
240
+
241
+ self.tm.controller.idle()
242
+ time.sleep(0.1)
243
+
244
+ self.tm.erase_config()
245
+ time.sleep(0.2)
246
+
247
+
248
+
249
+ if __name__ == "__main__":
250
+ unittest.main()
@@ -20,6 +20,7 @@ import warnings
20
20
  from enum import Enum
21
21
  from threading import Thread, Lock
22
22
  from tinymovr.bus_manager import BusManager
23
+ from logging import Logger
23
24
 
24
25
 
25
26
  bus_router = None
@@ -86,7 +87,7 @@ class BusRouter:
86
87
  self.bus_manager.send(frame)
87
88
 
88
89
 
89
- def init_router(bus_class, bus_params, logger, timeout=0.1):
90
+ def init_router(bus_class, bus_params, logger=Logger("tinymovr"), timeout=0.1):
90
91
  """
91
92
  Initializes a bus router using a python-can bus instance
92
93
  """
@@ -14,8 +14,8 @@ Options:
14
14
 
15
15
  import yaml
16
16
  import can
17
- import pkg_resources
18
17
  import IPython
18
+ from importlib.metadata import version as get_version
19
19
  from traitlets.config import Config
20
20
  from docopt import docopt
21
21
 
@@ -46,7 +46,7 @@ def spawn():
46
46
  """
47
47
  Spawns the Tinymovr Studio IPython-based CLI.
48
48
  """
49
- version = pkg_resources.require("tinymovr")[0].version
49
+ version = get_version("tinymovr")
50
50
  arguments = docopt(__doc__, version=app_name + " " + str(version))
51
51
 
52
52
  logger = configure_logging()
@@ -15,7 +15,7 @@ Options:
15
15
 
16
16
  import sys
17
17
  import yaml
18
- import pkg_resources
18
+ from importlib.metadata import version as get_version
19
19
  from importlib_resources import files
20
20
  from docopt import docopt
21
21
  from PySide6.QtWidgets import QApplication
@@ -44,7 +44,7 @@ this program. If not, see <http://www.gnu.org/licenses/>.
44
44
 
45
45
 
46
46
  def spawn():
47
- version = pkg_resources.require("tinymovr")[0].version
47
+ version = get_version("tinymovr")
48
48
  arguments = docopt(__doc__, version=app_name + " " + str(version))
49
49
 
50
50
  logger = configure_logging()
@@ -18,7 +18,7 @@ this program. If not, see <http://www.gnu.org/licenses/>.
18
18
  import sys
19
19
  import time
20
20
  import logging
21
- import pkg_resources
21
+ from importlib.metadata import version as get_version
22
22
  from contextlib import suppress
23
23
  import json
24
24
  from PySide6 import QtCore
@@ -439,7 +439,7 @@ class MainWindow(QMainWindow):
439
439
  self.delete_graph_by_attr_name(attr_name)
440
440
 
441
441
  def show_about_box(self):
442
- version_str = pkg_resources.require("tinymovr")[0].version
442
+ version_str = get_version("tinymovr")
443
443
  app_str = "{} {}".format(app_name, version_str)
444
444
  QMessageBox.about(
445
445
  self,
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: tinymovr
3
- Version: 2.3.1
3
+ Version: 2.3.3
4
4
  Summary: Tinymovr Studio
5
5
  Home-page: https://github.com/yconst/Tinymovr
6
6
  Author: Yannis Chatzikonstantinou
@@ -10,7 +10,32 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (G
10
10
  Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.9
12
12
  Description-Content-Type: text/markdown
13
+ Requires-Dist: ipython
14
+ Requires-Dist: importlib_resources
15
+ Requires-Dist: python-can
16
+ Requires-Dist: python-can-canine
17
+ Requires-Dist: python-can-slcan_disco
18
+ Requires-Dist: avlos>=0.6.6
19
+ Requires-Dist: pyserial
20
+ Requires-Dist: pyusb
21
+ Requires-Dist: pyyaml
22
+ Requires-Dist: docopt
23
+ Requires-Dist: flatten-dict
24
+ Requires-Dist: pint
25
+ Requires-Dist: pretty_errors
13
26
  Provides-Extra: gui
27
+ Requires-Dist: pyside6; extra == "gui"
28
+ Requires-Dist: pyqtgraph>=0.13.3; extra == "gui"
29
+ Dynamic: author
30
+ Dynamic: author-email
31
+ Dynamic: classifier
32
+ Dynamic: description
33
+ Dynamic: description-content-type
34
+ Dynamic: home-page
35
+ Dynamic: provides-extra
36
+ Dynamic: requires-dist
37
+ Dynamic: requires-python
38
+ Dynamic: summary
14
39
 
15
40
  ![Studio Lint Test](https://github.com/yconst/tinymovr/workflows/Tinymovr%20Studio%20Lint%2FTest/badge.svg)
16
41
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
@@ -2,9 +2,13 @@ MANIFEST.in
2
2
  README.md
3
3
  pyproject.toml
4
4
  setup.py
5
+ tests/test_amt22.py
6
+ tests/test_as5047.py
5
7
  tests/test_board.py
6
8
  tests/test_dfu.py
9
+ tests/test_hall.py
7
10
  tests/test_homing.py
11
+ tests/test_nvm.py
8
12
  tests/test_rates.py
9
13
  tests/test_simulation.py
10
14
  tests/test_traj_planner.py
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