Rewritten vl53l1x integration as a runnable plugin with entity support

This commit is contained in:
Fabio Manganiello 2023-03-31 14:25:05 +02:00
parent 226034946f
commit a3e8c7c155
2 changed files with 130 additions and 43 deletions

View File

@ -1,49 +1,84 @@
import time
from contextlib import contextmanager
from threading import RLock
from typing import List, Mapping
from typing_extensions import override
from platypush.plugins import action
from platypush.plugins.gpio.sensor import GpioSensorPlugin
from platypush.context import get_bus
from platypush.entities.managers.sensors import SensorEntityManager
from platypush.entities.devices import Device
from platypush.entities.distance import DistanceSensor
from platypush.message.event.sensor import SensorDataChangeEvent
from platypush.plugins import RunnablePlugin, action
from platypush.utils import get_plugin_name_by_class
class GpioSensorDistanceVl53L1XPlugin(GpioSensorPlugin):
class SensorDistanceVl53l1xPlugin(RunnablePlugin, SensorEntityManager):
"""
Plugin to interact with an `VL53L1x <https://www.st.com/en/imaging-and-photonics-solutions/vl53l1x.html>`_
Plugin to interact with an `VL53L1x
<https://www.st.com/en/imaging-and-photonics-solutions/vl53l1x.html>`_
laser ranger/distance sensor
Requires:
* ``smbus2`` (``pip install smbus2``)
* ``vl53l1x`` (``pip install vl53l1x``)
Triggers:
* :class:`platypush.message.event.sensor.SensorDataChangeEvent`
"""
def __init__(self, i2c_bus=1, i2c_address=0x29, **kwargs):
def __init__(self, i2c_bus=1, i2c_address=0x29, poll_interval=3, **kwargs):
"""
:param i2c_bus: I2C bus number (default: 1)
:param i2c_address: I2C address (default: 0x29)
:param poll_interval: How often the integration should poll for new
measurements (default: 3 seconds).
"""
super().__init__(**kwargs)
super().__init__(poll_interval=poll_interval, **kwargs)
self.i2c_bus = i2c_bus
self.i2c_address = i2c_address
self._device = None
self._device_lock = RLock()
self._last_data = {}
# noinspection PyUnresolvedReferences
@contextmanager
def _get_device(self, ranging=1):
if self._device:
return self._device
from VL53L1X import VL53L1X # type: ignore
from VL53L1X import VL53L1X
self._device = VL53L1X(i2c_bus=self.i2c_bus, i2c_address=self.i2c_address)
self._device.open()
self._device.start_ranging(ranging)
return self._device
with self._device_lock:
if self._device:
yield self._device
self._device = VL53L1X(i2c_bus=self.i2c_bus, i2c_address=self.i2c_address)
try:
self._device.open()
self._device.start_ranging(ranging)
yield self._device
finally:
try:
self._device.stop_ranging()
except Exception:
pass
self._device.close()
self._device = None
@action
def get_measurement(self, short=True, medium=False, long=False):
@override
def status(self, *_, **__):
self.publish_entities(self._last_data)
return self._last_data
def _status(self, *_, short=True, medium=True, long=True, **__):
"""
:param short: Enable short range measurement (default: True)
:param medium: Enable medium range measurement (default: False)
:param long: Enable long range measurement (default: False)
:param medium: Enable medium range measurement (default: True)
:param long: Enable long range measurement (default: True)
:returns: dict. Example:
@ -51,40 +86,90 @@ class GpioSensorDistanceVl53L1XPlugin(GpioSensorPlugin):
output = {
"short": 83, # Short range measurement in mm
"medium": 103, # Medium range measurement
"long": 43, # Long range measurement
"medium": 103, # Medium range measurement in mm
"long": 200, # Long range measurement
}
"""
ret = {}
range_idx = 0
range_name = None
range_conf = {
'short': short,
'medium': medium,
'long': long,
}
for i, r in enumerate(['short', 'medium', 'long']):
if eval(r):
range_idx = i+1
range_name = r
break
for i, (key, enabled) in enumerate(range_conf.items()):
if not enabled:
continue
assert range_name is not None
device = self._get_device(ranging=range_idx)
ret = {}
range_idx = i + 1
range_name = key
try:
ret[range_name] = device.get_distance()
except Exception as e:
self.logger.exception(e)
try:
self._device.stop_ranging()
self._device.close()
except Exception as e:
self.logger.warning('Error while closing device: {}'.format(str(e)))
self._device = None
time.sleep(1)
with self._get_device(ranging=range_idx) as device:
try:
ret[range_name] = device.get_distance()
except Exception as e:
self.logger.exception(e)
self.wait_stop(1)
return ret
@action
def get_data(self, *args, **kwargs):
"""
(Deprecated) alias for :meth:`.status`.
"""
return self.status(*args, **kwargs)
@action
def get_measurement(self, *args, **kwargs):
"""
(Deprecated) alias for :meth:`.status`.
"""
return self.status(*args, **kwargs)
@override
def transform_entities(self, entities: Mapping[str, int]) -> List[Device]:
return super().transform_entities( # type: ignore
[
Device(
id='vl53l1x',
name='VL53L1X Distance Sensor',
children=[
DistanceSensor(
id=f'vl53l1x:{key}',
name=f'{key} distance',
value=value,
unit='mm',
)
for key, value in entities.items()
],
)
]
)
@override
def publish_entities(self, entities: Mapping[str, int], *_, **__) -> List[Device]:
get_bus().post(
SensorDataChangeEvent(
data=entities,
source=get_plugin_name_by_class(self.__class__),
)
)
return super().publish_entities(entities) # type: ignore
@override
def main(self):
while not self.should_stop():
status = self._status()
if status != self._last_data:
self.publish_entities(status)
self._last_data = status
self.wait_stop(self.poll_interval)
# vim:sw=4:ts=4:et:

View File

@ -1,8 +1,10 @@
manifest:
events: []
events:
platypush.message.event.sensor import SensorDataChangeEvent:
install:
pip:
- smbus2
- vl53l1x
package: platypush.plugins.gpio.sensor.distance.vl53l1x
package: platypush.plugins.sensor.distance.vl53l1x
type: plugin