# Copyright (C) 2020 Alex Sonea
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
from ..utils import check_key, check_type, check_options, check_not_empty
from .device import BaseDevice
logger = logging.getLogger(__name__)
[docs]class Sensor():
"""A one-value sensor.
A sensor is associated with a device and has at least a connection
to a register in that device that represents the value the sensor is
representing. In addition a sensor can have an optional register used
to activate or deactivate the device and can publish a ``value`` that
can be either boolean if the ``bits`` parameter is used or float, in
which case the sensor can also apply an ``inverse`` and and ``offset``
to the values read from the device registry.
Parameters
----------
name: str
The name of the sensor
device: BaseDevice or subclass
The device associated with the sensor
value_read: str
The name of the register in device used to retrieve the sensor's
value
activate: str or None
The name of the register used to activate the device. If ``None``
is used no activation for the device can be done and the sensor
is by default assumed to be activated.
inverse: bool
Indicates if the value read from the register should be inverted
before being presented to the user in the :py:meth:`value`. The
inverse operation is performed before the ``offset`` (see below).
Default is ``False``. It is ignored if ``bits`` property is used.
offset: float
Indicates an offest to be adder to the value read from the register
(after ``inverse`` if ``True``). Default is 0.0. It is ignored if
``bits`` property is used.
auto: bool
Indicates if the sensor should be automatically activated when the
robot is started (:py:meth:roboglia.base.BaseRobot.`start` method).
Default is ``True``.
"""
[docs] def __init__(self, name='SENSOR', device=None, value_read=None,
activate=None, inverse=False, offset=0.0,
auto=True, **kwargs):
self.__name = name
check_not_empty(device, 'device', 'sensor', self.name, logger)
check_type(device, BaseDevice, 'sensor', self.name, logger)
self.__device = device
check_not_empty(value_read, 'value_read', 'sensor', self.name, logger)
check_key(value_read, device.__dict__, 'sensor', self.name, logger,
f'device {device.name} does not have a register '
f'{value_read}')
self.__value_r = getattr(device, value_read)
if activate:
check_key(activate, device.__dict__, 'sensor', self.__name, logger,
f'device {device.name} does not have a register '
f'{activate}')
self.__activate = getattr(device, activate)
else:
self.__activate = activate
check_options(inverse, [True, False], 'sensor', self.name, logger)
self.__inverse = inverse
check_type(offset, float, 'sensor', self.name, logger)
self.__offset = offset
check_options(auto, [True, False], 'joint', self.name, logger)
self.__auto_activate = auto
@property
def name(self):
"""The name of the sensor."""
return self.__name
@property
def device(self):
"""The devices associated with the sensor."""
return self.__device
@property
def read_register(self):
"""The register used to access the sensor value."""
return self.__value_r
@property
def activate_register(self):
"""(read-only) The register for activation sensor."""
return self.__activate
@property
def active(self):
"""(read-write) Accessor for activating the senser. If the activation
registry was not specified (``None``) the method will return
``True`` (assumes the sensors are active by default if not
controllable.
The setter will log a warning if you try to assign a value to this
property if there is no register assigned to it.
Returns
-------
bool:
Value of the activate register or ``True`` if no register was
specified when the sensor was created.
"""
if self.__activate:
return self.__activate.value
# default
return True
@active.setter
def active(self, value):
if self.__activate:
self.__activate.value = value
else:
logger.warning(f'attempted to change activation of sensor '
f'{self.name} that does not have an activation '
'registry assigned.')
@property
def auto_activate(self):
"""Indicates if the joint should automatically be activated when
the robot starts."""
return self.__auto_activate
@property
def inverse(self):
"""(read-only) sensor uses inverse coordinates versus the device."""
return self.__inverse
@property
def offset(self):
"""(read-only) The offset between sensor coords and device coords."""
return self.__offset
@property
def value(self):
"""Returns the value of the sensor.
Returns
-------
bool or float:
The value of the register is adjusted with the
``offset`` and the ``inverse`` attributes.
"""
reg_value = self.read_register.value
if isinstance(reg_value, bool):
# bool return
return ~ reg_value if self.inverse else reg_value
# float return
if self.inverse:
reg_value = - reg_value
return reg_value + self.offset
[docs]class SensorXYZ():
"""An XYZ sensor.
A sensor is associated with a device and has connections to 3 registers
in that device that represents the X, Y and Z values the sensor is
representing. In addition a sensor can have an optional register used
to activate or deactivate the device and can publish ``X``, ``Y`` and
``Z`` values that are floats where the sensor applies an ``inverse``
and and ``offset`` to the values read from the device registry.
Parameters
----------
name: str
The name of the sensor
device: BaseDevice or subclass
The device associated with the sensor
x_read: str
The name of the register in device used to retrieve the sensor's
value for x
x_inverse: bool
Indicates if the value read from the x register should be inverted
before being presented to the user in the :py:meth:`x`. The
inverse operation is performed before the ``x_offset`` (see below).
Default is ``False``.
x_offset: float
Indicates an offest to be adder to the value read from the register x
(after ``x_inverse`` if ``True``). Default is 0.0.
y_read: str
The name of the register in device used to retrieve the sensor's
value for y
y_inverse: bool
Indicates if the value read from the y register should be inverted
before being presented to the user in the :py:meth:`y`. The
inverse operation is performed before the ``y_offset`` (see below).
Default is ``False``.
y_offset: float
Indicates an offest to be adder to the value read from the register y
(after ``y_inverse`` if ``True``). Default is 0.0.
z_read: str
The name of the register in device used to retrieve the sensor's
value for z
z_inverse: bool
Indicates if the value read from the x register should be inverted
before being presented to the user in the :py:meth:`z`. The
inverse operation is performed before the ``z_offset`` (see below).
Default is ``False``.
z_offset: float
Indicates an offest to be adder to the value read from the register z
(after ``z_inverse`` if ``True``). Default is 0.0.
activate: str or None
The name of the register used to activate the device. If ``None``
is used no activation for the device can be done and the sensor
is by default assumed to be activated.
auto: bool
Indicates if the sensor should be automatically activated when the
robot is started (:py:meth:roboglia.base.BaseRobot.`start` method).
Default is ``True``.
"""
[docs] def __init__(self, name='SENSOR-XYZ', device=None,
x_read=None, x_inverse=False, x_offset=0.0,
y_read=None, y_inverse=False, y_offset=0.0,
z_read=None, z_inverse=False, z_offset=0.0,
activate=None, auto=True, **kwargs):
self.__name = name
check_not_empty(device, 'device', 'sensor', self.name, logger)
check_type(device, BaseDevice, 'sensor', self.name, logger)
self.__device = device
# X - value
check_not_empty(x_read, 'x_read', 'sensor', self.name, logger)
check_key(x_read, device.__dict__, 'sensor', self.name, logger,
f'device {device.name} does not have a register '
f'{x_read}')
self.__x_read = getattr(device, x_read)
check_options(x_inverse, [True, False], 'sensor', self.name, logger)
self.__x_inverse = x_inverse
check_type(x_offset, float, 'sensor', self.name, logger)
self.__x_offset = x_offset
# Y - value
check_not_empty(y_read, 'y_read', 'sensor', self.name, logger)
check_key(y_read, device.__dict__, 'sensor', self.name, logger,
f'device {device.name} does not have a register '
f'{y_read}')
self.__y_read = getattr(device, y_read)
check_options(y_inverse, [True, False], 'sensor', self.name, logger)
self.__y_inverse = y_inverse
check_type(y_offset, float, 'sensor', self.name, logger)
self.__y_offset = y_offset
# Z - value
check_not_empty(z_read, 'z_read', 'sensor', self.name, logger)
check_key(z_read, device.__dict__, 'sensor', self.name, logger,
f'device {device.name} does not have a register '
f'{z_read}')
self.__z_read = getattr(device, z_read)
check_options(z_inverse, [True, False], 'sensor', self.name, logger)
self.__z_inverse = z_inverse
check_type(z_offset, float, 'sensor', self.name, logger)
self.__z_offset = z_offset
# activate
if activate:
check_key(activate, device.__dict__, 'sensor', self.__name, logger,
f'device {device.name} does not have a register '
f'{activate}')
self.__activate = getattr(device, activate)
else:
self.__activate = activate
check_options(auto, [True, False], 'joint', self.name, logger)
self.__auto_activate = auto
@property
def name(self):
"""The name of the sensor."""
return self.__name
@property
def device(self):
"""The devices associated with the sensor."""
return self.__device
@property
def x_register(self):
"""The register used to access the sensor X value."""
return self.__x_read
@property
def x_inverse(self):
"""(read-only) Sensor uses inverse coordinates versus the device for
X value."""
return self.__x_inverse
@property
def x_offset(self):
"""(read-only) The offset between sensor coords and device coords for
X value."""
return self.__x_offset
@property
def y_register(self):
"""The register used to access the sensor Y value."""
return self.__y_read
@property
def y_inverse(self):
"""(read-only) Sensor uses inverse coordinates versus the device for
Y value."""
return self.__y_inverse
@property
def y_offset(self):
"""(read-only) The offset between sensor coords and device coords for
Y value."""
return self.__y_offset
@property
def z_register(self):
"""The register used to access the sensor Z value."""
return self.__z_read
@property
def z_inverse(self):
"""(read-only) Sensor uses inverse coordinates versus the device for
Z value."""
return self.__z_inverse
@property
def z_offset(self):
"""(read-only) The offset between sensor coords and device coords for
Z value."""
return self.__z_offset
@property
def activate_register(self):
"""(read-only) The register for activation sensor."""
return self.__activate
@property
def active(self):
"""(read-write) Accessor for activating the senser. If the activation
registry was not specified (``None``) the method will return
``True`` (assumes the sensors are active by default if not
controllable.
The setter will log a warning if you try to assign a value to this
property if there is no register assigned to it.
Returns
-------
bool:
Value of the activate register or ``True`` if no register was
specified when the sensor was created.
"""
if self.__activate:
return self.__activate.value
# default
return True
@active.setter
def active(self, value):
if self.__activate:
self.__activate.value = value
else:
logger.warning(f'attempted to change activation of sensor '
f'{self.name} that does not have an activation '
'registry assigned.')
@property
def auto_activate(self):
"""Indicates if the joint should automatically be activated when
the robot starts."""
return self.__auto_activate
@property
def x(self):
"""Returns the processed X value of register."""
reg_value = self.x_register.value
if self.x_inverse:
reg_value = - reg_value
return reg_value + self.x_offset
@property
def y(self):
"""Returns the processed Y value of register."""
reg_value = self.y_register.value
if self.y_inverse:
reg_value = - reg_value
return reg_value + self.y_offset
@property
def z(self):
"""Returns the processed Z value of register."""
reg_value = self.z_register.value
if self.z_inverse:
reg_value = - reg_value
return reg_value + self.z_offset
@property
def value(self):
"""Returns the value of the sensor as a tuple (X, Y, Z)."""
return (self.x, self.y, self.z)