Adding backends documentation
This commit is contained in:
parent
85c7faf21b
commit
28862d743d
36 changed files with 592 additions and 167 deletions
25
docs/source/backends.rst
Normal file
25
docs/source/backends.rst
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
Backends
|
||||||
|
========
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:caption: Backends:
|
||||||
|
|
||||||
|
platypush/backend.rst
|
||||||
|
platypush/backend/assistant.google.rst
|
||||||
|
platypush/backend/assistant.google.pushtotalk.rst
|
||||||
|
platypush/backend/assistant.snowboy.rst
|
||||||
|
platypush/backend/button.flic.rst
|
||||||
|
platypush/backend/camera.pi.rst
|
||||||
|
platypush/backend/http.rst
|
||||||
|
platypush/backend/http.poll.rst
|
||||||
|
platypush/backend/inotify.rst
|
||||||
|
platypush/backend/kafka.rst
|
||||||
|
platypush/backend/midi.rst
|
||||||
|
platypush/backend/mqtt.rst
|
||||||
|
platypush/backend/music.mpd.rst
|
||||||
|
platypush/backend/pushbullet.rst
|
||||||
|
platypush/backend/redis.rst
|
||||||
|
platypush/backend/scard.rst
|
||||||
|
platypush/backend/sensor.rst
|
||||||
|
|
|
@ -5,6 +5,7 @@ Platypush
|
||||||
:maxdepth: 3
|
:maxdepth: 3
|
||||||
:caption: Contents:
|
:caption: Contents:
|
||||||
|
|
||||||
|
backends
|
||||||
plugins
|
plugins
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
|
|
6
docs/source/platypush/backend.rst
Normal file
6
docs/source/platypush/backend.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend``
|
||||||
|
=====================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend
|
||||||
|
:members:
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.assistant.google.pushtotalk``
|
||||||
|
=================================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.assistant.google.pushtotalk
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/backend/assistant.google.rst
Normal file
6
docs/source/platypush/backend/assistant.google.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.assistant.google``
|
||||||
|
======================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.assistant.google
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/backend/assistant.snowboy.rst
Normal file
6
docs/source/platypush/backend/assistant.snowboy.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.assistant.snowboy``
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.assistant.snowboy
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/backend/button.flic.rst
Normal file
6
docs/source/platypush/backend/button.flic.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.button.flic``
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.button.flic
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/backend/camera.pi.rst
Normal file
6
docs/source/platypush/backend/camera.pi.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.camera.pi``
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.camera.pi
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/backend/http.poll.rst
Normal file
6
docs/source/platypush/backend/http.poll.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.http.poll``
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.http.poll
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/backend/http.rst
Normal file
6
docs/source/platypush/backend/http.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.http``
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.http
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/backend/inotify.rst
Normal file
6
docs/source/platypush/backend/inotify.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.inotify``
|
||||||
|
=============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.inotify
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/backend/kafka.rst
Normal file
6
docs/source/platypush/backend/kafka.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.kafka``
|
||||||
|
===========================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.kafka
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/backend/midi.rst
Normal file
6
docs/source/platypush/backend/midi.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.midi``
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.midi
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/backend/mqtt.rst
Normal file
6
docs/source/platypush/backend/mqtt.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.mqtt``
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.mqtt
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/backend/music.mpd.rst
Normal file
6
docs/source/platypush/backend/music.mpd.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.music.mpd``
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.music.mpd
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/backend/pushbullet.rst
Normal file
6
docs/source/platypush/backend/pushbullet.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.pushbullet``
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.pushbullet
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/backend/redis.rst
Normal file
6
docs/source/platypush/backend/redis.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.redis``
|
||||||
|
===========================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.redis
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/backend/scard.rst
Normal file
6
docs/source/platypush/backend/scard.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.backend.scard``
|
||||||
|
===========================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.scard
|
||||||
|
:members:
|
||||||
|
|
7
docs/source/platypush/backend/sensor.rst
Normal file
7
docs/source/platypush/backend/sensor.rst
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
``platypush.backend.sensor``
|
||||||
|
============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.sensor
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
|
@ -16,16 +16,26 @@ from platypush.message.response import Response
|
||||||
|
|
||||||
|
|
||||||
class Backend(Thread):
|
class Backend(Thread):
|
||||||
""" Parent class for backends """
|
"""
|
||||||
|
Parent class for backends.
|
||||||
|
|
||||||
|
A backend is basically a thread that checks for new events on some channel
|
||||||
|
(e.g. a network socket, a queue, some new entries on an API endpoint or an
|
||||||
|
RSS feed, a voice command through an assistant, a new measure from a sensor
|
||||||
|
etc.) and propagates event messages to the main application bus whenever a
|
||||||
|
new event happens. You can then build whichever type of custom logic you
|
||||||
|
want on such events.
|
||||||
|
"""
|
||||||
|
|
||||||
_default_response_timeout = 5
|
_default_response_timeout = 5
|
||||||
|
|
||||||
def __init__(self, bus=None, **kwargs):
|
def __init__(self, bus=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Params:
|
:param bus: Reference to the bus object to be used in the backend
|
||||||
bus -- Reference to the Platypush bus where the requests and the
|
:type bus: platypush.bus.Bus
|
||||||
responses will be posted [Bus]
|
|
||||||
kwargs -- key-value configuration for this backend [Dict]
|
:param kwargs: Key-value configuration for the backend
|
||||||
|
:type kwargs: dict
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# If no bus is specified, create an internal queue where
|
# If no bus is specified, create an internal queue where
|
||||||
|
@ -55,11 +65,9 @@ class Backend(Thread):
|
||||||
It should be called by the derived classes whenever
|
It should be called by the derived classes whenever
|
||||||
a new message should be processed.
|
a new message should be processed.
|
||||||
|
|
||||||
Params:
|
:param msg: Received message. It can be either a key-value dictionary, a platypush.message.Message object, or a string/byte UTF-8 encoded string
|
||||||
msg -- The message. It can be either a key-value
|
|
||||||
dictionary, a platypush.message.Message
|
|
||||||
object, or a string/byte UTF-8 encoded string
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
msg = Message.build(msg)
|
msg = Message.build(msg)
|
||||||
|
|
||||||
if not getattr(msg, 'target') or msg.target != self.device_id:
|
if not getattr(msg, 'target') or msg.target != self.device_id:
|
||||||
|
@ -120,10 +128,9 @@ class Backend(Thread):
|
||||||
|
|
||||||
def send_event(self, event, **kwargs):
|
def send_event(self, event, **kwargs):
|
||||||
"""
|
"""
|
||||||
Send an event message on the backend
|
Send an event message on the backend.
|
||||||
Params:
|
|
||||||
event -- The request, either a dict, a string/bytes UTF-8 JSON,
|
:param event: Event to send. It can be a dict, a string/bytes UTF-8 JSON, or a platypush.message.event.Event object.
|
||||||
or a platypush.message.event.Event object.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
event = Event.build(event)
|
event = Event.build(event)
|
||||||
|
@ -139,17 +146,15 @@ class Backend(Thread):
|
||||||
def send_request(self, request, on_response=None,
|
def send_request(self, request, on_response=None,
|
||||||
response_timeout=_default_response_timeout, **kwargs):
|
response_timeout=_default_response_timeout, **kwargs):
|
||||||
"""
|
"""
|
||||||
Send a request message on the backend
|
Send a request message on the backend.
|
||||||
Params:
|
|
||||||
request -- The request, either a dict, a string/bytes UTF-8 JSON,
|
|
||||||
or a platypush.message.request.Request object.
|
|
||||||
|
|
||||||
on_response -- Response handler, takes a platypush.message.response.Response
|
:param request: The request, either a dict, a string/bytes UTF-8 JSON, or a platypush.message.request.Request object.
|
||||||
as argument. If set, the method will wait for a
|
|
||||||
response before exiting (default: None)
|
:param on_response: Optional callback that will be called when a response is received. If set, this method will synchronously wait for a response before exiting.
|
||||||
response_timeout -- If on_response is set, the backend will raise
|
:type on_response: function
|
||||||
an exception if the response isn't received
|
|
||||||
within this number of seconds (default: 5)
|
:param response_timeout: If on_response is set, the backend will raise an exception if the response isn't received within this number of seconds (default: None)
|
||||||
|
:type response_timeout: float
|
||||||
"""
|
"""
|
||||||
|
|
||||||
request = Request.build(request)
|
request = Request.build(request)
|
||||||
|
@ -165,12 +170,10 @@ class Backend(Thread):
|
||||||
|
|
||||||
def send_response(self, response, request, **kwargs):
|
def send_response(self, response, request, **kwargs):
|
||||||
"""
|
"""
|
||||||
Send a response message on the backend
|
Send a response message on the backend.
|
||||||
Params:
|
|
||||||
response -- The response, either a dict, a string/bytes UTF-8 JSON,
|
:param response: The response, either a dict, a string/bytes UTF-8 JSON, or a platypush.message.response.Response object
|
||||||
or a platypush.message.response.Response object
|
:param request: Associated request, used to set the response parameters that will link them
|
||||||
request -- Associated request, used to set the response parameters
|
|
||||||
that will link them
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
response = Response.build(response)
|
response = Response.build(response)
|
||||||
|
@ -191,8 +194,7 @@ class Backend(Thread):
|
||||||
backend is configured then it will try to deliver the message to
|
backend is configured then it will try to deliver the message to
|
||||||
other consumers through the configured Redis main queue.
|
other consumers through the configured Redis main queue.
|
||||||
|
|
||||||
Params:
|
:param msg: The message to send
|
||||||
msg -- The message
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
redis = get_backend('redis')
|
redis = get_backend('redis')
|
||||||
|
|
|
@ -16,18 +16,35 @@ from platypush.message.event.assistant import \
|
||||||
|
|
||||||
|
|
||||||
class AssistantGoogleBackend(Backend):
|
class AssistantGoogleBackend(Backend):
|
||||||
""" Class for the Google Assistant backend. It creates and event source
|
"""
|
||||||
that posts recognized phrases on the main bus """
|
Google Assistant backend.
|
||||||
|
|
||||||
|
It listens for voice commands and post conversation events on the bus.
|
||||||
|
|
||||||
|
Triggers:
|
||||||
|
|
||||||
|
* :class:`platypush.message.event.assistant.ConversationStartEvent` when a new conversation starts
|
||||||
|
* :class:`platypush.message.event.assistant.SpeechRecognizedEvent` when a new voice command is recognized
|
||||||
|
* :class:`platypush.message.event.assistant.NoResponse` when a conversation returned no response
|
||||||
|
* :class:`platypush.message.event.assistant.ResponseEvent` when the assistant is speaking a response
|
||||||
|
* :class:`platypush.message.event.assistant.ConversationTimeoutEvent` when a conversation times out
|
||||||
|
* :class:`platypush.message.event.assistant.ConversationEndEvent` when a new conversation ends
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **google-assistant-sdk** (``pip install google-assistant-sdk``)
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, credentials_file=os.path.join(
|
def __init__(self, credentials_file=os.path.join(
|
||||||
os.path.expanduser('~/.config'),
|
os.path.expanduser('~/.config'),
|
||||||
'google-oauthlib-tool', 'credentials.json'),
|
'google-oauthlib-tool', 'credentials.json'),
|
||||||
device_model_id='Platypush', **kwargs):
|
device_model_id='Platypush', **kwargs):
|
||||||
""" Params:
|
"""
|
||||||
credentials_file -- Path to the Google OAuth credentials file
|
:param credentials_file: Path to the Google OAuth credentials file (default: ~/.config/google-oauthlib-tool/credentials.json). See https://developers.google.com/assistant/sdk/guides/library/python/embed/install-sample#generate_credentials for how to get your own credentials file.
|
||||||
(default: ~/.config/google-oauthlib-tool/credentials.json)
|
:type credentials_file: str
|
||||||
device_model_id -- Device model ID to use for the assistant
|
|
||||||
(default: Platypush)
|
:param device_model_id: Device model ID to use for the assistant (default: Platypush)
|
||||||
|
:type device_model_id: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
@ -61,20 +78,15 @@ class AssistantGoogleBackend(Backend):
|
||||||
|
|
||||||
|
|
||||||
def start_conversation(self):
|
def start_conversation(self):
|
||||||
|
""" Starts an assistant conversation """
|
||||||
if self.assistant: self.assistant.start_conversation()
|
if self.assistant: self.assistant.start_conversation()
|
||||||
|
|
||||||
|
|
||||||
def stop_conversation(self):
|
def stop_conversation(self):
|
||||||
|
""" Stops an assistant conversation """
|
||||||
if self.assistant: self.assistant.stop_conversation()
|
if self.assistant: self.assistant.stop_conversation()
|
||||||
|
|
||||||
|
|
||||||
def send_message(self, msg):
|
|
||||||
# Can't send a message on an event source, ignoring
|
|
||||||
# TODO Make a class for event sources like these. Event sources
|
|
||||||
# would be a subset of the backends which can fire events on the bus
|
|
||||||
# but not receive requests or process responses.
|
|
||||||
pass
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
super().run()
|
super().run()
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,25 @@ from platypush.message.event.assistant import \
|
||||||
|
|
||||||
|
|
||||||
class AssistantGooglePushtotalkBackend(Backend):
|
class AssistantGooglePushtotalkBackend(Backend):
|
||||||
""" Google Assistant pushtotalk backend. Instead of listening for
|
"""
|
||||||
the "OK Google" hotword like the assistant.google backend,
|
Google Assistant pushtotalk backend. Instead of listening for the "OK
|
||||||
this implementation programmatically starts a conversation
|
Google" hotword like the assistant.google backend, this implementation
|
||||||
upon start_conversation() method call. Use this backend on
|
programmatically starts a conversation upon start_conversation() method
|
||||||
devices that don't have an Assistant SDK package (e.g. arm6 devices
|
call. Use this backend on devices that don't have an Assistant SDK package
|
||||||
like the RaspberryPi Zero or the RaspberryPi 1) """
|
(e.g. arm6 devices like the RaspberryPi Zero or the RaspberryPi 1).
|
||||||
|
|
||||||
|
Triggers:
|
||||||
|
|
||||||
|
* :class:`platypush.message.event.assistant.ConversationStartEvent` when a new conversation starts
|
||||||
|
* :class:`platypush.message.event.assistant.SpeechRecognizedEvent` when a new voice command is recognized
|
||||||
|
* :class:`platypush.message.event.assistant.ConversationEndEvent` when a new conversation ends
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **tenacity** (``pip install tenacity``)
|
||||||
|
* **grpc** (``pip install grpc``)
|
||||||
|
* **google-assistant-grpc** (``pip install google-assistant-grpc``)
|
||||||
|
"""
|
||||||
|
|
||||||
api_endpoint = 'embeddedassistant.googleapis.com'
|
api_endpoint = 'embeddedassistant.googleapis.com'
|
||||||
audio_sample_rate = audio_helpers.DEFAULT_AUDIO_SAMPLE_RATE
|
audio_sample_rate = audio_helpers.DEFAULT_AUDIO_SAMPLE_RATE
|
||||||
|
@ -49,13 +62,15 @@ class AssistantGooglePushtotalkBackend(Backend):
|
||||||
lang='en-US',
|
lang='en-US',
|
||||||
conversation_start_fifo = os.path.join(os.path.sep, 'tmp', 'pushtotalk.fifo'),
|
conversation_start_fifo = os.path.join(os.path.sep, 'tmp', 'pushtotalk.fifo'),
|
||||||
*args, **kwargs):
|
*args, **kwargs):
|
||||||
""" Params:
|
"""
|
||||||
credentials_file -- Path to the Google OAuth credentials file
|
:param credentials_file: Path to the Google OAuth credentials file (default: ~/.config/google-oauthlib-tool/credentials.json). See https://developers.google.com/assistant/sdk/guides/library/python/embed/install-sample#generate_credentials for how to get your own credentials file.
|
||||||
(default: ~/.config/google-oauthlib-tool/credentials.json)
|
:type credentials_file: str
|
||||||
device_config -- Path to device_config.json. Register your
|
|
||||||
device and create a project, then run the pushtotalk.py
|
:param device_config: Path to device_config.json. Register your device (see https://developers.google.com/assistant/sdk/guides/library/python/embed/register-device) and create a project, then run the pushtotalk.py script from googlesamples to create your device_config.json
|
||||||
script from googlesamples to create your device_config.json
|
:type device_config: str
|
||||||
lang -- Assistant language (default: en-US)
|
|
||||||
|
:param lang: Assistant language (default: en-US)
|
||||||
|
:type lang: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -125,11 +140,13 @@ class AssistantGooglePushtotalkBackend(Backend):
|
||||||
self.device_handler = device_helpers.DeviceRequestHandler(self.device_id)
|
self.device_handler = device_helpers.DeviceRequestHandler(self.device_id)
|
||||||
|
|
||||||
def start_conversation(self):
|
def start_conversation(self):
|
||||||
|
""" Start a conversation """
|
||||||
if self.assistant:
|
if self.assistant:
|
||||||
with open(self.conversation_start_fifo, 'w') as f:
|
with open(self.conversation_start_fifo, 'w') as f:
|
||||||
f.write('1')
|
f.write('1')
|
||||||
|
|
||||||
def stop_conversation(self):
|
def stop_conversation(self):
|
||||||
|
""" Stop a conversation """
|
||||||
if self.assistant:
|
if self.assistant:
|
||||||
self.conversation_stream.stop_playback()
|
self.conversation_stream.stop_playback()
|
||||||
self.bus.post(ConversationEndEvent())
|
self.bus.post(ConversationEndEvent())
|
||||||
|
@ -345,5 +362,6 @@ class SampleAssistant(object):
|
||||||
# Subsequent requests need audio data, but not config.
|
# Subsequent requests need audio data, but not config.
|
||||||
yield embedded_assistant_pb2.AssistRequest(audio_in=data)
|
yield embedded_assistant_pb2.AssistRequest(audio_in=data)
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
||||||
|
|
|
@ -3,28 +3,46 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from snowboy import snowboydecoder
|
|
||||||
|
|
||||||
from platypush.backend import Backend
|
from platypush.backend import Backend
|
||||||
from platypush.message.event.assistant import \
|
from platypush.message.event.assistant import \
|
||||||
ConversationStartEvent, ConversationEndEvent, \
|
ConversationStartEvent, ConversationEndEvent, \
|
||||||
SpeechRecognizedEvent, HotwordDetectedEvent
|
SpeechRecognizedEvent, HotwordDetectedEvent
|
||||||
|
|
||||||
class AssistantSnowboyBackend(Backend):
|
class AssistantSnowboyBackend(Backend):
|
||||||
""" Backend for detecting custom voice hotwords through Snowboy.
|
"""
|
||||||
The purpose of this component is only to detect the hotword
|
Backend for detecting custom voice hotwords through Snowboy. The purpose of
|
||||||
specified in your Snowboy voice model. If you want to trigger
|
this component is only to detect the hotword specified in your Snowboy voice
|
||||||
proper assistant conversations or custom speech recognition,
|
model. If you want to trigger proper assistant conversations or custom
|
||||||
you should create a hook in your configuration on HotwordDetectedEvent
|
speech recognition, you should create a hook in your configuration on
|
||||||
to trigger the conversation on whichever assistant plugin you're using
|
HotwordDetectedEvent to trigger the conversation on whichever assistant
|
||||||
(Google, Alexa...) """
|
plugin you're using (Google, Alexa...)
|
||||||
|
|
||||||
|
Triggers:
|
||||||
|
|
||||||
|
* :class:`platypush.message.event.assistant.HotwordDetectedEvent` whenever the hotword has been detected
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **snowboy** (``pip install snowboy``)
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, voice_model_file, hotword=None, sensitivity=0.5,
|
def __init__(self, voice_model_file, hotword=None, sensitivity=0.5,
|
||||||
audio_gain=1.0, **kwargs):
|
audio_gain=1.0, **kwargs):
|
||||||
""" Params:
|
|
||||||
voice_model_file -- Snowboy voice model file
|
|
||||||
hotword -- Name of the hotword
|
|
||||||
"""
|
"""
|
||||||
|
:param voice_model_file: Snowboy voice model file - see https://snowboy.kitt.ai/
|
||||||
|
:type voice_model_file: str
|
||||||
|
|
||||||
|
:param hotword: Name of the hotword
|
||||||
|
:type hotword: str
|
||||||
|
|
||||||
|
:param sensitivity: Hotword recognition sensitivity, between 0 and 1
|
||||||
|
:type sensitivity: float
|
||||||
|
|
||||||
|
:param audio_gain: Audio gain, between 0 and 1
|
||||||
|
:type audio_gain: float
|
||||||
|
"""
|
||||||
|
|
||||||
|
from snowboy import snowboydecoder
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.voice_model_file = voice_model_file
|
self.voice_model_file = voice_model_file
|
||||||
|
@ -38,9 +56,6 @@ class AssistantSnowboyBackend(Backend):
|
||||||
|
|
||||||
self.logger.info('Initialized Snowboy hotword detection')
|
self.logger.info('Initialized Snowboy hotword detection')
|
||||||
|
|
||||||
def send_message(self, msg):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def hotword_detected(self):
|
def hotword_detected(self):
|
||||||
def callback():
|
def callback():
|
||||||
self.bus.post(HotwordDetectedEvent(hotword=self.hotword))
|
self.bus.post(HotwordDetectedEvent(hotword=self.hotword))
|
||||||
|
|
|
@ -11,13 +11,37 @@ from .fliclib.fliclib import FlicClient, ButtonConnectionChannel, ClickType
|
||||||
|
|
||||||
|
|
||||||
class ButtonFlicBackend(Backend):
|
class ButtonFlicBackend(Backend):
|
||||||
|
"""
|
||||||
|
Backend that listen for events from the Flic (https://flic.io/) bluetooth
|
||||||
|
smart buttons.
|
||||||
|
|
||||||
|
Triggers:
|
||||||
|
|
||||||
|
* :class:`platypush.message.event.button.flic.FlicButtonEvent` when a button is pressed. The event will also contain the press sequence (e.g. ``["ShortPressEvent", "LongPressEvent", "ShortPressEvent"]``)
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **fliclib** (https://github.com/50ButtonsEach/fliclib-linux-hci). For the backend to work properly you need to have the ``flicd`` daemon from the fliclib running, and you have to first pair the buttons with your device using any of the scanners provided by the library.
|
||||||
|
"""
|
||||||
|
|
||||||
_long_press_timeout = 0.3
|
_long_press_timeout = 0.3
|
||||||
_btn_timeout = 0.5
|
_btn_timeout = 0.5
|
||||||
ShortPressEvent = "ShortPressEvent"
|
ShortPressEvent = "ShortPressEvent"
|
||||||
LongPressEvent = "LongPressEvent"
|
LongPressEvent = "LongPressEvent"
|
||||||
|
|
||||||
def __init__(self, server, long_press_timeout=_long_press_timeout,
|
def __init__(self, server='localhost', long_press_timeout=_long_press_timeout,
|
||||||
btn_timeout=_btn_timeout, **kwargs):
|
btn_timeout=_btn_timeout, **kwargs):
|
||||||
|
"""
|
||||||
|
:param server: flicd server host (default: localhost)
|
||||||
|
:type server: str
|
||||||
|
|
||||||
|
:param long_press_timeout: How long you should press a button for a press action to be considered "long press" (default: 0.3 secohds)
|
||||||
|
:type long_press_timeout: float
|
||||||
|
|
||||||
|
:param btn_timeout: How long since the last button release before considering the user interaction completed (default: 0.5 seconds)
|
||||||
|
:type btn_timeout: float
|
||||||
|
"""
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.server = server
|
self.server = server
|
||||||
|
@ -88,9 +112,6 @@ class ButtonFlicBackend(Backend):
|
||||||
|
|
||||||
return _f
|
return _f
|
||||||
|
|
||||||
def send_message(self, msg):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
super().run()
|
super().run()
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import json
|
import json
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import picamera
|
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
|
@ -10,6 +9,17 @@ from threading import Thread
|
||||||
from platypush.backend import Backend
|
from platypush.backend import Backend
|
||||||
|
|
||||||
class CameraPiBackend(Backend):
|
class CameraPiBackend(Backend):
|
||||||
|
"""
|
||||||
|
Backend to interact with a Raspberry Pi camera. It can start and stop
|
||||||
|
recordings and take pictures. It can be programmatically controlled through
|
||||||
|
the :class:`platypush.plugins.camera.pi` plugin.
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **picamera** (``pip install picamera``)
|
||||||
|
* **redis** (``pip install redis``) for inter-process communication with the camera process
|
||||||
|
"""
|
||||||
|
|
||||||
class CameraAction(Enum):
|
class CameraAction(Enum):
|
||||||
START_RECORDING = 'START_RECORDING'
|
START_RECORDING = 'START_RECORDING'
|
||||||
STOP_RECORDING = 'STOP_RECORDING'
|
STOP_RECORDING = 'STOP_RECORDING'
|
||||||
|
@ -27,9 +37,15 @@ class CameraPiBackend(Backend):
|
||||||
exposure_mode='auto', meter_mode='average', awb_mode='auto',
|
exposure_mode='auto', meter_mode='average', awb_mode='auto',
|
||||||
image_effect='none', color_effects=None, rotation=0,
|
image_effect='none', color_effects=None, rotation=0,
|
||||||
crop=(0.0, 0.0, 1.0, 1.0), **kwargs):
|
crop=(0.0, 0.0, 1.0, 1.0), **kwargs):
|
||||||
""" See https://www.raspberrypi.org/documentation/usage/camera/python/README.md
|
"""
|
||||||
for a detailed reference about the Pi camera options """
|
See https://www.raspberrypi.org/documentation/usage/camera/python/README.md
|
||||||
|
for a detailed reference about the Pi camera options.
|
||||||
|
|
||||||
|
:param listen_port: Port where the camera process will provide the video output while recording
|
||||||
|
:type listen_port: int
|
||||||
|
"""
|
||||||
|
|
||||||
|
import picamera
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.listen_port = listen_port
|
self.listen_port = listen_port
|
||||||
|
@ -74,14 +90,30 @@ class CameraPiBackend(Backend):
|
||||||
self.redis.rpush(self.redis_queue, json.dumps(action))
|
self.redis.rpush(self.redis_queue, json.dumps(action))
|
||||||
|
|
||||||
def take_picture(self, image_file):
|
def take_picture(self, image_file):
|
||||||
|
"""
|
||||||
|
Take a picture.
|
||||||
|
|
||||||
|
:param image_file: Output image file
|
||||||
|
:type image_file: str
|
||||||
|
"""
|
||||||
self.logger.info('Capturing camera snapshot to {}'.format(image_file))
|
self.logger.info('Capturing camera snapshot to {}'.format(image_file))
|
||||||
self.camera.capture(image_file)
|
self.camera.capture(image_file)
|
||||||
self.logger.info('Captured camera snapshot to {}'.format(image_file))
|
self.logger.info('Captured camera snapshot to {}'.format(image_file))
|
||||||
|
|
||||||
def start_recording(self, video_file=None, format='h264'):
|
def start_recording(self, video_file=None, format='h264'):
|
||||||
|
"""
|
||||||
|
Start a recording.
|
||||||
|
|
||||||
|
:param video_file: Output video file. If specified, the video will be recorded to file, otherwise it will be served via TCP/IP on the listen_port. Use ``stop_recording`` to stop the recording.
|
||||||
|
:type video_file: str
|
||||||
|
|
||||||
|
:param format: Video format (default: h264)
|
||||||
|
:type format: str
|
||||||
|
"""
|
||||||
|
|
||||||
def recording_thread():
|
def recording_thread():
|
||||||
if video_file:
|
if video_file:
|
||||||
self.camera.start_recording(videofile, format=format)
|
self.camera.start_recording(video_file, format=format)
|
||||||
while True:
|
while True:
|
||||||
self.camera.wait_recording(60)
|
self.camera.wait_recording(60)
|
||||||
else:
|
else:
|
||||||
|
@ -114,6 +146,8 @@ class CameraPiBackend(Backend):
|
||||||
|
|
||||||
|
|
||||||
def stop_recording(self):
|
def stop_recording(self):
|
||||||
|
""" Stops recording """
|
||||||
|
|
||||||
self.logger.info('Stopping camera recording')
|
self.logger.info('Stopping camera recording')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -22,10 +22,30 @@ from .. import Backend
|
||||||
|
|
||||||
|
|
||||||
class HttpBackend(Backend):
|
class HttpBackend(Backend):
|
||||||
""" Example interaction with the HTTP backend to make requests:
|
"""
|
||||||
$ curl -XPOST -H 'Content-Type: application/json' -H "X-Token: your_token" \
|
The HTTP backend is a general-purpose web server that you can leverage:
|
||||||
-d '{"type":"request","target":"nodename","action":"tts.say","args": {"phrase":"This is a test"}}' \
|
|
||||||
http://localhost:8008/execute """
|
* To execute Platypush commands via HTTP calls. Example::
|
||||||
|
|
||||||
|
curl -XPOST -H 'Content-Type: application/json' -H "X-Token: your_token" \\
|
||||||
|
-d '{
|
||||||
|
"type":"request",
|
||||||
|
"target":"nodename",
|
||||||
|
"action":"tts.say",
|
||||||
|
"args": {"phrase":"This is a test"}
|
||||||
|
}' \\
|
||||||
|
http://localhost:8008/execute
|
||||||
|
|
||||||
|
* To interact with your system (and control plugins and backends) through the Platypush web panel, by default available on your web root document. Any plugin that you have configured and available as a panel plugin will appear on the web panel as well as a tab.
|
||||||
|
|
||||||
|
* To display a fullscreen dashboard with your configured widgets, by default available under ``/dashboard``
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **flask** (``pip install flask``)
|
||||||
|
* **redis** (``pip install redis``)
|
||||||
|
* **websockets** (``pip install websockets``)
|
||||||
|
"""
|
||||||
|
|
||||||
hidden_plugins = {
|
hidden_plugins = {
|
||||||
'assistant.google'
|
'assistant.google'
|
||||||
|
@ -34,6 +54,48 @@ class HttpBackend(Backend):
|
||||||
def __init__(self, port=8008, websocket_port=8009, disable_websocket=False,
|
def __init__(self, port=8008, websocket_port=8009, disable_websocket=False,
|
||||||
redis_queue='platypush_flask_mq', token=None, dashboard={},
|
redis_queue='platypush_flask_mq', token=None, dashboard={},
|
||||||
maps={}, **kwargs):
|
maps={}, **kwargs):
|
||||||
|
"""
|
||||||
|
:param port: Listen port for the web server (default: 8008)
|
||||||
|
:type port: int
|
||||||
|
|
||||||
|
:param websocket_port: Listen port for the websocket server (default: 8009)
|
||||||
|
:type websocket_port: int
|
||||||
|
|
||||||
|
:param disable_websocket: Disable the websocket interface (default: False)
|
||||||
|
:type disable_websocket: bool
|
||||||
|
|
||||||
|
:param redis_queue: Name of the Redis queue used to synchronize messages with the web server process (default: ``platypush_flask_mq``)
|
||||||
|
:type redis_queue: str
|
||||||
|
|
||||||
|
:param token: If set (recommended) any interaction with the web server needs to bear an ``X-Token: <token>`` header, or it will fail with a 403: Forbidden
|
||||||
|
:type token: str
|
||||||
|
|
||||||
|
:param dashboard: Set it if you want to use the dashboard service. It will contain the configuration for the widgets to be used (look under ``platypush/backend/http/templates/widgets/`` for the available widgets).
|
||||||
|
|
||||||
|
Example configuration::
|
||||||
|
|
||||||
|
dashboard:
|
||||||
|
background_image: https://site/image.png
|
||||||
|
widgets: # Each row of the dashboard will have 6 columns
|
||||||
|
calendar: # Calendar widget
|
||||||
|
columns: 6
|
||||||
|
music: # Music widget
|
||||||
|
columns: 3
|
||||||
|
date-time-weather: # Date, time and weather widget
|
||||||
|
columns: 3
|
||||||
|
image-carousel: # Image carousel
|
||||||
|
columns: 6
|
||||||
|
images_path: /static/resources/Dropbox/Photos/carousel # Path (relative to ``platypush/backend/http``) containing the carousel pictures
|
||||||
|
refresh_seconds: 15
|
||||||
|
rss-news: # RSS feeds widget
|
||||||
|
# Requires backend.http.poll to be enabled with some RSS sources and write them to sqlite db
|
||||||
|
columns: 6
|
||||||
|
limit: 25
|
||||||
|
db: "sqlite:////home/blacklight/.local/share/platypush/feeds/rss.db"
|
||||||
|
|
||||||
|
:type dashboard: dict
|
||||||
|
"""
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.port = port
|
self.port = port
|
||||||
|
@ -54,6 +116,7 @@ class HttpBackend(Backend):
|
||||||
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
""" Stop the web server """
|
||||||
self.logger.info('Received STOP event on HttpBackend')
|
self.logger.info('Received STOP event on HttpBackend')
|
||||||
|
|
||||||
if self.server_proc:
|
if self.server_proc:
|
||||||
|
@ -62,6 +125,7 @@ class HttpBackend(Backend):
|
||||||
|
|
||||||
|
|
||||||
def notify_web_clients(self, event):
|
def notify_web_clients(self, event):
|
||||||
|
""" Notify all the connected web clients (over websocket) of a new event """
|
||||||
import websockets
|
import websockets
|
||||||
|
|
||||||
async def send_event(websocket):
|
async def send_event(websocket):
|
||||||
|
@ -77,6 +141,7 @@ class HttpBackend(Backend):
|
||||||
|
|
||||||
|
|
||||||
def redis_poll(self):
|
def redis_poll(self):
|
||||||
|
""" Polls for new messages on the internal Redis queue """
|
||||||
while not self.should_stop():
|
while not self.should_stop():
|
||||||
msg = self.redis.blpop(self.redis_queue)
|
msg = self.redis.blpop(self.redis_queue)
|
||||||
msg = Message.build(json.loads(msg[1].decode('utf-8')))
|
msg = Message.build(json.loads(msg[1].decode('utf-8')))
|
||||||
|
@ -84,6 +149,7 @@ class HttpBackend(Backend):
|
||||||
|
|
||||||
|
|
||||||
def webserver(self):
|
def webserver(self):
|
||||||
|
""" Web server main process """
|
||||||
basedir = os.path.dirname(inspect.getfile(self.__class__))
|
basedir = os.path.dirname(inspect.getfile(self.__class__))
|
||||||
template_dir = os.path.join(basedir, 'templates')
|
template_dir = os.path.join(basedir, 'templates')
|
||||||
static_dir = os.path.join(basedir, 'static')
|
static_dir = os.path.join(basedir, 'static')
|
||||||
|
@ -92,6 +158,7 @@ class HttpBackend(Backend):
|
||||||
|
|
||||||
@app.route('/execute', methods=['POST'])
|
@app.route('/execute', methods=['POST'])
|
||||||
def execute():
|
def execute():
|
||||||
|
""" Endpoint to execute commands """
|
||||||
args = json.loads(http_request.data.decode('utf-8'))
|
args = json.loads(http_request.data.decode('utf-8'))
|
||||||
token = http_request.headers['X-Token'] if 'X-Token' in http_request.headers else None
|
token = http_request.headers['X-Token'] if 'X-Token' in http_request.headers else None
|
||||||
if token != self.token: abort(401)
|
if token != self.token: abort(401)
|
||||||
|
@ -111,6 +178,7 @@ class HttpBackend(Backend):
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
|
""" Route to the main web panel """
|
||||||
configured_plugins = Config.get_plugins()
|
configured_plugins = Config.get_plugins()
|
||||||
enabled_plugins = {}
|
enabled_plugins = {}
|
||||||
hidden_plugins = {}
|
hidden_plugins = {}
|
||||||
|
@ -129,6 +197,7 @@ class HttpBackend(Backend):
|
||||||
|
|
||||||
@app.route('/widget/<widget>', methods=['POST'])
|
@app.route('/widget/<widget>', methods=['POST'])
|
||||||
def widget_update(widget):
|
def widget_update(widget):
|
||||||
|
""" ``POST /widget/<widget_id>`` will update the specified widget_id on the dashboard with the specified key-values """
|
||||||
event = WidgetUpdateEvent(
|
event = WidgetUpdateEvent(
|
||||||
widget=widget, **(json.loads(http_request.data.decode('utf-8'))))
|
widget=widget, **(json.loads(http_request.data.decode('utf-8'))))
|
||||||
|
|
||||||
|
@ -137,10 +206,12 @@ class HttpBackend(Backend):
|
||||||
|
|
||||||
@app.route('/static/<path>', methods=['GET'])
|
@app.route('/static/<path>', methods=['GET'])
|
||||||
def static_path(path):
|
def static_path(path):
|
||||||
|
""" Static resources """
|
||||||
return send_from_directory(static_dir, filename)
|
return send_from_directory(static_dir, filename)
|
||||||
|
|
||||||
@app.route('/dashboard', methods=['GET'])
|
@app.route('/dashboard', methods=['GET'])
|
||||||
def dashboard():
|
def dashboard():
|
||||||
|
""" Route for the fullscreen dashboard """
|
||||||
return render_template('dashboard.html', config=self.dashboard, utils=HttpUtils,
|
return render_template('dashboard.html', config=self.dashboard, utils=HttpUtils,
|
||||||
token=self.token, websocket_port=self.websocket_port)
|
token=self.token, websocket_port=self.websocket_port)
|
||||||
|
|
||||||
|
@ -219,6 +290,7 @@ class HttpBackend(Backend):
|
||||||
|
|
||||||
|
|
||||||
def websocket(self):
|
def websocket(self):
|
||||||
|
""" Websocket main server """
|
||||||
import websockets
|
import websockets
|
||||||
|
|
||||||
async def register_websocket(websocket, path):
|
async def register_websocket(websocket, path):
|
||||||
|
|
|
@ -12,31 +12,47 @@ from platypush.message.request import Request
|
||||||
class HttpPollBackend(Backend):
|
class HttpPollBackend(Backend):
|
||||||
"""
|
"""
|
||||||
This backend will poll multiple HTTP endpoints/services and return events
|
This backend will poll multiple HTTP endpoints/services and return events
|
||||||
the bus whenever something new happened. Example configuration:
|
the bus whenever something new happened. Supported types:
|
||||||
|
:class:`platypush.backend.http.request.JsonHttpRequest` (for polling updates on
|
||||||
|
a JSON endpoint), :class:`platypush.backend.http.request.rss.RssUpdates`
|
||||||
|
(for polling updates on an RSS feed). Example configuration::
|
||||||
|
|
||||||
backend.http.poll:
|
backend.http.poll:
|
||||||
requests:
|
requests:
|
||||||
-
|
-
|
||||||
method: GET
|
# Poll for updates on a JSON endpoint
|
||||||
type: platypush.backend.http.request.JsonHttpRequest
|
method: GET
|
||||||
args:
|
type: platypush.backend.http.request.JsonHttpRequest
|
||||||
url: https://host.com/api/v1/endpoint
|
args:
|
||||||
headers:
|
url: https://host.com/api/v1/endpoint
|
||||||
Token: TOKEN
|
headers:
|
||||||
params:
|
Token: TOKEN
|
||||||
updatedSince: 1m
|
params:
|
||||||
timeout: 5 # Times out after 5 seconds (default)
|
updatedSince: 1m
|
||||||
poll_seconds: 60 # Check for updates on this endpoint every 60 seconds (default)
|
timeout: 5 # Times out after 5 seconds (default)
|
||||||
path: ${response['items']} # Path in the JSON to check for new items.
|
poll_seconds: 60 # Check for updates on this endpoint every 60 seconds (default)
|
||||||
# Python expressions are supported.
|
path: ${response['items']} # Path in the JSON to check for new items.
|
||||||
# Note that 'response' identifies the JSON root.
|
# Python expressions are supported.
|
||||||
# Default value: JSON root.
|
# Note that 'response' identifies the JSON root.
|
||||||
|
# Default value: JSON root.
|
||||||
|
-
|
||||||
|
# Poll for updates on an RSS feed
|
||||||
|
type: platypush.backend.http.request.rss.RssUpdates
|
||||||
|
url: http://www.theguardian.com/rss/world
|
||||||
|
title: The Guardian - World News
|
||||||
|
poll_seconds: 120
|
||||||
|
max_entries: 10
|
||||||
|
|
||||||
|
Triggers: an update event for the relevant HTTP source if it contains new items. For example:
|
||||||
|
|
||||||
|
* :class:`platypush.message.event.http.rss.NewFeedEvent` if a feed contains new items
|
||||||
|
* :class:`platypush.message.event.http.HttpEvent` if a JSON endpoint contains new items
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, requests, *args, **kwargs):
|
def __init__(self, requests, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Params:
|
:param requests: Configuration of the requests to make (see class description for examples)
|
||||||
requests -- List/iterable of HttpRequest objects
|
:type requests: dict
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -67,9 +83,5 @@ class HttpPollBackend(Backend):
|
||||||
time.sleep(0.1) # Prevent a tight loop
|
time.sleep(0.1) # Prevent a tight loop
|
||||||
|
|
||||||
|
|
||||||
def send_message(self, msg):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
||||||
|
|
|
@ -7,17 +7,38 @@ from platypush.message.event.path import PathCreateEvent, PathDeleteEvent, \
|
||||||
|
|
||||||
|
|
||||||
class InotifyBackend(Backend):
|
class InotifyBackend(Backend):
|
||||||
|
"""
|
||||||
|
(Linux only) This backend will listen for events on the filesystem (whether
|
||||||
|
a file/directory on a watch list is opened, modified, created, deleted,
|
||||||
|
closed or had its permissions changed) and will trigger a relevant event.
|
||||||
|
|
||||||
|
Triggers:
|
||||||
|
|
||||||
|
* :class:`platypush.message.event.path.PathCreateEvent` if a resource is created
|
||||||
|
* :class:`platypush.message.event.path.PathOpenEvent` if a resource is opened
|
||||||
|
* :class:`platypush.message.event.path.PathModifyEvent` if a resource is modified
|
||||||
|
* :class:`platypush.message.event.path.PathPermissionsChangeEvent` if the permissions of a resource are changed
|
||||||
|
* :class:`platypush.message.event.path.PathCloseEvent` if a resource is closed
|
||||||
|
* :class:`platypush.message.event.path.PathDeleteEvent` if a resource is removed
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **inotify** (``pip install inotify``)
|
||||||
|
"""
|
||||||
|
|
||||||
inotify_watch = None
|
inotify_watch = None
|
||||||
|
|
||||||
def __init__(self, watch_paths=[], **kwargs):
|
def __init__(self, watch_paths=[], **kwargs):
|
||||||
|
"""
|
||||||
|
:param watch_paths: Filesystem resources to watch for events
|
||||||
|
:type watch_paths: str
|
||||||
|
"""
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.watch_paths = set(map(
|
self.watch_paths = set(map(
|
||||||
lambda path: os.path.abspath(os.path.expanduser(path)),
|
lambda path: os.path.abspath(os.path.expanduser(path)),
|
||||||
watch_paths))
|
watch_paths))
|
||||||
|
|
||||||
def send_message(self, msg):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _cleanup(self):
|
def _cleanup(self):
|
||||||
if not self.inotify_watch:
|
if not self.inotify_watch:
|
||||||
return
|
return
|
||||||
|
|
|
@ -8,9 +8,26 @@ from .. import Backend
|
||||||
|
|
||||||
|
|
||||||
class KafkaBackend(Backend):
|
class KafkaBackend(Backend):
|
||||||
|
"""
|
||||||
|
Backend to interact with an Apache Kafka (https://kafka.apache.org/)
|
||||||
|
streaming platform, send and receive messages.
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **kafka** (``pip install kafka-python``)
|
||||||
|
"""
|
||||||
|
|
||||||
_conn_retry_secs = 5
|
_conn_retry_secs = 5
|
||||||
|
|
||||||
def __init__(self, server, topic, **kwargs):
|
def __init__(self, server, topic='platypush', **kwargs):
|
||||||
|
"""
|
||||||
|
:param server: Kafka server
|
||||||
|
:type server: str
|
||||||
|
|
||||||
|
:param topic: (Prefix) topic to listen to (default: platypush). The Platypush device_id (by default the hostname) will be appended to the topic (the real topic name will e.g. be "platypush.my_rpi")
|
||||||
|
:type topic: str
|
||||||
|
"""
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.server = server
|
self.server = server
|
||||||
|
|
|
@ -14,22 +14,28 @@ class MidiBackend(Backend):
|
||||||
This backend will listen for events from a MIDI device and post a
|
This backend will listen for events from a MIDI device and post a
|
||||||
MidiMessageEvent whenever a new MIDI event happens.
|
MidiMessageEvent whenever a new MIDI event happens.
|
||||||
|
|
||||||
It requires `rtmidi`, `pip install rtmidi`
|
Triggers:
|
||||||
|
|
||||||
|
* :class:`platypush.message.event.midi.MidiMessageEvent` when a new MIDI event is received
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **rtmidi** (``pip install rtmidi``)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, device_name=None, port_number=None,
|
def __init__(self, device_name=None, port_number=None,
|
||||||
midi_throttle_time=None, *args, **kwargs):
|
midi_throttle_time=None, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Params:
|
:param device_name: Name of the MIDI device. *N.B.* either `device_name` or `port_number` must be set
|
||||||
device_name -- Name of the MIDI device.
|
:type device_name: str
|
||||||
*N.B.* either `device_name` or `port_number` must be set
|
|
||||||
port_number -- MIDI port number
|
:param port_number: MIDI port number
|
||||||
*N.B.* either `device_name` or `port_number` must be set
|
:type port_number: int
|
||||||
midi_throttle_time -- If set, the MIDI events will be throttled -
|
|
||||||
max one per selected time frame (in seconds). Set this parameter
|
:param midi_throttle_time: If set, the MIDI events will be throttled - max one per selected time frame (in seconds). Set this parameter if you want to synchronize MIDI events with plugins that normally operate with a lower throughput.
|
||||||
if you want to synchronize MIDI events with plugins that
|
:type midi_throttle_time: int
|
||||||
normally operate with a lower throughput.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if (device_name and port_number is not None) or \
|
if (device_name and port_number is not None) or \
|
||||||
|
|
|
@ -9,17 +9,26 @@ from platypush.message import Message
|
||||||
|
|
||||||
class MqttBackend(Backend):
|
class MqttBackend(Backend):
|
||||||
"""
|
"""
|
||||||
Backend that reads messages from a configured MQTT topic
|
Backend that reads messages from a configured MQTT topic (default:
|
||||||
(default: `platypush_bus_mq/<device_id>`) and posts them to the application bus.
|
``platypush_bus_mq/<device_id>``) and posts them to the application bus.
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **paho-mqtt** (``pip install paho-mqtt``)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, host, port=1883, topic='platypush_bus_mq', *args, **kwargs):
|
def __init__(self, host, port=1883, topic='platypush_bus_mq', *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Params:
|
:param host: MQTT broker host
|
||||||
host -- MQTT broker host
|
:type host: str
|
||||||
port -- MQTT broker port (default: 1883)
|
|
||||||
topic -- Topic to read messages from (default: platypush_bus_mq/<device_id>)
|
:param port: MQTT broker port (default: 1883)
|
||||||
|
:type port: int
|
||||||
|
|
||||||
|
:param topic: Topic to read messages from (default: ``platypush_bus_mq/<device_id>``)
|
||||||
|
:type topic: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.host = host
|
self.host = host
|
||||||
|
|
|
@ -8,7 +8,28 @@ from platypush.message.event.music import MusicPlayEvent, MusicPauseEvent, \
|
||||||
|
|
||||||
|
|
||||||
class MusicMpdBackend(Backend):
|
class MusicMpdBackend(Backend):
|
||||||
|
"""
|
||||||
|
This backend listens for events on a MPD/Mopidy music server.
|
||||||
|
|
||||||
|
Triggers:
|
||||||
|
|
||||||
|
* :class:`platypush.message.event.music.MusicPlayEvent` if the playback state changed to play
|
||||||
|
* :class:`platypush.message.event.music.MusicPauseEvent` if the playback state changed to pause
|
||||||
|
* :class:`platypush.message.event.music.MusicStopEvent` if the playback state changed to stop
|
||||||
|
* :class:`platypush.message.event.music.NewPlayingTrackEvent` if a new track is being played
|
||||||
|
* :class:`platypush.message.event.music.PlaylistChangeEvent` if the main playlist has changed
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
* **python-mpd2** (``pip install python-mpd2``)
|
||||||
|
* The :mod:`platypush.plugins.music.mpd` plugin to be configured
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, server='localhost', port=6600, poll_seconds=3, **kwargs):
|
def __init__(self, server='localhost', port=6600, poll_seconds=3, **kwargs):
|
||||||
|
"""
|
||||||
|
:param poll_seconds: Interval between queries to the server (default: 3 seconds)
|
||||||
|
:type poll_seconds: float
|
||||||
|
"""
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.server = server
|
self.server = server
|
||||||
|
@ -16,10 +37,6 @@ class MusicMpdBackend(Backend):
|
||||||
self.poll_seconds = poll_seconds
|
self.poll_seconds = poll_seconds
|
||||||
|
|
||||||
|
|
||||||
def send_message(self, msg):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
super().run()
|
super().run()
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,33 @@ from .. import Backend
|
||||||
|
|
||||||
|
|
||||||
class PushbulletBackend(Backend):
|
class PushbulletBackend(Backend):
|
||||||
def __init__(self, token, device, **kwargs):
|
"""
|
||||||
|
This backend will listen for events on a Pushbullet (https://pushbullet.com)
|
||||||
|
channel and propagate them to the bus. This backend is quite useful if you
|
||||||
|
want to synchronize events and actions with your mobile phone (through the
|
||||||
|
Pushbullet app and/or through Tasker), synchronize clipboards, send pictures
|
||||||
|
and files to other devices etc. You can also wrap Platypush messages as JSON
|
||||||
|
into a push body to execute them.
|
||||||
|
|
||||||
|
Triggers:
|
||||||
|
|
||||||
|
* :class:`platypush.message.event.pushbullet` if a new push is received
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **requests** (``pip install requests``)
|
||||||
|
* **websocket-client** (``pip install websocket-client``)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, token, device='Platypush', **kwargs):
|
||||||
|
"""
|
||||||
|
:param token: Your Pushbullet API token, see https://docs.pushbullet.com/#authentication
|
||||||
|
:type token: str
|
||||||
|
|
||||||
|
:param device: Name of the virtual device for Platypush (default: Platypush)
|
||||||
|
:type device: str
|
||||||
|
"""
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.token = token
|
self.token = token
|
||||||
|
|
|
@ -8,20 +8,25 @@ from platypush.message import Message
|
||||||
|
|
||||||
class RedisBackend(Backend):
|
class RedisBackend(Backend):
|
||||||
"""
|
"""
|
||||||
Backend that reads messages from a configured Redis queue
|
Backend that reads messages from a configured Redis queue (default:
|
||||||
(default: `platypush_bus_mq`) and posts them to the application bus.
|
``platypush_bus_mq``) and posts them to the application bus. Very
|
||||||
Very useful when you have plugin whose code is executed in another process
|
useful when you have plugin whose code is executed in another process
|
||||||
and can't post events or requests to the application bus.
|
and can't post events or requests to the application bus.
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **redis** (``pip install redis``)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, queue='platypush_bus_mq', redis_args={}, *args, **kwargs):
|
def __init__(self, queue='platypush_bus_mq', redis_args={}, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Params:
|
:param queue: Queue name to listen on (default: ``platypush_bus_mq``)
|
||||||
queue -- Queue to poll for new messages
|
:type queue: str
|
||||||
redis_args -- Arguments that will be passed to the redis-py
|
|
||||||
constructor (e.g. host, port, password),
|
:param redis_args: Arguments that will be passed to the redis-py constructor (e.g. host, port, password), see http://redis-py.readthedocs.io/en/latest/
|
||||||
see http://redis-py.readthedocs.io/en/latest/
|
:type redis_args: dict
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
|
|
|
@ -11,22 +11,27 @@ from platypush.message.event.scard import SmartCardDetectedEvent, SmartCardRemov
|
||||||
|
|
||||||
class ScardBackend(Backend):
|
class ScardBackend(Backend):
|
||||||
"""
|
"""
|
||||||
Generic backend to read smart cards and trigger SmartCardDetectedEvent
|
Generic backend to read smart cards and NFC tags and trigger an event
|
||||||
messages with the card ATR whenever a card is detected. It requires
|
whenever a device is detected.
|
||||||
pyscard https://pypi.org/project/pyscard/
|
|
||||||
|
|
||||||
Extend this backend to implement more advanced communication with
|
Extend this backend to implement more advanced communication with custom
|
||||||
custom smart cards.
|
smart cards.
|
||||||
|
|
||||||
|
Triggers:
|
||||||
|
|
||||||
|
* :class:`platypush.message.event.scard.SmartCardDetectedEvent` when a smart card is detected
|
||||||
|
* :class:`platypush.message.event.scard.SmartCardRemovedEvent` when a smart card is removed
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **pyscard** (``pip install pyscard``)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, atr=None, *args, **kwargs):
|
def __init__(self, atr=None, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Params:
|
:param atr: If set, the backend will trigger events only for card(s) with the specified ATR(s). It can be either an ATR string (space-separated hex octects) or a list of ATR strings. Default: none (any card will be detected)
|
||||||
atr -- If set, the backend will trigger events only for card(s)
|
|
||||||
with the specified ATR(s). It can be either an ATR string
|
|
||||||
(space-separated hex octects) or a list of ATR strings.
|
|
||||||
Default: none (any card will be detected)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.ATRs = []
|
self.ATRs = []
|
||||||
|
|
||||||
|
|
|
@ -6,25 +6,28 @@ from platypush.message.event.sensor import SensorDataChangeEvent, \
|
||||||
|
|
||||||
|
|
||||||
class SensorBackend(Backend):
|
class SensorBackend(Backend):
|
||||||
|
"""
|
||||||
|
Abstract backend for polling sensors.
|
||||||
|
|
||||||
|
Triggers:
|
||||||
|
|
||||||
|
* :class:`platypush.message.event.sensor.SensorDataChangeEvent` if the measurements of a sensor have changed
|
||||||
|
* :class:`platypush.message.event.sensor.SensorDataAboveThresholdEvent` if the measurements of a sensor have gone above a configured threshold
|
||||||
|
* :class:`platypush.message.event.sensor.SensorDataBelowThresholdEvent` if the measurements of a sensor have gone below a configured threshold
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, thresholds=None, poll_seconds=None, *args, **kwargs):
|
def __init__(self, thresholds=None, poll_seconds=None, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Params:
|
:param thresholds: Thresholds can be either a scalar value or a dictionary (e.g. ``{"temperature": 20.0}``). Sensor threshold events will be fired when measurements get above or below these values. Set it as a scalar if your get_measurement() code returns a scalar, as a dictionary if it returns a dictionary of values.
|
||||||
-- thresholds: Thresholds can be either a scalr value or a dictionary.
|
|
||||||
|
|
||||||
If set, SensorDataAboveThresholdEvent and SensorDataBelowThresholdEvent
|
For instance, if your sensor code returns both humidity and
|
||||||
events will be triggered whenever the measurement goes above or
|
temperature in a format like ``{'humidity':60.0, 'temperature': 25.0}``,
|
||||||
below that value.
|
you'll want to set up a threshold on temperature with a syntax like
|
||||||
|
``{'temperature':20.0}`` to trigger events when the temperature goes
|
||||||
|
above/below 20 degrees.
|
||||||
|
|
||||||
Set it as a scalar if your get_measurement() code returns a scalar,
|
:param poll_seconds: If set, the thread will wait for the specificed number of seconds between a read and the next one.
|
||||||
as a dictionary if it returns a dictionary of values.
|
:type poll_seconds: float
|
||||||
|
|
||||||
For instance, if your sensor code returns both humidity and
|
|
||||||
temperature in a format like {'humidity':60.0, 'temperature': 25.0},
|
|
||||||
you'll want to set up a threshold on temperature with a syntax like
|
|
||||||
{'temperature':20.0}
|
|
||||||
|
|
||||||
-- poll_seconds: If set, the thread will wait for the specificed
|
|
||||||
number of seconds between a read and the next one.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
@ -34,6 +37,7 @@ class SensorBackend(Backend):
|
||||||
self.poll_seconds = poll_seconds
|
self.poll_seconds = poll_seconds
|
||||||
|
|
||||||
def get_measurement(self):
|
def get_measurement(self):
|
||||||
|
""" To be implemented in the derived classes """
|
||||||
raise NotImplementedError('To be implemented in a derived class')
|
raise NotImplementedError('To be implemented in a derived class')
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
Loading…
Reference in a new issue