Added support for continuous checks on distance sensor and DistanceSensorEvent
This commit is contained in:
parent
9c4f917b53
commit
d6515ed991
4 changed files with 130 additions and 46 deletions
|
@ -11,6 +11,7 @@ from platypush.config import Config
|
|||
from platypush.message import Message
|
||||
from platypush.utils import get_event_class_by_type
|
||||
|
||||
|
||||
class Event(Message):
|
||||
""" Event message class """
|
||||
|
||||
|
@ -39,6 +40,9 @@ class Event(Message):
|
|||
self.args = kwargs
|
||||
self.disable_logging = disable_logging
|
||||
|
||||
for arg, value in self.args.items():
|
||||
self.__setattr__(arg, value)
|
||||
|
||||
@classmethod
|
||||
def build(cls, msg):
|
||||
""" Builds an event message from a JSON UTF-8 string/bytearray, a
|
||||
|
@ -55,16 +59,14 @@ class Event(Message):
|
|||
args['timestamp'] = msg['_timestamp'] if '_timestamp' in msg else time.time()
|
||||
return event_class(**args)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _generate_id():
|
||||
""" Generate a unique event ID """
|
||||
id = ''
|
||||
for i in range(0,16):
|
||||
for i in range(0, 16):
|
||||
id += '%.2x' % random.randint(0, 255)
|
||||
return id
|
||||
|
||||
|
||||
def matches_condition(self, condition):
|
||||
"""
|
||||
If the event matches an event condition, it will return an EventMatchResult
|
||||
|
@ -75,7 +77,8 @@ class Event(Message):
|
|||
result = EventMatchResult(is_match=False, parsed_args=self.args)
|
||||
match_scores = []
|
||||
|
||||
if not isinstance(self, condition.type): return result
|
||||
if not isinstance(self, condition.type):
|
||||
return result
|
||||
|
||||
for (attr, value) in condition.args.items():
|
||||
if attr not in self.args:
|
||||
|
@ -100,7 +103,6 @@ class Event(Message):
|
|||
|
||||
return result
|
||||
|
||||
|
||||
def _matches_argument(self, argname, condition_value):
|
||||
"""
|
||||
Returns an EventMatchResult if the event argument [argname] matches
|
||||
|
@ -147,10 +149,9 @@ class Event(Message):
|
|||
else:
|
||||
result.parsed_args[argname] += ' ' + event_token
|
||||
|
||||
|
||||
if (len(condition_tokens) == 1 and len(event_tokens) == 1) \
|
||||
or (len(event_tokens) > 1 and len(condition_tokens) > 1 \
|
||||
and event_tokens[1] == condition_tokens[1]):
|
||||
or (len(event_tokens) > 1 and len(condition_tokens) > 1
|
||||
and event_tokens[1] == condition_tokens[1]):
|
||||
# Stop appending tokens to this argument, as the next
|
||||
# condition will be satisfied as well
|
||||
condition_tokens.pop(0)
|
||||
|
@ -164,7 +165,6 @@ class Event(Message):
|
|||
result.is_match = len(condition_tokens) == 0
|
||||
return result
|
||||
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Overrides the str() operator and converts
|
||||
|
@ -175,13 +175,13 @@ class Event(Message):
|
|||
flatten(args)
|
||||
|
||||
return json.dumps({
|
||||
'type' : 'event',
|
||||
'target' : self.target,
|
||||
'origin' : self.origin if hasattr(self, 'origin') else None,
|
||||
'id' : self.id if hasattr(self, 'id') else None,
|
||||
'_timestamp' : self.timestamp,
|
||||
'args' : {
|
||||
'type' : self.type,
|
||||
'type': 'event',
|
||||
'target': self.target,
|
||||
'origin': self.origin if hasattr(self, 'origin') else None,
|
||||
'id': self.id if hasattr(self, 'id') else None,
|
||||
'_timestamp': self.timestamp,
|
||||
'args': {
|
||||
'type': self.type,
|
||||
**args
|
||||
},
|
||||
})
|
||||
|
@ -226,18 +226,16 @@ class StopEvent(Event):
|
|||
|
||||
def flatten(args):
|
||||
if isinstance(args, dict):
|
||||
for (key,value) in args.items():
|
||||
for (key, value) in args.items():
|
||||
if isinstance(value, date):
|
||||
args[key] = value.isoformat()
|
||||
elif isinstance(value, dict) or isinstance(value, list):
|
||||
flatten(args[key])
|
||||
elif isinstance(args, list):
|
||||
for i in range(0,len(args)):
|
||||
for i in range(0, len(args)):
|
||||
if isinstance(args[i], date):
|
||||
args[i] = value.isoformat()
|
||||
args[i] = args[i].isoformat()
|
||||
elif isinstance(args[i], dict) or isinstance(args[i], list):
|
||||
flatten(args[i])
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
||||
|
|
12
platypush/message/event/distance.py
Normal file
12
platypush/message/event/distance.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from platypush.message.event import Event
|
||||
|
||||
|
||||
class DistanceSensorEvent(Event):
|
||||
"""
|
||||
Event triggered when a new value is processed by a distance sensor.
|
||||
"""
|
||||
def __init__(self, distance: float, unit: str = 'mm', *args, **kwargs):
|
||||
super().__init__(*args, distance=distance, unit=unit, **kwargs)
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
|
@ -176,9 +176,12 @@ class GpioPlugin(Plugin):
|
|||
Cleanup the state of the GPIO and resets PIN values.
|
||||
"""
|
||||
import RPi.GPIO as GPIO
|
||||
GPIO.cleanup()
|
||||
self._initialized_pins = {}
|
||||
self._initialized = False
|
||||
|
||||
with self._init_lock:
|
||||
if self._initialized:
|
||||
GPIO.cleanup()
|
||||
self._initialized_pins = {}
|
||||
self._initialized = False
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import threading
|
||||
import time
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from platypush.context import get_bus
|
||||
from platypush.message.event.distance import DistanceSensorEvent
|
||||
from platypush.plugins import action
|
||||
from platypush.plugins.gpio import GpioPlugin
|
||||
from platypush.plugins.gpio.sensor import GpioSensorPlugin
|
||||
|
@ -13,6 +18,11 @@ class GpioSensorDistancePlugin(GpioPlugin, GpioSensorPlugin):
|
|||
Requires:
|
||||
|
||||
* ``RPi.GPIO`` (``pip install RPi.GPIO``)
|
||||
|
||||
Triggers:
|
||||
|
||||
* :class:`platypush.message.event.distance.DistanceSensorEvent` when a new distance measurement is available
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, trigger_pin: int, echo_pin: int,
|
||||
|
@ -31,35 +41,42 @@ class GpioSensorDistancePlugin(GpioPlugin, GpioSensorPlugin):
|
|||
for the sensor to be ready (default: 2 seconds).
|
||||
"""
|
||||
|
||||
GpioPlugin.__init__(self, *args, **kwargs)
|
||||
GpioPlugin.__init__(self, pins={'trigger': trigger_pin, 'echo': echo_pin, }, *args, **kwargs)
|
||||
|
||||
self.trigger_pin = trigger_pin
|
||||
self.echo_pin = echo_pin
|
||||
self.timeout = timeout
|
||||
self.warmup_time = warmup_time
|
||||
self._initialized = False
|
||||
self._init_gpio()
|
||||
|
||||
def _init_gpio(self):
|
||||
if self._initialized:
|
||||
return
|
||||
self._measurement_thread: Optional[threading.Thread] = None
|
||||
self._measurement_thread_lock = threading.RLock()
|
||||
self._measurement_thread_can_run = False
|
||||
self._init_board()
|
||||
|
||||
def _init_board(self):
|
||||
import RPi.GPIO as GPIO
|
||||
GPIO.setmode(self.mode)
|
||||
GPIO.setup(self.trigger_pin, GPIO.OUT)
|
||||
GPIO.setup(self.echo_pin, GPIO.IN)
|
||||
GPIO.output(self.trigger_pin, GPIO.LOW)
|
||||
|
||||
self.logger.info('Waiting {} seconds for the sensor to be ready'.format(self.warmup_time))
|
||||
time.sleep(self.warmup_time)
|
||||
self.logger.info('Sensor ready')
|
||||
self._initialized = True
|
||||
with self._init_lock:
|
||||
if self._initialized:
|
||||
return
|
||||
|
||||
GpioPlugin._init_board(self)
|
||||
self._initialized = False
|
||||
|
||||
GPIO.setup(self.trigger_pin, GPIO.OUT)
|
||||
GPIO.setup(self.echo_pin, GPIO.IN)
|
||||
GPIO.output(self.trigger_pin, GPIO.LOW)
|
||||
|
||||
self.logger.info('Waiting {} seconds for the sensor to be ready'.format(self.warmup_time))
|
||||
time.sleep(self.warmup_time)
|
||||
self.logger.info('Sensor ready')
|
||||
self._initialized = True
|
||||
|
||||
def _get_data(self):
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
pulse_start = pulse_on = time.time()
|
||||
|
||||
self._init_gpio()
|
||||
self._init_board()
|
||||
GPIO.output(self.trigger_pin, GPIO.HIGH)
|
||||
time.sleep(0.00001) # 1 us pulse to trigger echo measurement
|
||||
GPIO.output(self.trigger_pin, GPIO.LOW)
|
||||
|
@ -94,7 +111,10 @@ class GpioSensorDistancePlugin(GpioPlugin, GpioSensorPlugin):
|
|||
"""
|
||||
|
||||
try:
|
||||
return self._get_data()
|
||||
distance = self._get_data()
|
||||
bus = get_bus()
|
||||
bus.post(DistanceSensorEvent(distance=distance, unit='mm'))
|
||||
return distance
|
||||
except TimeoutError as e:
|
||||
self.logger.warning(str(e))
|
||||
return
|
||||
|
@ -104,16 +124,67 @@ class GpioSensorDistancePlugin(GpioPlugin, GpioSensorPlugin):
|
|||
|
||||
@action
|
||||
def close(self):
|
||||
import RPi.GPIO as GPIO
|
||||
if self._initialized:
|
||||
GPIO.cleanup()
|
||||
self._initialized = False
|
||||
return self.cleanup()
|
||||
|
||||
def __enter__(self):
|
||||
self._init_gpio()
|
||||
self._init_board()
|
||||
|
||||
def __exit__(self):
|
||||
self.close()
|
||||
|
||||
def _get_measurement_thread(self, duration: float):
|
||||
def _thread():
|
||||
with self:
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
while self._measurement_thread_can_run and (
|
||||
not duration or time.time() - start_time <= duration):
|
||||
self.get_measurement()
|
||||
finally:
|
||||
self._measurement_thread = None
|
||||
|
||||
return _thread
|
||||
|
||||
def _is_measurement_thread_running(self):
|
||||
with self._measurement_thread_lock:
|
||||
return self._measurement_thread is not None
|
||||
|
||||
@action
|
||||
def start_measurement(self, duration: Optional[float] = None):
|
||||
"""
|
||||
Start the measurement thread. It will trigger :class:`platypush.message.event.distance.DistanceSensorEvent`
|
||||
events when new measurements are available.
|
||||
|
||||
:param duration: If set, then the thread will run for the specified amount of seconds (default: None)
|
||||
"""
|
||||
with self._measurement_thread_lock:
|
||||
if self._is_measurement_thread_running():
|
||||
self.logger.warning('A measurement thread is already running')
|
||||
return
|
||||
|
||||
thread_func = self._get_measurement_thread(duration=duration)
|
||||
self._measurement_thread = threading.Thread(target=thread_func)
|
||||
self._measurement_thread_can_run = True
|
||||
self._measurement_thread.start()
|
||||
|
||||
@action
|
||||
def stop_measurement(self):
|
||||
"""
|
||||
Stop the running measurement thread.
|
||||
"""
|
||||
with self._measurement_thread_lock:
|
||||
if not self._is_measurement_thread_running():
|
||||
self.logger.warning('No measurement thread is running')
|
||||
return
|
||||
|
||||
self._measurement_thread_can_run = False
|
||||
self.logger.info('Waiting for the measurement thread to end')
|
||||
|
||||
if self._measurement_thread:
|
||||
self._measurement_thread.join(timeout=self.timeout)
|
||||
|
||||
self.logger.info('Measurement thread terminated')
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
Loading…
Reference in a new issue