Skip to content

Commit 48b2f17

Browse files
authored
Merge pull request #42 from arduino/thread_locks
Improved threading - Thread locks on methods accessing UART
2 parents 7144b81 + c08e38a commit 48b2f17

File tree

3 files changed

+98
-15
lines changed

3 files changed

+98
-15
lines changed

arduino_alvik/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
__author__ = "Lucio Rossi <[email protected]>, Giovanni Bruno <[email protected]>"
66
__license__ = "MPL 2.0"
7-
__version__ = "1.1.2"
7+
__version__ = "1.1.3"
88
__maintainer__ = "Lucio Rossi <[email protected]>, Giovanni Bruno <[email protected]>"
9-
__required_firmware_version__ = "1.1.0"
9+
__required_firmware_version__ = "1.1.1"
1010

1111
from .arduino_alvik import *

arduino_alvik/arduino_alvik.py

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,72 @@
1616
from .__init__ import __required_firmware_version__
1717

1818

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+
1976
class ArduinoAlvik:
2077
_update_thread_running = False
2178
_update_thread_id = None
2279
_events_thread_running = False
2380
_events_thread_id = None
2481

82+
_write_lock = _AlvikRLock()
83+
_read_lock = _AlvikRLock()
84+
2585
def __new__(cls):
2686
if not hasattr(cls, '_instance'):
2787
cls._instance = super(ArduinoAlvik, cls).__new__(cls)
@@ -119,7 +179,8 @@ def _print_battery_status(percentage: float, is_charging) -> None:
119179
word = marks_str + f" {percentage}% {charging_str} \t"
120180
print(word, end='')
121181

122-
def _lenghty_op(self, iterations=10000000) -> int:
182+
@staticmethod
183+
def _lengthy_op(self, iterations=10000000) -> int:
123184
result = 0
124185
for i in range(1, iterations):
125186
result += i * i
@@ -134,7 +195,7 @@ def _idle(self, delay_=1, check_on_thread=False, blocking=False) -> None:
134195
self.i2c.set_single_thread(True)
135196

136197
if blocking:
137-
self._lenghty_op(50000)
198+
self._lengthy_op(50000)
138199
else:
139200
sleep_ms(500)
140201
led_val = 0
@@ -157,7 +218,7 @@ def _idle(self, delay_=1, check_on_thread=False, blocking=False) -> None:
157218
self._battery_perc = abs(soc_perc)
158219
self._print_battery_status(round(soc_perc), self._battery_is_charging)
159220
if blocking:
160-
self._lenghty_op(10000)
221+
self._lengthy_op(10000)
161222
else:
162223
sleep_ms(delay_)
163224
if soc_perc > 97:
@@ -169,13 +230,13 @@ def _idle(self, delay_=1, check_on_thread=False, blocking=False) -> None:
169230
led_val = (led_val + 1) % 2
170231
self.i2c.set_single_thread(False)
171232
if self.is_on():
172-
print("********** Alvik is on **********")
233+
print("\n********** Alvik is on **********")
173234
except KeyboardInterrupt:
174235
self.stop()
175236
sys.exit()
176237
except Exception as e:
177238
pass
178-
print(f'Unable to read SOC: {e}')
239+
print(f'\nUnable to read SOC: {e}')
179240
finally:
180241
LEDR.value(1)
181242
LEDG.value(1)
@@ -232,7 +293,7 @@ def begin(self) -> int:
232293
self.set_behaviour(2)
233294
self._set_color_reference()
234295
if self._has_events_registered():
235-
print('Starting events thread')
296+
print('\n********** Starting events thread **********\n')
236297
self._start_events_thread()
237298
self.set_servo_positions(90, 90)
238299
return 0
@@ -279,6 +340,7 @@ def _wait_for_fw_check(self, timeout=5) -> bool:
279340
return False
280341

281342
@staticmethod
343+
@reads_uart
282344
def _flush_uart():
283345
"""
284346
Empties the UART buffer
@@ -313,6 +375,7 @@ def _wait_for_target(self, idle_time):
313375
# print(self._last_ack)
314376
sleep_ms(100)
315377

378+
@writes_uart
316379
def is_target_reached(self) -> bool:
317380
"""
318381
Returns True if robot has sent an M or R acknowledgment.
@@ -330,6 +393,7 @@ def is_target_reached(self) -> bool:
330393
return True
331394
return False
332395

396+
@writes_uart
333397
def set_behaviour(self, behaviour: int):
334398
"""
335399
Sets the behaviour of Alvik
@@ -339,6 +403,7 @@ def set_behaviour(self, behaviour: int):
339403
self._packeter.packetC1B(ord('B'), behaviour & 0xFF)
340404
uart.write(self._packeter.msg[0:self._packeter.msg_size])
341405

406+
@writes_uart
342407
def rotate(self, angle: float, unit: str = 'deg', blocking: bool = True):
343408
"""
344409
Rotates the robot by given angle
@@ -355,6 +420,7 @@ def rotate(self, angle: float, unit: str = 'deg', blocking: bool = True):
355420
if blocking:
356421
self._wait_for_target(idle_time=(angle / MOTOR_CONTROL_DEG_S))
357422

423+
@writes_uart
358424
def move(self, distance: float, unit: str = 'cm', blocking: bool = True):
359425
"""
360426
Moves the robot by given distance
@@ -408,6 +474,7 @@ def get_wheels_speed(self, unit: str = 'rpm') -> (float | None, float | None):
408474
"""
409475
return self.left_wheel.get_speed(unit), self.right_wheel.get_speed(unit)
410476

477+
@writes_uart
411478
def set_wheels_speed(self, left_speed: float, right_speed: float, unit: str = 'rpm'):
412479
"""
413480
Sets left/right motor speed
@@ -427,6 +494,7 @@ def set_wheels_speed(self, left_speed: float, right_speed: float, unit: str = 'r
427494
self._packeter.packetC2F(ord('J'), left_speed, right_speed)
428495
uart.write(self._packeter.msg[0:self._packeter.msg_size])
429496

497+
@writes_uart
430498
def set_wheels_position(self, left_angle: float, right_angle: float, unit: str = 'deg', blocking: bool = True):
431499
"""
432500
Sets left/right motor angle
@@ -490,6 +558,7 @@ def get_line_sensors(self) -> (int | None, int | None, int | None):
490558

491559
return self._left_line, self._center_line, self._right_line
492560

561+
@writes_uart
493562
def drive(self, linear_velocity: float, angular_velocity: float, linear_unit: str = 'cm/s',
494563
angular_unit: str = 'deg/s'):
495564
"""
@@ -530,6 +599,7 @@ def get_drive_speed(self, linear_unit: str = 'cm/s', angular_unit: str = 'deg/s'
530599

531600
return convert_speed(self._linear_velocity, 'mm/s', linear_unit), angular_velocity
532601

602+
@writes_uart
533603
def reset_pose(self, x: float, y: float, theta: float, distance_unit: str = 'cm', angle_unit: str = 'deg'):
534604
"""
535605
Resets the robot pose
@@ -559,6 +629,7 @@ def get_pose(self, distance_unit: str = 'cm', angle_unit: str = 'deg') \
559629
convert_distance(self._y, 'mm', distance_unit),
560630
convert_angle(self._theta, 'deg', angle_unit))
561631

632+
@writes_uart
562633
def set_servo_positions(self, a_position: int, b_position: int):
563634
"""
564635
Sets A/B servomotor angle
@@ -586,10 +657,16 @@ def get_ack(self) -> str:
586657
"""
587658
return self._last_ack
588659

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])
592668

669+
@writes_uart
593670
def _set_leds(self, led_state: int):
594671
"""
595672
Sets the LEDs state
@@ -651,6 +728,7 @@ def _update(self, delay_=1):
651728
self._read_message()
652729
sleep_ms(delay_)
653730

731+
@reads_uart
654732
def _read_message(self) -> None:
655733
"""
656734
Read a message from the uC
@@ -1539,15 +1617,15 @@ def writeto_mem(self, addr, memaddr, buf, addrsize=8) -> None:
15391617
return i2c.writeto_mem(addr, memaddr, buf, addrsize=addrsize)
15401618

15411619

1542-
15431620
class _ArduinoAlvikServo:
15441621

15451622
def __init__(self, packeter: ucPack, label: str, servo_id: int, position: list[int | None]):
15461623
self._packeter = packeter
15471624
self._label = label
15481625
self._id = servo_id
15491626
self._position = position
1550-
1627+
1628+
@writes_uart
15511629
def set_position(self, position):
15521630
"""
15531631
Sets the position of the servo
@@ -1576,7 +1654,8 @@ def __init__(self, packeter: ucPack, label: int, wheel_diameter_mm: float = WHEE
15761654
self._speed = None
15771655
self._position = None
15781656
self._alvik = alvik
1579-
1657+
1658+
@writes_uart
15801659
def reset(self, initial_position: float = 0.0, unit: str = 'deg'):
15811660
"""
15821661
Resets the wheel reference position
@@ -1588,6 +1667,7 @@ def reset(self, initial_position: float = 0.0, unit: str = 'deg'):
15881667
self._packeter.packetC2B1F(ord('W'), self._label & 0xFF, ord('Z'), initial_position)
15891668
uart.write(self._packeter.msg[0:self._packeter.msg_size])
15901669

1670+
@writes_uart
15911671
def set_pid_gains(self, kp: float = MOTOR_KP_DEFAULT, ki: float = MOTOR_KI_DEFAULT, kd: float = MOTOR_KD_DEFAULT):
15921672
"""
15931673
Set PID gains for Alvik wheels
@@ -1607,6 +1687,7 @@ def stop(self):
16071687
"""
16081688
self.set_speed(0)
16091689

1690+
@writes_uart
16101691
def set_speed(self, velocity: float, unit: str = 'rpm'):
16111692
"""
16121693
Sets the motor speed
@@ -1643,6 +1724,7 @@ def get_position(self, unit: str = 'deg') -> float | None:
16431724
"""
16441725
return convert_angle(self._position, 'deg', unit)
16451726

1727+
@writes_uart
16461728
def set_position(self, position: float, unit: str = 'deg', blocking: bool = True):
16471729
"""
16481730
Sets left/right motor speed
@@ -1673,6 +1755,7 @@ def __init__(self, packeter: ucPack, label: str, led_state: list[int | None], rg
16731755
self._rgb_mask = rgb_mask
16741756
self._led_state = led_state
16751757

1758+
@writes_uart
16761759
def set_color(self, red: bool, green: bool, blue: bool):
16771760
"""
16781761
Sets the LED's r,g,b state

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@
1313
["github:arduino/ucPack-mpy", "0.1.7"],
1414
["github:arduino/arduino-runtime-mpy", "0.4.0"]
1515
],
16-
"version": "1.1.2"
16+
"version": "1.1.3"
1717
}

0 commit comments

Comments
 (0)