Skip to content

Commit c77b095

Browse files
authored
Merge pull request #41 from arduino/alvik_is_lifted
Alvik lifted/dropped events
2 parents 524b360 + 48b2f17 commit c77b095

File tree

5 files changed

+190
-17
lines changed

5 files changed

+190
-17
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 <l.rossi@arduino.cc>, Giovanni Bruno <g.bruno@arduino.cc>"
66
__license__ = "MPL 2.0"
7-
__version__ = "1.1.2"
7+
__version__ = "1.1.3"
88
__maintainer__ = "Lucio Rossi <l.rossi@arduino.cc>, Giovanni Bruno <g.bruno@arduino.cc>"
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: 141 additions & 14 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,12 +494,13 @@ 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
433501
:param left_angle:
434502
:param right_angle:
435-
:param unit: the speed unit of measurement (default: 'rpm')
503+
:param unit: the speed unit of measurement (default: 'deg')
436504
:param blocking:
437505
:return:
438506
"""
@@ -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
@@ -1259,6 +1337,24 @@ def on_shake(self, callback: callable, args: tuple = ()) -> None:
12591337
"""
12601338
self._move_events.register_callback('on_shake', callback, args)
12611339

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+
12621358
def on_x_tilt(self, callback: callable, args: tuple = ()) -> None:
12631359
"""
12641360
Register callback when Alvik is tilted on X-axis
@@ -1521,15 +1617,15 @@ def writeto_mem(self, addr, memaddr, buf, addrsize=8) -> None:
15211617
return i2c.writeto_mem(addr, memaddr, buf, addrsize=addrsize)
15221618

15231619

1524-
15251620
class _ArduinoAlvikServo:
15261621

15271622
def __init__(self, packeter: ucPack, label: str, servo_id: int, position: list[int | None]):
15281623
self._packeter = packeter
15291624
self._label = label
15301625
self._id = servo_id
15311626
self._position = position
1532-
1627+
1628+
@writes_uart
15331629
def set_position(self, position):
15341630
"""
15351631
Sets the position of the servo
@@ -1558,7 +1654,8 @@ def __init__(self, packeter: ucPack, label: int, wheel_diameter_mm: float = WHEE
15581654
self._speed = None
15591655
self._position = None
15601656
self._alvik = alvik
1561-
1657+
1658+
@writes_uart
15621659
def reset(self, initial_position: float = 0.0, unit: str = 'deg'):
15631660
"""
15641661
Resets the wheel reference position
@@ -1570,6 +1667,7 @@ def reset(self, initial_position: float = 0.0, unit: str = 'deg'):
15701667
self._packeter.packetC2B1F(ord('W'), self._label & 0xFF, ord('Z'), initial_position)
15711668
uart.write(self._packeter.msg[0:self._packeter.msg_size])
15721669

1670+
@writes_uart
15731671
def set_pid_gains(self, kp: float = MOTOR_KP_DEFAULT, ki: float = MOTOR_KI_DEFAULT, kd: float = MOTOR_KD_DEFAULT):
15741672
"""
15751673
Set PID gains for Alvik wheels
@@ -1589,6 +1687,7 @@ def stop(self):
15891687
"""
15901688
self.set_speed(0)
15911689

1690+
@writes_uart
15921691
def set_speed(self, velocity: float, unit: str = 'rpm'):
15931692
"""
15941693
Sets the motor speed
@@ -1625,6 +1724,7 @@ def get_position(self, unit: str = 'deg') -> float | None:
16251724
"""
16261725
return convert_angle(self._position, 'deg', unit)
16271726

1727+
@writes_uart
16281728
def set_position(self, position: float, unit: str = 'deg', blocking: bool = True):
16291729
"""
16301730
Sets left/right motor speed
@@ -1655,6 +1755,7 @@ def __init__(self, packeter: ucPack, label: str, led_state: list[int | None], rg
16551755
self._rgb_mask = rgb_mask
16561756
self._led_state = led_state
16571757

1758+
@writes_uart
16581759
def set_color(self, red: bool, green: bool, blue: bool):
16591760
"""
16601761
Sets the LED's r,g,b state
@@ -1965,7 +2066,7 @@ class _ArduinoAlvikMoveEvents(_ArduinoAlvikEvents):
19652066
Event class to handle move events
19662067
"""
19672068

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',
19692070
'on_nx_tilt', 'on_ny_tilt', 'on_nz_tilt']
19702071

19712072
NZ_TILT = 0x80
@@ -1992,6 +2093,26 @@ def _is_shaken(current_state, new_state) -> bool:
19922093
"""
19932094
return not bool(current_state & 0b00000001) and bool(new_state & 0b00000001)
19942095

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+
19952116
@staticmethod
19962117
def _is_x_tilted(current_state, new_state) -> bool:
19972118
"""
@@ -2065,6 +2186,12 @@ def update_state(self, state: int | None):
20652186
if self._is_shaken(self._current_state, state):
20662187
self.execute_callback('on_shake')
20672188

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+
20682195
if self._is_x_tilted(self._current_state, state):
20692196
self.execute_callback('on_x_tilt')
20702197

0 commit comments

Comments
 (0)