Skip to content
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ python:
sudo: false
install:
- pip install Pillow
- pip install evdev
script:
- ./tests/api_tests.py
deploy:
Expand Down
120 changes: 98 additions & 22 deletions ev3dev/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import stat
import time
import errno
import evdev
from os.path import abspath
from struct import pack, unpack
from subprocess import Popen, check_output, PIPE
Expand Down Expand Up @@ -137,6 +138,7 @@ def matches(attribute, pattern):
if all([matches(path + '/' + k, kwargs[k]) for k in kwargs]):
yield f


# -----------------------------------------------------------------------------
# Define the base class from which all other ev3dev classes are defined.

Expand Down Expand Up @@ -205,8 +207,8 @@ def __str__(self):
else:
return self.__class__.__name__

def _attribute_file_open( self, name ):
path = self._path + '/' + name
def _attribute_file_open(self, name):
path = os.path.join(self._path, name)
mode = stat.S_IMODE(os.stat(path)[stat.ST_MODE])
r_ok = mode & stat.S_IRGRP
w_ok = mode & stat.S_IWGRP
Expand Down Expand Up @@ -2245,8 +2247,6 @@ class TouchSensor(Sensor):
Touch Sensor
"""

__slots__ = ['_poll', '_value0']

SYSTEM_CLASS_NAME = Sensor.SYSTEM_CLASS_NAME
SYSTEM_DEVICE_NAME_CONVENTION = Sensor.SYSTEM_DEVICE_NAME_CONVENTION

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

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

@property
def is_pressed(self):
Expand All @@ -2272,40 +2270,42 @@ def is_pressed(self):
def is_released(self):
return not self.is_pressed

def _wait(self, wait_for_press, timeout_ms):
def _wait(self, wait_for_press, timeout_ms, sleep_ms):
tic = time.time()

if self._poll is None:
self._value0 = self._attribute_file_open('value0')
self._poll = select.poll()
self._poll.register(self._value0, select.POLLPRI)
if sleep_ms:
sleep_ms = float(sleep_ms/1000)

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

if self.is_pressed == wait_for_press:
return True

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

def wait_for_pressed(self, timeout_ms=None):
return self._wait(True, timeout_ms)
if sleep_ms:
time.sleep(sleep_ms)

def wait_for_pressed(self, timeout_ms=None, sleep_ms=10):
return self._wait(True, timeout_ms, sleep_ms)

def wait_for_released(self, timeout_ms=None):
return self._wait(False, timeout_ms)
def wait_for_released(self, timeout_ms=None, sleep_ms=10):
return self._wait(False, timeout_ms, sleep_ms)

def wait_for_bump(self, timeout_ms=None):
def wait_for_bump(self, timeout_ms=None, sleep_ms=10):
"""
Wait for the touch sensor to be pressed down and then released.
Both actions must happen within timeout_ms.
"""
start_time = time.time()

if self.wait_for_pressed(timeout_ms):
if self.wait_for_pressed(timeout_ms, sleep_ms):
if timeout_ms is not None:
timeout_ms -= int((time.time() - start_time) * 1000)
return self.wait_for_released(timeout_ms)
return self.wait_for_released(timeout_ms, sleep_ms)

return False

Expand Down Expand Up @@ -2629,6 +2629,8 @@ class ButtonBase(object):
Abstract button interface.
"""

_state = set([])

def __str__(self):
return self.__class__.__name__

Expand All @@ -2641,8 +2643,6 @@ def on_change(changed_buttons):
"""
pass

_state = set([])

def any(self):
"""
Checks if any button is pressed.
Expand All @@ -2655,6 +2655,19 @@ def check_buttons(self, buttons=[]):
"""
return set(self.buttons_pressed) == set(buttons)

@property
def evdev_device(self):
"""
Return our corresponding evdev device object
"""
devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()]

for device in devices:
if device.name == self.evdev_device_name:
return device

raise Exception("%s: could not find evdev device '%s'" % (self, self.evdev_device_name))

def process(self, new_state=None):
"""
Check for currenly pressed buttons. If the new state differs from the
Expand All @@ -2673,10 +2686,69 @@ def process(self, new_state=None):
if self.on_change is not None and state_diff:
self.on_change([(button, button in new_state) for button in state_diff])

def process_forever(self):
for event in self.evdev_device.read_loop():
if event.type == evdev.ecodes.EV_KEY:
self.process()

@property
def buttons_pressed(self):
raise NotImplementedError()

def _wait(self, wait_for_button_press, wait_for_button_release, timeout_ms):
tic = time.time()

# wait_for_button_press/release can be a list of buttons or a string
# with the name of a single button. If it is a string of a single
# button convert that to a list.
if isinstance(wait_for_button_press, str):
wait_for_button_press = [wait_for_button_press, ]

if isinstance(wait_for_button_release, str):
wait_for_button_release = [wait_for_button_release, ]

for event in self.evdev_device.read_loop():
if event.type == evdev.ecodes.EV_KEY:
all_pressed = True
all_released = True
pressed = self.buttons_pressed

for button in wait_for_button_press:
if button not in pressed:
all_pressed = False
break

for button in wait_for_button_release:
if button in pressed:
all_released = False
break

if all_pressed and all_released:
return True

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

def wait_for_pressed(self, buttons, timeout_ms=None):
return self._wait(buttons, [], timeout_ms)

def wait_for_released(self, buttons, timeout_ms=None):
return self._wait([], buttons, timeout_ms)

def wait_for_bump(self, buttons, timeout_ms=None):
"""
Wait for the button to be pressed down and then released.
Both actions must happen within timeout_ms.
"""
start_time = time.time()

if self.wait_for_pressed(buttons, timeout_ms):
if timeout_ms is not None:
timeout_ms -= int((time.time() - start_time) * 1000)
return self.wait_for_released(buttons, timeout_ms)

return False


class InfraredSensor(Sensor, ButtonBase):
"""
Expand Down Expand Up @@ -3141,8 +3213,10 @@ class ButtonEVIO(ButtonBase):
_buttons = {}

def __init__(self):
ButtonBase.__init__(self)
self._file_cache = {}
self._buffer_cache = {}

for b in self._buttons:
name = self._buttons[b]['name']
if name not in self._file_cache:
Expand All @@ -3167,8 +3241,10 @@ def buttons_pressed(self):
for k, v in self._buttons.items():
buf = self._buffer_cache[v['name']]
bit = v['value']

if bool(buf[int(bit / 8)] & 1 << bit % 8):
pressed += [k]
pressed.append(k)

return pressed


Expand Down
80 changes: 19 additions & 61 deletions ev3dev/ev3.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,69 +102,27 @@ class Button(ButtonEVIO):
EV3 Buttons
"""

@staticmethod
def on_up(state):
"""
This handler is called by `process()` whenever state of 'up' button
has changed since last `process()` call. `state` parameter is the new
state of the button.
"""
pass

@staticmethod
def on_down(state):
"""
This handler is called by `process()` whenever state of 'down' button
has changed since last `process()` call. `state` parameter is the new
state of the button.
"""
pass

@staticmethod
def on_left(state):
"""
This handler is called by `process()` whenever state of 'left' button
has changed since last `process()` call. `state` parameter is the new
state of the button.
"""
pass

@staticmethod
def on_right(state):
"""
This handler is called by `process()` whenever state of 'right' button
has changed since last `process()` call. `state` parameter is the new
state of the button.
"""
pass

@staticmethod
def on_enter(state):
"""
This handler is called by `process()` whenever state of 'enter' button
has changed since last `process()` call. `state` parameter is the new
state of the button.
"""
pass

@staticmethod
def on_backspace(state):
"""
This handler is called by `process()` whenever state of 'backspace' button
has changed since last `process()` call. `state` parameter is the new
state of the button.
"""
pass


_buttons_filename = '/dev/input/by-path/platform-gpio_keys-event'
_buttons = {
'up': {'name': '/dev/input/by-path/platform-gpio_keys-event', 'value': 103},
'down': {'name': '/dev/input/by-path/platform-gpio_keys-event', 'value': 108},
'left': {'name': '/dev/input/by-path/platform-gpio_keys-event', 'value': 105},
'right': {'name': '/dev/input/by-path/platform-gpio_keys-event', 'value': 106},
'enter': {'name': '/dev/input/by-path/platform-gpio_keys-event', 'value': 28},
'backspace': {'name': '/dev/input/by-path/platform-gpio_keys-event', 'value': 14},
'up': {'name': _buttons_filename, 'value': 103},
'down': {'name': _buttons_filename, 'value': 108},
'left': {'name': _buttons_filename, 'value': 105},
'right': {'name': _buttons_filename, 'value': 106},
'enter': {'name': _buttons_filename, 'value': 28},
'backspace': {'name': _buttons_filename, 'value': 14},
}
evdev_device_name = 'EV3 brick buttons'

'''
These handlers are called by `process()` whenever state of 'up', 'down',
etc buttons have changed since last `process()` call
'''
on_up = None
on_down = None
on_left = None
on_right = None
on_enter = None
on_backspace = None

@property
def up(self):
Expand Down
Loading