Skip to content

Commit 6315c83

Browse files
authored
EV3-G API for Button (#386)
* EV3-G API for Button * EV3-G API for Button - use evdev for event driven reads * Add python-evdev as required package * Revert "Add python-evdev as required package" This reverts commit 2985e93. * Undo file_open * Update .travis.yml to install python3-evdev * Update .travis.yml to install python3-evdev * Update .travis.yml to 'pip install evdev' * Option to specifiy sleep_ms in TouchSensor _wait()
1 parent 2fa6cb0 commit 6315c83

File tree

4 files changed

+137
-143
lines changed

4 files changed

+137
-143
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ python:
44
sudo: false
55
install:
66
- pip install Pillow
7+
- pip install evdev
78
script:
89
- ./tests/api_tests.py
910
deploy:

ev3dev/core.py

Lines changed: 98 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import stat
4242
import time
4343
import errno
44+
import evdev
4445
from os.path import abspath
4546
from struct import pack, unpack
4647
from subprocess import Popen, check_output, PIPE
@@ -137,6 +138,7 @@ def matches(attribute, pattern):
137138
if all([matches(path + '/' + k, kwargs[k]) for k in kwargs]):
138139
yield f
139140

141+
140142
# -----------------------------------------------------------------------------
141143
# Define the base class from which all other ev3dev classes are defined.
142144

@@ -205,8 +207,8 @@ def __str__(self):
205207
else:
206208
return self.__class__.__name__
207209

208-
def _attribute_file_open( self, name ):
209-
path = self._path + '/' + name
210+
def _attribute_file_open(self, name):
211+
path = os.path.join(self._path, name)
210212
mode = stat.S_IMODE(os.stat(path)[stat.ST_MODE])
211213
r_ok = mode & stat.S_IRGRP
212214
w_ok = mode & stat.S_IWGRP
@@ -2245,8 +2247,6 @@ class TouchSensor(Sensor):
22452247
Touch Sensor
22462248
"""
22472249

2248-
__slots__ = ['_poll', '_value0']
2249-
22502250
SYSTEM_CLASS_NAME = Sensor.SYSTEM_CLASS_NAME
22512251
SYSTEM_DEVICE_NAME_CONVENTION = Sensor.SYSTEM_DEVICE_NAME_CONVENTION
22522252

@@ -2256,8 +2256,6 @@ class TouchSensor(Sensor):
22562256

22572257
def __init__(self, address=None, name_pattern=SYSTEM_DEVICE_NAME_CONVENTION, name_exact=False, **kwargs):
22582258
super(TouchSensor, self).__init__(address, name_pattern, name_exact, driver_name=['lego-ev3-touch', 'lego-nxt-touch'], **kwargs)
2259-
self._poll = None
2260-
self._value0 = None
22612259

22622260
@property
22632261
def is_pressed(self):
@@ -2272,40 +2270,42 @@ def is_pressed(self):
22722270
def is_released(self):
22732271
return not self.is_pressed
22742272

2275-
def _wait(self, wait_for_press, timeout_ms):
2273+
def _wait(self, wait_for_press, timeout_ms, sleep_ms):
22762274
tic = time.time()
22772275

2278-
if self._poll is None:
2279-
self._value0 = self._attribute_file_open('value0')
2280-
self._poll = select.poll()
2281-
self._poll.register(self._value0, select.POLLPRI)
2276+
if sleep_ms:
2277+
sleep_ms = float(sleep_ms/1000)
22822278

2279+
# The kernel does not supoort POLLPRI or POLLIN for sensors so we have
2280+
# to drop into a loop and check often
22832281
while True:
2284-
self._poll.poll(timeout_ms)
22852282

22862283
if self.is_pressed == wait_for_press:
22872284
return True
22882285

22892286
if timeout_ms is not None and time.time() >= tic + timeout_ms / 1000:
22902287
return False
22912288

2292-
def wait_for_pressed(self, timeout_ms=None):
2293-
return self._wait(True, timeout_ms)
2289+
if sleep_ms:
2290+
time.sleep(sleep_ms)
2291+
2292+
def wait_for_pressed(self, timeout_ms=None, sleep_ms=10):
2293+
return self._wait(True, timeout_ms, sleep_ms)
22942294

2295-
def wait_for_released(self, timeout_ms=None):
2296-
return self._wait(False, timeout_ms)
2295+
def wait_for_released(self, timeout_ms=None, sleep_ms=10):
2296+
return self._wait(False, timeout_ms, sleep_ms)
22972297

2298-
def wait_for_bump(self, timeout_ms=None):
2298+
def wait_for_bump(self, timeout_ms=None, sleep_ms=10):
22992299
"""
23002300
Wait for the touch sensor to be pressed down and then released.
23012301
Both actions must happen within timeout_ms.
23022302
"""
23032303
start_time = time.time()
23042304

2305-
if self.wait_for_pressed(timeout_ms):
2305+
if self.wait_for_pressed(timeout_ms, sleep_ms):
23062306
if timeout_ms is not None:
23072307
timeout_ms -= int((time.time() - start_time) * 1000)
2308-
return self.wait_for_released(timeout_ms)
2308+
return self.wait_for_released(timeout_ms, sleep_ms)
23092309

23102310
return False
23112311

@@ -2629,6 +2629,8 @@ class ButtonBase(object):
26292629
Abstract button interface.
26302630
"""
26312631

2632+
_state = set([])
2633+
26322634
def __str__(self):
26332635
return self.__class__.__name__
26342636

@@ -2641,8 +2643,6 @@ def on_change(changed_buttons):
26412643
"""
26422644
pass
26432645

2644-
_state = set([])
2645-
26462646
def any(self):
26472647
"""
26482648
Checks if any button is pressed.
@@ -2655,6 +2655,19 @@ def check_buttons(self, buttons=[]):
26552655
"""
26562656
return set(self.buttons_pressed) == set(buttons)
26572657

2658+
@property
2659+
def evdev_device(self):
2660+
"""
2661+
Return our corresponding evdev device object
2662+
"""
2663+
devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()]
2664+
2665+
for device in devices:
2666+
if device.name == self.evdev_device_name:
2667+
return device
2668+
2669+
raise Exception("%s: could not find evdev device '%s'" % (self, self.evdev_device_name))
2670+
26582671
def process(self, new_state=None):
26592672
"""
26602673
Check for currenly pressed buttons. If the new state differs from the
@@ -2673,10 +2686,69 @@ def process(self, new_state=None):
26732686
if self.on_change is not None and state_diff:
26742687
self.on_change([(button, button in new_state) for button in state_diff])
26752688

2689+
def process_forever(self):
2690+
for event in self.evdev_device.read_loop():
2691+
if event.type == evdev.ecodes.EV_KEY:
2692+
self.process()
2693+
26762694
@property
26772695
def buttons_pressed(self):
26782696
raise NotImplementedError()
26792697

2698+
def _wait(self, wait_for_button_press, wait_for_button_release, timeout_ms):
2699+
tic = time.time()
2700+
2701+
# wait_for_button_press/release can be a list of buttons or a string
2702+
# with the name of a single button. If it is a string of a single
2703+
# button convert that to a list.
2704+
if isinstance(wait_for_button_press, str):
2705+
wait_for_button_press = [wait_for_button_press, ]
2706+
2707+
if isinstance(wait_for_button_release, str):
2708+
wait_for_button_release = [wait_for_button_release, ]
2709+
2710+
for event in self.evdev_device.read_loop():
2711+
if event.type == evdev.ecodes.EV_KEY:
2712+
all_pressed = True
2713+
all_released = True
2714+
pressed = self.buttons_pressed
2715+
2716+
for button in wait_for_button_press:
2717+
if button not in pressed:
2718+
all_pressed = False
2719+
break
2720+
2721+
for button in wait_for_button_release:
2722+
if button in pressed:
2723+
all_released = False
2724+
break
2725+
2726+
if all_pressed and all_released:
2727+
return True
2728+
2729+
if timeout_ms is not None and time.time() >= tic + timeout_ms / 1000:
2730+
return False
2731+
2732+
def wait_for_pressed(self, buttons, timeout_ms=None):
2733+
return self._wait(buttons, [], timeout_ms)
2734+
2735+
def wait_for_released(self, buttons, timeout_ms=None):
2736+
return self._wait([], buttons, timeout_ms)
2737+
2738+
def wait_for_bump(self, buttons, timeout_ms=None):
2739+
"""
2740+
Wait for the button to be pressed down and then released.
2741+
Both actions must happen within timeout_ms.
2742+
"""
2743+
start_time = time.time()
2744+
2745+
if self.wait_for_pressed(buttons, timeout_ms):
2746+
if timeout_ms is not None:
2747+
timeout_ms -= int((time.time() - start_time) * 1000)
2748+
return self.wait_for_released(buttons, timeout_ms)
2749+
2750+
return False
2751+
26802752

26812753
class InfraredSensor(Sensor, ButtonBase):
26822754
"""
@@ -3141,8 +3213,10 @@ class ButtonEVIO(ButtonBase):
31413213
_buttons = {}
31423214

31433215
def __init__(self):
3216+
ButtonBase.__init__(self)
31443217
self._file_cache = {}
31453218
self._buffer_cache = {}
3219+
31463220
for b in self._buttons:
31473221
name = self._buttons[b]['name']
31483222
if name not in self._file_cache:
@@ -3167,8 +3241,10 @@ def buttons_pressed(self):
31673241
for k, v in self._buttons.items():
31683242
buf = self._buffer_cache[v['name']]
31693243
bit = v['value']
3244+
31703245
if bool(buf[int(bit / 8)] & 1 << bit % 8):
3171-
pressed += [k]
3246+
pressed.append(k)
3247+
31723248
return pressed
31733249

31743250

ev3dev/ev3.py

Lines changed: 19 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -102,69 +102,27 @@ class Button(ButtonEVIO):
102102
EV3 Buttons
103103
"""
104104

105-
@staticmethod
106-
def on_up(state):
107-
"""
108-
This handler is called by `process()` whenever state of 'up' button
109-
has changed since last `process()` call. `state` parameter is the new
110-
state of the button.
111-
"""
112-
pass
113-
114-
@staticmethod
115-
def on_down(state):
116-
"""
117-
This handler is called by `process()` whenever state of 'down' button
118-
has changed since last `process()` call. `state` parameter is the new
119-
state of the button.
120-
"""
121-
pass
122-
123-
@staticmethod
124-
def on_left(state):
125-
"""
126-
This handler is called by `process()` whenever state of 'left' button
127-
has changed since last `process()` call. `state` parameter is the new
128-
state of the button.
129-
"""
130-
pass
131-
132-
@staticmethod
133-
def on_right(state):
134-
"""
135-
This handler is called by `process()` whenever state of 'right' button
136-
has changed since last `process()` call. `state` parameter is the new
137-
state of the button.
138-
"""
139-
pass
140-
141-
@staticmethod
142-
def on_enter(state):
143-
"""
144-
This handler is called by `process()` whenever state of 'enter' button
145-
has changed since last `process()` call. `state` parameter is the new
146-
state of the button.
147-
"""
148-
pass
149-
150-
@staticmethod
151-
def on_backspace(state):
152-
"""
153-
This handler is called by `process()` whenever state of 'backspace' button
154-
has changed since last `process()` call. `state` parameter is the new
155-
state of the button.
156-
"""
157-
pass
158-
159-
105+
_buttons_filename = '/dev/input/by-path/platform-gpio_keys-event'
160106
_buttons = {
161-
'up': {'name': '/dev/input/by-path/platform-gpio_keys-event', 'value': 103},
162-
'down': {'name': '/dev/input/by-path/platform-gpio_keys-event', 'value': 108},
163-
'left': {'name': '/dev/input/by-path/platform-gpio_keys-event', 'value': 105},
164-
'right': {'name': '/dev/input/by-path/platform-gpio_keys-event', 'value': 106},
165-
'enter': {'name': '/dev/input/by-path/platform-gpio_keys-event', 'value': 28},
166-
'backspace': {'name': '/dev/input/by-path/platform-gpio_keys-event', 'value': 14},
107+
'up': {'name': _buttons_filename, 'value': 103},
108+
'down': {'name': _buttons_filename, 'value': 108},
109+
'left': {'name': _buttons_filename, 'value': 105},
110+
'right': {'name': _buttons_filename, 'value': 106},
111+
'enter': {'name': _buttons_filename, 'value': 28},
112+
'backspace': {'name': _buttons_filename, 'value': 14},
167113
}
114+
evdev_device_name = 'EV3 brick buttons'
115+
116+
'''
117+
These handlers are called by `process()` whenever state of 'up', 'down',
118+
etc buttons have changed since last `process()` call
119+
'''
120+
on_up = None
121+
on_down = None
122+
on_left = None
123+
on_right = None
124+
on_enter = None
125+
on_backspace = None
168126

169127
@property
170128
def up(self):

0 commit comments

Comments
 (0)