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,6 +494,7 @@ 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
@@ -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
@@ -1539,15 +1617,15 @@ def writeto_mem(self, addr, memaddr, buf, addrsize=8) -> None:
1539
1617
return i2c .writeto_mem (addr , memaddr , buf , addrsize = addrsize )
1540
1618
1541
1619
1542
-
1543
1620
class _ArduinoAlvikServo :
1544
1621
1545
1622
def __init__ (self , packeter : ucPack , label : str , servo_id : int , position : list [int | None ]):
1546
1623
self ._packeter = packeter
1547
1624
self ._label = label
1548
1625
self ._id = servo_id
1549
1626
self ._position = position
1550
-
1627
+
1628
+ @writes_uart
1551
1629
def set_position (self , position ):
1552
1630
"""
1553
1631
Sets the position of the servo
@@ -1576,7 +1654,8 @@ def __init__(self, packeter: ucPack, label: int, wheel_diameter_mm: float = WHEE
1576
1654
self ._speed = None
1577
1655
self ._position = None
1578
1656
self ._alvik = alvik
1579
-
1657
+
1658
+ @writes_uart
1580
1659
def reset (self , initial_position : float = 0.0 , unit : str = 'deg' ):
1581
1660
"""
1582
1661
Resets the wheel reference position
@@ -1588,6 +1667,7 @@ def reset(self, initial_position: float = 0.0, unit: str = 'deg'):
1588
1667
self ._packeter .packetC2B1F (ord ('W' ), self ._label & 0xFF , ord ('Z' ), initial_position )
1589
1668
uart .write (self ._packeter .msg [0 :self ._packeter .msg_size ])
1590
1669
1670
+ @writes_uart
1591
1671
def set_pid_gains (self , kp : float = MOTOR_KP_DEFAULT , ki : float = MOTOR_KI_DEFAULT , kd : float = MOTOR_KD_DEFAULT ):
1592
1672
"""
1593
1673
Set PID gains for Alvik wheels
@@ -1607,6 +1687,7 @@ def stop(self):
1607
1687
"""
1608
1688
self .set_speed (0 )
1609
1689
1690
+ @writes_uart
1610
1691
def set_speed (self , velocity : float , unit : str = 'rpm' ):
1611
1692
"""
1612
1693
Sets the motor speed
@@ -1643,6 +1724,7 @@ def get_position(self, unit: str = 'deg') -> float | None:
1643
1724
"""
1644
1725
return convert_angle (self ._position , 'deg' , unit )
1645
1726
1727
+ @writes_uart
1646
1728
def set_position (self , position : float , unit : str = 'deg' , blocking : bool = True ):
1647
1729
"""
1648
1730
Sets left/right motor speed
@@ -1673,6 +1755,7 @@ def __init__(self, packeter: ucPack, label: str, led_state: list[int | None], rg
1673
1755
self ._rgb_mask = rgb_mask
1674
1756
self ._led_state = led_state
1675
1757
1758
+ @writes_uart
1676
1759
def set_color (self , red : bool , green : bool , blue : bool ):
1677
1760
"""
1678
1761
Sets the LED's r,g,b state
0 commit comments