legoEv3/ev3dev2/sensor/__init__.py
# -----------------------------------------------------------------------------
# Copyright (c) 2015 Ralph Hempel <rhempel@hempeldesigngroup.com>
# Copyright (c) 2015 Anton Vanhoucke <antonvh@gmail.com>
# Copyright (c) 2015 Denis Demidov <dennis.demidov@gmail.com>
# Copyright (c) 2015 Eric Pascual <eric@pobot.org>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# -----------------------------------------------------------------------------
import sys
if sys.version_info < (3,4):
raise SystemError('Must be using Python 3.4 or higher')
import numbers
from os.path import abspath
from struct import unpack
from ev3dev2 import get_current_platform, Device, list_device_names
# INPUT ports have platform specific values that we must import
platform = get_current_platform()
if platform == 'ev3':
from ev3dev2._platform.ev3 import INPUT_1, INPUT_2, INPUT_3, INPUT_4
elif platform == 'evb':
from ev3dev2._platform.evb import INPUT_1, INPUT_2, INPUT_3, INPUT_4
elif platform == 'pistorms':
from ev3dev2._platform.pistorms import INPUT_1, INPUT_2, INPUT_3, INPUT_4
elif platform == 'brickpi':
from ev3dev2._platform.brickpi import INPUT_1, INPUT_2, INPUT_3, INPUT_4
elif platform == 'brickpi3':
from ev3dev2._platform.brickpi3 import INPUT_1, INPUT_2, INPUT_3, INPUT_4
elif platform == 'fake':
from ev3dev2._platform.fake import INPUT_1, INPUT_2, INPUT_3, INPUT_4
else:
raise Exception("Unsupported platform '%s'" % platform)
class Sensor(Device):
"""
The sensor class provides a uniform interface for using most of the
sensors available for the EV3.
"""
SYSTEM_CLASS_NAME = 'lego-sensor'
SYSTEM_DEVICE_NAME_CONVENTION = 'sensor*'
__slots__ = [
'_address',
'_command',
'_commands',
'_decimals',
'_driver_name',
'_mode',
'_modes',
'_num_values',
'_units',
'_value',
'_bin_data_format',
'_bin_data_size',
'_bin_data',
'_mode_scale'
]
def __init__(self, address=None, name_pattern=SYSTEM_DEVICE_NAME_CONVENTION, name_exact=False, **kwargs):
if address is not None:
kwargs['address'] = address
super(Sensor, self).__init__(self.SYSTEM_CLASS_NAME, name_pattern, name_exact, **kwargs)
self._address = None
self._command = None
self._commands = None
self._decimals = None
self._driver_name = None
self._mode = None
self._modes = None
self._num_values = None
self._units = None
self._value = [None,None,None,None,None,None,None,None]
self._bin_data_format = None
self._bin_data_size = None
self._bin_data = None
self._mode_scale = {}
def _scale(self, mode):
"""
Returns value scaling coefficient for the given mode.
"""
if mode in self._mode_scale:
scale = self._mode_scale[mode]
else:
scale = 10**(-self.decimals)
self._mode_scale[mode] = scale
return scale
@property
def address(self):
"""
Returns the name of the port that the sensor is connected to, e.g. `ev3:in1`.
I2C sensors also include the I2C address (decimal), e.g. `ev3:in1:i2c8`.
"""
self._address, value = self.get_attr_string(self._address, 'address')
return value
@property
def command(self):
"""
Sends a command to the sensor.
"""
raise Exception("command is a write-only property!")
@command.setter
def command(self, value):
self._command = self.set_attr_string(self._command, 'command', value)
@property
def commands(self):
"""
Returns a list of the valid commands for the sensor.
Returns -EOPNOTSUPP if no commands are supported.
"""
self._commands, value = self.get_attr_set(self._commands, 'commands')
return value
@property
def decimals(self):
"""
Returns the number of decimal places for the values in the `value<N>`
attributes of the current mode.
"""
self._decimals, value = self.get_attr_int(self._decimals, 'decimals')
return value
@property
def driver_name(self):
"""
Returns the name of the sensor device/driver. See the list of [supported
sensors] for a complete list of drivers.
"""
self._driver_name, value = self.get_attr_string(self._driver_name, 'driver_name')
return value
@property
def mode(self):
"""
Returns the current mode. Writing one of the values returned by `modes`
sets the sensor to that mode.
"""
self._mode, value = self.get_attr_string(self._mode, 'mode')
return value
@mode.setter
def mode(self, value):
self._mode = self.set_attr_string(self._mode, 'mode', value)
@property
def modes(self):
"""
Returns a list of the valid modes for the sensor.
"""
self._modes, value = self.get_attr_set(self._modes, 'modes')
return value
@property
def num_values(self):
"""
Returns the number of `value<N>` attributes that will return a valid value
for the current mode.
"""
self._num_values, value = self.get_attr_int(self._num_values, 'num_values')
return value
@property
def units(self):
"""
Returns the units of the measured value for the current mode. May return
empty string
"""
self._units, value = self.get_attr_string(self._units, 'units')
return value
def value(self, n=0):
"""
Returns the value or values measured by the sensor. Check num_values to
see how many values there are. Values with N >= num_values will return
an error. The values are fixed point numbers, so check decimals to see
if you need to divide to get the actual value.
"""
n = int(n)
self._value[n], value = self.get_attr_int(self._value[n], 'value'+str(n))
return value
@property
def bin_data_format(self):
"""
Returns the format of the values in `bin_data` for the current mode.
Possible values are:
- `u8`: Unsigned 8-bit integer (byte)
- `s8`: Signed 8-bit integer (sbyte)
- `u16`: Unsigned 16-bit integer (ushort)
- `s16`: Signed 16-bit integer (short)
- `s16_be`: Signed 16-bit integer, big endian
- `s32`: Signed 32-bit integer (int)
- `float`: IEEE 754 32-bit floating point (float)
"""
self._bin_data_format, value = self.get_attr_string(self._bin_data_format, 'bin_data_format')
return value
def bin_data(self, fmt=None):
"""
Returns the unscaled raw values in the `value<N>` attributes as raw byte
array. Use `bin_data_format`, `num_values` and the individual sensor
documentation to determine how to interpret the data.
Use `fmt` to unpack the raw bytes into a struct.
Example::
>>> from ev3dev2.sensor.lego import InfraredSensor
>>> ir = InfraredSensor()
>>> ir.value()
28
>>> ir.bin_data('<b')
(28,)
"""
if self._bin_data_size == None:
self._bin_data_size = {
"u8": 1,
"s8": 1,
"u16": 2,
"s16": 2,
"s16_be": 2,
"s32": 4,
"float": 4
}.get(self.bin_data_format, 1) * self.num_values
if None == self._bin_data:
self._bin_data = self._attribute_file_open( 'bin_data' )
self._bin_data.seek(0)
raw = bytearray(self._bin_data.read(self._bin_data_size))
if fmt is None: return raw
return unpack(fmt, raw)
def _ensure_mode(self, mode):
if self.mode != mode:
self.mode = mode
def list_sensors(name_pattern=Sensor.SYSTEM_DEVICE_NAME_CONVENTION, **kwargs):
"""
This is a generator function that enumerates all sensors that match the
provided arguments.
Parameters:
name_pattern: pattern that device name should match.
For example, 'sensor*'. Default value: '*'.
keyword arguments: used for matching the corresponding device
attributes. For example, driver_name='lego-ev3-touch', or
address=['in1', 'in3']. When argument value is a list,
then a match against any entry of the list is enough.
"""
class_path = abspath(Device.DEVICE_ROOT_PATH + '/' + Sensor.SYSTEM_CLASS_NAME)
return (Sensor(name_pattern=name, name_exact=True)
for name in list_device_names(class_path, name_pattern, **kwargs))
class I2cSensor(Sensor):
"""
A generic interface to control I2C-type EV3 sensors.
"""
SYSTEM_CLASS_NAME = Sensor.SYSTEM_CLASS_NAME
SYSTEM_DEVICE_NAME_CONVENTION = 'sensor*'
def __init__(self, address=None, name_pattern=SYSTEM_DEVICE_NAME_CONVENTION, name_exact=False, **kwargs):
super(I2cSensor, self).__init__(address, name_pattern, name_exact, driver_name=['nxt-i2c-sensor'], **kwargs)
self._fw_version = None
self._poll_ms = None
@property
def fw_version(self):
"""
Returns the firmware version of the sensor if available. Currently only
I2C/NXT sensors support this.
"""
self._fw_version, value = self.get_attr_string(self._fw_version, 'fw_version')
return value
@property
def poll_ms(self):
"""
Returns the polling period of the sensor in milliseconds. Writing sets the
polling period. Setting to 0 disables polling. Minimum value is hard
coded as 50 msec. Returns -EOPNOTSUPP if changing polling is not supported.
Currently only I2C/NXT sensors support changing the polling period.
"""
self._poll_ms, value = self.get_attr_int(self._poll_ms, 'poll_ms')
return value
@poll_ms.setter
def poll_ms(self, value):
self._poll_ms = self.set_attr_int(self._poll_ms, 'poll_ms', value)