diff --git a/docs/source/conf.py b/docs/source/conf.py index 9d5a34bb..ffc0db8c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -223,6 +223,7 @@ autodoc_mock_imports = ['googlesamples.assistant.grpc.audio_helpers', 'envirophat', 'gps', 'picamera', + 'pwm3901', ] sys.path.insert(0, os.path.abspath('../..')) diff --git a/platypush/backend/sensor/motion/__init__.py b/platypush/backend/sensor/motion/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/platypush/backend/sensor/motion/pwm3901.py b/platypush/backend/sensor/motion/pwm3901.py new file mode 100644 index 00000000..20a63c97 --- /dev/null +++ b/platypush/backend/sensor/motion/pwm3901.py @@ -0,0 +1,32 @@ +from platypush.backend.sensor import SensorBackend + + +class SensorPwm3901Backend(SensorBackend): + """ + Backend to poll an `PWM3901 `_ + optical flow and motion sensor + + Requires: + + * ``pwm3901`` (``pip install pwm3901``) + """ + + def __init__(self, absolute=True, relative=True, **kwargs): + """ + :param absolute: Enable absolute motion sensor events (default: true) + :param relative: Enable relative motion sensor events (default: true) + """ + + enabled_sensors = { + 'motion_rel_x': relative, + 'motion_rel_y': relative, + 'motion_rel_mod': relative, + 'motion_abs_x': absolute, + 'motion_abs_y': absolute, + 'motion_abs_mod': absolute, + } + + super().__init__(plugin='gpio.sensor.motion.pwm3901', enabled_sensors=enabled_sensors, **kwargs) + + +# vim:sw=4:ts=4:et: diff --git a/platypush/plugins/gpio/sensor/motion/__init__.py b/platypush/plugins/gpio/sensor/motion/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/platypush/plugins/gpio/sensor/motion/pwm3901.py b/platypush/plugins/gpio/sensor/motion/pwm3901.py new file mode 100644 index 00000000..5135eb4e --- /dev/null +++ b/platypush/plugins/gpio/sensor/motion/pwm3901.py @@ -0,0 +1,107 @@ +import enum +import math + +# noinspection PyUnresolvedReferences,PyPackageRequirements +from pmw3901 import PMW3901, BG_CS_FRONT_BCM, BG_CS_BACK_BCM + +from platypush.plugins import action +from platypush.plugins.gpio.sensor import GpioSensorPlugin + + +class Rotation(enum.IntEnum): + ROTATE_0 = 0 + ROTATE_90 = 90 + ROTATE_180 = 180 + ROTATE_270 = 270 + + +class SPISlot(enum.Enum): + FRONT = 'front', + BACK = 'back' + + +class GpioSensorMotionPwm3901Plugin(GpioSensorPlugin): + """ + Plugin to interact with an `PWM3901 `_ + optical flow and motion sensor + + Requires: + + * ``pwm3901`` (``pip install pwm3901``) + """ + + def __init__(self, rotation=Rotation.ROTATE_0.value, spi_slot=SPISlot.FRONT.value, spi_port=0, **kwargs): + """ + :param rotation: Rotation angle for the captured optical flow. Possible options: 0, 90, 180, 270 (default: 0) + :type rotation: int + + :param spi_slot: SPI slot where the sensor is connected if you're using a Breakout Garden interface. + Possible options: 'front', 'back' (default: 'front') + :type spi_slot: str + + :param spi_port: SPI port (default: 0) + :type spi_slot: int + """ + super().__init__(**kwargs) + self.spi_port = spi_port + self._sensor = None + (self.x, self.y) = (0, 0) + + try: + if isinstance(rotation, int): + rotation = [r for r in Rotation if r.value == rotation][0] + self.rotation = rotation + except IndexError: + raise ValueError('{} is not a valid value for rotation - possible values: {}'.format( + rotation, [r.value for r in Rotation])) + + try: + if isinstance(spi_slot, str): + spi_slot = [s for s in SPISlot if s.value == spi_slot][0] + + self.spi_slot = BG_CS_FRONT_BCM if spi_slot == SPISlot.FRONT else BG_CS_BACK_BCM + except IndexError: + raise ValueError('{} is not a valid value for spi_slot - possible values: {}'.format( + spi_slot, [s.value for s in SPISlot])) + + def _get_sensor(self): + if not self._sensor: + self._sensor = PMW3901(spi_port=self.spi_port, + spi_cs=1, + spi_cs_gpio=self.spi_slot) + self._sensor.set_rotation(self.rotation) + + return self._sensor + + @action + def get_measurement(self): + """ + :returns: dict. Example:: + + output = { + "motion_rel_x": 0, # Detected relative motion vector X-coord + "motion_rel_y": 1, # Detected relative motion vector Y-coord + "motion_abs_x": 3, # Detected absolute motion vector X-coord + "motion_abs_y": 3, # Detected absolute motion vector Y-coord + "motion_rel_mod": 1, # Detected relative motion vector module + "motion_abs_mod": 5 # Detected absolute motion vector module + } + + """ + + sensor = self._get_sensor() + x, y = sensor.get_motion() + self.x += x + self.y += y + + return { + 'motion_rel_x': x, + 'motion_rel_y': y, + 'motion_abs_x': self.x, + 'motion_abs_y': self.y, + 'motion_rel_mod': math.sqrt(x * x + y * y), + 'motion_abs_mod': math.sqrt(self.x * self.x + self.y * self.y), + } + + +# vim:sw=4:ts=4:et: diff --git a/requirements.txt b/requirements.txt index c66388e8..7633d0f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -162,3 +162,6 @@ pyScss # Support for VL53L1X laser ranger/distance sensor # smbus2 # vl53l1x + +# Support for PWM3901 2-Dimensional Optical Flow Sensor +# pwm3901