├── .gitignore
├── LICENSE
├── README.md
├── arduino_alvik
├── __init__.py
├── arduino_alvik.py
├── constants.py
├── conversions.py
├── pinout_definitions.py
├── robot_definitions.py
├── stm32_flash.py
└── uart.py
├── examples
├── actuators
│ ├── leds_setting.py
│ ├── move_wheels.py
│ ├── pose_example.py
│ ├── set_servo.py
│ ├── wheels_position.py
│ └── wheels_speed.py
├── arduino-runtime
│ ├── blink.py
│ ├── line_follower.py
│ └── read_tof.py
├── communication
│ ├── i2c_scan.py
│ └── modulino.py
├── demo
│ ├── demo.py
│ ├── hand_follower.py
│ ├── line_follower.py
│ ├── main.py
│ └── touch_move.py
├── events
│ ├── hot_wheels.py
│ ├── motion_events.py
│ ├── timer_one_shot_events.py
│ ├── timer_periodic_events.py
│ └── touch_events.py
├── flash_firmware.py
├── reload_modules.py
├── sensors
│ ├── read_color_sensor.py
│ ├── read_imu.py
│ ├── read_orientation.py
│ ├── read_tof.py
│ └── read_touch.py
├── tests
│ ├── message_reader.py
│ ├── test_idle.py
│ ├── test_meas_units.py
│ └── test_version.py
└── update_firmware.py
├── install.bat
├── install.sh
├── package.json
└── utilities
├── flash_firmware.bat
└── flash_firmware.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # arduino-alvik-mpy
2 |
3 | **Arduino Alvik micropython** library.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | ## How to Install the Micropython library
13 |
14 | ### 1. install mpremote
15 |
16 | [mpremote](https://docs.micropython.org/en/latest/reference/mpremote.html) is needed to upload files on the [Arduino® Nano ESP32](https://store.arduino.cc/products/nano-esp32?gad_source=1&gclid=Cj0KCQiA2KitBhCIARIsAPPMEhLtIxV_s7KyLJO4-69RdR1UeFTdgGK_XmI8w7xdbur4gs1oJU4Jl68aAhbaEALw_wcB).
17 | Minimum suggested mpremote release is 1.22.0
18 |
19 | ```shell
20 | (venv)$ pip install mpremote
21 | ```
22 |
23 | ### 2. install library
24 |
25 | Run the following line to upload all files and download the dependencies needed to run the Arduino Alvik micropython library.
26 |
27 | ```shell
28 | Linux
29 | $ ./install.sh -p
30 |
31 | Windows
32 | > install.bat -p
33 | ```
34 |
35 | ***Note: The -p parameter is optional***
36 |
37 |
38 | __WARNING: do not open bin files with Arduino Lab for Micropython 0.8.0 because they will be corrupted__
39 |
40 | ### 2.1 mip (MicroPython Package Manager)
41 | This is the recommended method for boards which can connect to Internet. Make sure your board is connected to the Internet and
42 | run the following MicroPython script using your favourite editor:
43 |
44 | ```py
45 | import mip
46 |
47 | mip.install('github:arduino/arduino-alvik-mpy')
48 |
49 | ```
50 |
51 |
52 |
53 |
54 | ### 3. Update firmware on your Arduino® Alvik
55 |
56 | Download the latest [Arduino Alvik Carrier Firmware code](https://github.com/arduino-libraries/Arduino_AlvikCarrier) (to compile the firmware using Arduino IDE) or the [pre-compiled firmware](https://github.com/arduino-libraries/Arduino_AlvikCarrier/releases/latest)
57 |
58 | Go into `utilities` folder and run:
59 | ```shell
60 | Linux
61 | $ ./flash_firmware.sh -p
62 |
63 | Windows
64 | > flash_firmware.bat -p
65 | ```
66 | Answer `y` to flash firmware.
67 |
68 | ***Note: The -p parameter is optional***
69 |
70 |
71 |
72 |
73 |
74 | ## Examples
75 |
76 | Use `mpremote` to copy files into your Arduino® Nano ESP32.
77 |
78 | e.g.
79 | ``` shell
80 | (venv)$ mpremote connect "COM1" fs cp ./examples/led_setting.py :led_setting.py
81 | ```
82 |
83 | You can now use Arduino Lab for Micropython to run your examples remotely from the device filesystem.
84 |
85 |
86 |
87 | ## Default Demo
88 |
89 | Use `mpremote` to copy following the files from the `examples\demo` folder:
90 | - `main.py`, this file allows you to automatically start the demo
91 | - `demo.py`, demo launcher
92 | - `touch_move.py`, programming the robot movements via touch buttons demo
93 | - `line_follower.py`, black line follower demo
94 | - `hand_follower.py`, hand following demo, the robot stays always at 10cm from an obstacle placed in front of it.
95 |
96 | When the robot is turned on, the demo launcher starts after Alvik boot.
97 |
98 | Blue leds on top turn on.
99 |
100 | By pressing up and down arrows, it is possible to select different demos identified by different colors (blue, green and red).
101 |
102 | Each color allows to run a different demo as following:
103 | - `red` launches the touch-move demo
104 | - `green` launches the hand following demo
105 | - `blue` launches the line follower demo
106 |
107 | To run a demo, press the `OK touch button`, after selecting the right demo color.
108 |
109 | To run a different demo, press the `CANCEL touch button` and you will be redirected to the main menu.
110 |
111 | ### 1. Touch mode example (RED)
112 | This example starts with the red leds on.
113 |
114 | `directional touch buttons` (UP, DOWN, LEFT, RIGHT) program the desired movements.
115 |
116 | Everytime a `directional touch button` is pressed, the leds blink in a purple color indicating that the command has been registered.
117 | - `UP touch button` will register a 10 cm forward movement
118 | - `DOWN touch button` will register a 10 cm backward movement
119 | - `RIGHT touch button` will register a 90° clockwise rotation movement
120 | - `LEFT touch button` will register a 90° counterclockwise rotation movement
121 |
122 | To clear the commands queue, press the `CANCEL touch button`.
123 | The leds will blink in red.
124 |
125 | To start the sequence, press the `OK touch button`.
126 |
127 | Pressing the `CANCEL touch button` at any time stops the robot and resets the sequence.
128 | Pressing the `CANCEL touch button` two times during sequence programming you will be redirected to the main menu.
129 |
130 |
131 |
132 | ### 2. Hand follower example (GREEN)
133 | This example starts with the green leds on.
134 |
135 | Place an obstacle or your hand in front of the robot.
136 |
137 | To start the robot press the `OK touch button`.
138 |
139 | The robot will move to keep a 10 centimeters distance from the obstacle/hand.
140 |
141 | It is possible to stop the robot at any time by pressing the `CANCEL touch button`.
142 |
143 |
144 |
145 | ### 3. Line Follower example (BLUE)
146 | This example starts with the blue leds on.
147 |
148 | To run this example, a white board and black tape (2cm wide) is required.
149 |
150 | Place the robot at the center of the line and press the `OK touch button`.
151 |
152 | It is possible to stop the robot at any time by pressing the `CANCEL touch button`.
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 | __WARNING: do not open bin files with Arduino Lab for Micropython 0.8.0 because they will be corrupted__
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | ## Useful links
169 | - [Arduino_Alvik](https://github.com/arduino-libraries/Arduino_Alvik): Arduino library required to program Alvik
170 | - [Arduino_AlvikCarrier](https://github.com/arduino-libraries/Arduino_AlvikCarrier): Arduino library required to build the firmware
171 | - [Arduino Alvik product page](https://store.arduino.cc/pages/alvik)
172 |
--------------------------------------------------------------------------------
/arduino_alvik/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Arduino Alvik micropython library
3 | """
4 |
5 | __author__ = "Lucio Rossi , Giovanni Bruno "
6 | __license__ = "MPL 2.0"
7 | __version__ = "1.1.4"
8 | __maintainer__ = "Lucio Rossi , Giovanni Bruno "
9 | __required_firmware_version__ = "1.1.1"
10 |
11 | from .arduino_alvik import *
12 |
--------------------------------------------------------------------------------
/arduino_alvik/arduino_alvik.py:
--------------------------------------------------------------------------------
1 | import struct
2 | from machine import I2C
3 | import _thread
4 | from time import sleep_ms, ticks_ms, ticks_diff
5 |
6 | from ucPack import ucPack
7 |
8 | from .uart import uart
9 | from .conversions import *
10 | from .pinout_definitions import *
11 | from .robot_definitions import *
12 | from .constants import *
13 |
14 | from .__init__ import __version__
15 | from .__init__ import __required_firmware_version__
16 |
17 |
18 | def writes_uart(method):
19 | def wrapper(*args, **kwargs):
20 | with ArduinoAlvik._write_lock:
21 | return method(*args, **kwargs)
22 |
23 | return wrapper
24 |
25 |
26 | def reads_uart(method):
27 | def wrapper(*args, **kwargs):
28 | with ArduinoAlvik._read_lock:
29 | return method(*args, **kwargs)
30 |
31 | return wrapper
32 |
33 |
34 | class _AlvikRLock:
35 | def __init__(self):
36 | """Alvik re-entrant Lock implementation"""
37 | self._lock = _thread.allocate_lock()
38 | self._owner = None
39 | self._count = 0
40 |
41 | def acquire(self):
42 | tid = _thread.get_ident()
43 |
44 | if self._owner == tid:
45 | self._count += 1
46 | return True
47 |
48 | self._lock.acquire()
49 | self._owner = tid
50 | self._count = 1
51 | return True
52 |
53 | def release(self):
54 | tid = _thread.get_ident()
55 |
56 | if self._owner != tid:
57 | raise RuntimeError("Cannot release an unowned lock")
58 |
59 | self._count -= 1
60 | if self._count == 0:
61 | self._owner = None
62 | self._lock.release()
63 |
64 | def locked(self):
65 | return self._lock.locked()
66 |
67 | def __enter__(self):
68 | self.acquire()
69 | return self
70 |
71 | def __exit__(self, exc_type, exc_value, traceback):
72 | self.release()
73 |
74 |
75 | class ArduinoAlvik:
76 | _update_thread_running = False
77 | _update_thread_id = None
78 | _events_thread_running = False
79 | _events_thread_id = None
80 |
81 | _write_lock = _AlvikRLock()
82 | _read_lock = _AlvikRLock()
83 |
84 | def __new__(cls):
85 | if not hasattr(cls, '_instance'):
86 | cls._instance = super(ArduinoAlvik, cls).__new__(cls)
87 | return cls._instance
88 |
89 | def __init__(self, stack_size = THREAD_STACK_SIZE):
90 | _thread.stack_size(stack_size)
91 | self.i2c = _ArduinoAlvikI2C(A4, A5)
92 | self._packeter = ucPack(200)
93 | self.left_wheel = _ArduinoAlvikWheel(self._packeter, ord('L'), alvik=self)
94 | self.right_wheel = _ArduinoAlvikWheel(self._packeter, ord('R'), alvik=self)
95 | self._servo_positions = list((None, None,))
96 | self.servo_A = _ArduinoAlvikServo(self._packeter, 'A', 0, self._servo_positions)
97 | self.servo_B = _ArduinoAlvikServo(self._packeter, 'B', 1, self._servo_positions)
98 | self._led_state = list((None,))
99 | self.left_led = self.DL1 = _ArduinoAlvikRgbLed(self._packeter, 'left', self._led_state,
100 | rgb_mask=[0b00000100, 0b00001000, 0b00010000])
101 | self.right_led = self.DL2 = _ArduinoAlvikRgbLed(self._packeter, 'right', self._led_state,
102 | rgb_mask=[0b00100000, 0b01000000, 0b10000000])
103 | self._battery_perc = None
104 | self._battery_is_charging = None
105 | self._touch_byte = None
106 | self._move_byte = None
107 | self._behaviour = None
108 | self._red = None
109 | self._green = None
110 | self._blue = None
111 | self._white_cal = None
112 | self._black_cal = None
113 | self._left_line = None
114 | self._center_line = None
115 | self._right_line = None
116 | self._roll = None
117 | self._pitch = None
118 | self._yaw = None
119 | self._x = None
120 | self._y = None
121 | self._theta = None
122 | self._ax = None
123 | self._ay = None
124 | self._az = None
125 | self._gx = None
126 | self._gy = None
127 | self._gz = None
128 | self._left_tof = None
129 | self._center_left_tof = None
130 | self._center_tof = None
131 | self._center_right_tof = None
132 | self._right_tof = None
133 | self._top_tof = None
134 | self._bottom_tof = None
135 | self._linear_velocity = None
136 | self._angular_velocity = None
137 | self._last_ack = None
138 | self._waiting_ack = None
139 | self._version = list(map(int, __version__.split('.')))
140 | self._fw_version = [None, None, None]
141 | self._required_fw_version = list(map(int, __required_firmware_version__.split('.')))
142 | self._touch_events = _ArduinoAlvikTouchEvents()
143 | self._move_events = _ArduinoAlvikMoveEvents()
144 | self._timer_events = _ArduinoAlvikTimerEvents(-1)
145 |
146 | def __del__(self):
147 | """
148 | This method is a stub. __del__ is not implemented in MicroPython (https://docs.micropython.org/en/latest/genrst/core_language.html#special-method-del-not-implemented-for-user-defined-classes)
149 | :return:
150 | """
151 | ...
152 | # self.__class__._instance = None
153 |
154 | @staticmethod
155 | def is_on() -> bool:
156 | """
157 | Returns true if robot is on
158 | :return:
159 | """
160 | return CHECK_STM32.value() == 1
161 |
162 | @staticmethod
163 | def _print_battery_status(percentage: float, is_charging) -> None:
164 | """
165 | Pretty prints the battery status
166 | :param percentage: SOC of the battery
167 | :param is_charging: True if the battery is charging
168 | :return:
169 | """
170 | print("\033[2K\033[1G", end='\r')
171 | if percentage > 97:
172 | marks_str = ' \U0001F50B'
173 | else:
174 | marks_str = ' \U0001FAAB'
175 | if is_charging:
176 | charging_str = ' \U0001F50C '
177 | else:
178 | charging_str = ' \U000026A0 WARNING: battery is discharging!'
179 | word = marks_str + f" {percentage}% {charging_str} \t"
180 | print(word, end='')
181 |
182 | @staticmethod
183 | def _lengthy_op(self, iterations=10000000) -> int:
184 | result = 0
185 | for i in range(1, iterations):
186 | result += i * i
187 | return result
188 |
189 | def _idle(self, delay_=1, check_on_thread=False, blocking=False) -> None:
190 | """
191 | Alvik's idle mode behaviour
192 | :return:
193 | """
194 | NANO_CHK.value(1)
195 | self.i2c.set_single_thread(True)
196 |
197 | if blocking:
198 | self._lengthy_op(50000)
199 | else:
200 | sleep_ms(500)
201 | led_val = 0
202 |
203 | try:
204 | while not self.is_on():
205 |
206 | if check_on_thread and not self.__class__._update_thread_running:
207 | break
208 |
209 | cmd = bytearray(1)
210 | cmd[0] = 0x06
211 |
212 | self.i2c.start()
213 | self.i2c.writeto(0x36, cmd)
214 |
215 | soc_raw = struct.unpack('h', self.i2c.readfrom(0x36, 2))[0]
216 | soc_perc = soc_raw * 0.00390625
217 | self._battery_is_charging = soc_perc > 0
218 | self._battery_perc = abs(soc_perc)
219 | self._print_battery_status(round(soc_perc), self._battery_is_charging)
220 | if blocking:
221 | self._lengthy_op(10000)
222 | else:
223 | sleep_ms(delay_)
224 | if soc_perc > 97:
225 | LEDG.value(0)
226 | LEDR.value(1)
227 | else:
228 | LEDR.value(led_val)
229 | LEDG.value(1)
230 | led_val = (led_val + 1) % 2
231 | self.i2c.set_single_thread(False)
232 | if self.is_on():
233 | print("\n********** Alvik is on **********")
234 | except KeyboardInterrupt as e:
235 | self.stop()
236 | raise e
237 | except OSError as e:
238 | print(f'\nUnable to read SOC: {e}')
239 | raise e
240 | except Exception as e:
241 | print(f'\nUnhandled exception: {e} {type(e)}')
242 | raise e
243 | finally:
244 | LEDR.value(1)
245 | LEDG.value(1)
246 | NANO_CHK.value(0)
247 | self.i2c.set_single_thread(False)
248 |
249 | @staticmethod
250 | def _snake_robot(duration: int = 1000):
251 | """
252 | Snake robot animation
253 | :param duration:
254 | :return:
255 | """
256 |
257 | robot = '\U0001F916'
258 | snake = '\U0001F40D'
259 |
260 | cycles = int(duration / 200)
261 |
262 | frame = ''
263 | for i in range(0, cycles):
264 | print("\033[2K\033[1G", end='\r')
265 | pre = ' ' * i
266 | between = ' ' * (i % 2 + 1)
267 | post = ' ' * 5
268 | frame = pre + snake + between + robot + post
269 | print(frame, end='')
270 | sleep_ms(200)
271 |
272 | def begin(self) -> int:
273 | """
274 | Begins all Alvik operations
275 | :return:
276 | """
277 | if not self.is_on():
278 | print("\n********** Please turn on your Arduino Alvik! **********\n")
279 | sleep_ms(1000)
280 | self.i2c.set_main_thread(_thread.get_ident())
281 | self._idle(1000)
282 | self._begin_update_thread()
283 |
284 | sleep_ms(100)
285 |
286 | self._reset_hw()
287 | self._flush_uart()
288 | self._snake_robot(1000)
289 | self._wait_for_ack()
290 | if not self._wait_for_fw_check():
291 | self.stop()
292 | raise Exception('\n********** PLEASE UPDATE ALVIK FIRMWARE (required: '+'.'.join(map(str,self._required_fw_version))+')! Check documentation **********\n')
293 | self._snake_robot(2000)
294 | self.set_illuminator(True)
295 | self.set_behaviour(1)
296 | self.set_behaviour(2)
297 | self._set_color_reference()
298 | if self._has_events_registered():
299 | print('\n********** Starting events thread **********\n')
300 | self._start_events_thread()
301 | self.set_servo_positions(90, 90)
302 | return 0
303 |
304 | def _has_events_registered(self) -> bool:
305 | """
306 | Returns True if Alvik has some events registered
307 | :return:
308 | """
309 |
310 | return any([
311 | self._touch_events.has_callbacks(),
312 | self._move_events.has_callbacks(),
313 | self._timer_events.has_callbacks()
314 | # more events check
315 | ])
316 |
317 | def _wait_for_ack(self) -> None:
318 | """
319 | Waits until receives 0x00 ack from robot
320 | :return:
321 | """
322 | self._waiting_ack = 0x00
323 | while self._last_ack != 0x00:
324 | sleep_ms(20)
325 | self._waiting_ack = None
326 |
327 | def _wait_for_fw_check(self, timeout=5) -> bool:
328 | """
329 | Waits until receives version from robot, check required version and return true if everything is ok
330 | :param timeout: wait for fw timeout in seconds
331 | :return:
332 | """
333 | start = ticks_ms()
334 | while self._fw_version == [None, None, None]:
335 | sleep_ms(20)
336 | if ticks_diff(ticks_ms(), start) > timeout * 1000:
337 | print("Could not get FW version")
338 | return False
339 |
340 | if self.check_firmware_compatibility():
341 | return True
342 | else:
343 | return False
344 |
345 | @staticmethod
346 | @reads_uart
347 | def _flush_uart():
348 | """
349 | Empties the UART buffer
350 | :return:
351 | """
352 | uart.read(uart.any())
353 |
354 | def _begin_update_thread(self):
355 | """
356 | Runs robot background operations (e.g. threaded update)
357 | :return:
358 | """
359 |
360 | if not self.__class__._update_thread_running:
361 | self.__class__._update_thread_running = True
362 | self.__class__._update_thread_id = _thread.start_new_thread(self._update, (1,))
363 |
364 | @classmethod
365 | def _stop_update_thread(cls):
366 | """
367 | Stops the background operations
368 | :return:
369 | """
370 | cls._update_thread_running = False
371 |
372 | def _wait_for_target(self, idle_time):
373 | start = ticks_ms()
374 | while True:
375 | if ticks_diff(ticks_ms(), start) >= idle_time * 1000 and self.is_target_reached():
376 | break
377 | else:
378 | # print(self._last_ack)
379 | sleep_ms(100)
380 |
381 | @writes_uart
382 | def is_target_reached(self) -> bool:
383 | """
384 | Returns True if robot has sent an M or R acknowledgment.
385 | It also responds with an ack received message
386 | :return:
387 | """
388 | if self._waiting_ack is None:
389 | return True
390 | if self._last_ack == self._waiting_ack:
391 | self._packeter.packetC1B(ord('X'), ord('K'))
392 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
393 | self._waiting_ack = None
394 | self._last_ack = 0x00
395 | sleep_ms(100)
396 | return True
397 | return False
398 |
399 | @writes_uart
400 | def set_behaviour(self, behaviour: int):
401 | """
402 | Sets the behaviour of Alvik
403 | :param behaviour: behaviour code
404 | :return:
405 | """
406 | self._packeter.packetC1B(ord('B'), behaviour & 0xFF)
407 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
408 |
409 | @writes_uart
410 | def rotate(self, angle: float, unit: str = 'deg', blocking: bool = True):
411 | """
412 | Rotates the robot by given angle
413 | :param angle:
414 | :param blocking:
415 | :param unit: the angle unit
416 | :return:
417 | """
418 | angle = convert_angle(angle, unit, 'deg')
419 | sleep_ms(200)
420 | self._packeter.packetC1F(ord('R'), angle)
421 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
422 | self._waiting_ack = ord('R')
423 | if blocking:
424 | self._wait_for_target(idle_time=(angle / MOTOR_CONTROL_DEG_S))
425 |
426 | @writes_uart
427 | def move(self, distance: float, unit: str = 'cm', blocking: bool = True):
428 | """
429 | Moves the robot by given distance
430 | :param distance:
431 | :param blocking:
432 | :param unit: the distance unit
433 | :return:
434 | """
435 | distance = convert_distance(distance, unit, 'mm')
436 | sleep_ms(200)
437 | self._packeter.packetC1F(ord('G'), distance)
438 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
439 | self._waiting_ack = ord('M')
440 | if blocking:
441 | self._wait_for_target(idle_time=(distance / MOTOR_CONTROL_MM_S))
442 |
443 | def stop(self):
444 | """
445 | Stops all Alvik operations
446 | :return:
447 | """
448 | # stop engines
449 | self.set_wheels_speed(0, 0)
450 |
451 | # turn off UI leds
452 | self._set_leds(0x00)
453 |
454 | # stop the update thread
455 | self._stop_update_thread()
456 |
457 | # stop touch events thread
458 | self._stop_events_thread()
459 |
460 | @staticmethod
461 | def _reset_hw():
462 | """
463 | Resets the STM32
464 | :return:
465 | """
466 |
467 | RESET_STM32.value(0)
468 | sleep_ms(100)
469 | RESET_STM32.value(1)
470 | sleep_ms(100)
471 |
472 | def get_wheels_speed(self, unit: str = 'rpm') -> (float | None, float | None):
473 | """
474 | Returns the speed of the wheels
475 | :param unit: the speed unit of measurement (default: 'rpm')
476 | :return: left_wheel_speed, right_wheel_speed
477 | """
478 | return self.left_wheel.get_speed(unit), self.right_wheel.get_speed(unit)
479 |
480 | @writes_uart
481 | def set_wheels_speed(self, left_speed: float, right_speed: float, unit: str = 'rpm'):
482 | """
483 | Sets left/right motor speed
484 | :param left_speed:
485 | :param right_speed:
486 | :param unit: the speed unit of measurement (default: 'rpm')
487 | :return:
488 | """
489 |
490 | if unit == '%':
491 | left_speed = (left_speed / 100) * MOTOR_MAX_RPM
492 | right_speed = (right_speed / 100) * MOTOR_MAX_RPM
493 | else:
494 | left_speed = convert_rotational_speed(left_speed, unit, 'rpm')
495 | right_speed = convert_rotational_speed(right_speed, unit, 'rpm')
496 |
497 | self._packeter.packetC2F(ord('J'), left_speed, right_speed)
498 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
499 |
500 | @writes_uart
501 | def set_wheels_position(self, left_angle: float, right_angle: float, unit: str = 'deg', blocking: bool = True):
502 | """
503 | Sets left/right motor angle
504 | :param left_angle:
505 | :param right_angle:
506 | :param unit: the speed unit of measurement (default: 'deg')
507 | :param blocking:
508 | :return:
509 | """
510 | left_angle = convert_angle(left_angle, unit, 'deg')
511 | right_angle = convert_angle(right_angle, unit, 'deg')
512 | self._packeter.packetC2F(ord('A'), left_angle, right_angle)
513 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
514 | self._waiting_ack = ord('P')
515 | if blocking:
516 | self._wait_for_target(idle_time=(max(left_angle, right_angle) / MOTOR_CONTROL_DEG_S))
517 |
518 | def get_wheels_position(self, unit: str = 'deg') -> (float | None, float | None):
519 | """
520 | Returns the angle of the wheels
521 | :param unit: the angle unit of measurement (default: 'deg')
522 | :return: left_wheel_angle, right_wheel_angle
523 | """
524 | return (convert_angle(self.left_wheel.get_position(), 'deg', unit),
525 | convert_angle(self.right_wheel.get_position(), 'deg', unit))
526 |
527 | def get_orientation(self) -> (float | None, float | None, float | None):
528 | """
529 | Returns the orientation of the IMU
530 | :return: roll, pitch, yaw
531 | """
532 |
533 | return self._roll, self._pitch, self._yaw
534 |
535 | def get_accelerations(self) -> (float | None, float | None, float | None):
536 | """
537 | Returns the 3-axial acceleration of the IMU
538 | :return: ax, ay, az
539 | """
540 | return self._ax, self._ay, self._az
541 |
542 | def get_gyros(self) -> (float | None, float | None, float | None):
543 | """
544 | Returns the 3-axial angular acceleration of the IMU
545 | :return: gx, gy, gz
546 | """
547 | return self._gx, self._gy, self._gz
548 |
549 | def get_imu(self) -> (float | None, float | None, float | None, float | None, float | None, float | None):
550 | """
551 | Returns all the IMUs readouts
552 | :return: ax, ay, az, gx, gy, gz
553 | """
554 | return self._ax, self._ay, self._az, self._gx, self._gy, self._gz
555 |
556 | def get_line_sensors(self) -> (int | None, int | None, int | None):
557 | """
558 | Returns the line sensors readout
559 | :return: left_line, center_line, right_line
560 | """
561 |
562 | return self._left_line, self._center_line, self._right_line
563 |
564 | @writes_uart
565 | def drive(self, linear_velocity: float, angular_velocity: float, linear_unit: str = 'cm/s',
566 | angular_unit: str = 'deg/s'):
567 | """
568 | Drives the robot by linear and angular velocity
569 | :param linear_velocity:
570 | :param angular_velocity:
571 | :param linear_unit: output linear velocity unit of meas
572 | :param angular_unit: output angular velocity unit of meas
573 | :return:
574 | """
575 | linear_velocity = convert_speed(linear_velocity, linear_unit, 'mm/s')
576 | if angular_unit == '%':
577 | angular_velocity = (angular_velocity / 100) * ROBOT_MAX_DEG_S
578 | else:
579 | angular_velocity = convert_rotational_speed(angular_velocity, angular_unit, 'deg/s')
580 | self._packeter.packetC2F(ord('V'), linear_velocity, angular_velocity)
581 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
582 |
583 | def brake(self):
584 | """
585 | Brakes the robot
586 | :return:
587 | """
588 | self.drive(0, 0)
589 |
590 | def get_drive_speed(self, linear_unit: str = 'cm/s', angular_unit: str = 'deg/s') -> (float | None, float | None):
591 | """
592 | Returns linear and angular velocity of the robot
593 | :param linear_unit: output linear velocity unit of meas
594 | :param angular_unit: output angular velocity unit of meas
595 | :return: linear_velocity, angular_velocity
596 | """
597 | if angular_unit == '%':
598 | angular_velocity = (self._angular_velocity / ROBOT_MAX_DEG_S) * 100 \
599 | if self._angular_velocity is not None else None
600 | else:
601 | angular_velocity = convert_rotational_speed(self._angular_velocity, 'deg/s', angular_unit)
602 |
603 | return convert_speed(self._linear_velocity, 'mm/s', linear_unit), angular_velocity
604 |
605 | @writes_uart
606 | def reset_pose(self, x: float, y: float, theta: float, distance_unit: str = 'cm', angle_unit: str = 'deg'):
607 | """
608 | Resets the robot pose
609 | :param x: x coordinate of the robot
610 | :param y: y coordinate of the robot
611 | :param theta: angle of the robot
612 | :param distance_unit: angle of the robot
613 | :param angle_unit: angle of the robot
614 | :return:
615 | """
616 | x = convert_distance(x, distance_unit, 'mm')
617 | y = convert_distance(y, distance_unit, 'mm')
618 | theta = convert_angle(theta, angle_unit, 'deg')
619 | self._packeter.packetC3F(ord('Z'), x, y, theta)
620 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
621 | sleep_ms(1000)
622 |
623 | def get_pose(self, distance_unit: str = 'cm', angle_unit: str = 'deg') \
624 | -> (float | None, float | None, float | None):
625 | """
626 | Returns the current pose of the robot
627 | :param distance_unit: unit of x and y outputs
628 | :param angle_unit: unit of theta output
629 | :return: x, y, theta
630 | """
631 | return (convert_distance(self._x, 'mm', distance_unit),
632 | convert_distance(self._y, 'mm', distance_unit),
633 | convert_angle(self._theta, 'deg', angle_unit))
634 |
635 | @writes_uart
636 | def set_servo_positions(self, a_position: int, b_position: int):
637 | """
638 | Sets A/B servomotor angle
639 | :param a_position: position of A servomotor (0-180)
640 | :param b_position: position of B servomotor (0-180)
641 | :return:
642 | """
643 | self._servo_positions[0] = a_position
644 | self._servo_positions[1] = b_position
645 | self._packeter.packetC2B(ord('S'), a_position & 0xFF, b_position & 0xFF)
646 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
647 |
648 | def get_servo_positions(self) -> (int, int):
649 | """
650 | Returns the current servomotor positions
651 | :return: position of A/B servomotor (0-180)
652 | """
653 |
654 | return self._servo_positions[0], self._servo_positions[1]
655 |
656 | def get_ack(self) -> str:
657 | """
658 | Returns last acknowledgement
659 | :return:
660 | """
661 | return self._last_ack
662 |
663 | @writes_uart
664 | def send_ack(self, ack: str = 'K'):
665 | """
666 | Sends an ack message on UART
667 | :return:
668 | """
669 | self._packeter.packetC1B(ord('X'), ord(ack))
670 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
671 |
672 | @writes_uart
673 | def _set_leds(self, led_state: int):
674 | """
675 | Sets the LEDs state
676 | :param led_state: one byte 0->builtin 1->illuminator 2->left_red 3->left_green 4->left_blue
677 | 5->right_red 6->right_green 7->right_blue
678 | :return:
679 | """
680 | self._led_state[0] = led_state & 0xFF
681 | self._packeter.packetC1B(ord('L'), self._led_state[0])
682 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
683 |
684 | def set_builtin_led(self, value: bool):
685 | """
686 | Turns on/off the builtin led
687 | :param value:
688 | :return:
689 | """
690 | if self._led_state[0] is None:
691 | self._set_leds(0x00)
692 | self._led_state[0] = self._led_state[0] | 0b00000001 if value else self._led_state[0] & 0b11111110
693 | self._set_leds(self._led_state[0])
694 |
695 | def set_illuminator(self, value: bool):
696 | """
697 | Turns on/off the illuminator led
698 | :param value:
699 | :return:
700 | """
701 | if self._led_state[0] is None:
702 | self._set_leds(0x00)
703 | self._led_state[0] = self._led_state[0] | 0b00000010 if value else self._led_state[0] & 0b11111101
704 | self._set_leds(self._led_state[0])
705 |
706 | def _update(self, delay_=1):
707 | """
708 | Updates the robot status reading/parsing messages from UART.
709 | This method is blocking and meant as a thread callback
710 | Use the method stop to terminate _update and exit the thread
711 | :param delay_: while loop delay (ms)
712 | :return:
713 | """
714 |
715 | self.i2c.set_main_thread(_thread.get_ident())
716 |
717 | while True:
718 | if not self.is_on():
719 | print("Alvik is off")
720 | self._idle(1000, check_on_thread=True)
721 | self._reset_hw()
722 | self._flush_uart()
723 | sleep_ms(1000)
724 | self._wait_for_ack()
725 | sleep_ms(2000)
726 | self.set_illuminator(True)
727 | self.set_behaviour(1)
728 | self.set_behaviour(2)
729 | if not ArduinoAlvik._update_thread_running:
730 | break
731 | self._read_message()
732 | sleep_ms(delay_)
733 |
734 | @reads_uart
735 | def _read_message(self) -> None:
736 | """
737 | Read a message from the uC
738 | :return: True if a message terminator was reached
739 | """
740 | buf = bytearray(uart.any())
741 | uart.readinto(buf)
742 | if len(buf):
743 | uart.readinto(buf)
744 | for b in buf:
745 | self._packeter.buffer.push(b)
746 | if b == self._packeter.end_index and self._packeter.checkPayload():
747 | self._parse_message()
748 |
749 | def _parse_message(self) -> int:
750 | """
751 | Parse a received message
752 | :return: -1 if parse error 0 if ok
753 | """
754 | code = self._packeter.payloadTop()
755 | if code == ord('j'):
756 | # joint speed
757 | _, self.left_wheel._speed, self.right_wheel._speed = self._packeter.unpacketC2F()
758 | elif code == ord('l'):
759 | # line sensor
760 | _, self._left_line, self._center_line, self._right_line = self._packeter.unpacketC3I()
761 | elif code == ord('c'):
762 | # color sensor
763 | _, self._red, self._green, self._blue = self._packeter.unpacketC3I()
764 | elif code == ord('i'):
765 | # imu
766 | _, self._ax, self._ay, self._az, self._gx, self._gy, self._gz = self._packeter.unpacketC6F()
767 | elif code == ord('p'):
768 | # battery percentage
769 | _, battery_perc = self._packeter.unpacketC1F()
770 | self._battery_is_charging = battery_perc > 0
771 | self._battery_perc = abs(battery_perc)
772 | elif code == ord('d'):
773 | # distance sensor
774 | _, self._left_tof, self._center_tof, self._right_tof = self._packeter.unpacketC3I()
775 | elif code == ord('t'):
776 | # touch input
777 | _, self._touch_byte = self._packeter.unpacketC1B()
778 | elif code == ord('m'):
779 | # tilt/shake input
780 | _, self._move_byte = self._packeter.unpacketC1B()
781 | elif code == ord('b'):
782 | # behaviour
783 | _, self._behaviour = self._packeter.unpacketC1B()
784 | elif code == ord('f'):
785 | # tof matrix
786 | (_, self._left_tof, self._center_left_tof, self._center_tof,
787 | self._center_right_tof, self._right_tof, self._top_tof, self._bottom_tof) = self._packeter.unpacketC7I()
788 | elif code == ord('q'):
789 | # imu position
790 | _, self._roll, self._pitch, self._yaw = self._packeter.unpacketC3F()
791 | elif code == ord('w'):
792 | # wheels position
793 | _, self.left_wheel._position, self.right_wheel._position = self._packeter.unpacketC2F()
794 | elif code == ord('v'):
795 | # robot velocity
796 | _, self._linear_velocity, self._angular_velocity = self._packeter.unpacketC2F()
797 | elif code == ord('x'):
798 | # robot ack
799 | if self._waiting_ack is not None:
800 | _, self._last_ack = self._packeter.unpacketC1B()
801 | else:
802 | self._packeter.unpacketC1B()
803 | self._last_ack = 0x00
804 | elif code == ord('z'):
805 | # robot ack
806 | _, self._x, self._y, self._theta = self._packeter.unpacketC3F()
807 | elif code == 0x7E:
808 | # firmware version
809 | _, *self._fw_version = self._packeter.unpacketC3B()
810 | else:
811 | return -1
812 |
813 | return 0
814 |
815 | def get_battery_charge(self) -> int | None:
816 | """
817 | Returns the battery SOC
818 | :return:
819 | """
820 | if self._battery_perc is None:
821 | return None
822 | if self._battery_perc > 100:
823 | return 100
824 | return round(self._battery_perc)
825 |
826 | def is_battery_charging(self) -> bool:
827 | """
828 | Returns True if the device battery is charging
829 | :return:
830 | """
831 | return self._battery_is_charging
832 |
833 | @property
834 | def _touch_bits(self) -> int:
835 | """
836 | Returns the touch sensor's state
837 | :return: touch_bits
838 | """
839 | return (self._touch_byte & 0xFF) if self._touch_byte is not None else 0x00
840 |
841 | def get_touch_any(self) -> bool:
842 | """
843 | Returns true if any button is pressed
844 | :return:
845 | """
846 | return bool(self._touch_bits & 0b00000001)
847 |
848 | def get_touch_ok(self) -> bool:
849 | """
850 | Returns true if ok button is pressed
851 | :return:
852 | """
853 | return bool(self._touch_bits & 0b00000010)
854 |
855 | def get_touch_cancel(self) -> bool:
856 | """
857 | Returns true if cancel button is pressed
858 | :return:
859 | """
860 | return bool(self._touch_bits & 0b00000100)
861 |
862 | def get_touch_center(self) -> bool:
863 | """
864 | Returns true if center button is pressed
865 | :return:
866 | """
867 | return bool(self._touch_bits & 0b00001000)
868 |
869 | def get_touch_up(self) -> bool:
870 | """
871 | Returns true if up button is pressed
872 | :return:
873 | """
874 | return bool(self._touch_bits & 0b00010000)
875 |
876 | def get_touch_left(self) -> bool:
877 | """
878 | Returns true if left button is pressed
879 | :return:
880 | """
881 | return bool(self._touch_bits & 0b00100000)
882 |
883 | def get_touch_down(self) -> bool:
884 | """
885 | Returns true if down button is pressed
886 | :return:
887 | """
888 | return bool(self._touch_bits & 0b01000000)
889 |
890 | def get_touch_right(self) -> bool:
891 | """
892 | Returns true if right button is pressed
893 | :return:
894 | """
895 | return bool(self._touch_bits & 0b10000000)
896 |
897 | @property
898 | def _move_bits(self) -> int:
899 | """
900 | Returns the shake/tilt state
901 | :return:
902 | """
903 | return (self._move_byte & 0xFF) if self._move_byte is not None else 0x80
904 |
905 | def get_shake(self) -> bool:
906 | """
907 | Returns true if Alvik is shaken
908 | :return:
909 | """
910 | return bool(self._move_bits & 0b00000001)
911 |
912 | def get_lifted(self) -> bool:
913 | """
914 | Returns true if Alvik is lifted
915 | :return:
916 | """
917 | return bool(self._move_bits & 0b00000010)
918 |
919 | def get_tilt(self) -> str:
920 | """
921 | Returns the tilt string eg: "X", "-Z" etc
922 | :return:
923 | """
924 |
925 | if bool(self._move_bits & 0b00000100):
926 | return "X"
927 | if bool(self._move_bits & 0b00001000):
928 | return "-X"
929 | if bool(self._move_bits & 0b00010000):
930 | return "Y"
931 | if bool(self._move_bits & 0b00100000):
932 | return "-Y"
933 | if bool(self._move_bits & 0b01000000):
934 | return "Z"
935 | if bool(self._move_bits & 0b10000000):
936 | return "-Z"
937 |
938 | return ""
939 |
940 | @staticmethod
941 | def _limit(value: float, lower: float, upper: float) -> float:
942 | """
943 | Utility function to limit a value between a lower and upper limit
944 | :param value:
945 | :param lower:
946 | :param upper:
947 | :return:
948 | """
949 | assert lower < upper
950 | if value > upper:
951 | value = upper
952 | if value < lower:
953 | value = lower
954 | return value
955 |
956 | def _set_color_reference(self):
957 | try:
958 | from color_calibration import BLACK_CAL as _B
959 | except ImportError:
960 | _B = BLACK_CAL
961 | try:
962 | from color_calibration import WHITE_CAL as _W
963 | except ImportError:
964 | _W = WHITE_CAL
965 |
966 | self._black_cal = _B
967 | self._white_cal = _W
968 |
969 | def color_calibration(self, background: str = 'white') -> None:
970 | """
971 | Calibrates the color sensor
972 | :param background: str white or black
973 | :return:
974 | """
975 | if background not in ['black', 'white']:
976 | return
977 |
978 | red_avg = green_avg = blue_avg = 0
979 |
980 | for _ in range(0, 100):
981 | red, green, blue = self.get_color_raw()
982 | red_avg += red
983 | green_avg += green
984 | blue_avg += blue
985 | sleep_ms(10)
986 |
987 | red_avg = int(red_avg / 100)
988 | green_avg = int(green_avg / 100)
989 | blue_avg = int(blue_avg / 100)
990 |
991 | if background == 'white':
992 | self._white_cal = [red_avg, green_avg, blue_avg]
993 | elif background == 'black':
994 | self._black_cal = [red_avg, green_avg, blue_avg]
995 |
996 | file_path = './color_calibration.py'
997 |
998 | try:
999 | with open(file_path, 'r') as file:
1000 | content = file.read().split('\n')
1001 | lines = [line + '\n' for line in content if line]
1002 | except OSError:
1003 | open(file_path, 'a').close()
1004 | lines = []
1005 |
1006 | found_param_line = False
1007 |
1008 | for i, line in enumerate(lines):
1009 | if line.startswith(background.upper()):
1010 | lines[i] = f'{background.upper()}_CAL = [{red_avg}, {green_avg}, {blue_avg}]\n'
1011 | found_param_line = True
1012 | break
1013 |
1014 | if not found_param_line:
1015 | lines.extend([f'{background.upper()}_CAL = [{red_avg}, {green_avg}, {blue_avg}]\n'])
1016 |
1017 | with open(file_path, 'w') as file:
1018 | for line in lines:
1019 | file.write(line)
1020 |
1021 | def get_color_raw(self) -> (int | None, int | None, int | None):
1022 | """
1023 | Returns the color sensor's raw readout
1024 | :return: red, green, blue
1025 | """
1026 |
1027 | return self._red, self._green, self._blue
1028 |
1029 | def _normalize_color(self, r: float, g: float, b: float) -> (float, float, float):
1030 | """
1031 | Color normalization
1032 | :param r:
1033 | :param g:
1034 | :param b:
1035 | :return:
1036 | """
1037 | r = self._limit(r, self._black_cal[0], self._white_cal[0])
1038 | g = self._limit(g, self._black_cal[1], self._white_cal[1])
1039 | b = self._limit(b, self._black_cal[2], self._white_cal[2])
1040 |
1041 | r = (r - self._black_cal[0]) / (self._white_cal[0] - self._black_cal[0])
1042 | g = (g - self._black_cal[1]) / (self._white_cal[1] - self._black_cal[1])
1043 | b = (b - self._black_cal[2]) / (self._white_cal[2] - self._black_cal[2])
1044 |
1045 | return r, g, b
1046 |
1047 | @staticmethod
1048 | def rgb2hsv(r: float, g: float, b: float) -> (float, float, float):
1049 | """
1050 | Converts normalized rgb to hsv
1051 | :param r:
1052 | :param g:
1053 | :param b:
1054 | :return:
1055 | """
1056 | min_ = min(r, g, b)
1057 | max_ = max(r, g, b)
1058 |
1059 | v = max_
1060 | delta = max_ - min_
1061 |
1062 | if delta < 0.00001:
1063 | h = 0
1064 | s = 0
1065 | return h, s, v
1066 |
1067 | if max_ > 0:
1068 | s = delta / max_
1069 | else:
1070 | s = 0
1071 | h = None
1072 | return h, s, v
1073 |
1074 | if r >= max_:
1075 | h = (g - b) / delta # color is between yellow and magenta
1076 | elif g >= max_:
1077 | h = 2.0 + (b - r) / delta
1078 | else:
1079 | h = 4.0 + (r - g) / delta
1080 |
1081 | h *= 60.0
1082 | if h < 0:
1083 | h += 360.0
1084 |
1085 | return h, s, v
1086 |
1087 | def get_color(self, color_format: str = 'rgb') -> (float | None, float | None, float | None):
1088 | """
1089 | Returns the normalized color readout of the color sensor
1090 | :param color_format: rgb or hsv only
1091 | :return:
1092 | """
1093 | assert color_format in ['rgb', 'hsv']
1094 |
1095 | if None in list(self.get_color_raw()):
1096 | return None, None, None
1097 |
1098 | if color_format == 'rgb':
1099 | return self._normalize_color(*self.get_color_raw())
1100 | elif color_format == 'hsv':
1101 | return self.rgb2hsv(*self._normalize_color(*self.get_color_raw()))
1102 |
1103 | def get_color_label(self) -> str:
1104 | """
1105 | Returns the label of the color as recognized by the sensor
1106 | :return:
1107 | """
1108 | return self.hsv2label(*self.get_color(color_format='hsv'))
1109 |
1110 | @staticmethod
1111 | def hsv2label(h, s, v) -> str:
1112 | """
1113 | Returns the color label corresponding to the given normalized HSV color input
1114 | :param h:
1115 | :param s:
1116 | :param v:
1117 | :return:
1118 | """
1119 |
1120 | if None in [h, s, v]:
1121 | return 'UNDEFINED'
1122 |
1123 | if s < 0.1:
1124 | if v < 0.05:
1125 | label = 'BLACK'
1126 | elif v < 0.15:
1127 | label = 'GREY'
1128 | elif v < 0.8:
1129 | label = 'LIGHT GREY'
1130 | else:
1131 | label = 'WHITE'
1132 | else:
1133 | if v > 0.1:
1134 | if 20 <= h < 90:
1135 | label = 'YELLOW'
1136 | elif 90 <= h < 140:
1137 | label = 'LIGHT GREEN'
1138 | elif 140 <= h < 170:
1139 | label = 'GREEN'
1140 | elif 170 <= h < 210:
1141 | label = 'LIGHT BLUE'
1142 | elif 210 <= h < 250:
1143 | label = 'BLUE'
1144 | elif 250 <= h < 280:
1145 | label = 'VIOLET'
1146 | else: # h<20 or h>=280 is more problematic
1147 | if v < 0.5 and s < 0.45:
1148 | label = 'BROWN'
1149 | else:
1150 | if v > 0.77:
1151 | label = 'ORANGE'
1152 | else:
1153 | label = 'RED'
1154 | else:
1155 | label = 'BLACK'
1156 | return label
1157 |
1158 | def get_distance(self, unit: str = 'cm') -> (float | None, float | None, float | None, float | None, float | None):
1159 | """
1160 | Returns the distance readout of the TOF sensor
1161 | :param unit: distance output unit
1162 | :return: left_tof, center_left_tof, center_tof, center_right_tof, right_tof
1163 | """
1164 |
1165 | return (convert_distance(self._left_tof, 'mm', unit),
1166 | convert_distance(self._center_left_tof, 'mm', unit),
1167 | convert_distance(self._center_tof, 'mm', unit),
1168 | convert_distance(self._center_right_tof, 'mm', unit),
1169 | convert_distance(self._right_tof, 'mm', unit))
1170 |
1171 | def get_distance_top(self, unit: str = 'cm') -> float | None:
1172 | """
1173 | Returns the obstacle top distance readout
1174 | :param unit:
1175 | :return:
1176 | """
1177 | return convert_distance(self._top_tof, 'mm', unit)
1178 |
1179 | def get_distance_bottom(self, unit: str = 'cm') -> float | None:
1180 | """
1181 | Returns the obstacle bottom distance readout
1182 | :param unit:
1183 | :return:
1184 | """
1185 | return convert_distance(self._bottom_tof, 'mm', unit)
1186 |
1187 | def get_version(self, version: str = 'fw') -> str:
1188 | """
1189 | Returns the version of the Alvik firmware or micropython library
1190 | :param version:
1191 | :return:
1192 | """
1193 | if version == 'fw' or version == 'FW' or version == 'firmware':
1194 | return self.get_fw_version()
1195 | elif version == 'lib' or version == 'LIB':
1196 | return self.get_lib_version()
1197 | else:
1198 | return f'{None, None, None}'
1199 |
1200 | def get_lib_version(self) -> str:
1201 | """
1202 | Returns the micropython library version of the Alvik
1203 | :return:
1204 | """
1205 | return f'{self._version[0]}.{self._version[1]}.{self._version[2]}'
1206 |
1207 | def get_fw_version(self) -> str:
1208 | """
1209 | Returns the firmware version of the Alvik Carrier
1210 | :return:
1211 | """
1212 | return f'{self._fw_version[0]}.{self._fw_version[1]}.{self._fw_version[2]}'
1213 |
1214 | def get_required_fw_version(self) -> str:
1215 | """
1216 | Returns the required firmware version of the Alvik Carrier for this micropython library
1217 | :return:
1218 | """
1219 | return f'{self._required_fw_version[0]}.{self._required_fw_version[1]}.{self._required_fw_version[2]}'
1220 |
1221 | def check_firmware_compatibility(self) -> bool:
1222 | """
1223 | Returns true if the library and the firmware are compatible
1224 | :return:
1225 | """
1226 | return self._fw_version == self._required_fw_version
1227 |
1228 | def print_status(self):
1229 | """
1230 | Prints the Alvik status
1231 | :return:
1232 | """
1233 | print('---ALVIK STATUS---')
1234 | print(f'LIBRARY VERSION: {self._version}')
1235 | print(f'REQUIRED FW VERSION: {self._required_fw_version}')
1236 | print(f'FIRMWARE VERSION: {self._fw_version}')
1237 |
1238 | print('---SENSORS---')
1239 | print(f'TOF: T:{self._top_tof} B:{self._bottom_tof} L:{self._left_tof} CL:{self._center_left_tof}' +
1240 | f' C:{self._center_tof} CR:{self._center_right_tof} R:{self._right_tof}')
1241 | print(f'LINE: L:{self._left_line} C:{self._center_line} R:{self._right_line}')
1242 | print(f'ACC: X:{self._ax} Y:{self._ay} Z:{self._az}')
1243 | print(f'GYR: X:{self._gx} Y:{self._gy} Z:{self._gz}')
1244 | print(f'POS: X:{self._x} Y:{self._y} TH:{self._theta}')
1245 | print(f'IMU: ROLL:{self._roll} PITCH:{self._pitch} YAW:{self._yaw}')
1246 | print(f'COLOR: R:{self._red} G:{self._green} B:{self._blue}')
1247 | print(f'BATT(%) {self._battery_perc}')
1248 |
1249 | print('---COMMUNICATION---')
1250 | print(f'TOUCH BYTE: {self._touch_byte}')
1251 | print(f'LAST ACK: {self._last_ack}')
1252 |
1253 | print('---MOTORS---')
1254 | print(f'LINEAR VEL: {self._linear_velocity}')
1255 | print(f'ANGULAR VEL: {self._angular_velocity}')
1256 |
1257 | def set_timer(self, mode: str, period: int, callback: callable, args: tuple = ()) -> None:
1258 | """
1259 | Register a timer callback
1260 | :param mode: _ArduinoAlvikTimerEvents.PERIODIC or .ONE_SHOT
1261 | :param period: period in milliseconds
1262 | :param callback:
1263 | :param args:
1264 | :return:
1265 | """
1266 |
1267 | self._timer_events = _ArduinoAlvikTimerEvents(period)
1268 | self._timer_events.register_callback(mode, callback, args)
1269 |
1270 | @property
1271 | def timer(self):
1272 | """
1273 | Gives access to the timer object
1274 | :return:
1275 | """
1276 | return self._timer_events
1277 |
1278 | def on_touch_ok_pressed(self, callback: callable, args: tuple = ()) -> None:
1279 | """
1280 | Register callback when touch button OK is pressed
1281 | :param callback:
1282 | :param args:
1283 | :return:
1284 | """
1285 | self._touch_events.register_callback('on_ok_pressed', callback, args)
1286 |
1287 | def on_touch_cancel_pressed(self, callback: callable, args: tuple = ()) -> None:
1288 | """
1289 | Register callback when touch button CANCEL is pressed
1290 | :param callback:
1291 | :param args:
1292 | :return:
1293 | """
1294 | self._touch_events.register_callback('on_cancel_pressed', callback, args)
1295 |
1296 | def on_touch_center_pressed(self, callback: callable, args: tuple = ()) -> None:
1297 | """
1298 | Register callback when touch button CENTER is pressed
1299 | :param callback:
1300 | :param args:
1301 | :return:
1302 | """
1303 | self._touch_events.register_callback('on_center_pressed', callback, args)
1304 |
1305 | def on_touch_up_pressed(self, callback: callable, args: tuple = ()) -> None:
1306 | """
1307 | Register callback when touch button UP is pressed
1308 | :param callback:
1309 | :param args:
1310 | :return:
1311 | """
1312 | self._touch_events.register_callback('on_up_pressed', callback, args)
1313 |
1314 | def on_touch_left_pressed(self, callback: callable, args: tuple = ()) -> None:
1315 | """
1316 | Register callback when touch button LEFT is pressed
1317 | :param callback:
1318 | :param args:
1319 | :return:
1320 | """
1321 | self._touch_events.register_callback('on_left_pressed', callback, args)
1322 |
1323 | def on_touch_down_pressed(self, callback: callable, args: tuple = ()) -> None:
1324 | """
1325 | Register callback when touch button DOWN is pressed
1326 | :param callback:
1327 | :param args:
1328 | :return:
1329 | """
1330 | self._touch_events.register_callback('on_down_pressed', callback, args)
1331 |
1332 | def on_touch_right_pressed(self, callback: callable, args: tuple = ()) -> None:
1333 | """
1334 | Register callback when touch button RIGHT is pressed
1335 | :param callback:
1336 | :param args:
1337 | :return:
1338 | """
1339 | self._touch_events.register_callback('on_right_pressed', callback, args)
1340 |
1341 | def on_shake(self, callback: callable, args: tuple = ()) -> None:
1342 | """
1343 | Register callback when Alvik is shaken
1344 | :param callback:
1345 | :param args:
1346 | :return:
1347 | """
1348 | self._move_events.register_callback('on_shake', callback, args)
1349 |
1350 | def on_lift(self, callback: callable, args: tuple = ()) -> None:
1351 | """
1352 | Register callback when Alvik is lifted
1353 | :param callback:
1354 | :param args:
1355 | :return:
1356 | """
1357 | self._move_events.register_callback('on_lift', callback, args)
1358 |
1359 | def on_drop(self, callback: callable, args: tuple = ()) -> None:
1360 | """
1361 | Register callback when Alvik is dropped
1362 | :param callback:
1363 | :param args:
1364 | :return:
1365 | """
1366 | self._move_events.register_callback('on_drop', callback, args)
1367 |
1368 | def on_x_tilt(self, callback: callable, args: tuple = ()) -> None:
1369 | """
1370 | Register callback when Alvik is tilted on X-axis
1371 | :param callback:
1372 | :param args:
1373 | :return:
1374 | """
1375 | self._move_events.register_callback('on_x_tilt', callback, args)
1376 |
1377 | def on_nx_tilt(self, callback: callable, args: tuple = ()) -> None:
1378 | """
1379 | Register callback when Alvik is tilted on negative X-axis
1380 | :param callback:
1381 | :param args:
1382 | :return:
1383 | """
1384 | self._move_events.register_callback('on_nx_tilt', callback, args)
1385 |
1386 | def on_y_tilt(self, callback: callable, args: tuple = ()) -> None:
1387 | """
1388 | Register callback when Alvik is tilted on Y-axis
1389 | :param callback:
1390 | :param args:
1391 | :return:
1392 | """
1393 | self._move_events.register_callback('on_y_tilt', callback, args)
1394 |
1395 | def on_ny_tilt(self, callback: callable, args: tuple = ()) -> None:
1396 | """
1397 | Register callback when Alvik is tilted on negative Y-axis
1398 | :param callback:
1399 | :param args:
1400 | :return:
1401 | """
1402 | self._move_events.register_callback('on_ny_tilt', callback, args)
1403 |
1404 | def on_z_tilt(self, callback: callable, args: tuple = ()) -> None:
1405 | """
1406 | Register callback when Alvik is tilted on Z-axis
1407 | :param callback:
1408 | :param args:
1409 | :return:
1410 | """
1411 | self._move_events.register_callback('on_z_tilt', callback, args)
1412 |
1413 | def on_nz_tilt(self, callback: callable, args: tuple = ()) -> None:
1414 | """
1415 | Register callback when Alvik is tilted on negative Z-axis
1416 | :param callback:
1417 | :param args:
1418 | :return:
1419 | """
1420 | self._move_events.register_callback('on_nz_tilt', callback, args)
1421 |
1422 | def _start_events_thread(self) -> None:
1423 | """
1424 | Starts the touch events thread
1425 | :return:
1426 | """
1427 | if not self.__class__._events_thread_running:
1428 | self.__class__._events_thread_running = True
1429 | self._timer_events.reset() # resets the timer before starting
1430 | self._move_events.reset(_ArduinoAlvikMoveEvents.NZ_TILT) # resets the orientation to -Z tilted
1431 | self.__class__._events_thread_id = _thread.start_new_thread(self._update_events, (50,))
1432 |
1433 | def _update_events(self, delay_: int = 100):
1434 | """
1435 | Updates the touch state so that touch events can be generated
1436 | :param delay_:
1437 | :return:
1438 | """
1439 | while True:
1440 | if not ArduinoAlvik._events_thread_running:
1441 | break
1442 |
1443 | if self.is_on():
1444 | self._touch_events.update_state(self._touch_byte)
1445 | self._move_events.update_state(self._move_byte)
1446 | self._timer_events.update_state(ticks_ms())
1447 | # MORE events update callbacks to be added
1448 |
1449 | sleep_ms(delay_)
1450 |
1451 | @classmethod
1452 | def _stop_events_thread(cls):
1453 | """
1454 | Stops the touch events thread
1455 | :return:
1456 | """
1457 | cls._events_thread_running = False
1458 |
1459 |
1460 | class _ArduinoAlvikI2C:
1461 |
1462 | _main_thread_id = None
1463 |
1464 | def __init__(self, sda: int, scl: int):
1465 | """
1466 | Alvik I2C wrapper
1467 | :param sda:
1468 | :param scl:
1469 | """
1470 | self._lock = _thread.allocate_lock()
1471 |
1472 | self._is_single_thread = False
1473 |
1474 | self.sda = sda
1475 | self.scl = scl
1476 |
1477 | def set_main_thread(self, thread_id: int):
1478 | """
1479 | Sets the main thread of control. It will be the only thread allowed if set_single_thread is True
1480 | """
1481 | with self._lock:
1482 | self.__class__._main_thread_id = thread_id
1483 |
1484 | def set_single_thread(self, value):
1485 | """
1486 | Sets the single thread mode on/off.
1487 | In single mode only the main thread is allowed to access the bus
1488 | """
1489 | self._is_single_thread = value
1490 |
1491 | def is_accessible(self):
1492 | """
1493 | Returns True if bus is accessible by the current thread
1494 | """
1495 | return not self._is_single_thread or _thread.get_ident() == self.__class__._main_thread_id
1496 |
1497 | def start(self):
1498 | """
1499 | Bitbanging start condition
1500 | :return:
1501 | """
1502 | _SDA = Pin(self.sda, Pin.OUT)
1503 | _SDA.value(1)
1504 | sleep_ms(100)
1505 | _SDA.value(0)
1506 |
1507 | def init(self, scl, sda, freq=400_000) -> None:
1508 | """
1509 | init method just for call compatibility
1510 | """
1511 | print("AlvikWarning:: init Unsupported. Alvik defines/initializes its own I2C bus")
1512 |
1513 | def deinit(self):
1514 | """
1515 | deinit method just for call compatibility
1516 | """
1517 | print("AlvikWarning:: deinit Unsupported. Alvik defines/initializes its own I2C bus")
1518 |
1519 | def stop(self):
1520 | """ Bitbanging stop condition (untested)
1521 | :return:
1522 | """
1523 | _SDA = Pin(self.sda, Pin.OUT)
1524 | _SDA.value(0)
1525 | sleep_ms(100)
1526 | _SDA.value(1)
1527 |
1528 | def scan(self) -> list[int]:
1529 | """
1530 | I2C scan method
1531 | :return:
1532 | """
1533 | if not self.is_accessible():
1534 | return []
1535 | with self._lock:
1536 | i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT))
1537 | return i2c.scan()
1538 |
1539 | def readfrom(self, addr, nbytes, stop=True) -> bytes:
1540 | """
1541 | Wrapping i2c readfrom
1542 | """
1543 | if not self.is_accessible():
1544 | return bytes(nbytes)
1545 | with self._lock:
1546 | i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT))
1547 | return i2c.readfrom(addr, nbytes, stop)
1548 |
1549 | def writeto(self, addr, buf, stop=True) -> int:
1550 | """
1551 | Wrapping i2c writeto
1552 | """
1553 | if not self.is_accessible():
1554 | return 0
1555 | with self._lock:
1556 | i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT))
1557 | return i2c.writeto(addr, buf, stop)
1558 |
1559 | def readinto(self, buf, nack=True) -> None:
1560 | """
1561 | Wrapping i2c readinto
1562 | """
1563 | if not self.is_accessible():
1564 | return
1565 | with self._lock:
1566 | i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT))
1567 | return i2c.readinto(buf, nack)
1568 |
1569 | def write(self, buf) -> int:
1570 | """
1571 | Wrapping i2c write
1572 | """
1573 | if not self.is_accessible():
1574 | return 0
1575 | with self._lock:
1576 | i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT))
1577 | return i2c.write(buf)
1578 |
1579 | def readfrom_into(self, addr, buf, stop=True) -> None:
1580 | """
1581 | Wrapping i2c readfrom_into
1582 | """
1583 | if not self.is_accessible():
1584 | return
1585 | with self._lock:
1586 | i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT))
1587 | return i2c.readfrom_into(addr, buf, stop)
1588 |
1589 | def writevto(self, addr, vector, stop=True) -> int:
1590 | """
1591 | Wrapping i2c writevto
1592 | """
1593 | if not self.is_accessible():
1594 | return 0
1595 | with self._lock:
1596 | i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT))
1597 | return i2c.writevto(addr, vector, stop)
1598 |
1599 | def readfrom_mem(self, addr, memaddr, nbytes, addrsize=8) -> bytes:
1600 | """
1601 | Wrapping i2c readfrom_mem
1602 | """
1603 | if not self.is_accessible():
1604 | return bytes(nbytes)
1605 | with self._lock:
1606 | i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT))
1607 | return i2c.readfrom_mem(addr, memaddr, nbytes, addrsize=addrsize)
1608 |
1609 | def readfrom_mem_into(self, addr, memaddr, buf, addrsize=8) -> None:
1610 | """
1611 | Wrapping i2c readfrom_mem_into
1612 | """
1613 | if not self.is_accessible():
1614 | return
1615 | with self._lock:
1616 | i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT))
1617 | return i2c.readfrom_mem_into(addr, memaddr, buf, addrsize=addrsize)
1618 |
1619 | def writeto_mem(self, addr, memaddr, buf, addrsize=8) -> None:
1620 | """
1621 | Wrapping i2c writeto_mem
1622 | """
1623 | if not self.is_accessible():
1624 | return
1625 | with self._lock:
1626 | i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT))
1627 | return i2c.writeto_mem(addr, memaddr, buf, addrsize=addrsize)
1628 |
1629 |
1630 | class _ArduinoAlvikServo:
1631 |
1632 | def __init__(self, packeter: ucPack, label: str, servo_id: int, position: list[int | None]):
1633 | self._packeter = packeter
1634 | self._label = label
1635 | self._id = servo_id
1636 | self._position = position
1637 |
1638 | @writes_uart
1639 | def set_position(self, position):
1640 | """
1641 | Sets the position of the servo
1642 | :param position:
1643 | :return:
1644 | """
1645 | self._position[self._id] = position
1646 | self._packeter.packetC2B(ord('S'), self._position[0] & 0xFF, self._position[1] & 0xFF)
1647 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
1648 |
1649 | def get_position(self) -> int:
1650 | """
1651 | Returns the position of the servo
1652 | :return:
1653 | """
1654 | return self._position[self._id]
1655 |
1656 |
1657 | class _ArduinoAlvikWheel:
1658 |
1659 | def __init__(self, packeter: ucPack, label: int, wheel_diameter_mm: float = WHEEL_DIAMETER_MM,
1660 | alvik: ArduinoAlvik = None):
1661 | self._packeter = packeter
1662 | self._label = label
1663 | self._wheel_diameter_mm = wheel_diameter_mm
1664 | self._speed = None
1665 | self._position = None
1666 | self._alvik = alvik
1667 |
1668 | @writes_uart
1669 | def reset(self, initial_position: float = 0.0, unit: str = 'deg'):
1670 | """
1671 | Resets the wheel reference position
1672 | :param initial_position:
1673 | :param unit: reference position unit (defaults to deg)
1674 | :return:
1675 | """
1676 | initial_position = convert_angle(initial_position, unit, 'deg')
1677 | self._packeter.packetC2B1F(ord('W'), self._label & 0xFF, ord('Z'), initial_position)
1678 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
1679 |
1680 | @writes_uart
1681 | def set_pid_gains(self, kp: float = MOTOR_KP_DEFAULT, ki: float = MOTOR_KI_DEFAULT, kd: float = MOTOR_KD_DEFAULT):
1682 | """
1683 | Set PID gains for Alvik wheels
1684 | :param kp: proportional gain
1685 | :param ki: integration gain
1686 | :param kd: derivative gain
1687 | :return:
1688 | """
1689 |
1690 | self._packeter.packetC1B3F(ord('P'), self._label & 0xFF, kp, ki, kd)
1691 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
1692 |
1693 | def stop(self):
1694 | """
1695 | Stop Alvik wheel
1696 | :return:
1697 | """
1698 | self.set_speed(0)
1699 |
1700 | @writes_uart
1701 | def set_speed(self, velocity: float, unit: str = 'rpm'):
1702 | """
1703 | Sets the motor speed
1704 | :param velocity: the speed of the motor
1705 | :param unit: the unit of measurement
1706 | :return:
1707 | """
1708 |
1709 | if unit == '%':
1710 | velocity = (velocity / 100) * MOTOR_MAX_RPM
1711 | else:
1712 | velocity = convert_rotational_speed(velocity, unit, 'rpm')
1713 |
1714 | self._packeter.packetC2B1F(ord('W'), self._label & 0xFF, ord('V'), velocity)
1715 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
1716 |
1717 | def get_speed(self, unit: str = 'rpm') -> float | None:
1718 | """
1719 | Returns the current RPM speed of the wheel
1720 | :param unit: the unit of the output speed
1721 | :return:
1722 | """
1723 | if unit == '%':
1724 | speed = (self._speed / MOTOR_MAX_RPM) * 100 if self._speed is not None else None
1725 | else:
1726 | speed = convert_rotational_speed(self._speed, 'rpm', unit)
1727 | return speed
1728 |
1729 | def get_position(self, unit: str = 'deg') -> float | None:
1730 | """
1731 | Returns the wheel position (angle with respect to the reference)
1732 | :param unit: the unit of the output position
1733 | :return:
1734 | """
1735 | return convert_angle(self._position, 'deg', unit)
1736 |
1737 | @writes_uart
1738 | def set_position(self, position: float, unit: str = 'deg', blocking: bool = True):
1739 | """
1740 | Sets left/right motor speed
1741 | :param position: the position of the motor
1742 | :param unit: the unit of measurement
1743 | :param blocking:
1744 | :return:
1745 | """
1746 | position = convert_angle(position, unit, 'deg')
1747 | self._packeter.packetC2B1F(ord('W'), self._label & 0xFF, ord('P'), position)
1748 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
1749 | self._alvik._waiting_ack = ord('P')
1750 | if blocking:
1751 | self._alvik._wait_for_target(idle_time=(position / MOTOR_CONTROL_DEG_S))
1752 |
1753 | def is_target_reached(self):
1754 | """
1755 | Checks if the target position is reached
1756 | :return:
1757 | """
1758 | return self._alvik.is_target_reached()
1759 |
1760 |
1761 | class _ArduinoAlvikRgbLed:
1762 | def __init__(self, packeter: ucPack, label: str, led_state: list[int | None], rgb_mask: list[int]):
1763 | self._packeter = packeter
1764 | self.label = label
1765 | self._rgb_mask = rgb_mask
1766 | self._led_state = led_state
1767 |
1768 | @writes_uart
1769 | def set_color(self, red: bool, green: bool, blue: bool):
1770 | """
1771 | Sets the LED's r,g,b state
1772 | :param red:
1773 | :param green:
1774 | :param blue:
1775 | :return:
1776 | """
1777 | led_status = self._led_state[0]
1778 | if led_status is None:
1779 | return
1780 | led_status = led_status | self._rgb_mask[0] if red else led_status & (0b11111111 - self._rgb_mask[0])
1781 | led_status = led_status | self._rgb_mask[1] if green else led_status & (0b11111111 - self._rgb_mask[1])
1782 | led_status = led_status | self._rgb_mask[2] if blue else led_status & (0b11111111 - self._rgb_mask[2])
1783 | self._led_state[0] = led_status
1784 | self._packeter.packetC1B(ord('L'), led_status & 0xFF)
1785 | uart.write(self._packeter.msg[0:self._packeter.msg_size])
1786 |
1787 |
1788 | class _ArduinoAlvikEvents:
1789 | """
1790 | This is a generic events class
1791 | """
1792 |
1793 | available_events = []
1794 |
1795 | def __init__(self):
1796 | self._callbacks = dict()
1797 |
1798 | def register_callback(self, event_name: str, callback: callable, args: tuple = None):
1799 | """
1800 | Registers a callback to execute on an event
1801 | :param event_name:
1802 | :param callback: the callable
1803 | :param args: arguments tuple to pass to the callable. remember the comma! (value,)
1804 | :return:
1805 | """
1806 |
1807 | if event_name not in self.__class__.available_events:
1808 | return
1809 | self._callbacks[event_name] = (callback, args,)
1810 |
1811 | def has_callbacks(self) -> bool:
1812 | """
1813 | True if the _callbacks dictionary has any callback registered
1814 | :return:
1815 | """
1816 | return bool(self._callbacks)
1817 |
1818 | def execute_callback(self, event_name: str):
1819 | """
1820 | Executes the callback associated to the event_name
1821 | :param event_name:
1822 | :return:
1823 | """
1824 | if event_name not in self._callbacks.keys():
1825 | return
1826 | self._callbacks[event_name][0](*self._callbacks[event_name][1])
1827 |
1828 | def update_state(self, state):
1829 | """
1830 | Updates the internal state of the events handler
1831 | :return:
1832 | """
1833 | pass
1834 |
1835 |
1836 | class _ArduinoAlvikTimerEvents(_ArduinoAlvikEvents):
1837 | """
1838 | Event class to handle timer events
1839 | """
1840 |
1841 | available_events = ['periodic', 'one_shot']
1842 | PERIODIC = 'periodic'
1843 | ONE_SHOT = 'one_shot'
1844 |
1845 | def __init__(self, period: int):
1846 | """
1847 | Timer initialization
1848 | :param period: Timer period in milliseconds
1849 | """
1850 | self._last_trigger = ticks_ms()
1851 | self._period = period
1852 | self._triggered = False
1853 | self._stopped = False
1854 | super().__init__()
1855 |
1856 | def is_triggered(self):
1857 | """
1858 | Returns the trigger state
1859 | :return:
1860 | """
1861 | return self._triggered
1862 |
1863 | def is_stopped(self):
1864 | """
1865 | Return True if timer is stopped
1866 | :return:
1867 | """
1868 | return self._stopped
1869 |
1870 | def set(self, start=None, period: int = None):
1871 | """
1872 | Sets the last trigger time
1873 | :param start:
1874 | :param period:
1875 | :return:
1876 | """
1877 | self._last_trigger = start if start is not None else ticks_ms()
1878 | if period is not None:
1879 | self._period = period
1880 |
1881 | def reset(self, start=None, period: int = None):
1882 | """
1883 | Resets the timer. Use just before starting the events thread or if you want to restart the Timer
1884 | :param start:
1885 | :param period:
1886 | :return:
1887 | """
1888 | self._last_trigger = start if start is not None else ticks_ms()
1889 | if period is not None:
1890 | self._period = period
1891 | self._triggered = False
1892 |
1893 | def stop(self):
1894 | """
1895 | Stops the timer
1896 | :return:
1897 | """
1898 |
1899 | self._stopped = True
1900 |
1901 | def resume(self):
1902 | """
1903 | Resumes the timer
1904 | :return:
1905 | """
1906 | self._stopped = False
1907 |
1908 | def get(self) -> int:
1909 | """
1910 | Returns the time passed since the last trigger in ms
1911 | :return:
1912 | """
1913 | return ticks_diff(ticks_ms(), self._last_trigger)
1914 |
1915 | def register_callback(self, event_name: str, callback: callable, args: tuple = None):
1916 | """
1917 | Repeated calls to register_callback will overwrite the timer's behaviour. The Timer can be either PERIODIC
1918 | or ONE_SHOT
1919 | :param event_name:
1920 | :param callback:
1921 | :param args:
1922 | :return:
1923 | """
1924 | self._callbacks = dict()
1925 | super().register_callback(event_name, callback, args)
1926 |
1927 | def _is_period_expired(self, now=None) -> bool:
1928 | """
1929 | True if the timer period is expired
1930 | :return:
1931 | """
1932 |
1933 | if now is None:
1934 | now = ticks_ms()
1935 | return ticks_diff(now, self._last_trigger) > self._period
1936 |
1937 | def update_state(self, ticks):
1938 | """
1939 | Updates the internal state of the events handler and executes the related callback
1940 | :return:
1941 | """
1942 |
1943 | if list(self._callbacks.keys()) == [self.PERIODIC]:
1944 | if self._is_period_expired(ticks):
1945 | self._last_trigger = ticks
1946 | if not self._stopped:
1947 | self.execute_callback(self.PERIODIC)
1948 | elif list(self._callbacks.keys()) == [self.ONE_SHOT] and not self._triggered:
1949 | if self._is_period_expired(ticks):
1950 | self._last_trigger = ticks
1951 | if not self._stopped:
1952 | self.execute_callback(self.ONE_SHOT)
1953 | self._triggered = True
1954 |
1955 |
1956 | class _ArduinoAlvikTouchEvents(_ArduinoAlvikEvents):
1957 | """
1958 | This is the event class to handle touch button events
1959 | """
1960 |
1961 | available_events = ['on_ok_pressed', 'on_cancel_pressed',
1962 | 'on_center_pressed', 'on_left_pressed',
1963 | 'on_right_pressed', 'on_up_pressed',
1964 | 'on_down_pressed']
1965 |
1966 | def __init__(self):
1967 | self._current_touch_state = 0
1968 | super().__init__()
1969 |
1970 | @staticmethod
1971 | def _is_ok_pressed(current_state, new_state) -> bool:
1972 | """
1973 | True if OK was pressed
1974 | :param current_state:
1975 | :param new_state:
1976 | :return:
1977 | """
1978 | return not bool(current_state & 0b00000010) and bool(new_state & 0b00000010)
1979 |
1980 | @staticmethod
1981 | def _is_cancel_pressed(current_state, new_state) -> bool:
1982 | """
1983 | True if CANCEL was pressed
1984 | :param current_state:
1985 | :param new_state:
1986 | :return:
1987 | """
1988 | return not bool(current_state & 0b00000100) and bool(new_state & 0b00000100)
1989 |
1990 | @staticmethod
1991 | def _is_center_pressed(current_state, new_state) -> bool:
1992 | """
1993 | True if CENTER was pressed
1994 | :param current_state:
1995 | :param new_state:
1996 | :return:
1997 | """
1998 | return not bool(current_state & 0b00001000) and bool(new_state & 0b00001000)
1999 |
2000 | @staticmethod
2001 | def _is_up_pressed(current_state, new_state) -> bool:
2002 | """
2003 | True if UP was pressed
2004 | :param current_state:
2005 | :param new_state:
2006 | :return:
2007 | """
2008 | return not bool(current_state & 0b00010000) and bool(new_state & 0b00010000)
2009 |
2010 | @staticmethod
2011 | def _is_left_pressed(current_state, new_state) -> bool:
2012 | """
2013 | True if LEFT was pressed
2014 | :param current_state:
2015 | :param new_state:
2016 | :return:
2017 | """
2018 | return not bool(current_state & 0b00100000) and bool(new_state & 0b00100000)
2019 |
2020 | @staticmethod
2021 | def _is_down_pressed(current_state, new_state) -> bool:
2022 | """
2023 | True if DOWN was pressed
2024 | :param current_state:
2025 | :param new_state:
2026 | :return:
2027 | """
2028 | return not bool(current_state & 0b01000000) and bool(new_state & 0b01000000)
2029 |
2030 | @staticmethod
2031 | def _is_right_pressed(current_state, new_state) -> bool:
2032 | """
2033 | True if RIGHT was pressed
2034 | :param current_state:
2035 | :param new_state:
2036 | :return:
2037 | """
2038 | return not bool(current_state & 0b10000000) and bool(new_state & 0b10000000)
2039 |
2040 | def update_state(self, state: int | None):
2041 | """
2042 | Updates the internal touch state and executes any possible callback
2043 | :param state:
2044 | :return:
2045 | """
2046 |
2047 | if state is None:
2048 | return
2049 |
2050 | if self._is_ok_pressed(self._current_touch_state, state):
2051 | self.execute_callback('on_ok_pressed')
2052 |
2053 | if self._is_cancel_pressed(self._current_touch_state, state):
2054 | self.execute_callback('on_cancel_pressed')
2055 |
2056 | if self._is_center_pressed(self._current_touch_state, state):
2057 | self.execute_callback('on_center_pressed')
2058 |
2059 | if self._is_up_pressed(self._current_touch_state, state):
2060 | self.execute_callback('on_up_pressed')
2061 |
2062 | if self._is_left_pressed(self._current_touch_state, state):
2063 | self.execute_callback('on_left_pressed')
2064 |
2065 | if self._is_down_pressed(self._current_touch_state, state):
2066 | self.execute_callback('on_down_pressed')
2067 |
2068 | if self._is_right_pressed(self._current_touch_state, state):
2069 | self.execute_callback('on_right_pressed')
2070 |
2071 | self._current_touch_state = state
2072 |
2073 |
2074 | class _ArduinoAlvikMoveEvents(_ArduinoAlvikEvents):
2075 | """
2076 | Event class to handle move events
2077 | """
2078 |
2079 | available_events = ['on_shake', 'on_lift', 'on_drop', 'on_x_tilt', 'on_y_tilt', 'on_z_tilt',
2080 | 'on_nx_tilt', 'on_ny_tilt', 'on_nz_tilt']
2081 |
2082 | NZ_TILT = 0x80
2083 |
2084 | def __init__(self):
2085 | self._current_state = 0
2086 | super().__init__()
2087 |
2088 | def reset(self, state: int = 0x00):
2089 | """
2090 | Sets the initial state
2091 | :param state:
2092 | :return:
2093 | """
2094 | self._current_state = state
2095 |
2096 | @staticmethod
2097 | def _is_shaken(current_state, new_state) -> bool:
2098 | """
2099 | True if Alvik was shaken
2100 | :param current_state:
2101 | :param new_state:
2102 | :return:
2103 | """
2104 | return not bool(current_state & 0b00000001) and bool(new_state & 0b00000001)
2105 |
2106 | @staticmethod
2107 | def _is_lifted(current_state, new_state) -> bool:
2108 | """
2109 | True if Alvik was lifted
2110 | :param current_state:
2111 | :param new_state:
2112 | :return:
2113 | """
2114 | return not bool(current_state & 0b00000010) and bool(new_state & 0b00000010)
2115 |
2116 | @staticmethod
2117 | def _is_dropped(current_state, new_state) -> bool:
2118 | """
2119 | True if Alvik was dropped
2120 | :param current_state:
2121 | :param new_state:
2122 | :return:
2123 | """
2124 | return bool(current_state & 0b00000010) and not bool(new_state & 0b00000010)
2125 |
2126 | @staticmethod
2127 | def _is_x_tilted(current_state, new_state) -> bool:
2128 | """
2129 | True if Alvik is tilted on X-axis
2130 | :param current_state:
2131 | :param new_state:
2132 | :return:
2133 | """
2134 | return not bool(current_state & 0b00000100) and bool(new_state & 0b00000100)
2135 |
2136 | @staticmethod
2137 | def _is_neg_x_tilted(current_state, new_state) -> bool:
2138 | """
2139 | True if Alvik is tilted on negative X-axis
2140 | :param current_state:
2141 | :param new_state:
2142 | :return:
2143 | """
2144 | return not bool(current_state & 0b00001000) and bool(new_state & 0b00001000)
2145 |
2146 | @staticmethod
2147 | def _is_y_tilted(current_state, new_state) -> bool:
2148 | """
2149 | True if Alvik is tilted on Y-axis
2150 | :param current_state:
2151 | :param new_state:
2152 | :return:
2153 | """
2154 | return not bool(current_state & 0b00010000) and bool(new_state & 0b00010000)
2155 |
2156 | @staticmethod
2157 | def _is_neg_y_tilted(current_state, new_state) -> bool:
2158 | """
2159 | True if Alvik is tilted on negative Y-axis
2160 | :param current_state:
2161 | :param new_state:
2162 | :return:
2163 | """
2164 | return not bool(current_state & 0b00100000) and bool(new_state & 0b00100000)
2165 |
2166 | @staticmethod
2167 | def _is_z_tilted(current_state, new_state) -> bool:
2168 | """
2169 | True if Alvik is tilted on Z-axis
2170 | :param current_state:
2171 | :param new_state:
2172 | :return:
2173 | """
2174 | return not bool(current_state & 0b01000000) and bool(new_state & 0b01000000)
2175 |
2176 | @staticmethod
2177 | def _is_neg_z_tilted(current_state, new_state) -> bool:
2178 | """
2179 | True if Alvik is tilted on negative Z-axis
2180 | :param current_state:
2181 | :param new_state:
2182 | :return:
2183 | """
2184 | return not bool(current_state & 0b10000000) and bool(new_state & 0b10000000)
2185 |
2186 | def update_state(self, state: int | None):
2187 | """
2188 | Updates the internal state and executes any possible callback
2189 | :param state:
2190 | :return:
2191 | """
2192 |
2193 | if state is None:
2194 | return
2195 |
2196 | if self._is_shaken(self._current_state, state):
2197 | self.execute_callback('on_shake')
2198 |
2199 | if self._is_lifted(self._current_state, state):
2200 | self.execute_callback('on_lift')
2201 |
2202 | if self._is_dropped(self._current_state, state):
2203 | self.execute_callback('on_drop')
2204 |
2205 | if self._is_x_tilted(self._current_state, state):
2206 | self.execute_callback('on_x_tilt')
2207 |
2208 | if self._is_neg_x_tilted(self._current_state, state):
2209 | self.execute_callback('on_nx_tilt')
2210 |
2211 | if self._is_y_tilted(self._current_state, state):
2212 | self.execute_callback('on_y_tilt')
2213 |
2214 | if self._is_neg_y_tilted(self._current_state, state):
2215 | self.execute_callback('on_ny_tilt')
2216 |
2217 | if self._is_z_tilted(self._current_state, state):
2218 | self.execute_callback('on_z_tilt')
2219 |
2220 | if self._is_neg_z_tilted(self._current_state, state):
2221 | self.execute_callback('on_nz_tilt')
2222 |
2223 | self._current_state = state
2224 |
2225 |
2226 | # UPDATE FIRMWARE METHOD #
2227 |
2228 | def update_firmware(file_path: str):
2229 | """
2230 |
2231 | :param file_path: path of your FW bin
2232 | :return:
2233 | """
2234 |
2235 | from sys import exit
2236 | from .stm32_flash import (
2237 | CHECK_STM32,
2238 | STM32_endCommunication,
2239 | STM32_startCommunication,
2240 | STM32_NACK,
2241 | STM32_eraseMEM,
2242 | STM32_writeMEM, )
2243 |
2244 | def flash_toggle():
2245 | i = 0
2246 |
2247 | while True:
2248 | if i == 0:
2249 | LEDR.value(1)
2250 | LEDG.value(0)
2251 | else:
2252 | LEDR.value(0)
2253 | LEDG.value(1)
2254 | i = (i + 1) % 2
2255 | yield
2256 |
2257 | if CHECK_STM32.value() is not 1:
2258 | print("Turn on your Alvik to continue...")
2259 | while CHECK_STM32.value() is not 1:
2260 | sleep_ms(500)
2261 |
2262 | ans = STM32_startCommunication()
2263 | if ans == STM32_NACK:
2264 | print("Cannot establish connection with STM32")
2265 | exit(-1)
2266 |
2267 | print('\nSTM32 FOUND')
2268 |
2269 | print('\nERASING MEM')
2270 | STM32_eraseMEM(0xFFFF)
2271 |
2272 | print("\nWRITING MEM")
2273 | toggle = flash_toggle()
2274 | STM32_writeMEM(file_path, toggle)
2275 |
2276 | print("\nDONE")
2277 | print("\nLower Boot0 and reset STM32")
2278 |
2279 | LEDR.value(1)
2280 | LEDG.value(1)
2281 |
2282 | STM32_endCommunication()
2283 |
--------------------------------------------------------------------------------
/arduino_alvik/constants.py:
--------------------------------------------------------------------------------
1 |
2 | # COLOR SENSOR
3 | COLOR_FULL_SCALE = 4097
4 | WHITE_CAL = [450, 500, 510]
5 | BLACK_CAL = [160, 200, 190]
--------------------------------------------------------------------------------
/arduino_alvik/conversions.py:
--------------------------------------------------------------------------------
1 | # MEASUREMENT UNITS CONVERSION #
2 |
3 | from math import pi
4 |
5 |
6 | def conversion_method(func):
7 | def wrapper(*args, **kwargs):
8 | try:
9 | return func(*args, **kwargs)
10 | except KeyError:
11 | raise ConversionError(f'Cannot {func.__name__} from {args[1]} to {args[2]}')
12 | except TypeError:
13 | return None
14 | except Exception as e:
15 | raise ConversionError(f'Unexpected error: {e}')
16 | return wrapper
17 |
18 |
19 | @conversion_method
20 | def convert_rotational_speed(value: float, from_unit: str, to_unit: str) -> float:
21 | """
22 | Converts a rotational speed value from one unit to another
23 | :param value:
24 | :param from_unit: unit of input value
25 | :param to_unit: unit of output value
26 | :return:
27 | """
28 | speeds = {'rpm': 1.0, 'deg/s': 1/6, 'rad/s': 60/(2*pi), 'rev/s': 60}
29 | return value * speeds[from_unit.lower()] / speeds[to_unit.lower()]
30 |
31 |
32 | @conversion_method
33 | def convert_angle(value: float, from_unit: str, to_unit: str) -> float:
34 | """
35 | Converts an angle value from one unit to another
36 | :param value:
37 | :param from_unit: unit of input value
38 | :param to_unit: unit of output value
39 | :return:
40 | """
41 | angles = {'deg': 1.0, 'rad': 180/pi, 'rev': 360, 'revolution': 360, '%': 3.6, 'perc': 3.6}
42 | return value * angles[from_unit.lower()] / angles[to_unit.lower()]
43 |
44 |
45 | @conversion_method
46 | def convert_distance(value: float, from_unit: str, to_unit: str) -> float:
47 | """
48 | Converts a distance value from one unit to another
49 | :param value:
50 | :param from_unit: unit of input value
51 | :param to_unit: unit of output value
52 | :return:
53 | """
54 | distances = {'cm': 1.0, 'mm': 0.1, 'm': 100, 'inch': 2.54, 'in': 2.54}
55 | return value * distances[from_unit.lower()] / distances[to_unit.lower()]
56 |
57 |
58 | @conversion_method
59 | def convert_speed(value: float, from_unit: str, to_unit: str) -> float:
60 | """
61 | Converts a distance value from one unit to another
62 | :param value:
63 | :param from_unit: unit of input value
64 | :param to_unit: unit of output value
65 | :return:
66 | """
67 | distances = {'cm/s': 1.0, 'mm/s': 0.1, 'm/s': 100, 'inch/s': 2.54, 'in/s': 2.54}
68 | return value * distances[from_unit.lower()] / distances[to_unit.lower()]
69 |
70 |
71 | class ConversionError(Exception):
72 | pass
73 |
--------------------------------------------------------------------------------
/arduino_alvik/pinout_definitions.py:
--------------------------------------------------------------------------------
1 | from machine import Pin
2 |
3 | # NANO to STM32 PINS
4 | D2 = 5 # ESP32 pin5 -> nano D2
5 | D3 = 6 # ESP32 pin6 -> nano D3
6 | D4 = 7 # ESP32 pin7 -> nano D4
7 |
8 | A4 = 11 # ESP32 pin11 SDA -> nano A4
9 | A5 = 12 # ESP32 pin12 SCL -> nano A5
10 | A6 = 13 # ESP32 pin13 -> nano A6/D23
11 |
12 | BOOT0_STM32 = Pin(D2, Pin.OUT) # nano D2 -> STM32 Boot0
13 | RESET_STM32 = Pin(D3, Pin.OUT) # nano D3 -> STM32 NRST
14 | NANO_CHK = Pin(D4, Pin.OUT) # nano D4 -> STM32 NANO_CHK
15 | CHECK_STM32 = Pin(A6, Pin.IN, Pin.PULL_DOWN) # nano A6/D23 -> STM32 ROBOT_CHK
16 | # ESP32_SDA = Pin(A4, Pin.OUT) # ESP32_SDA
17 | # ESP32_SCL = Pin(A5, Pin.OUT) # ESP32_SCL
18 |
19 | # LEDS
20 | LEDR = Pin(46, Pin.OUT) #RED ESP32 LEDR
21 | LEDG = Pin(0, Pin.OUT) #GREEN ESP32 LEDG
22 | LEDB = Pin(45, Pin.OUT) #BLUE ESP32 LEDB
23 |
--------------------------------------------------------------------------------
/arduino_alvik/robot_definitions.py:
--------------------------------------------------------------------------------
1 | # Motor control and mechanical parameters
2 | MOTOR_KP_DEFAULT = 32.0
3 | MOTOR_KI_DEFAULT = 450.0
4 | MOTOR_KD_DEFAULT = 0.0
5 | MOTOR_MAX_RPM = 70.0
6 | MOTOR_CONTROL_DEG_S = 100
7 | MOTOR_CONTROL_MM_S = 100
8 | WHEEL_TRACK_MM = 89.0
9 |
10 | # Wheels parameters
11 | WHEEL_DIAMETER_MM = 34.0
12 |
13 | # Robot params
14 | ROBOT_MAX_DEG_S = 6*(2*MOTOR_MAX_RPM*WHEEL_DIAMETER_MM)/WHEEL_TRACK_MM
15 |
16 | # Thread stack size
17 | THREAD_STACK_SIZE = 8*1024
18 |
--------------------------------------------------------------------------------
/arduino_alvik/stm32_flash.py:
--------------------------------------------------------------------------------
1 | import os
2 | from time import sleep_ms
3 | from machine import UART, Pin
4 |
5 | A6 = 13 # ESP32 pin13 -> nano A6/D23
6 | CHECK_STM32 = Pin(A6, Pin.IN) # nano A6/D23 -> TO CHECK STM32 IS ON
7 |
8 | STM32_INIT = b'\x7F'
9 | STM32_NACK = b'\x1F'
10 | STM32_ACK = b'\x79'
11 |
12 | # STM32 COMMANDS
13 | STM32_GET = b'\x00'
14 | STM32_GET_VERSION = b'\x01'
15 | STM32_GET_ID = b'\x02'
16 | STM32_READ = b'\x11'
17 | STM32_GO = b'\x21'
18 | STM32_WRITE = b'\x31'
19 | STM32_ERASE = b'\x44' # 0x44 is Extended Erase for bootloader v3.0 and higher. 0x43 is standard (1-byte address) erase
20 |
21 | STM32_ADDRESS = bytes.fromhex('08000000') # [b'\x08',b'\x00',b'\x00',b'\x00']
22 |
23 | # NANO ESP32 SETTINGS
24 | _D2 = 5 # ESP32 pin5 -> nano D2
25 | _D3 = 6 # ESP32 pin6 -> nano D3
26 | _Boot0 = Pin(_D2, Pin.OUT) # STM32 Boot0
27 | _NRST = Pin(_D3, Pin.OUT) # STM32 NRST
28 |
29 | # UART SETTINGS
30 | _UART_ID = 1
31 | _TX_PIN = 43
32 | _RX_PIN = 44
33 | _BAUDRATE = 115200
34 | _BITS = 8
35 | _PARITY = 0
36 | _STOP = 1
37 |
38 | readAddress = bytearray(STM32_ADDRESS)
39 | writeAddress = bytearray(STM32_ADDRESS)
40 |
41 | uart = UART(_UART_ID, baudrate=_BAUDRATE, bits=_BITS, parity=_PARITY, stop=_STOP, tx=_TX_PIN,
42 | rx=_RX_PIN) # parity 0 equals to Even, 1 to Odd
43 |
44 |
45 | def STM32_startCommunication() -> bytes:
46 | """
47 | Starts communication with STM32 sending just 0x7F. Blocking
48 | :return:
49 | """
50 | STM32_bootMode(bootloader=True)
51 | STM32_reset()
52 | uart.write(STM32_INIT)
53 | return _STM32_waitForAnswer()
54 |
55 |
56 | def STM32_endCommunication():
57 | """
58 | Ends communication with STM32 restoring flash boot mode and resetting
59 | :return:
60 | """
61 | STM32_bootMode(bootloader=False)
62 | STM32_reset()
63 |
64 |
65 | def _STM32_waitForAnswer() -> bytes:
66 | """
67 | Blocking wait
68 | :return: returns ACK or NACK
69 | """
70 |
71 | while True:
72 | res = uart.read(1)
73 | if res == STM32_ACK or res == STM32_NACK:
74 | break
75 | else:
76 | sleep_ms(10)
77 |
78 | return res
79 |
80 |
81 | def STM32_reset():
82 | """
83 | Resets STM32 from the host pins _D3
84 | :return:
85 | """
86 | _NRST.value(0)
87 | sleep_ms(100)
88 | _NRST.value(1)
89 | sleep_ms(500)
90 |
91 |
92 | def STM32_bootMode(bootloader: bool = False):
93 | """
94 | Sets boot mode for STM32
95 | :param bootloader: if True, STM32 bootloader is run on boot
96 | :return:
97 | """
98 | _Boot0.value(bootloader)
99 |
100 |
101 | def STM32_sendCommand(cmd: bytes):
102 | """
103 | Sends a command and its complement according to AN3155
104 | :param cmd: the command byte
105 | :return:
106 | """
107 | _cmd = bytes([cmd[0] ^ 0xFF])
108 | uart.write(cmd)
109 | uart.write(_cmd)
110 |
111 |
112 | def STM32_readResponse() -> [bytearray, bytes]:
113 | """
114 | Blocking read to get the STM32 response to command, according to AN3155
115 | :return: returns a response bytearray dropping leading and trailing ACKs. returns -1 if NACK
116 | """
117 | out = bytearray(0)
118 |
119 | acks = 0
120 | while True:
121 | b = uart.read(1)
122 | if b is None:
123 | continue
124 | if b == STM32_NACK:
125 | return STM32_NACK
126 | elif b == STM32_ACK:
127 | if acks == 1:
128 | break
129 | else:
130 | acks = acks + 1
131 | continue
132 | out.append(b[0])
133 |
134 | return out
135 |
136 |
137 | def STM32_get() -> bytearray:
138 | """
139 | GET Command according to AN3155
140 | :return: returns a bytearray containing bootloader version (1 byte) and available commands
141 | """
142 | STM32_sendCommand(STM32_GET)
143 | res = STM32_readResponse()
144 | if res == -1:
145 | print("GET: STM32 responded with NACK")
146 | return bytearray(0)
147 | return res[1:]
148 |
149 |
150 | def STM32_getID() -> bytearray:
151 | """
152 | GET ID Command according to AN3155
153 | :return: returns device ID (2 bytes)
154 | """
155 | STM32_sendCommand(STM32_GET_ID)
156 | res = STM32_readResponse()
157 | if res == STM32_NACK:
158 | print("GET_ID: STM32 responded with NACK")
159 | return bytearray(0)
160 | return res[1:]
161 |
162 |
163 | def STM32_getVER() -> bytearray:
164 | """
165 | GET ID Command according to AN3155
166 | :return: returns bootloader version (3 bytes)
167 | """
168 | STM32_sendCommand(STM32_GET_VERSION)
169 | res = STM32_readResponse()
170 | if res == STM32_NACK:
171 | print("GET VER: STM32 responded with NACK")
172 | return bytearray(0)
173 | return res
174 |
175 |
176 | def _STM32_readMode() -> bytes:
177 | """
178 | Enters read memory mode. Blocking
179 | :return: returns ACK or NACK
180 | """
181 | STM32_sendCommand(STM32_READ)
182 | return _STM32_waitForAnswer()
183 |
184 |
185 | def _STM32_writeMode() -> bytes:
186 | """
187 | Enters write memory mode. Blocking
188 | :return: returns ACK or NACK
189 | """
190 | STM32_sendCommand(STM32_WRITE)
191 | return _STM32_waitForAnswer()
192 |
193 |
194 | def _STM32_eraseMode() -> bytes:
195 | """
196 | Enters erase memory mode. Blocking
197 | :return: returns ACK or NACK
198 | """
199 | STM32_sendCommand(STM32_ERASE)
200 | return _STM32_waitForAnswer()
201 |
202 |
203 | def _STM32_sendAddress(address: bytes) -> bytes:
204 | """
205 | Sends the start address of read/write operations. Blocking
206 | :param address:
207 | :return:
208 | """
209 | assert len(address) == 4
210 |
211 | checksum = address[0] ^ address[1] ^ address[2] ^ address[3]
212 | uart.write(address)
213 | uart.write(bytes([checksum]))
214 |
215 | return _STM32_waitForAnswer()
216 |
217 |
218 | def _incrementAddress(address: bytearray):
219 | """
220 | Increments address by one page (256 bytes)
221 | :param address:
222 | :return:
223 | """
224 |
225 | address[2] = address[2] + 1
226 | if address[2] == 0:
227 | address[1] = address[1] + 1
228 | if address[1] == 0:
229 | address[0] = address[0] + 1
230 |
231 |
232 | def _STM32_readPage() -> bytearray:
233 | """
234 | Reads a 256 bytes data page from STM32. Returns a 256 bytearray. Blocking
235 | :return: page bytearray
236 | """
237 |
238 | STM32_sendCommand(b'\xFF')
239 | res = _STM32_waitForAnswer()
240 | if res != STM32_ACK:
241 | print("READ PAGE: Cannot read STM32")
242 | return bytearray(0)
243 | out = bytearray(0)
244 | i = 0
245 | while i < 256:
246 | b = uart.read(1)
247 | if b is None:
248 | continue
249 | out.append(b[0])
250 | i = i+1
251 | return out
252 |
253 |
254 | def _STM32_flashPage(data: bytearray) -> bytes:
255 | """
256 | Sends a 256 bytes data page to STM32. Blocking
257 | :param data:
258 | :return:
259 | """
260 |
261 | assert len(data) == 256
262 |
263 | uart.write(b'\xff') # page length
264 | checksum = 0xff # starting checksum = page length
265 |
266 | for d in data:
267 | uart.write(bytes([d]))
268 | checksum = checksum ^ d
269 |
270 | uart.write(bytes([checksum]))
271 |
272 | return _STM32_waitForAnswer()
273 |
274 |
275 | def STM32_readMEM(pages: int):
276 | """
277 | Reads n 256-bytes pages from memory
278 | :param pages: number of pages to read
279 | :return:
280 | """
281 |
282 | for i in range(0, pages):
283 | if _STM32_readMode() != STM32_ACK:
284 | print("COULD NOT ENTER READ MODE")
285 | return
286 |
287 | if _STM32_sendAddress(readAddress) != STM32_ACK:
288 | print("STM32 ERROR ON ADDRESS SENT")
289 | return
290 |
291 | page = _STM32_readPage()
292 | print(f"Page {i+1} content:\n")
293 | print(page.hex())
294 |
295 | _incrementAddress(readAddress)
296 |
297 |
298 | def STM32_writeMEM(file_path: str, toggle: "Generator" = None):
299 |
300 | with open(file_path, 'rb') as f:
301 | print(f"Flashing {file_path}\n")
302 | file_size = os.stat(file_path)[-4]
303 | file_pages = int(file_size / 256) + (1 if file_size % 256 != 0 else 0)
304 | i = 1
305 | while True:
306 | data = bytearray(f.read(256))
307 | read_bytes = len(data)
308 | if read_bytes == 0:
309 | break
310 | data.extend(bytearray([255]*(256-read_bytes))) # 0xFF padding
311 |
312 | if _STM32_writeMode() != STM32_ACK:
313 | print("COULD NOT ENTER WRITE MODE")
314 | return
315 |
316 | if _STM32_sendAddress(writeAddress) != STM32_ACK:
317 | print("STM32 ERROR ON ADDRESS SENT")
318 | return
319 |
320 | if _STM32_flashPage(data) != STM32_ACK:
321 | print(f"STM32 ERROR FLASHING PAGE: {writeAddress}")
322 | return
323 |
324 | percentage = int((i / file_pages) * 100)
325 | print("\033[2K\033[1G", end='\r')
326 | print(f"Flashing STM32: {percentage:>3}%", end='')
327 | i = i + 1
328 | _incrementAddress(writeAddress)
329 | if toggle is not None:
330 | next(toggle)
331 |
332 |
333 | def _STM32_standardEraseMEM(pages: int, page_list: bytearray = None):
334 | """
335 | Standard Erase (0x43) flash mem pages according to AN3155
336 | :param pages: number of pages to be erased
337 | :param page_list: page codes to be erased
338 | :return:
339 | """
340 |
341 | if _STM32_eraseMode() == STM32_NACK:
342 | print("COULD NOT ENTER ERASE MODE")
343 | return
344 |
345 | if pages == 0xFF:
346 | # Mass erase
347 | uart.write(b'\xFF')
348 | uart.write(b'\x00')
349 | else:
350 | print("Not yet implemented erase")
351 |
352 | if _STM32_waitForAnswer() != STM32_ACK:
353 | print("ERASE OPERATION ABORTED")
354 |
355 |
356 | def _STM32_extendedEraseMEM(pages: int, page_list: bytearray = None):
357 | """
358 | Extended Erase (0x44) flash mem pages according to AN3155
359 | :param pages: number of pages to be erased
360 | :param page_list: page codes to be erased
361 | :return:
362 | """
363 |
364 | if _STM32_eraseMode() == STM32_NACK:
365 | print("COULD NOT ENTER ERASE MODE")
366 | return
367 |
368 | if pages == 0xFFFF:
369 | # Mass erase
370 | uart.write(b'\xFF')
371 | uart.write(b'\xFF')
372 | uart.write(b'\x00')
373 | elif pages == 0xFFFE:
374 | # Bank1 erase
375 | uart.write(b'\xFF')
376 | uart.write(b'\xFE')
377 | uart.write(b'\x01')
378 | elif pages == 0xFFFD:
379 | # Bank2 erase
380 | uart.write(b'\xFF')
381 | uart.write(b'\xFD')
382 | uart.write(b'\x02')
383 | else:
384 | print("Not yet implemented erase")
385 |
386 | if _STM32_waitForAnswer() != STM32_ACK:
387 | print("ERASE OPERATION ABORTED")
388 |
389 |
390 | def STM32_eraseMEM(pages: int, page_list: bytearray = None):
391 | """
392 | Erases flash mem pages according to AN3155
393 | :param pages: number of pages to be erased
394 | :param page_list: page codes to be erased
395 | :return:
396 | """
397 |
398 | if STM32_ERASE == b'\x43':
399 | _STM32_standardEraseMEM(pages, page_list)
400 | elif STM32_ERASE == b'\x44':
401 | _STM32_extendedEraseMEM(pages, page_list)
402 |
--------------------------------------------------------------------------------
/arduino_alvik/uart.py:
--------------------------------------------------------------------------------
1 | from machine import UART
2 |
3 | # UART SETTINGS
4 | _UART_ID = 1
5 | _TX_PIN = 43
6 | _RX_PIN = 44
7 | _BAUDRATE = 460800
8 | _BITS = 8
9 | _PARITY = None
10 | _STOP = 1
11 |
12 | uart = UART(_UART_ID, baudrate=_BAUDRATE, bits=_BITS, parity=_PARITY, stop=_STOP, tx=_TX_PIN,
13 | rx=_RX_PIN) # parity 0 equals to Even, 1 to Odd
14 |
--------------------------------------------------------------------------------
/examples/actuators/leds_setting.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 | alvik.begin()
7 |
8 | while True:
9 | try:
10 | alvik.set_builtin_led(1)
11 | sleep_ms(1000)
12 | alvik.set_illuminator(1)
13 | sleep_ms(1000)
14 | alvik.set_builtin_led(0)
15 | sleep_ms(1000)
16 | alvik.set_illuminator(0)
17 | sleep_ms(1000)
18 | alvik.left_led.set_color(0, 0, 1)
19 | sleep_ms(1000)
20 | alvik.left_led.set_color(0, 1, 0)
21 | sleep_ms(1000)
22 | alvik.left_led.set_color(1, 0, 0)
23 | sleep_ms(1000)
24 | alvik.left_led.set_color(1, 1, 1)
25 | sleep_ms(1000)
26 | alvik.right_led.set_color(0, 0, 1)
27 | sleep_ms(1000)
28 | alvik.right_led.set_color(0, 1, 0)
29 | sleep_ms(1000)
30 | alvik.right_led.set_color(1, 0, 0)
31 | sleep_ms(1000)
32 | alvik.right_led.set_color(1, 1, 1)
33 | sleep_ms(1000)
34 | except KeyboardInterrupt as e:
35 | print('over')
36 | alvik.stop()
37 | break
38 |
--------------------------------------------------------------------------------
/examples/actuators/move_wheels.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 | alvik.begin()
7 |
8 | while True:
9 | try:
10 | alvik.left_wheel.set_speed(10)
11 | sleep_ms(1000)
12 | print(f'LSP: {alvik.left_wheel.get_speed()}')
13 | print(f'RSP: {alvik.right_wheel.get_speed()}')
14 |
15 | alvik.right_wheel.set_speed(10)
16 | sleep_ms(1000)
17 |
18 | print(f'LSP: {alvik.left_wheel.get_speed()}')
19 | print(f'RSP: {alvik.right_wheel.get_speed()}')
20 |
21 | alvik.left_wheel.set_speed(20)
22 | sleep_ms(1000)
23 |
24 | print(f'LSP: {alvik.left_wheel.get_speed()}')
25 | print(f'RSP: {alvik.right_wheel.get_speed()}')
26 |
27 | alvik.right_wheel.set_speed(20)
28 | sleep_ms(1000)
29 |
30 | print(f'LSP: {alvik.left_wheel.get_speed()}')
31 | print(f'RSP: {alvik.right_wheel.get_speed()}')
32 |
33 | except KeyboardInterrupt as e:
34 | print('over')
35 | alvik.stop()
36 | break
37 |
--------------------------------------------------------------------------------
/examples/actuators/pose_example.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 | alvik.begin()
7 |
8 | try:
9 |
10 | alvik.move(100.0, 'mm')
11 | print("on target after move")
12 |
13 | alvik.move(50.0, 'mm')
14 | print("on target after move")
15 |
16 | alvik.rotate(90.0, 'deg')
17 | print("on target after rotation")
18 |
19 | alvik.rotate(-45.00, 'deg')
20 | print("on target after rotation")
21 |
22 | x, y, theta = alvik.get_pose()
23 | print(f'Current pose is x(cm)={x}, y(cm)={y}, theta(deg)={theta}')
24 |
25 | alvik.reset_pose(0, 0, 0)
26 |
27 | x, y, theta = alvik.get_pose()
28 | print(f'Updated pose is x(cm)={x}, y(cm)={y}, theta(deg)={theta}')
29 | sleep_ms(500)
30 |
31 | print("___________NON-BLOCKING__________________")
32 |
33 | alvik.move(50.0, 'mm', blocking=False)
34 |
35 | while not alvik.is_target_reached():
36 | alvik.left_led.set_color(1, 0, 0)
37 | sleep_ms(500)
38 | alvik.left_led.set_color(0, 0, 0)
39 | sleep_ms(500)
40 | print("on target after move")
41 |
42 | alvik.rotate(45.0, 'deg', blocking=False)
43 | while not alvik.is_target_reached():
44 | alvik.left_led.set_color(1, 0, 0)
45 | sleep_ms(500)
46 | alvik.left_led.set_color(0, 0, 0)
47 | sleep_ms(500)
48 | print("on target after rotation")
49 |
50 | alvik.move(100.0, 'mm', blocking=False)
51 | while not alvik.is_target_reached():
52 | alvik.left_led.set_color(1, 0, 0)
53 | sleep_ms(500)
54 | alvik.left_led.set_color(0, 0, 0)
55 | sleep_ms(500)
56 | print("on target after move")
57 |
58 | alvik.rotate(-90.00, 'deg', blocking=False)
59 | while not alvik.is_target_reached():
60 | alvik.left_led.set_color(1, 0, 0)
61 | sleep_ms(500)
62 | alvik.left_led.set_color(0, 0, 0)
63 | sleep_ms(500)
64 | print("on target after rotation")
65 |
66 | x, y, theta = alvik.get_pose()
67 | print(f'Current pose is x(cm)={x}, y(cm)={y}, theta(deg)={theta}')
68 |
69 | alvik.reset_pose(0, 0, 0)
70 |
71 | x, y, theta = alvik.get_pose()
72 | print(f'Updated pose is x={x}, y={y}, theta(deg)={theta}')
73 | sleep_ms(500)
74 |
75 | except KeyboardInterrupt as e:
76 | print('Test interrupted')
77 |
78 | finally:
79 | alvik.stop()
80 | print("END of pose example")
--------------------------------------------------------------------------------
/examples/actuators/set_servo.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 | alvik.begin()
7 |
8 | i = 0
9 |
10 | while True:
11 | try:
12 |
13 | alvik.servo_A.set_position(i)
14 | alvik.servo_B.set_position(i)
15 |
16 | i = (i + 1) % 180
17 |
18 | sleep_ms(100)
19 | except KeyboardInterrupt as e:
20 | print('over')
21 | alvik.stop()
22 | break
23 |
--------------------------------------------------------------------------------
/examples/actuators/wheels_position.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep, sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 | alvik.begin()
7 |
8 | alvik.left_wheel.reset()
9 | alvik.right_wheel.reset()
10 |
11 | while True:
12 | try:
13 |
14 | alvik.set_wheels_position(45, 45)
15 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
16 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
17 |
18 | alvik.left_wheel.set_position(30)
19 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
20 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
21 |
22 | alvik.right_wheel.set_position(10)
23 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
24 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
25 |
26 | alvik.left_wheel.set_position(180)
27 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
28 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
29 |
30 | alvik.right_wheel.set_position(270)
31 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
32 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
33 |
34 | print("___________NON-BLOCKING__________________")
35 |
36 | alvik.set_wheels_position(90, 90, blocking=False)
37 | while not alvik.is_target_reached():
38 | alvik.left_led.set_color(1, 0, 0)
39 | alvik.right_led.set_color(1, 0, 0)
40 | sleep_ms(200)
41 | alvik.left_led.set_color(0, 0, 0)
42 | alvik.right_led.set_color(0, 0, 0)
43 | sleep_ms(200)
44 | print(f'Wheels position reached: R:{alvik.right_wheel.get_position()} L:{alvik.left_wheel.get_position()}')
45 |
46 | alvik.left_wheel.set_position(180, blocking=False)
47 | while not alvik.left_wheel.is_target_reached():
48 | alvik.left_led.set_color(1, 0, 0)
49 | sleep_ms(200)
50 | alvik.left_led.set_color(0, 0, 0)
51 | sleep_ms(200)
52 | print(f'Left wheel position reached: {alvik.left_wheel.get_position()}')
53 |
54 | alvik.right_wheel.set_position(180, blocking=False)
55 | while not alvik.right_wheel.is_target_reached():
56 | alvik.right_led.set_color(1, 0, 0)
57 | sleep_ms(200)
58 | alvik.right_led.set_color(0, 0, 0)
59 | sleep_ms(200)
60 | print(f'Left wheel position reached: {alvik.right_wheel.get_position()}')
61 |
62 | except KeyboardInterrupt as e:
63 | print('over')
64 | alvik.stop()
65 | break
66 |
--------------------------------------------------------------------------------
/examples/actuators/wheels_speed.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 | alvik.begin()
7 |
8 | while True:
9 | try:
10 | alvik.set_wheels_speed(10, 10)
11 | print(f'Wheels speed: {alvik.get_wheels_speed()}')
12 | sleep_ms(1000)
13 |
14 | alvik.set_wheels_speed(30, 60)
15 | print(f'Wheels speed: {alvik.get_wheels_speed()}')
16 | sleep_ms(1000)
17 |
18 | alvik.set_wheels_speed(60, 30)
19 | print(f'Wheels speed: {alvik.get_wheels_speed()}')
20 | sleep_ms(1000)
21 | except KeyboardInterrupt as e:
22 | print('over')
23 | alvik.stop()
24 | break
25 |
--------------------------------------------------------------------------------
/examples/arduino-runtime/blink.py:
--------------------------------------------------------------------------------
1 | from time import sleep_ms
2 |
3 | from arduino import start
4 | from arduino_alvik import ArduinoAlvik
5 |
6 |
7 | alvik = ArduinoAlvik()
8 |
9 |
10 | def setup():
11 | alvik.begin()
12 |
13 |
14 | def loop():
15 | print('blinking LEDs')
16 | alvik.left_led.set_color(0, 0, 1)
17 | alvik.right_led.set_color(0, 0, 1)
18 | sleep_ms(500)
19 | alvik.left_led.set_color(1, 0, 0)
20 | alvik.right_led.set_color(1, 0, 0)
21 | sleep_ms(500)
22 |
23 |
24 | def cleanup():
25 | alvik.stop()
26 |
27 |
28 | start(setup=setup, loop=loop, cleanup=cleanup)
29 |
--------------------------------------------------------------------------------
/examples/arduino-runtime/line_follower.py:
--------------------------------------------------------------------------------
1 | from time import sleep_ms
2 |
3 | from arduino import start
4 | from arduino_alvik import ArduinoAlvik
5 |
6 |
7 | alvik = ArduinoAlvik()
8 |
9 |
10 | def calculate_center(left: int, center: int, right: int):
11 | centroid = 0
12 | sum_weight = left + center + right
13 | sum_values = left + 2 * center + 3 * right
14 | if sum_weight != 0:
15 | centroid = sum_values / sum_weight
16 | centroid = 2 - centroid
17 | return centroid
18 |
19 |
20 | def run_line_follower(alvik):
21 |
22 | kp = 50.0
23 | line_sensors = alvik.get_line_sensors()
24 | print(f' {line_sensors}')
25 |
26 | error = calculate_center(*line_sensors)
27 | control = error * kp
28 |
29 | if control > 0.2:
30 | alvik.left_led.set_color(1, 0, 0)
31 | alvik.right_led.set_color(0, 0, 0)
32 | elif control < -0.2:
33 | alvik.left_led.set_color(1, 0, 0)
34 | alvik.right_led.set_color(0, 0, 0)
35 | else:
36 | alvik.left_led.set_color(0, 1, 0)
37 | alvik.right_led.set_color(0, 1, 0)
38 |
39 | alvik.set_wheels_speed(30 - control, 30 + control)
40 | sleep_ms(100)
41 |
42 |
43 | def setup():
44 | alvik.begin()
45 | alvik.left_led.set_color(0, 0, 1)
46 | alvik.right_led.set_color(0, 0, 1)
47 |
48 |
49 | def loop():
50 | while not alvik.get_touch_ok():
51 | alvik.left_led.set_color(0, 0, 1)
52 | alvik.right_led.set_color(0, 0, 1)
53 | alvik.brake()
54 | sleep_ms(100)
55 |
56 | while not alvik.get_touch_cancel():
57 | run_line_follower(alvik)
58 |
59 |
60 | def cleanup():
61 | alvik.stop()
62 |
63 |
64 | start(setup=setup, loop=loop, cleanup=cleanup)
65 |
--------------------------------------------------------------------------------
/examples/arduino-runtime/read_tof.py:
--------------------------------------------------------------------------------
1 | from time import sleep_ms
2 |
3 | from arduino import start
4 | from arduino_alvik import ArduinoAlvik
5 |
6 |
7 | alvik = ArduinoAlvik()
8 |
9 |
10 | def setup():
11 | alvik.begin()
12 |
13 |
14 | def loop():
15 | L, CL, C, CR, R = alvik.get_distance()
16 | T = alvik.get_distance_top()
17 | B = alvik.get_distance_bottom()
18 | print(f'T: {T} | B: {B} | L: {L} | CL: {CL} | C: {C} | CR: {CR} | R: {R}')
19 | sleep_ms(100)
20 |
21 |
22 | def cleanup():
23 | alvik.stop()
24 |
25 |
26 | start(setup=setup, loop=loop, cleanup=cleanup)
27 |
--------------------------------------------------------------------------------
/examples/communication/i2c_scan.py:
--------------------------------------------------------------------------------
1 | from time import sleep_ms
2 |
3 |
4 | from arduino_alvik import ArduinoAlvik
5 |
6 | alvik = ArduinoAlvik()
7 | alvik.begin()
8 |
9 | sleep_ms(1000)
10 |
11 | while True:
12 | try:
13 | out = alvik.i2c.scan()
14 |
15 | if len(out) == 0:
16 | print("\nNo device found on I2C")
17 | else:
18 | print("\nList of devices")
19 | for o in out:
20 | print(hex(o))
21 |
22 | sleep_ms(100)
23 | except KeyboardInterrupt as e:
24 | alvik.stop()
25 | break
--------------------------------------------------------------------------------
/examples/communication/modulino.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | try:
6 | from modulino import ModulinoPixels
7 | except ImportError as e:
8 | print("\nImportError: ModulinoPixels not installed")
9 | raise e
10 |
11 | alvik = ArduinoAlvik()
12 | alvik.begin()
13 |
14 | pixels = ModulinoPixels(alvik.i2c)
15 |
16 | if not pixels.connected:
17 | raise Exception("🤷 No pixel modulino found")
18 |
19 | while True:
20 | try:
21 | for i in range(0, 8):
22 | pixels.clear_all()
23 | pixels.set_rgb(i, 255, 0, 0, 100)
24 | pixels.show()
25 | sleep_ms(50)
26 |
27 | for i in range(7, -1, -1):
28 | pixels.clear_all()
29 | pixels.set_rgb(i, 255, 0, 0, 100)
30 | pixels.show()
31 | sleep_ms(50)
32 |
33 | except KeyboardInterrupt as e:
34 | alvik.stop()
35 | break
36 |
--------------------------------------------------------------------------------
/examples/demo/demo.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 | from line_follower import run_line_follower
5 | from touch_move import run_touch_move
6 | from hand_follower import run_hand_follower
7 |
8 |
9 | alvik = ArduinoAlvik()
10 | alvik.begin()
11 |
12 | menu_status = 0
13 |
14 |
15 | def update_led_status(val):
16 | if val == 0:
17 | alvik.left_led.set_color(0, 0, 1)
18 | alvik.right_led.set_color(0, 0, 1)
19 | elif val == 1:
20 | alvik.left_led.set_color(0, 1, 0)
21 | alvik.right_led.set_color(0, 1, 0)
22 | elif val == -1:
23 | alvik.left_led.set_color(1, 0, 0)
24 | alvik.right_led.set_color(1, 0, 0)
25 |
26 |
27 | while True:
28 |
29 | update_led_status(menu_status)
30 |
31 | try:
32 |
33 | if alvik.get_touch_ok():
34 | alvik.left_led.set_color(0, 0, 0)
35 | alvik.right_led.set_color(0, 0, 0)
36 | sleep_ms(500)
37 | while not alvik.get_touch_cancel():
38 | if menu_status == 0:
39 | run_line_follower(alvik)
40 | elif menu_status == 1:
41 | run_hand_follower(alvik)
42 | elif menu_status == -1:
43 | if run_touch_move(alvik) < 0:
44 | break
45 | alvik.left_led.set_color(0, 0, 0)
46 | alvik.right_led.set_color(0, 0, 0)
47 | sleep_ms(500)
48 | alvik.brake()
49 |
50 | if alvik.get_touch_up() and menu_status < 1:
51 | menu_status += 1
52 | update_led_status(menu_status)
53 | while alvik.get_touch_up():
54 | sleep_ms(100)
55 | if alvik.get_touch_down() and menu_status > -1:
56 | menu_status -= 1
57 | update_led_status(menu_status)
58 | while alvik.get_touch_down():
59 | sleep_ms(100)
60 |
61 | sleep_ms(100)
62 |
63 | except KeyboardInterrupt as e:
64 | print('over')
65 | alvik.stop()
66 | break
67 |
--------------------------------------------------------------------------------
/examples/demo/hand_follower.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | def run_hand_follower(alvik):
6 | reference = 10.0
7 | alvik.left_led.set_color(0, 0, 0)
8 | alvik.right_led.set_color(0, 0, 0)
9 | L, CL, C, CR, R = alvik.get_distance()
10 | print(f'C: {C}')
11 | error = C - reference
12 | alvik.set_wheels_speed(error * 10, error * 10)
13 | sleep_ms(100)
14 |
15 |
16 | if __name__ == "__main__":
17 |
18 | alvik = ArduinoAlvik()
19 | alvik.begin()
20 |
21 | alvik.left_led.set_color(0, 1, 0)
22 | alvik.right_led.set_color(0, 1, 0)
23 |
24 | while alvik.get_touch_ok():
25 | sleep_ms(50)
26 |
27 | while not alvik.get_touch_ok():
28 | sleep_ms(50)
29 |
30 | while True:
31 | try:
32 | while not alvik.get_touch_cancel():
33 | run_hand_follower(alvik)
34 |
35 | while not alvik.get_touch_ok():
36 | alvik.left_led.set_color(0, 1, 0)
37 | alvik.right_led.set_color(0, 1, 0)
38 | alvik.brake()
39 | sleep_ms(100)
40 | except KeyboardInterrupt as e:
41 | print('over')
42 | alvik.stop()
43 | break
44 |
--------------------------------------------------------------------------------
/examples/demo/line_follower.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | def calculate_center(left: int, center: int, right: int):
6 | centroid = 0
7 | sum_weight = left + center + right
8 | sum_values = left + 2 * center + 3 * right
9 | if sum_weight != 0:
10 | centroid = sum_values / sum_weight
11 | centroid = 2 - centroid
12 | return centroid
13 |
14 |
15 | def run_line_follower(alvik):
16 | kp = 50.0
17 | line_sensors = alvik.get_line_sensors()
18 | print(f' {line_sensors}')
19 |
20 | error = calculate_center(*line_sensors)
21 | control = error * kp
22 |
23 | if control > 0.2:
24 | alvik.left_led.set_color(1, 0, 0)
25 | alvik.right_led.set_color(0, 0, 0)
26 | elif control < -0.2:
27 | alvik.left_led.set_color(1, 0, 0)
28 | alvik.right_led.set_color(0, 0, 0)
29 | else:
30 | alvik.left_led.set_color(0, 1, 0)
31 | alvik.right_led.set_color(0, 1, 0)
32 |
33 | alvik.set_wheels_speed(30 - control, 30 + control)
34 | sleep_ms(100)
35 |
36 |
37 | if __name__ == "__main__":
38 |
39 | alvik = ArduinoAlvik()
40 | alvik.begin()
41 |
42 | alvik.left_led.set_color(0, 0, 1)
43 | alvik.right_led.set_color(0, 0, 1)
44 |
45 | while alvik.get_touch_ok():
46 | sleep_ms(50)
47 |
48 | while not alvik.get_touch_ok():
49 | sleep_ms(50)
50 |
51 | while True:
52 | try:
53 | while not alvik.get_touch_cancel():
54 | run_line_follower(alvik)
55 |
56 | while not alvik.get_touch_ok():
57 | alvik.left_led.set_color(0, 0, 1)
58 | alvik.right_led.set_color(0, 0, 1)
59 | alvik.brake()
60 | sleep_ms(100)
61 |
62 | except KeyboardInterrupt as e:
63 | print('over')
64 | alvik.stop()
65 | break
66 |
--------------------------------------------------------------------------------
/examples/demo/main.py:
--------------------------------------------------------------------------------
1 | import demo
--------------------------------------------------------------------------------
/examples/demo/touch_move.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | def blink(alvik):
6 | alvik.left_led.set_color(1, 0, 1)
7 | alvik.right_led.set_color(1, 0, 1)
8 | sleep_ms(200)
9 | alvik.left_led.set_color(1, 0, 0)
10 | alvik.right_led.set_color(1, 0, 0)
11 |
12 |
13 | def add_movement(alvik, movements):
14 | if alvik.get_touch_up():
15 | movements.append('forward')
16 | blink(alvik)
17 | while alvik.get_touch_up():
18 | sleep_ms(100)
19 | if alvik.get_touch_down():
20 | movements.append('backward')
21 | blink(alvik)
22 | while alvik.get_touch_down():
23 | sleep_ms(100)
24 | if alvik.get_touch_left():
25 | movements.append('left')
26 | blink(alvik)
27 | while alvik.get_touch_left():
28 | sleep_ms(100)
29 | if alvik.get_touch_right():
30 | movements.append('right')
31 | blink(alvik)
32 | while alvik.get_touch_right():
33 | sleep_ms(100)
34 | if alvik.get_touch_cancel():
35 | movements = []
36 | for i in range(0, 3):
37 | val = i % 2
38 | alvik.left_led.set_color(val, 0, 0)
39 | alvik.right_led.set_color(val, 0, 0)
40 | sleep_ms(200)
41 | while alvik.get_touch_cancel():
42 | sleep_ms(100)
43 |
44 |
45 | def run_movement(alvik, movement):
46 | if movement == 'forward':
47 | alvik.move(10, blocking=False)
48 | if movement == 'backward':
49 | alvik.move(-10, blocking=False)
50 | if movement == 'left':
51 | alvik.rotate(90, blocking=False)
52 | if movement == 'right':
53 | alvik.rotate(-90, blocking=False)
54 | while not alvik.get_touch_cancel() and not alvik.is_target_reached():
55 | alvik.left_led.set_color(1, 0, 0)
56 | alvik.right_led.set_color(1, 0, 0)
57 | sleep_ms(100)
58 | alvik.left_led.set_color(0, 0, 0)
59 | alvik.right_led.set_color(0, 0, 0)
60 | sleep_ms(100)
61 |
62 |
63 | def run_touch_move(alvik) -> int:
64 | movements = []
65 | while not (alvik.get_touch_ok() and len(movements) != 0):
66 | if alvik.get_touch_cancel():
67 | if len(movements) == 0:
68 | return -1
69 | movements.clear()
70 | blink(alvik)
71 | alvik.left_led.set_color(1, 0, 0)
72 | alvik.right_led.set_color(1, 0, 0)
73 | alvik.brake()
74 | add_movement(alvik, movements)
75 | sleep_ms(100)
76 |
77 | alvik.left_led.set_color(0, 0, 0)
78 | alvik.right_led.set_color(0, 0, 0)
79 | for move in movements:
80 | run_movement(alvik, move)
81 | if alvik.get_touch_cancel():
82 | movements.clear()
83 | blink(alvik)
84 | sleep_ms(100)
85 | return 1
86 |
87 |
88 | if __name__ == "__main__":
89 | alvik = ArduinoAlvik()
90 | alvik.begin()
91 |
92 | alvik.left_led.set_color(1, 0, 0)
93 | alvik.right_led.set_color(1, 0, 0)
94 |
95 | while True:
96 | try:
97 |
98 | run_touch_move(alvik)
99 |
100 | except KeyboardInterrupt as e:
101 | print('over')
102 | alvik.stop()
103 | break
104 |
--------------------------------------------------------------------------------
/examples/events/hot_wheels.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | def stop_when_up(alvik):
6 | print("lift")
7 | alvik.set_wheels_speed(0, 0)
8 |
9 |
10 | def run_when_down(alvik):
11 | print("drop")
12 | alvik.set_wheels_speed(20, 20)
13 |
14 |
15 | alvik = ArduinoAlvik()
16 | alvik.on_lift(stop_when_up, (alvik,))
17 | alvik.on_drop(run_when_down, (alvik,))
18 | alvik.begin()
19 | color_val = 0
20 |
21 |
22 | def blinking_leds(val):
23 | alvik.left_led.set_color(val & 0x01, val & 0x02, val & 0x04)
24 | alvik.right_led.set_color(val & 0x02, val & 0x04, val & 0x01)
25 |
26 |
27 | while not alvik.get_touch_ok():
28 | sleep_ms(100)
29 |
30 | alvik.set_wheels_speed(20, 20)
31 |
32 | while not alvik.get_touch_cancel():
33 |
34 | try:
35 | blinking_leds(color_val)
36 | color_val = (color_val + 1) % 7
37 | sleep_ms(500)
38 |
39 | except KeyboardInterrupt as e:
40 | print('over')
41 | alvik.stop()
42 | break
43 |
44 | alvik.stop()
45 |
--------------------------------------------------------------------------------
/examples/events/motion_events.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep
3 |
4 |
5 | def toggle_value():
6 | """
7 | This function yields a generator object that toggles values between 0 and 1.
8 | :return:
9 | """
10 | value = 0
11 | while True:
12 | yield value % 2
13 | value += 1
14 |
15 |
16 | def toggle_left_led(custom_text: str, val) -> None:
17 | """
18 | This function toggles the lef led in the red channel. It also writes some custom text.
19 | :param custom_text: your custom text
20 | :param val: a toggle signal generator
21 | :return:
22 | """
23 | led_val = next(val)
24 | alvik.left_led.set_color(led_val, 0, 0)
25 | print(f"RED {'ON' if led_val else 'OFF'}! {custom_text}")
26 |
27 |
28 | def simple_print(custom_text: str = '') -> None:
29 | print(custom_text)
30 |
31 |
32 | alvik = ArduinoAlvik()
33 | alvik.on_shake(toggle_left_led, ("ALVIK WAS SHAKEN... YOU MAKE ME SHIVER :)", toggle_value(), ))
34 | alvik.on_lift(simple_print, ("ALVIK WAS LIFTED",))
35 | alvik.on_drop(simple_print, ("ALVIK WAS DROPPED",))
36 | alvik.on_x_tilt(simple_print, ("TILTED ON X",))
37 | alvik.on_nx_tilt(simple_print, ("TILTED ON -X",))
38 | alvik.on_y_tilt(simple_print, ("TILTED ON Y",))
39 | alvik.on_ny_tilt(simple_print, ("TILTED ON -Y",))
40 | alvik.on_z_tilt(simple_print, ("TILTED ON Z",))
41 | alvik.on_nz_tilt(simple_print, ("TILTED ON -Z",))
42 |
43 | alvik.begin()
44 |
45 | while True:
46 | try:
47 | print(alvik.get_distance())
48 | sleep(2)
49 |
50 | except KeyboardInterrupt as e:
51 | print('over')
52 | alvik.stop()
53 | break
54 |
--------------------------------------------------------------------------------
/examples/events/timer_one_shot_events.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep
3 |
4 |
5 | def toggle_left_led(custom_text: str, val) -> None:
6 | """
7 | This function toggles the lef led in the red channel. It also writes some custom text.
8 | :param custom_text: your custom text
9 | :param val: a toggle signal generator
10 | :return:
11 | """
12 | alvik.left_led.set_color(val, 0, 0)
13 | print(f"LEFT LED -> RED {'ON' if val else 'OFF'}! {custom_text}")
14 |
15 |
16 | alvik = ArduinoAlvik()
17 | alvik.set_timer('one_shot', 10000, toggle_left_led, ("10 seconds have passed... I won't do this again", 1, ))
18 |
19 | alvik.begin()
20 |
21 | alvik.left_wheel.reset()
22 | alvik.right_wheel.reset()
23 |
24 | while True:
25 | try:
26 | alvik.left_wheel.set_position(30)
27 | sleep(2)
28 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
29 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
30 |
31 | alvik.right_wheel.set_position(10)
32 | sleep(2)
33 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
34 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
35 |
36 | alvik.left_wheel.set_position(180)
37 | sleep(2)
38 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
39 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
40 |
41 | alvik.right_wheel.set_position(270)
42 | sleep(2)
43 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
44 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
45 |
46 | if alvik.timer.is_triggered():
47 | alvik.timer.reset(period=1000)
48 | alvik.timer.stop()
49 | for _ in range(0, 10):
50 | if _ == 2:
51 | alvik.timer.resume()
52 | print(f'TRIGGERED:{alvik.timer.is_triggered()} STOPPED:{alvik.timer.is_stopped()} TIME: {alvik.timer.get()}')
53 | sleep(1)
54 |
55 | except KeyboardInterrupt as e:
56 | print('over')
57 | alvik.stop()
58 | break
--------------------------------------------------------------------------------
/examples/events/timer_periodic_events.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep
3 |
4 |
5 | def toggle_value():
6 | """
7 | This function yields a generator object that toggles values between 0 and 1.
8 | :return:
9 | """
10 | value = 0
11 | while True:
12 | yield value % 2
13 | value += 1
14 |
15 |
16 | def toggle_left_led(custom_text: str, val) -> None:
17 | """
18 | This function toggles the lef led in the red channel. It also writes some custom text.
19 | :param custom_text: your custom text
20 | :param val: a toggle signal generator
21 | :return:
22 | """
23 | led_val = next(val)
24 | alvik.left_led.set_color(led_val, 0, 0)
25 | print(f"RED {'ON' if led_val else 'OFF'}! {custom_text}")
26 |
27 |
28 | alvik = ArduinoAlvik()
29 | alvik.set_timer('periodic', 500, toggle_left_led, ("500 ms have passed...", toggle_value(), ))
30 |
31 | alvik.begin()
32 |
33 | alvik.left_wheel.reset()
34 | alvik.right_wheel.reset()
35 |
36 | while True:
37 | try:
38 | alvik.left_wheel.set_position(30)
39 | sleep(2)
40 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
41 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
42 |
43 | alvik.right_wheel.set_position(10)
44 | sleep(2)
45 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
46 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
47 |
48 | alvik.left_wheel.set_position(180)
49 | sleep(2)
50 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
51 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
52 |
53 | alvik.right_wheel.set_position(270)
54 | sleep(2)
55 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
56 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
57 |
58 | alvik.timer.reset(period=1000)
59 | alvik.timer.stop()
60 | for _ in range(0, 20):
61 | if _ == 5:
62 | alvik.timer.resume()
63 | print(f'TRIGGERED:{alvik.timer.is_triggered()} STOPPED:{alvik.timer.is_stopped()} TIME: {alvik.timer.get()}')
64 | sleep(1)
65 |
66 | except KeyboardInterrupt as e:
67 | print('over')
68 | alvik.stop()
69 | break
70 |
--------------------------------------------------------------------------------
/examples/events/touch_events.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep
3 |
4 |
5 | def toggle_value():
6 | """
7 | This function yields a generator object that toggles values between 0 and 1.
8 | :return:
9 | """
10 | value = 0
11 | while True:
12 | yield value % 2
13 | value += 1
14 |
15 |
16 | def toggle_left_led(custom_text: str, val) -> None:
17 | """
18 | This function toggles the lef led in the red channel. It also writes some custom text.
19 | :param custom_text: your custom text
20 | :param val: a toggle signal generator
21 | :return:
22 | """
23 | alvik.left_led.set_color(next(val), 0, 0)
24 | print(f"RED BLINKS! {custom_text}")
25 |
26 |
27 | def simple_print(custom_text: str = '') -> None:
28 | print(custom_text)
29 |
30 | alvik = ArduinoAlvik()
31 | alvik.on_touch_ok_pressed(toggle_left_led, ("OK WAS PRESSED... THAT'S COOL", toggle_value(), ))
32 | alvik.on_touch_center_pressed(simple_print, ("CENTER PRESSED",))
33 | alvik.on_touch_cancel_pressed(simple_print, ("CANCEL PRESSED",))
34 | alvik.on_touch_up_pressed(simple_print, ("UP PRESSED",))
35 | alvik.on_touch_left_pressed(simple_print, ("LEFT PRESSED",))
36 | alvik.on_touch_down_pressed(simple_print, ("DOWN PRESSED",))
37 | alvik.on_touch_right_pressed(simple_print, ("RIGHT PRESSED",))
38 |
39 | alvik.begin()
40 |
41 | alvik.left_wheel.reset()
42 | alvik.right_wheel.reset()
43 |
44 | while True:
45 | try:
46 | alvik.left_wheel.set_position(30)
47 | sleep(2)
48 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
49 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
50 |
51 | alvik.right_wheel.set_position(10)
52 | sleep(2)
53 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
54 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
55 |
56 | alvik.left_wheel.set_position(180)
57 | sleep(2)
58 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
59 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
60 |
61 | alvik.right_wheel.set_position(270)
62 | sleep(2)
63 | print(f'Left wheel degs: {alvik.left_wheel.get_position()}')
64 | print(f'Right wheel degs: {alvik.right_wheel.get_position()}')
65 |
66 | except KeyboardInterrupt as e:
67 | print('over')
68 | alvik.stop()
69 | break
70 |
--------------------------------------------------------------------------------
/examples/flash_firmware.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 |
4 | def reload_modules():
5 | to_be_reloaded = []
6 |
7 | for m in sys.modules:
8 | to_be_reloaded.append(m)
9 | del sys.modules[m]
10 |
11 | for m in to_be_reloaded:
12 | exec(f'import {m}')
13 |
14 |
15 | reload_modules()
16 | from arduino_alvik import update_firmware
17 |
18 | # this is a patch to fix possible running threads on Alvik
19 | from arduino_alvik import ArduinoAlvik
20 | alvik = ArduinoAlvik()
21 | alvik.stop()
22 |
23 | update_firmware('/firmware.bin')
24 |
--------------------------------------------------------------------------------
/examples/reload_modules.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | def reload_modules():
4 | to_be_reloaded = []
5 |
6 | for m in sys.modules:
7 | to_be_reloaded.append(m)
8 | del sys.modules[m]
9 |
10 | for m in to_be_reloaded:
11 | exec(f'import {m}')
12 |
13 |
14 | reload_modules()
--------------------------------------------------------------------------------
/examples/sensors/read_color_sensor.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 | alvik.begin()
7 |
8 | while True:
9 | try:
10 | r, g, b = alvik.get_color()
11 | h, s, v = alvik.get_color('hsv')
12 | print(f'RED: {r}, Green: {g}, Blue: {b}, HUE: {h}, SAT: {s}, VAL: {v}')
13 | print(f'COLOR LABEL: {alvik.get_color_label()}')
14 | sleep_ms(100)
15 | except KeyboardInterrupt as e:
16 | print('over')
17 | alvik.stop()
18 | break
19 |
--------------------------------------------------------------------------------
/examples/sensors/read_imu.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 | alvik.begin()
7 |
8 | while True:
9 | try:
10 | ax, ay, az = alvik.get_accelerations()
11 | gx, gy, gz = alvik.get_gyros()
12 | print(f'ax: {ax}, ay: {ay}, az: {az}, gx: {gx}, gy: {gy}, gz: {gz}')
13 | sleep_ms(100)
14 | except KeyboardInterrupt as e:
15 | print('over')
16 | alvik.stop()
17 | break
18 |
--------------------------------------------------------------------------------
/examples/sensors/read_orientation.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 | alvik.begin()
7 |
8 | while True:
9 | try:
10 | roll, pitch, yaw = alvik.get_orientation()
11 | print(f'ROLL: {roll}, PITCH: {pitch}, YAW: {yaw}')
12 | sleep_ms(50)
13 | except KeyboardInterrupt as e:
14 | print('over')
15 | alvik.stop()
16 | break
17 |
--------------------------------------------------------------------------------
/examples/sensors/read_tof.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 | alvik.begin()
7 |
8 | while True:
9 | try:
10 | L, CL, C, CR, R = alvik.get_distance()
11 | T = alvik.get_distance_top()
12 | B = alvik.get_distance_bottom()
13 | print(f'T: {T} | B: {B} | L: {L} | CL: {CL} | C: {C} | CR: {CR} | R: {R}')
14 | sleep_ms(100)
15 | except KeyboardInterrupt as e:
16 | print('over')
17 | alvik.stop()
18 | break
19 |
--------------------------------------------------------------------------------
/examples/sensors/read_touch.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 | alvik.begin()
7 |
8 | while True:
9 | try:
10 | if alvik.get_touch_any():
11 | if alvik.get_touch_up():
12 | print("UP")
13 | if alvik.get_touch_down():
14 | print("DOWN")
15 | if alvik.get_touch_left():
16 | print("LEFT")
17 | if alvik.get_touch_right():
18 | print("RIGHT")
19 | if alvik.get_touch_ok():
20 | print("OK")
21 | if alvik.get_touch_cancel():
22 | print("CANCEL")
23 | if alvik.get_touch_center():
24 | print("CENTER")
25 |
26 | sleep_ms(100)
27 | except KeyboardInterrupt as e:
28 | print('over')
29 | alvik.stop()
30 | break
31 |
--------------------------------------------------------------------------------
/examples/tests/message_reader.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 | alvik.begin()
7 |
8 | speed = 0
9 |
10 | while True:
11 | try:
12 | print(f'FW VER: {alvik.get_fw_version()}')
13 | print(f'LSP: {alvik.left_wheel.get_speed()}')
14 | print(f'RSP: {alvik.right_wheel.get_speed()}')
15 | print(f'LPOS: {alvik.left_wheel.get_position()}')
16 | print(f'RPOS: {alvik.right_wheel.get_position()}')
17 | print(f'TOUCH (UP): {alvik.get_touch_up()}')
18 | print(f'RGB: {alvik.get_color()}')
19 | print(f'LINE: {alvik.get_line_sensors()}')
20 | print(f'SOC: {alvik.get_battery_charge()}%')
21 |
22 | alvik.set_wheels_speed(speed, speed)
23 | speed = (speed + 1) % 60
24 | sleep_ms(1000)
25 | except KeyboardInterrupt as e:
26 | print('over')
27 | alvik.stop()
28 | break
29 |
30 |
--------------------------------------------------------------------------------
/examples/tests/test_idle.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 | alvik.begin()
7 |
8 | speed = 0
9 |
10 | while True:
11 | try:
12 |
13 | if alvik.is_on():
14 | print(f'VER: {alvik.get_version()}')
15 | print(f'LSP: {alvik.left_wheel.get_speed()}')
16 | alvik.set_wheels_speed(speed, speed)
17 | speed = (speed + 1) % 30
18 | sleep_ms(1000)
19 | except KeyboardInterrupt as e:
20 | print('over')
21 | alvik.stop()
22 | break
23 |
24 |
--------------------------------------------------------------------------------
/examples/tests/test_meas_units.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 | alvik.begin()
7 |
8 | try:
9 |
10 | # -- LINEAR MOVEMENTS --
11 |
12 | print("Move fw 0.05 m")
13 | alvik.move(0.05, unit='m')
14 | sleep_ms(2000)
15 |
16 | print("Move fw 10 cm")
17 | alvik.move(5, unit='cm')
18 | sleep_ms(2000)
19 |
20 | print("Move bw 100 mm")
21 | alvik.move(-100, unit='mm')
22 | sleep_ms(2000)
23 |
24 | print("Move fw 1 inch")
25 | alvik.move(1, unit='in')
26 | sleep_ms(2000)
27 |
28 | print(f"Current position: {alvik.get_pose()}")
29 | alvik.reset_pose(0, 0, theta=3.1415, angle_unit='rad')
30 | sleep_ms(2000)
31 |
32 | print(f"Current position: {alvik.get_pose()}")
33 |
34 | # -- WHEEL ROTATIONS --
35 | alvik.right_wheel.reset()
36 | sleep_ms(2000)
37 | curr_pos = alvik.right_wheel.get_position()
38 | print(f'R wheel pos: {curr_pos}')
39 | sleep_ms(2000)
40 |
41 | print("Rotate right wheel 25% fw")
42 | alvik.right_wheel.set_position(25, unit='%')
43 | sleep_ms(2000)
44 | curr_pos = alvik.right_wheel.get_position()
45 | print(f'R wheel pos: {curr_pos}')
46 |
47 | print("Rotate right wheel 90 deg bw")
48 | alvik.right_wheel.set_position(-90, unit='deg')
49 | sleep_ms(2000)
50 | curr_pos = alvik.right_wheel.get_position()
51 | print(f'R wheel pos: {curr_pos}')
52 |
53 | print("Rotate right wheel pi rad fw")
54 | alvik.right_wheel.set_position(3.14, unit='rad')
55 | sleep_ms(2000)
56 | curr_pos = alvik.right_wheel.get_position()
57 | print(f'R wheel pos: {curr_pos}')
58 |
59 | print("Rotate right wheel a quarter revolution bw")
60 | alvik.right_wheel.set_position(-0.25, unit='rev')
61 | sleep_ms(2000)
62 | curr_pos = alvik.right_wheel.get_position()
63 | print(f'R wheel pos: {curr_pos}')
64 |
65 | # -- WHEELS SPEED --
66 | print("Set speed 50% max_rpm (35.0 rpm)")
67 | alvik.set_wheels_speed(50, 50, '%')
68 | sleep_ms(1000)
69 | print(f"Current speed is {alvik.get_wheels_speed()} rpm")
70 |
71 | print("Set speed 12 rpm (1 rev in 5 sec)")
72 | alvik.set_wheels_speed(12, 12, 'rpm')
73 | sleep_ms(1000)
74 | print(f"Current speed is {alvik.get_wheels_speed()} rpm")
75 |
76 | print("Set speed -pi rad/s (1 back rev in 2 sec)")
77 | alvik.set_wheels_speed(-3.1415, -3.1415, 'rad/s')
78 | sleep_ms(1000)
79 | print(f"Current speed is {alvik.get_wheels_speed()} rpm")
80 |
81 | print("Set speed 180 deg/s (1 back rev in 2 sec)")
82 | alvik.set_wheels_speed(180, 180, 'deg/s')
83 | sleep_ms(1000)
84 | print(f"Current speed is {alvik.get_wheels_speed()} rpm")
85 |
86 | # -- DRIVE --
87 | print("Driving at 10 mm/s (expecting approx 5.6 rpm 64 deg/s)")
88 | alvik.drive(10, 20, linear_unit='mm/s', angular_unit='%')
89 | sleep_ms(2000)
90 | print(f"Current speed is {alvik.get_drive_speed()} (mm/s, deg/s))")
91 |
92 | print("Driving at 10 mm/s (expecting approx 5.6 rpm)")
93 | alvik.drive(10, 0, linear_unit='mm/s')
94 | sleep_ms(2000)
95 | print(f"Current speed is {alvik.get_wheels_speed()} rpm")
96 |
97 | print("Driving at 2 cm/s (expecting approx 11.2 rpm)")
98 | alvik.drive(2, 0, linear_unit='cm/s')
99 | sleep_ms(2000)
100 | print(f"Current speed is {alvik.get_wheels_speed()} rpm")
101 |
102 | print("Driving at 1 in/s (expecting approx 14 rpm)")
103 | alvik.drive(1, 0, linear_unit='in/s')
104 | sleep_ms(2000)
105 | print(f"Current speed is {alvik.get_wheels_speed()} rpm")
106 |
107 | print("Driving at 5 mm/s (expecting approx 5.6 rpm) pi/8 rad/s (22.5 deg/s)")
108 | alvik.drive(5, 3.1415/8, linear_unit='mm/s', angular_unit='rad/s')
109 | sleep_ms(2000)
110 | print(f"Current speed is {alvik.get_drive_speed()} (mm/s) (rpm)")
111 |
112 | print("Driving at 5 mm/s (expecting approx 5.6 rpm) 1/8 rev/s (45 deg/s)")
113 | alvik.drive(5, 1/8, linear_unit='mm/s', angular_unit='rev/s')
114 | sleep_ms(2000)
115 | print(f"Current speed is {alvik.get_drive_speed()} (mm/s) (rpm)")
116 |
117 | except KeyboardInterrupt as e:
118 | print('Test interrupted')
119 |
120 | finally:
121 | alvik.stop()
122 | print('END of measurement units test')
123 |
--------------------------------------------------------------------------------
/examples/tests/test_version.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import ArduinoAlvik
2 | from time import sleep_ms
3 |
4 |
5 | alvik = ArduinoAlvik()
6 |
7 | alvik.begin()
8 |
9 | while True:
10 | try:
11 | print(f'\nLIBRARY VERSION: {alvik.get_lib_version()}')
12 | print(f'FIRMWARE VERSION: {alvik.get_fw_version()}')
13 | print(f'REQUIRED FW VERSION: {alvik.get_required_fw_version()}')
14 | print(f'FIRMWARE VERSION COMPATIBILITY CHECK: {alvik.check_firmware_compatibility()}\n')
15 | sleep_ms(1000)
16 | except KeyboardInterrupt as e:
17 | print('over')
18 | alvik.stop()
19 | break
--------------------------------------------------------------------------------
/examples/update_firmware.py:
--------------------------------------------------------------------------------
1 | from arduino_alvik import update_firmware
2 |
3 | # this is a patch to fix possible running threads on Alvik
4 | from arduino_alvik import ArduinoAlvik
5 | alvik = ArduinoAlvik()
6 | alvik.stop()
7 |
8 | update_firmware('/firmware.bin')
--------------------------------------------------------------------------------
/install.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | set "port_string="
4 |
5 | :parse_args
6 | if "%1"=="" goto install
7 | if /i "%1"=="-p" (
8 | set "port_string=connect %2"
9 | shift
10 | shift
11 | goto parse_args
12 | )
13 | if /i "%1"=="-h" (
14 | call :display_help
15 | exit /b 0
16 | )
17 |
18 | :install
19 |
20 | python -m mpremote %port_string% fs mkdir lib
21 | python -m mpremote %port_string% fs mkdir lib/arduino_alvik
22 | python -m mpremote %port_string% fs cp arduino_alvik/__init__.py :lib/arduino_alvik/__init__.py
23 | python -m mpremote %port_string% fs cp arduino_alvik/arduino_alvik.py :lib/arduino_alvik/arduino_alvik.py
24 | python -m mpremote %port_string% fs cp arduino_alvik/constants.py :lib/arduino_alvik/constants.py
25 | python -m mpremote %port_string% fs cp arduino_alvik/conversions.py :lib/arduino_alvik/conversions.py
26 | python -m mpremote %port_string% fs cp arduino_alvik/pinout_definitions.py :lib/arduino_alvik/pinout_definitions.py
27 | python -m mpremote %port_string% fs cp arduino_alvik/robot_definitions.py :lib/arduino_alvik/robot_definitions.py
28 | python -m mpremote %port_string% fs cp arduino_alvik/stm32_flash.py :lib/arduino_alvik/stm32_flash.py
29 | python -m mpremote %port_string% fs cp arduino_alvik/uart.py :lib/arduino_alvik/uart.py
30 |
31 | echo Installing dependencies
32 | python -m mpremote %port_string% mip install github:arduino/ucPack-mpy
33 | python -m mpremote %port_string% mip install github:arduino/arduino-runtime-mpy
34 |
35 | python -m mpremote %port_string% reset
36 | exit /b 0
37 |
38 | :display_help
39 | echo Usage: %~nx0 [-p PORT]
40 | echo Options:
41 | echo -p PORT Specify the device port
42 | echo -h Display this help message
43 | exit /b 0
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | connect_string=""
4 |
5 | # Display help message
6 | function display_help {
7 | echo "Usage: $0 [-p PORT]"
8 | echo "Options:"
9 | echo " -p PORT Specify the device port"
10 | echo " -h Display this help message"
11 | }
12 |
13 | # Parse command-line options
14 | while getopts ":p:h" opt; do
15 | case $opt in
16 | p)
17 | # If -p is provided, set the port string
18 | connect_string="connect $OPTARG"
19 | ;;
20 | h)
21 | display_help
22 | exit 0
23 | ;;
24 | \?)
25 | echo "Invalid option: -$OPTARG" >&2
26 | exit 1
27 | ;;
28 | :)
29 | echo "Option -$OPTARG requires an argument." >&2
30 | exit 1
31 | ;;
32 | esac
33 | done
34 |
35 | if command -v python3 &>/dev/null; then
36 | python_command="python3"
37 | else
38 | # If python3 is not available, use python
39 | python_command="python"
40 | fi
41 |
42 | # Uncomment the following line on windows machines
43 | # python_command="python"
44 |
45 | $python_command -m mpremote $connect_string fs mkdir lib
46 | $python_command -m mpremote $connect_string fs mkdir lib/arduino_alvik
47 | $python_command -m mpremote $connect_string fs cp arduino_alvik/__init__.py :lib/arduino_alvik/__init__.py
48 | $python_command -m mpremote $connect_string fs cp arduino_alvik/arduino_alvik.py :lib/arduino_alvik/arduino_alvik.py
49 | $python_command -m mpremote $connect_string fs cp arduino_alvik/constants.py :lib/arduino_alvik/constants.py
50 | $python_command -m mpremote $connect_string fs cp arduino_alvik/conversions.py :lib/arduino_alvik/conversions.py
51 | $python_command -m mpremote $connect_string fs cp arduino_alvik/pinout_definitions.py :lib/arduino_alvik/pinout_definitions.py
52 | $python_command -m mpremote $connect_string fs cp arduino_alvik/robot_definitions.py :lib/arduino_alvik/robot_definitions.py
53 | $python_command -m mpremote $connect_string fs cp arduino_alvik/stm32_flash.py :lib/arduino_alvik/stm32_flash.py
54 | $python_command -m mpremote $connect_string fs cp arduino_alvik/uart.py :lib/arduino_alvik/uart.py
55 |
56 | echo "Installing dependencies"
57 | $python_command -m mpremote $connect_string mip install github:arduino/ucPack-mpy
58 | $python_command -m mpremote $connect_string mip install github:arduino/arduino-runtime-mpy
59 |
60 | $python_command -m mpremote $connect_string reset
61 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "urls": [
3 | ["arduino_alvik/__init__.py", "github:arduino/arduino-alvik-mpy/arduino_alvik/__init__.py"],
4 | ["arduino_alvik/arduino_alvik.py", "github:arduino/arduino-alvik-mpy/arduino_alvik/arduino_alvik.py"],
5 | ["arduino_alvik/constants.py", "github:arduino/arduino-alvik-mpy/arduino_alvik/constants.py"],
6 | ["arduino_alvik/conversions.py", "github:arduino/arduino-alvik-mpy/arduino_alvik/conversions.py"],
7 | ["arduino_alvik/pinout_definitions.py", "github:arduino/arduino-alvik-mpy/arduino_alvik/pinout_definitions.py"],
8 | ["arduino_alvik/robot_definitions.py", "github:arduino/arduino-alvik-mpy/arduino_alvik/robot_definitions.py"],
9 | ["arduino_alvik/uart.py", "github:arduino/arduino-alvik-mpy/arduino_alvik/uart.py"],
10 | ["arduino_alvik/stm32_flash.py", "github:arduino/arduino-alvik-mpy/arduino_alvik/stm32_flash.py"]
11 | ],
12 | "deps": [
13 | ["github:arduino/ucPack-mpy", "0.1.7"],
14 | ["github:arduino/arduino-runtime-mpy", "0.4.0"]
15 | ],
16 | "version": "1.1.4"
17 | }
18 |
--------------------------------------------------------------------------------
/utilities/flash_firmware.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | set "port_string="
4 | set "filename="
5 |
6 | :parse_args
7 | if "%1"=="" goto check_params
8 | if /i "%1"=="-p" (
9 | set "port_string=connect %2"
10 | shift
11 | shift
12 | goto parse_args
13 | )
14 | if /i "%1"=="-h" (
15 | call :display_help
16 | exit /b 0
17 | )
18 |
19 | :check_params
20 | if "%1"=="" (
21 | echo Error: Filename parameter is mandatory.
22 | exit /b 1
23 | ) else (
24 | set "filename=%1"
25 | )
26 |
27 | echo Uploading %filename%
28 |
29 | python -m mpremote %port_string% fs cp %filename% :firmware.bin
30 |
31 | set /p userInput=Do you want to flash the firmware right now? (y/N):
32 |
33 | if /i "%userInput%"=="y" (
34 | python -m mpremote %port_string% run ../examples/flash_firmware.py
35 | ) else (
36 | echo The firmware will not be written to the device.
37 | )
38 |
39 | python -m mpremote %port_string% reset
40 | exit /b 0
41 |
42 | :display_help
43 | echo Usage: %~nx0 [-p PORT] FILENAME
44 | echo Options:
45 | echo -p PORT Specify the device port
46 | echo -h Display this help message
47 | exit /b 0
48 |
--------------------------------------------------------------------------------
/utilities/flash_firmware.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | connect_string=""
4 |
5 | # Display help message
6 | function display_help {
7 | echo "Usage: $0 [-p PORT] FILENAME"
8 | echo "Options:"
9 | echo " -p PORT Specify the device port"
10 | echo " -h Display this help message"
11 | }
12 |
13 | # Parse command-line options
14 | while getopts ":p:h" opt; do
15 | case $opt in
16 | p)
17 | # If -p is provided, set the port string
18 | connect_string="connect $OPTARG"
19 | ;;
20 | h)
21 | display_help
22 | exit 0
23 | ;;
24 | \?)
25 | echo "Invalid option: -$OPTARG" >&2
26 | exit 1
27 | ;;
28 | :)
29 | echo "Option -$OPTARG requires an argument." >&2
30 | exit 1
31 | ;;
32 | esac
33 | done
34 |
35 | shift $((OPTIND - 1))
36 |
37 | if [ -z "$1" ]; then
38 | echo "Error: Filename parameter not provided."
39 | exit 1
40 | fi
41 |
42 | filename="$1"
43 |
44 | if command -v python3 &>/dev/null; then
45 | python_command="python3"
46 | else
47 | # If python3 is not available, use python
48 | python_command="python"
49 | fi
50 |
51 | # Uncomment the following line on windows machines
52 | # python_command="python"
53 |
54 | echo "Uploading $filename..."
55 |
56 | $python_command -m mpremote $connect_string fs cp $filename :firmware.bin
57 |
58 | echo "Do you want to flash the firmware right now? (y/N)"
59 | read do_flash
60 |
61 | if [ "$do_flash" == "y" ] || [ "$do_flash" == "Y" ]; then
62 | $python_command -m mpremote $connect_string run ../examples/flash_firmware.py
63 | else
64 | echo "The firmware will not be written to the device."
65 | fi
66 |
67 | $python_command -m mpremote $connect_string reset
68 |
--------------------------------------------------------------------------------