16
16
from .__init__ import __required_firmware_version__
17
17
18
18
19
+ def writes_uart (method ):
20
+ def wrapper (* args , ** kwargs ):
21
+ with ArduinoAlvik ._write_lock :
22
+ return method (* args , ** kwargs )
23
+
24
+ return wrapper
25
+
26
+
27
+ def reads_uart (method ):
28
+ def wrapper (* args , ** kwargs ):
29
+ with ArduinoAlvik ._read_lock :
30
+ return method (* args , ** kwargs )
31
+
32
+ return wrapper
33
+
34
+
35
+ class _AlvikRLock :
36
+ def __init__ (self ):
37
+ """Alvik re-entrant Lock implementation"""
38
+ self ._lock = _thread .allocate_lock ()
39
+ self ._owner = None
40
+ self ._count = 0
41
+
42
+ def acquire (self ):
43
+ tid = _thread .get_ident ()
44
+
45
+ if self ._owner == tid :
46
+ self ._count += 1
47
+ return True
48
+
49
+ self ._lock .acquire ()
50
+ self ._owner = tid
51
+ self ._count = 1
52
+ return True
53
+
54
+ def release (self ):
55
+ tid = _thread .get_ident ()
56
+
57
+ if self ._owner != tid :
58
+ raise RuntimeError ("Cannot release an unowned lock" )
59
+
60
+ self ._count -= 1
61
+ if self ._count == 0 :
62
+ self ._owner = None
63
+ self ._lock .release ()
64
+
65
+ def locked (self ):
66
+ return self ._lock .locked ()
67
+
68
+ def __enter__ (self ):
69
+ self .acquire ()
70
+ return self
71
+
72
+ def __exit__ (self , exc_type , exc_value , traceback ):
73
+ self .release ()
74
+
75
+
19
76
class ArduinoAlvik :
20
77
_update_thread_running = False
21
78
_update_thread_id = None
22
79
_events_thread_running = False
23
80
_events_thread_id = None
24
81
82
+ _write_lock = _AlvikRLock ()
83
+ _read_lock = _AlvikRLock ()
84
+
25
85
def __new__ (cls ):
26
86
if not hasattr (cls , '_instance' ):
27
87
cls ._instance = super (ArduinoAlvik , cls ).__new__ (cls )
@@ -119,7 +179,8 @@ def _print_battery_status(percentage: float, is_charging) -> None:
119
179
word = marks_str + f" { percentage } % { charging_str } \t "
120
180
print (word , end = '' )
121
181
122
- def _lenghty_op (self , iterations = 10000000 ) -> int :
182
+ @staticmethod
183
+ def _lengthy_op (self , iterations = 10000000 ) -> int :
123
184
result = 0
124
185
for i in range (1 , iterations ):
125
186
result += i * i
@@ -134,7 +195,7 @@ def _idle(self, delay_=1, check_on_thread=False, blocking=False) -> None:
134
195
self .i2c .set_single_thread (True )
135
196
136
197
if blocking :
137
- self ._lenghty_op (50000 )
198
+ self ._lengthy_op (50000 )
138
199
else :
139
200
sleep_ms (500 )
140
201
led_val = 0
@@ -157,7 +218,7 @@ def _idle(self, delay_=1, check_on_thread=False, blocking=False) -> None:
157
218
self ._battery_perc = abs (soc_perc )
158
219
self ._print_battery_status (round (soc_perc ), self ._battery_is_charging )
159
220
if blocking :
160
- self ._lenghty_op (10000 )
221
+ self ._lengthy_op (10000 )
161
222
else :
162
223
sleep_ms (delay_ )
163
224
if soc_perc > 97 :
@@ -169,13 +230,13 @@ def _idle(self, delay_=1, check_on_thread=False, blocking=False) -> None:
169
230
led_val = (led_val + 1 ) % 2
170
231
self .i2c .set_single_thread (False )
171
232
if self .is_on ():
172
- print ("********** Alvik is on **********" )
233
+ print ("\n ********** Alvik is on **********" )
173
234
except KeyboardInterrupt :
174
235
self .stop ()
175
236
sys .exit ()
176
237
except Exception as e :
177
238
pass
178
- print (f'Unable to read SOC: { e } ' )
239
+ print (f'\n Unable to read SOC: { e } ' )
179
240
finally :
180
241
LEDR .value (1 )
181
242
LEDG .value (1 )
@@ -232,7 +293,7 @@ def begin(self) -> int:
232
293
self .set_behaviour (2 )
233
294
self ._set_color_reference ()
234
295
if self ._has_events_registered ():
235
- print ('Starting events thread' )
296
+ print ('\n ********** Starting events thread ********** \n ' )
236
297
self ._start_events_thread ()
237
298
self .set_servo_positions (90 , 90 )
238
299
return 0
@@ -279,6 +340,7 @@ def _wait_for_fw_check(self, timeout=5) -> bool:
279
340
return False
280
341
281
342
@staticmethod
343
+ @reads_uart
282
344
def _flush_uart ():
283
345
"""
284
346
Empties the UART buffer
@@ -313,6 +375,7 @@ def _wait_for_target(self, idle_time):
313
375
# print(self._last_ack)
314
376
sleep_ms (100 )
315
377
378
+ @writes_uart
316
379
def is_target_reached (self ) -> bool :
317
380
"""
318
381
Returns True if robot has sent an M or R acknowledgment.
@@ -330,6 +393,7 @@ def is_target_reached(self) -> bool:
330
393
return True
331
394
return False
332
395
396
+ @writes_uart
333
397
def set_behaviour (self , behaviour : int ):
334
398
"""
335
399
Sets the behaviour of Alvik
@@ -339,6 +403,7 @@ def set_behaviour(self, behaviour: int):
339
403
self ._packeter .packetC1B (ord ('B' ), behaviour & 0xFF )
340
404
uart .write (self ._packeter .msg [0 :self ._packeter .msg_size ])
341
405
406
+ @writes_uart
342
407
def rotate (self , angle : float , unit : str = 'deg' , blocking : bool = True ):
343
408
"""
344
409
Rotates the robot by given angle
@@ -355,6 +420,7 @@ def rotate(self, angle: float, unit: str = 'deg', blocking: bool = True):
355
420
if blocking :
356
421
self ._wait_for_target (idle_time = (angle / MOTOR_CONTROL_DEG_S ))
357
422
423
+ @writes_uart
358
424
def move (self , distance : float , unit : str = 'cm' , blocking : bool = True ):
359
425
"""
360
426
Moves the robot by given distance
@@ -408,6 +474,7 @@ def get_wheels_speed(self, unit: str = 'rpm') -> (float | None, float | None):
408
474
"""
409
475
return self .left_wheel .get_speed (unit ), self .right_wheel .get_speed (unit )
410
476
477
+ @writes_uart
411
478
def set_wheels_speed (self , left_speed : float , right_speed : float , unit : str = 'rpm' ):
412
479
"""
413
480
Sets left/right motor speed
@@ -427,12 +494,13 @@ def set_wheels_speed(self, left_speed: float, right_speed: float, unit: str = 'r
427
494
self ._packeter .packetC2F (ord ('J' ), left_speed , right_speed )
428
495
uart .write (self ._packeter .msg [0 :self ._packeter .msg_size ])
429
496
497
+ @writes_uart
430
498
def set_wheels_position (self , left_angle : float , right_angle : float , unit : str = 'deg' , blocking : bool = True ):
431
499
"""
432
500
Sets left/right motor angle
433
501
:param left_angle:
434
502
:param right_angle:
435
- :param unit: the speed unit of measurement (default: 'rpm ')
503
+ :param unit: the speed unit of measurement (default: 'deg ')
436
504
:param blocking:
437
505
:return:
438
506
"""
@@ -490,6 +558,7 @@ def get_line_sensors(self) -> (int | None, int | None, int | None):
490
558
491
559
return self ._left_line , self ._center_line , self ._right_line
492
560
561
+ @writes_uart
493
562
def drive (self , linear_velocity : float , angular_velocity : float , linear_unit : str = 'cm/s' ,
494
563
angular_unit : str = 'deg/s' ):
495
564
"""
@@ -530,6 +599,7 @@ def get_drive_speed(self, linear_unit: str = 'cm/s', angular_unit: str = 'deg/s'
530
599
531
600
return convert_speed (self ._linear_velocity , 'mm/s' , linear_unit ), angular_velocity
532
601
602
+ @writes_uart
533
603
def reset_pose (self , x : float , y : float , theta : float , distance_unit : str = 'cm' , angle_unit : str = 'deg' ):
534
604
"""
535
605
Resets the robot pose
@@ -559,6 +629,7 @@ def get_pose(self, distance_unit: str = 'cm', angle_unit: str = 'deg') \
559
629
convert_distance (self ._y , 'mm' , distance_unit ),
560
630
convert_angle (self ._theta , 'deg' , angle_unit ))
561
631
632
+ @writes_uart
562
633
def set_servo_positions (self , a_position : int , b_position : int ):
563
634
"""
564
635
Sets A/B servomotor angle
@@ -586,10 +657,16 @@ def get_ack(self) -> str:
586
657
"""
587
658
return self ._last_ack
588
659
589
- # def send_ack(self):
590
- # self._packeter.packetC1B(ord('X'), ACK_)
591
- # uart.write(self._packeter.msg[0:self._packeter.msg_size])
660
+ @writes_uart
661
+ def send_ack (self , ack : str = 'K' ):
662
+ """
663
+ Sends an ack message on UART
664
+ :return:
665
+ """
666
+ self ._packeter .packetC1B (ord ('X' ), ord (ack ))
667
+ uart .write (self ._packeter .msg [0 :self ._packeter .msg_size ])
592
668
669
+ @writes_uart
593
670
def _set_leds (self , led_state : int ):
594
671
"""
595
672
Sets the LEDs state
@@ -651,6 +728,7 @@ def _update(self, delay_=1):
651
728
self ._read_message ()
652
729
sleep_ms (delay_ )
653
730
731
+ @reads_uart
654
732
def _read_message (self ) -> None :
655
733
"""
656
734
Read a message from the uC
@@ -1259,6 +1337,24 @@ def on_shake(self, callback: callable, args: tuple = ()) -> None:
1259
1337
"""
1260
1338
self ._move_events .register_callback ('on_shake' , callback , args )
1261
1339
1340
+ def on_lift (self , callback : callable , args : tuple = ()) -> None :
1341
+ """
1342
+ Register callback when Alvik is lifted
1343
+ :param callback:
1344
+ :param args:
1345
+ :return:
1346
+ """
1347
+ self ._move_events .register_callback ('on_lift' , callback , args )
1348
+
1349
+ def on_drop (self , callback : callable , args : tuple = ()) -> None :
1350
+ """
1351
+ Register callback when Alvik is dropped
1352
+ :param callback:
1353
+ :param args:
1354
+ :return:
1355
+ """
1356
+ self ._move_events .register_callback ('on_drop' , callback , args )
1357
+
1262
1358
def on_x_tilt (self , callback : callable , args : tuple = ()) -> None :
1263
1359
"""
1264
1360
Register callback when Alvik is tilted on X-axis
@@ -1521,15 +1617,15 @@ def writeto_mem(self, addr, memaddr, buf, addrsize=8) -> None:
1521
1617
return i2c .writeto_mem (addr , memaddr , buf , addrsize = addrsize )
1522
1618
1523
1619
1524
-
1525
1620
class _ArduinoAlvikServo :
1526
1621
1527
1622
def __init__ (self , packeter : ucPack , label : str , servo_id : int , position : list [int | None ]):
1528
1623
self ._packeter = packeter
1529
1624
self ._label = label
1530
1625
self ._id = servo_id
1531
1626
self ._position = position
1532
-
1627
+
1628
+ @writes_uart
1533
1629
def set_position (self , position ):
1534
1630
"""
1535
1631
Sets the position of the servo
@@ -1558,7 +1654,8 @@ def __init__(self, packeter: ucPack, label: int, wheel_diameter_mm: float = WHEE
1558
1654
self ._speed = None
1559
1655
self ._position = None
1560
1656
self ._alvik = alvik
1561
-
1657
+
1658
+ @writes_uart
1562
1659
def reset (self , initial_position : float = 0.0 , unit : str = 'deg' ):
1563
1660
"""
1564
1661
Resets the wheel reference position
@@ -1570,6 +1667,7 @@ def reset(self, initial_position: float = 0.0, unit: str = 'deg'):
1570
1667
self ._packeter .packetC2B1F (ord ('W' ), self ._label & 0xFF , ord ('Z' ), initial_position )
1571
1668
uart .write (self ._packeter .msg [0 :self ._packeter .msg_size ])
1572
1669
1670
+ @writes_uart
1573
1671
def set_pid_gains (self , kp : float = MOTOR_KP_DEFAULT , ki : float = MOTOR_KI_DEFAULT , kd : float = MOTOR_KD_DEFAULT ):
1574
1672
"""
1575
1673
Set PID gains for Alvik wheels
@@ -1589,6 +1687,7 @@ def stop(self):
1589
1687
"""
1590
1688
self .set_speed (0 )
1591
1689
1690
+ @writes_uart
1592
1691
def set_speed (self , velocity : float , unit : str = 'rpm' ):
1593
1692
"""
1594
1693
Sets the motor speed
@@ -1625,6 +1724,7 @@ def get_position(self, unit: str = 'deg') -> float | None:
1625
1724
"""
1626
1725
return convert_angle (self ._position , 'deg' , unit )
1627
1726
1727
+ @writes_uart
1628
1728
def set_position (self , position : float , unit : str = 'deg' , blocking : bool = True ):
1629
1729
"""
1630
1730
Sets left/right motor speed
@@ -1655,6 +1755,7 @@ def __init__(self, packeter: ucPack, label: str, led_state: list[int | None], rg
1655
1755
self ._rgb_mask = rgb_mask
1656
1756
self ._led_state = led_state
1657
1757
1758
+ @writes_uart
1658
1759
def set_color (self , red : bool , green : bool , blue : bool ):
1659
1760
"""
1660
1761
Sets the LED's r,g,b state
@@ -1965,7 +2066,7 @@ class _ArduinoAlvikMoveEvents(_ArduinoAlvikEvents):
1965
2066
Event class to handle move events
1966
2067
"""
1967
2068
1968
- available_events = ['on_shake' , 'on_x_tilt' , 'on_y_tilt' , 'on_z_tilt' ,
2069
+ available_events = ['on_shake' , 'on_lift' , 'on_drop' , ' on_x_tilt' , 'on_y_tilt' , 'on_z_tilt' ,
1969
2070
'on_nx_tilt' , 'on_ny_tilt' , 'on_nz_tilt' ]
1970
2071
1971
2072
NZ_TILT = 0x80
@@ -1992,6 +2093,26 @@ def _is_shaken(current_state, new_state) -> bool:
1992
2093
"""
1993
2094
return not bool (current_state & 0b00000001 ) and bool (new_state & 0b00000001 )
1994
2095
2096
+ @staticmethod
2097
+ def _is_lifted (current_state , new_state ) -> bool :
2098
+ """
2099
+ True if Alvik was lifted
2100
+ :param current_state:
2101
+ :param new_state:
2102
+ :return:
2103
+ """
2104
+ return not bool (current_state & 0b00000010 ) and bool (new_state & 0b00000010 )
2105
+
2106
+ @staticmethod
2107
+ def _is_dropped (current_state , new_state ) -> bool :
2108
+ """
2109
+ True if Alvik was dropped
2110
+ :param current_state:
2111
+ :param new_state:
2112
+ :return:
2113
+ """
2114
+ return bool (current_state & 0b00000010 ) and not bool (new_state & 0b00000010 )
2115
+
1995
2116
@staticmethod
1996
2117
def _is_x_tilted (current_state , new_state ) -> bool :
1997
2118
"""
@@ -2065,6 +2186,12 @@ def update_state(self, state: int | None):
2065
2186
if self ._is_shaken (self ._current_state , state ):
2066
2187
self .execute_callback ('on_shake' )
2067
2188
2189
+ if self ._is_lifted (self ._current_state , state ):
2190
+ self .execute_callback ('on_lift' )
2191
+
2192
+ if self ._is_dropped (self ._current_state , state ):
2193
+ self .execute_callback ('on_drop' )
2194
+
2068
2195
if self ._is_x_tilted (self ._current_state , state ):
2069
2196
self .execute_callback ('on_x_tilt' )
2070
2197
0 commit comments