forked from platypush/platypush
Updated email addresses and black'd some old source files.
This commit is contained in:
parent
cf8ecf349b
commit
66981bd00b
18 changed files with 358 additions and 256 deletions
|
@ -25,7 +25,7 @@ from .message.request import Request
|
||||||
from .message.response import Response
|
from .message.response import Response
|
||||||
from .utils import set_thread_name, get_enabled_plugins
|
from .utils import set_thread_name, get_enabled_plugins
|
||||||
|
|
||||||
__author__ = 'Fabio Manganiello <info@fabiomanganiello.com>'
|
__author__ = 'Fabio Manganiello <fabio@manganiello.tech>'
|
||||||
__version__ = '0.50.2'
|
__version__ = '0.50.2'
|
||||||
|
|
||||||
log = logging.getLogger('platypush')
|
log = logging.getLogger('platypush')
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
.. license: MIT
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
|
@ -15,9 +10,17 @@ from platypush.bus import Bus
|
||||||
from platypush.common import ExtensionWithManifest
|
from platypush.common import ExtensionWithManifest
|
||||||
from platypush.config import Config
|
from platypush.config import Config
|
||||||
from platypush.context import get_backend
|
from platypush.context import get_backend
|
||||||
from platypush.message.event.zeroconf import ZeroconfServiceAddedEvent, ZeroconfServiceRemovedEvent
|
from platypush.message.event.zeroconf import (
|
||||||
from platypush.utils import set_timeout, clear_timeout, \
|
ZeroconfServiceAddedEvent,
|
||||||
get_redis_queue_name_by_message, set_thread_name, get_backend_name_by_class
|
ZeroconfServiceRemovedEvent,
|
||||||
|
)
|
||||||
|
from platypush.utils import (
|
||||||
|
set_timeout,
|
||||||
|
clear_timeout,
|
||||||
|
get_redis_queue_name_by_message,
|
||||||
|
set_thread_name,
|
||||||
|
get_backend_name_by_class,
|
||||||
|
)
|
||||||
|
|
||||||
from platypush import __version__
|
from platypush import __version__
|
||||||
from platypush.event import EventGenerator
|
from platypush.event import EventGenerator
|
||||||
|
@ -44,7 +47,9 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
|
||||||
# Loop function, can be implemented by derived classes
|
# Loop function, can be implemented by derived classes
|
||||||
loop = None
|
loop = None
|
||||||
|
|
||||||
def __init__(self, bus: Optional[Bus] = None, poll_seconds: Optional[float] = None, **kwargs):
|
def __init__(
|
||||||
|
self, bus: Optional[Bus] = None, poll_seconds: Optional[float] = None, **kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
:param bus: Reference to the bus object to be used in the backend
|
:param bus: Reference to the bus object to be used in the backend
|
||||||
:param poll_seconds: If the backend implements a ``loop`` method, this parameter expresses how often the
|
:param poll_seconds: If the backend implements a ``loop`` method, this parameter expresses how often the
|
||||||
|
@ -65,14 +70,15 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
|
||||||
self.thread_id = None
|
self.thread_id = None
|
||||||
self._stop_event = ThreadEvent()
|
self._stop_event = ThreadEvent()
|
||||||
self._kwargs = kwargs
|
self._kwargs = kwargs
|
||||||
self.logger = logging.getLogger('platypush:backend:' + get_backend_name_by_class(self.__class__))
|
self.logger = logging.getLogger(
|
||||||
|
'platypush:backend:' + get_backend_name_by_class(self.__class__)
|
||||||
|
)
|
||||||
self.zeroconf = None
|
self.zeroconf = None
|
||||||
self.zeroconf_info = None
|
self.zeroconf_info = None
|
||||||
|
|
||||||
# Internal-only, we set the request context on a backend if that
|
# Internal-only, we set the request context on a backend if that
|
||||||
# backend is intended to react for a response to a specific request
|
# backend is intended to react for a response to a specific request
|
||||||
self._request_context = kwargs['_req_ctx'] if '_req_ctx' in kwargs \
|
self._request_context = kwargs['_req_ctx'] if '_req_ctx' in kwargs else None
|
||||||
else None
|
|
||||||
|
|
||||||
if 'logging' in kwargs:
|
if 'logging' in kwargs:
|
||||||
self.logger.setLevel(getattr(logging, kwargs.get('logging').upper()))
|
self.logger.setLevel(getattr(logging, kwargs.get('logging').upper()))
|
||||||
|
@ -90,11 +96,14 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
|
||||||
|
|
||||||
msg = Message.build(msg)
|
msg = Message.build(msg)
|
||||||
|
|
||||||
if not getattr(msg, 'target') or msg.target != self.device_id:
|
if not getattr(msg, 'target', None) or msg.target != self.device_id:
|
||||||
return # Not for me
|
return # Not for me
|
||||||
|
|
||||||
self.logger.debug('Message received on the {} backend: {}'.format(
|
self.logger.debug(
|
||||||
self.__class__.__name__, msg))
|
'Message received on the {} backend: {}'.format(
|
||||||
|
self.__class__.__name__, msg
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if self._is_expected_response(msg):
|
if self._is_expected_response(msg):
|
||||||
# Expected response, trigger the response handler
|
# Expected response, trigger the response handler
|
||||||
|
@ -104,26 +113,31 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
|
||||||
self.stop()
|
self.stop()
|
||||||
return
|
return
|
||||||
|
|
||||||
msg.backend = self # Augment message to be able to process responses
|
msg.backend = self # Augment message to be able to process responses
|
||||||
self.bus.post(msg)
|
self.bus.post(msg)
|
||||||
|
|
||||||
def _is_expected_response(self, msg):
|
def _is_expected_response(self, msg):
|
||||||
""" Internal only - returns true if we are expecting for a response
|
"""Internal only - returns true if we are expecting for a response
|
||||||
and msg is that response """
|
and msg is that response"""
|
||||||
|
|
||||||
# pylint: disable=unsubscriptable-object
|
# pylint: disable=unsubscriptable-object
|
||||||
return self._request_context \
|
return (
|
||||||
and isinstance(msg, Response) \
|
self._request_context
|
||||||
|
and isinstance(msg, Response)
|
||||||
and msg.id == self._request_context['request'].id
|
and msg.id == self._request_context['request'].id
|
||||||
|
)
|
||||||
|
|
||||||
def _get_backend_config(self):
|
def _get_backend_config(self):
|
||||||
config_name = 'backend.' + self.__class__.__name__.split('Backend', maxsplit=1)[0].lower()
|
config_name = (
|
||||||
|
'backend.' + self.__class__.__name__.split('Backend', maxsplit=1)[0].lower()
|
||||||
|
)
|
||||||
return Config.get(config_name)
|
return Config.get(config_name)
|
||||||
|
|
||||||
def _setup_response_handler(self, request, on_response, response_timeout):
|
def _setup_response_handler(self, request, on_response, response_timeout):
|
||||||
def _timeout_hndl():
|
def _timeout_hndl():
|
||||||
raise RuntimeError('Timed out while waiting for a response from {}'.
|
raise RuntimeError(
|
||||||
format(request.target))
|
'Timed out while waiting for a response from {}'.format(request.target)
|
||||||
|
)
|
||||||
|
|
||||||
req_ctx = {
|
req_ctx = {
|
||||||
'request': request,
|
'request': request,
|
||||||
|
@ -131,8 +145,9 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
|
||||||
'response_timeout': response_timeout,
|
'response_timeout': response_timeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp_backend = self.__class__(bus=self.bus, _req_ctx=req_ctx,
|
resp_backend = self.__class__(
|
||||||
**self._get_backend_config(), **self._kwargs)
|
bus=self.bus, _req_ctx=req_ctx, **self._get_backend_config(), **self._kwargs
|
||||||
|
)
|
||||||
|
|
||||||
# Set the response timeout
|
# Set the response timeout
|
||||||
if response_timeout:
|
if response_timeout:
|
||||||
|
@ -157,8 +172,13 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
|
||||||
|
|
||||||
self.send_message(event, **kwargs)
|
self.send_message(event, **kwargs)
|
||||||
|
|
||||||
def send_request(self, request, on_response=None,
|
def send_request(
|
||||||
response_timeout=_default_response_timeout, **kwargs):
|
self,
|
||||||
|
request,
|
||||||
|
on_response=None,
|
||||||
|
response_timeout=_default_response_timeout,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Send a request message on the backend.
|
Send a request message on the backend.
|
||||||
|
|
||||||
|
@ -215,16 +235,18 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
|
||||||
if not redis:
|
if not redis:
|
||||||
raise KeyError()
|
raise KeyError()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.logger.warning((
|
self.logger.warning(
|
||||||
"Backend {} does not implement send_message "
|
(
|
||||||
"and the fallback Redis backend isn't configured"
|
"Backend {} does not implement send_message "
|
||||||
).format(self.__class__.__name__))
|
"and the fallback Redis backend isn't configured"
|
||||||
|
).format(self.__class__.__name__)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
redis.send_message(msg, queue_name=queue_name)
|
redis.send_message(msg, queue_name=queue_name)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
""" Starts the backend thread. To be implemented in the derived classes if the loop method isn't defined. """
|
"""Starts the backend thread. To be implemented in the derived classes if the loop method isn't defined."""
|
||||||
self.thread_id = get_ident()
|
self.thread_id = get_ident()
|
||||||
set_thread_name(self._thread_name)
|
set_thread_name(self._thread_name)
|
||||||
if not callable(self.loop):
|
if not callable(self.loop):
|
||||||
|
@ -249,24 +271,29 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
|
||||||
elif has_error:
|
elif has_error:
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error('{} initialization error: {}'.format(self.__class__.__name__, str(e)))
|
self.logger.error(
|
||||||
|
'{} initialization error: {}'.format(
|
||||||
|
self.__class__.__name__, str(e)
|
||||||
|
)
|
||||||
|
)
|
||||||
self.logger.exception(e)
|
self.logger.exception(e)
|
||||||
time.sleep(self.poll_seconds or 5)
|
time.sleep(self.poll_seconds or 5)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
""" Invoked when the backend is initialized, if the main logic is within a ``loop()`` function """
|
"""Invoked when the backend is initialized, if the main logic is within a ``loop()`` function"""
|
||||||
self.logger.info('Initialized backend {}'.format(self.__class__.__name__))
|
self.logger.info('Initialized backend {}'.format(self.__class__.__name__))
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
""" Invoked when the backend is terminated, if the main logic is within a ``loop()`` function """
|
"""Invoked when the backend is terminated, if the main logic is within a ``loop()`` function"""
|
||||||
self.on_stop()
|
self.on_stop()
|
||||||
self.logger.info('Terminated backend {}'.format(self.__class__.__name__))
|
self.logger.info('Terminated backend {}'.format(self.__class__.__name__))
|
||||||
|
|
||||||
def on_stop(self):
|
def on_stop(self):
|
||||||
""" Callback invoked when the process stops """
|
"""Callback invoked when the process stops"""
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
""" Stops the backend thread by sending a STOP event on its bus """
|
"""Stops the backend thread by sending a STOP event on its bus"""
|
||||||
|
|
||||||
def _async_stop():
|
def _async_stop():
|
||||||
self._stop_event.set()
|
self._stop_event.set()
|
||||||
self.unregister_service()
|
self.unregister_service()
|
||||||
|
@ -285,8 +312,10 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
|
||||||
|
|
||||||
redis_backend = get_backend('redis')
|
redis_backend = get_backend('redis')
|
||||||
if not redis_backend:
|
if not redis_backend:
|
||||||
self.logger.warning('Redis backend not configured - some '
|
self.logger.warning(
|
||||||
'web server features may not be working properly')
|
'Redis backend not configured - some '
|
||||||
|
'web server features may not be working properly'
|
||||||
|
)
|
||||||
redis_args = {}
|
redis_args = {}
|
||||||
else:
|
else:
|
||||||
redis_args = redis_backend.redis_args
|
redis_args = redis_backend.redis_args
|
||||||
|
@ -305,7 +334,9 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
|
||||||
|
|
||||||
return response
|
return response
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error('Error while processing response to {}: {}'.format(msg, str(e)))
|
self.logger.error(
|
||||||
|
'Error while processing response to {}: {}'.format(msg, str(e))
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_ip() -> str:
|
def _get_ip() -> str:
|
||||||
|
@ -318,13 +349,15 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
|
||||||
s.close()
|
s.close()
|
||||||
return addr
|
return addr
|
||||||
|
|
||||||
def register_service(self,
|
def register_service(
|
||||||
port: Optional[int] = None,
|
self,
|
||||||
name: Optional[str] = None,
|
port: Optional[int] = None,
|
||||||
srv_type: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
srv_name: Optional[str] = None,
|
srv_type: Optional[str] = None,
|
||||||
udp: bool = False,
|
srv_name: Optional[str] = None,
|
||||||
properties: Optional[Dict] = None):
|
udp: bool = False,
|
||||||
|
properties: Optional[Dict] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Initialize the Zeroconf service configuration for this backend.
|
Initialize the Zeroconf service configuration for this backend.
|
||||||
|
|
||||||
|
@ -348,7 +381,9 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
|
||||||
from zeroconf import ServiceInfo, Zeroconf
|
from zeroconf import ServiceInfo, Zeroconf
|
||||||
from platypush.plugins.zeroconf import ZeroconfListener
|
from platypush.plugins.zeroconf import ZeroconfListener
|
||||||
except ImportError:
|
except ImportError:
|
||||||
self.logger.warning('zeroconf package not available, service discovery will be disabled.')
|
self.logger.warning(
|
||||||
|
'zeroconf package not available, service discovery will be disabled.'
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.zeroconf = Zeroconf()
|
self.zeroconf = Zeroconf()
|
||||||
|
@ -360,28 +395,40 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
|
||||||
}
|
}
|
||||||
|
|
||||||
name = name or re.sub(r'Backend$', '', self.__class__.__name__).lower()
|
name = name or re.sub(r'Backend$', '', self.__class__.__name__).lower()
|
||||||
srv_type = srv_type or '_platypush-{name}._{proto}.local.'.format(name=name, proto='udp' if udp else 'tcp')
|
srv_type = srv_type or '_platypush-{name}._{proto}.local.'.format(
|
||||||
srv_name = srv_name or '{host}.{type}'.format(host=self.device_id, type=srv_type)
|
name=name, proto='udp' if udp else 'tcp'
|
||||||
|
)
|
||||||
|
srv_name = srv_name or '{host}.{type}'.format(
|
||||||
|
host=self.device_id, type=srv_type
|
||||||
|
)
|
||||||
|
|
||||||
if port:
|
if port:
|
||||||
srv_port = port
|
srv_port = port
|
||||||
else:
|
else:
|
||||||
srv_port = self.port if hasattr(self, 'port') else None
|
srv_port = self.port if hasattr(self, 'port') else None
|
||||||
|
|
||||||
self.zeroconf_info = ServiceInfo(srv_type, srv_name,
|
self.zeroconf_info = ServiceInfo(
|
||||||
addresses=[socket.inet_aton(self._get_ip())],
|
srv_type,
|
||||||
port=srv_port,
|
srv_name,
|
||||||
weight=0,
|
addresses=[socket.inet_aton(self._get_ip())],
|
||||||
priority=0,
|
port=srv_port,
|
||||||
properties=srv_desc)
|
weight=0,
|
||||||
|
priority=0,
|
||||||
|
properties=srv_desc,
|
||||||
|
)
|
||||||
|
|
||||||
if not self.zeroconf_info:
|
if not self.zeroconf_info:
|
||||||
self.logger.warning('Could not register Zeroconf service')
|
self.logger.warning('Could not register Zeroconf service')
|
||||||
return
|
return
|
||||||
|
|
||||||
self.zeroconf.register_service(self.zeroconf_info)
|
self.zeroconf.register_service(self.zeroconf_info)
|
||||||
self.bus.post(ZeroconfServiceAddedEvent(service_type=srv_type, service_name=srv_name,
|
self.bus.post(
|
||||||
service_info=ZeroconfListener.parse_service_info(self.zeroconf_info)))
|
ZeroconfServiceAddedEvent(
|
||||||
|
service_type=srv_type,
|
||||||
|
service_name=srv_name,
|
||||||
|
service_info=ZeroconfListener.parse_service_info(self.zeroconf_info),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def unregister_service(self):
|
def unregister_service(self):
|
||||||
"""
|
"""
|
||||||
|
@ -391,17 +438,26 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
|
||||||
try:
|
try:
|
||||||
self.zeroconf.unregister_service(self.zeroconf_info)
|
self.zeroconf.unregister_service(self.zeroconf_info)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning('Could not register Zeroconf service {}: {}: {}'.format(
|
self.logger.warning(
|
||||||
self.zeroconf_info.name, type(e).__name__, str(e)))
|
'Could not register Zeroconf service {}: {}: {}'.format(
|
||||||
|
self.zeroconf_info.name, type(e).__name__, str(e)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if self.zeroconf:
|
if self.zeroconf:
|
||||||
self.zeroconf.close()
|
self.zeroconf.close()
|
||||||
|
|
||||||
if self.zeroconf_info:
|
if self.zeroconf_info:
|
||||||
self.bus.post(ZeroconfServiceRemovedEvent(service_type=self.zeroconf_info.type,
|
self.bus.post(
|
||||||
service_name=self.zeroconf_info.name))
|
ZeroconfServiceRemovedEvent(
|
||||||
|
service_type=self.zeroconf_info.type,
|
||||||
|
service_name=self.zeroconf_info.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.bus.post(ZeroconfServiceRemovedEvent(service_type=None, service_name=None))
|
self.bus.post(
|
||||||
|
ZeroconfServiceRemovedEvent(service_type=None, service_name=None)
|
||||||
|
)
|
||||||
|
|
||||||
self.zeroconf_info = None
|
self.zeroconf_info = None
|
||||||
self.zeroconf = None
|
self.zeroconf = None
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
.. license: MIT
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
.. license: MIT
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
@ -89,10 +84,14 @@ class AssistantSnowboyBackend(AssistantBackend):
|
||||||
self.detector = snowboydecoder.HotwordDetector(
|
self.detector = snowboydecoder.HotwordDetector(
|
||||||
[model['voice_model_file'] for model in self.models.values()],
|
[model['voice_model_file'] for model in self.models.values()],
|
||||||
sensitivity=[model['sensitivity'] for model in self.models.values()],
|
sensitivity=[model['sensitivity'] for model in self.models.values()],
|
||||||
audio_gain=self.audio_gain)
|
audio_gain=self.audio_gain,
|
||||||
|
)
|
||||||
|
|
||||||
self.logger.info('Initialized Snowboy hotword detection with {} voice model configurations'.
|
self.logger.info(
|
||||||
format(len(self.models)))
|
'Initialized Snowboy hotword detection with {} voice model configurations'.format(
|
||||||
|
len(self.models)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def _init_models(self, models):
|
def _init_models(self, models):
|
||||||
if not models:
|
if not models:
|
||||||
|
@ -107,7 +106,9 @@ class AssistantSnowboyBackend(AssistantBackend):
|
||||||
detect_sound = conf.get('detect_sound')
|
detect_sound = conf.get('detect_sound')
|
||||||
|
|
||||||
if not model_file:
|
if not model_file:
|
||||||
raise AttributeError('No voice_model_file specified for model {}'.format(name))
|
raise AttributeError(
|
||||||
|
'No voice_model_file specified for model {}'.format(name)
|
||||||
|
)
|
||||||
|
|
||||||
model_file = os.path.abspath(os.path.expanduser(model_file))
|
model_file = os.path.abspath(os.path.expanduser(model_file))
|
||||||
assistant_plugin_name = conf.get('assistant_plugin')
|
assistant_plugin_name = conf.get('assistant_plugin')
|
||||||
|
@ -116,14 +117,19 @@ class AssistantSnowboyBackend(AssistantBackend):
|
||||||
detect_sound = os.path.abspath(os.path.expanduser(detect_sound))
|
detect_sound = os.path.abspath(os.path.expanduser(detect_sound))
|
||||||
|
|
||||||
if not os.path.isfile(model_file):
|
if not os.path.isfile(model_file):
|
||||||
raise FileNotFoundError('Voice model file {} does not exist or it not a regular file'.
|
raise FileNotFoundError(
|
||||||
format(model_file))
|
'Voice model file {} does not exist or it not a regular file'.format(
|
||||||
|
model_file
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.models[name] = {
|
self.models[name] = {
|
||||||
'voice_model_file': model_file,
|
'voice_model_file': model_file,
|
||||||
'sensitivity': conf.get('sensitivity', 0.5),
|
'sensitivity': conf.get('sensitivity', 0.5),
|
||||||
'detect_sound': detect_sound,
|
'detect_sound': detect_sound,
|
||||||
'assistant_plugin': get_plugin(assistant_plugin_name) if assistant_plugin_name else None,
|
'assistant_plugin': get_plugin(assistant_plugin_name)
|
||||||
|
if assistant_plugin_name
|
||||||
|
else None,
|
||||||
'assistant_language': conf.get('assistant_language'),
|
'assistant_language': conf.get('assistant_language'),
|
||||||
'tts_plugin': conf.get('tts_plugin'),
|
'tts_plugin': conf.get('tts_plugin'),
|
||||||
'tts_args': conf.get('tts_args', {}),
|
'tts_args': conf.get('tts_args', {}),
|
||||||
|
@ -143,7 +149,9 @@ class AssistantSnowboyBackend(AssistantBackend):
|
||||||
|
|
||||||
def callback():
|
def callback():
|
||||||
if not self.is_detecting():
|
if not self.is_detecting():
|
||||||
self.logger.info('Hotword detected but assistant response currently paused')
|
self.logger.info(
|
||||||
|
'Hotword detected but assistant response currently paused'
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.bus.post(HotwordDetectedEvent(hotword=hotword))
|
self.bus.post(HotwordDetectedEvent(hotword=hotword))
|
||||||
|
@ -159,8 +167,11 @@ class AssistantSnowboyBackend(AssistantBackend):
|
||||||
threading.Thread(target=sound_thread, args=(detect_sound,)).start()
|
threading.Thread(target=sound_thread, args=(detect_sound,)).start()
|
||||||
|
|
||||||
if assistant_plugin:
|
if assistant_plugin:
|
||||||
assistant_plugin.start_conversation(language=assistant_language, tts_plugin=tts_plugin,
|
assistant_plugin.start_conversation(
|
||||||
tts_args=tts_args)
|
language=assistant_language,
|
||||||
|
tts_plugin=tts_plugin,
|
||||||
|
tts_args=tts_args,
|
||||||
|
)
|
||||||
|
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
|
@ -172,10 +183,11 @@ class AssistantSnowboyBackend(AssistantBackend):
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
super().run()
|
super().run()
|
||||||
self.detector.start(detected_callback=[
|
self.detector.start(
|
||||||
self.hotword_detected(hotword)
|
detected_callback=[
|
||||||
for hotword in self.models.keys()
|
self.hotword_detected(hotword) for hotword in self.models.keys()
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from platypush.backend import Backend
|
from platypush.backend import Backend
|
||||||
from platypush.message.event.wiimote import WiimoteEvent, \
|
from platypush.message.event.wiimote import (
|
||||||
WiimoteConnectionEvent, WiimoteDisconnectionEvent
|
WiimoteEvent,
|
||||||
|
WiimoteConnectionEvent,
|
||||||
|
WiimoteDisconnectionEvent,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class WiimoteBackend(Backend):
|
class WiimoteBackend(Backend):
|
||||||
|
@ -30,8 +29,9 @@ class WiimoteBackend(Backend):
|
||||||
_last_btn_event_time = 0
|
_last_btn_event_time = 0
|
||||||
_bdaddr = None
|
_bdaddr = None
|
||||||
|
|
||||||
def __init__(self, bdaddr=_bdaddr, inactivity_timeout=_inactivity_timeout,
|
def __init__(
|
||||||
*args, **kwargs):
|
self, bdaddr=_bdaddr, inactivity_timeout=_inactivity_timeout, *args, **kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
:param bdaddr: If set, connect to this specific Wiimote physical address (example: 00:11:22:33:44:55)
|
:param bdaddr: If set, connect to this specific Wiimote physical address (example: 00:11:22:33:44:55)
|
||||||
:type bdaddr: str
|
:type bdaddr: str
|
||||||
|
@ -55,7 +55,9 @@ class WiimoteBackend(Backend):
|
||||||
self._wiimote = cwiid.Wiimote()
|
self._wiimote = cwiid.Wiimote()
|
||||||
|
|
||||||
self._wiimote.enable(cwiid.FLAG_MOTIONPLUS)
|
self._wiimote.enable(cwiid.FLAG_MOTIONPLUS)
|
||||||
self._wiimote.rpt_mode = cwiid.RPT_ACC | cwiid.RPT_BTN | cwiid.RPT_MOTIONPLUS
|
self._wiimote.rpt_mode = (
|
||||||
|
cwiid.RPT_ACC | cwiid.RPT_BTN | cwiid.RPT_MOTIONPLUS
|
||||||
|
)
|
||||||
|
|
||||||
self.logger.info('WiiMote connected')
|
self.logger.info('WiiMote connected')
|
||||||
self._last_btn_event_time = time.time()
|
self._last_btn_event_time = time.time()
|
||||||
|
@ -65,24 +67,34 @@ class WiimoteBackend(Backend):
|
||||||
|
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
import cwiid
|
import cwiid
|
||||||
|
|
||||||
wm = self.get_wiimote()
|
wm = self.get_wiimote()
|
||||||
state = wm.state
|
state = wm.state
|
||||||
parsed_state = {}
|
parsed_state = {}
|
||||||
|
|
||||||
# Get buttons
|
# Get buttons
|
||||||
all_btns = [attr for attr in dir(cwiid) if attr.startswith('BTN_')]
|
all_btns = [attr for attr in dir(cwiid) if attr.startswith('BTN_')]
|
||||||
parsed_state['buttons'] = {btn: True for btn in all_btns
|
parsed_state['buttons'] = {
|
||||||
if state.get('buttons', 0) & getattr(cwiid, btn) != 0}
|
btn: True
|
||||||
|
for btn in all_btns
|
||||||
|
if state.get('buttons', 0) & getattr(cwiid, btn) != 0
|
||||||
|
}
|
||||||
|
|
||||||
# Get LEDs
|
# Get LEDs
|
||||||
all_leds = [attr for attr in dir(cwiid) if re.match('LED\d_ON', attr)]
|
all_leds = [attr for attr in dir(cwiid) if re.match(r'LED\d_ON', attr)]
|
||||||
parsed_state['led'] = {led[:4]: True for led in all_leds
|
parsed_state['led'] = {
|
||||||
if state.get('leds', 0) & getattr(cwiid, led) != 0}
|
led[:4]: True
|
||||||
|
for led in all_leds
|
||||||
|
if state.get('leds', 0) & getattr(cwiid, led) != 0
|
||||||
|
}
|
||||||
|
|
||||||
# Get errors
|
# Get errors
|
||||||
all_errs = [attr for attr in dir(cwiid) if attr.startswith('ERROR_')]
|
all_errs = [attr for attr in dir(cwiid) if attr.startswith('ERROR_')]
|
||||||
parsed_state['error'] = {err: True for err in all_errs
|
parsed_state['error'] = {
|
||||||
if state.get('errs', 0) & getattr(cwiid, err) != 0}
|
err: True
|
||||||
|
for err in all_errs
|
||||||
|
if state.get('errs', 0) & getattr(cwiid, err) != 0
|
||||||
|
}
|
||||||
|
|
||||||
parsed_state['battery'] = round(state.get('battery', 0) / cwiid.BATTERY_MAX, 3)
|
parsed_state['battery'] = round(state.get('battery', 0) / cwiid.BATTERY_MAX, 3)
|
||||||
parsed_state['rumble'] = bool(state.get('rumble', 0))
|
parsed_state['rumble'] = bool(state.get('rumble', 0))
|
||||||
|
@ -92,8 +104,9 @@ class WiimoteBackend(Backend):
|
||||||
|
|
||||||
if 'motionplus' in state:
|
if 'motionplus' in state:
|
||||||
parsed_state['motionplus'] = {
|
parsed_state['motionplus'] = {
|
||||||
'angle_rate': tuple(int(angle / 100) for angle
|
'angle_rate': tuple(
|
||||||
in state['motionplus']['angle_rate']),
|
int(angle / 100) for angle in state['motionplus']['angle_rate']
|
||||||
|
),
|
||||||
'low_speed': state['motionplus']['low_speed'],
|
'low_speed': state['motionplus']['low_speed'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,23 +134,30 @@ class WiimoteBackend(Backend):
|
||||||
while not self.should_stop():
|
while not self.should_stop():
|
||||||
try:
|
try:
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
changed_state = {k: state[k] for k in state.keys()
|
changed_state = {
|
||||||
if state[k] != last_state.get(k)}
|
k: state[k] for k in state.keys() if state[k] != last_state.get(k)
|
||||||
|
}
|
||||||
|
|
||||||
if changed_state:
|
if changed_state:
|
||||||
self.bus.post(WiimoteEvent(**changed_state))
|
self.bus.post(WiimoteEvent(**changed_state))
|
||||||
|
|
||||||
if 'buttons' in changed_state:
|
if 'buttons' in changed_state:
|
||||||
self._last_btn_event_time = time.time()
|
self._last_btn_event_time = time.time()
|
||||||
elif last_state and time.time() - \
|
elif (
|
||||||
self._last_btn_event_time >= self._inactivity_timeout:
|
last_state
|
||||||
|
and time.time() - self._last_btn_event_time
|
||||||
|
>= self._inactivity_timeout
|
||||||
|
):
|
||||||
self.logger.info('Wiimote disconnected upon timeout')
|
self.logger.info('Wiimote disconnected upon timeout')
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
last_state = state
|
last_state = state
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
if type(e) == RuntimeError and str(e) == 'Error opening wiimote connection':
|
if (
|
||||||
|
type(e) == RuntimeError
|
||||||
|
and str(e) == 'Error opening wiimote connection'
|
||||||
|
):
|
||||||
if self._connection_attempts == 0:
|
if self._connection_attempts == 0:
|
||||||
self.logger.info('Press 1+2 to pair your WiiMote controller')
|
self.logger.info('Press 1+2 to pair your WiiMote controller')
|
||||||
else:
|
else:
|
||||||
|
@ -146,4 +166,5 @@ class WiimoteBackend(Backend):
|
||||||
self.close()
|
self.close()
|
||||||
self._connection_attempts += 1
|
self._connection_attempts += 1
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -3,9 +3,6 @@ Platydock
|
||||||
|
|
||||||
Platydock is a helper that allows you to easily manage (create, destroy, start,
|
Platydock is a helper that allows you to easily manage (create, destroy, start,
|
||||||
stop and list) Platypush instances as Docker images.
|
stop and list) Platypush instances as Docker images.
|
||||||
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
.. license: MIT
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from platypush.context import get_bus
|
from platypush.context import get_bus
|
||||||
from platypush.plugins import action
|
from platypush.plugins import action
|
||||||
from platypush.plugins.assistant import AssistantPlugin
|
from platypush.plugins.assistant import AssistantPlugin
|
||||||
|
|
||||||
from platypush.message.event.assistant import ConversationStartEvent, \
|
from platypush.message.event.assistant import (
|
||||||
ConversationEndEvent, SpeechRecognizedEvent, ResponseEvent
|
ConversationStartEvent,
|
||||||
|
ConversationEndEvent,
|
||||||
|
SpeechRecognizedEvent,
|
||||||
|
ResponseEvent,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AssistantEchoPlugin(AssistantPlugin):
|
class AssistantEchoPlugin(AssistantPlugin):
|
||||||
|
@ -38,8 +39,13 @@ class AssistantEchoPlugin(AssistantPlugin):
|
||||||
* **avs** (``pip install avs``)
|
* **avs** (``pip install avs``)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, avs_config_file: str = None, audio_device: str = 'default',
|
def __init__(
|
||||||
audio_player: str = 'default', **kwargs):
|
self,
|
||||||
|
avs_config_file: Optional[str] = None,
|
||||||
|
audio_device: str = 'default',
|
||||||
|
audio_player: str = 'default',
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
:param avs_config_file: AVS credentials file - default: ~/.avs.json. If the file doesn't exist then
|
:param avs_config_file: AVS credentials file - default: ~/.avs.json. If the file doesn't exist then
|
||||||
an instance of the AVS authentication service will be spawned. You can login through an Amazon
|
an instance of the AVS authentication service will be spawned. You can login through an Amazon
|
||||||
|
@ -61,9 +67,12 @@ class AssistantEchoPlugin(AssistantPlugin):
|
||||||
|
|
||||||
if not avs_config_file or not os.path.isfile(avs_config_file):
|
if not avs_config_file or not os.path.isfile(avs_config_file):
|
||||||
from avs.auth import auth
|
from avs.auth import auth
|
||||||
|
|
||||||
auth(None, avs_config_file)
|
auth(None, avs_config_file)
|
||||||
self.logger.warning('Amazon Echo assistant credentials not configured. Open http://localhost:3000 ' +
|
self.logger.warning(
|
||||||
'to authenticate this client')
|
'Amazon Echo assistant credentials not configured. Open http://localhost:3000 '
|
||||||
|
+ 'to authenticate this client'
|
||||||
|
)
|
||||||
|
|
||||||
self.audio_device = audio_device
|
self.audio_device = audio_device
|
||||||
self.audio_player = audio_player
|
self.audio_player = audio_player
|
||||||
|
@ -84,37 +93,43 @@ class AssistantEchoPlugin(AssistantPlugin):
|
||||||
def _on_ready(self):
|
def _on_ready(self):
|
||||||
def _callback():
|
def _callback():
|
||||||
self._ready = True
|
self._ready = True
|
||||||
|
|
||||||
return _callback
|
return _callback
|
||||||
|
|
||||||
def _on_listening(self):
|
def _on_listening(self):
|
||||||
def _callback():
|
def _callback():
|
||||||
get_bus().post(ConversationStartEvent(assistant=self))
|
get_bus().post(ConversationStartEvent(assistant=self))
|
||||||
|
|
||||||
return _callback
|
return _callback
|
||||||
|
|
||||||
def _on_speaking(self):
|
def _on_speaking(self):
|
||||||
def _callback():
|
def _callback():
|
||||||
# AVS doesn't provide a way to access the response text
|
# AVS doesn't provide a way to access the response text
|
||||||
get_bus().post(ResponseEvent(assistant=self, response_text=''))
|
get_bus().post(ResponseEvent(assistant=self, response_text=''))
|
||||||
|
|
||||||
return _callback
|
return _callback
|
||||||
|
|
||||||
def _on_finished(self):
|
def _on_finished(self):
|
||||||
def _callback():
|
def _callback():
|
||||||
get_bus().post(ConversationEndEvent(assistant=self))
|
get_bus().post(ConversationEndEvent(assistant=self))
|
||||||
|
|
||||||
return _callback
|
return _callback
|
||||||
|
|
||||||
def _on_disconnected(self):
|
def _on_disconnected(self):
|
||||||
def _callback():
|
def _callback():
|
||||||
self._ready = False
|
self._ready = False
|
||||||
|
|
||||||
return _callback
|
return _callback
|
||||||
|
|
||||||
def _on_thinking(self):
|
def _on_thinking(self):
|
||||||
def _callback():
|
def _callback():
|
||||||
# AVS doesn't provide a way to access the detected text
|
# AVS doesn't provide a way to access the detected text
|
||||||
get_bus().post(SpeechRecognizedEvent(assistant=self, phrase=''))
|
get_bus().post(SpeechRecognizedEvent(assistant=self, phrase=''))
|
||||||
|
|
||||||
return _callback
|
return _callback
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def start_conversation(self, **kwargs):
|
def start_conversation(self, **_):
|
||||||
if not self._ready:
|
if not self._ready:
|
||||||
raise RuntimeError('Echo assistant not ready')
|
raise RuntimeError('Echo assistant not ready')
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
from platypush.backend.assistant.google import AssistantGoogleBackend
|
from platypush.backend.assistant.google import AssistantGoogleBackend
|
||||||
from platypush.context import get_backend
|
from platypush.context import get_backend
|
||||||
from platypush.plugins import action
|
from platypush.plugins import action
|
||||||
|
@ -19,10 +15,12 @@ class AssistantGooglePlugin(AssistantPlugin):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def _get_assistant(self) -> AssistantGoogleBackend:
|
def _get_assistant(self) -> AssistantGoogleBackend:
|
||||||
return get_backend('assistant.google')
|
backend = get_backend('assistant.google')
|
||||||
|
assert backend, 'The assistant.google backend is not configured.'
|
||||||
|
return backend
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def start_conversation(self, **kwargs):
|
def start_conversation(self):
|
||||||
"""
|
"""
|
||||||
Programmatically start a conversation with the assistant
|
Programmatically start a conversation with the assistant
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
|
@ -53,7 +49,9 @@ class CalendarPlugin(Plugin, CalendarInterface):
|
||||||
|
|
||||||
for calendar in calendars:
|
for calendar in calendars:
|
||||||
if 'type' not in calendar:
|
if 'type' not in calendar:
|
||||||
self.logger.warning("Invalid calendar with no type specified: {}".format(calendar))
|
self.logger.warning(
|
||||||
|
"Invalid calendar with no type specified: {}".format(calendar)
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
cal_type = calendar.pop('type')
|
cal_type = calendar.pop('type')
|
||||||
|
@ -62,7 +60,6 @@ class CalendarPlugin(Plugin, CalendarInterface):
|
||||||
module = importlib.import_module(module_name)
|
module = importlib.import_module(module_name)
|
||||||
self.calendars.append(getattr(module, class_name)(**calendar))
|
self.calendars.append(getattr(module, class_name)(**calendar))
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def get_upcoming_events(self, max_results=10):
|
def get_upcoming_events(self, max_results=10):
|
||||||
"""
|
"""
|
||||||
|
@ -71,7 +68,8 @@ class CalendarPlugin(Plugin, CalendarInterface):
|
||||||
:param max_results: Maximum number of results to be returned (default: 10)
|
:param max_results: Maximum number of results to be returned (default: 10)
|
||||||
:type max_results: int
|
:type max_results: int
|
||||||
|
|
||||||
:returns: platypush.message.Response -- Response object with the list of events in the Google calendar API format.
|
:returns: platypush.message.Response -- Response object with the list of
|
||||||
|
events in the Google calendar API format.
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
|
@ -113,15 +111,16 @@ class CalendarPlugin(Plugin, CalendarInterface):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning('Could not retrieve events: {}'.format(str(e)))
|
self.logger.warning('Could not retrieve events: {}'.format(str(e)))
|
||||||
|
|
||||||
events = sorted(events, key=lambda event:
|
events = sorted(
|
||||||
dateutil.parser.parse(
|
events,
|
||||||
event['start']['dateTime']
|
key=lambda event: dateutil.parser.parse(
|
||||||
if 'dateTime' in event['start']
|
event['start']['dateTime']
|
||||||
else event['start']['date'] + 'T00:00:00+00:00'
|
if 'dateTime' in event['start']
|
||||||
))[:max_results]
|
else event['start']['date'] + 'T00:00:00+00:00'
|
||||||
|
),
|
||||||
|
)[:max_results]
|
||||||
|
|
||||||
return events
|
return events
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import requests
|
import requests
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
@ -35,15 +31,14 @@ class CalendarIcalPlugin(Plugin, CalendarInterface):
|
||||||
return
|
return
|
||||||
|
|
||||||
if type(t.dt) == datetime.date:
|
if type(t.dt) == datetime.date:
|
||||||
return (
|
return datetime.datetime(
|
||||||
datetime.datetime(
|
t.dt.year, t.dt.month, t.dt.day, tzinfo=datetime.timezone.utc
|
||||||
t.dt.year, t.dt.month, t.dt.day, tzinfo=datetime.timezone.utc
|
).isoformat()
|
||||||
).isoformat()
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
datetime.datetime.utcfromtimestamp(t.dt.timestamp())
|
datetime.datetime.utcfromtimestamp(t.dt.timestamp())
|
||||||
.replace(tzinfo=datetime.timezone.utc).isoformat()
|
.replace(tzinfo=datetime.timezone.utc)
|
||||||
|
.isoformat()
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -52,23 +47,27 @@ class CalendarIcalPlugin(Plugin, CalendarInterface):
|
||||||
'id': str(event.get('uid')) if event.get('uid') else None,
|
'id': str(event.get('uid')) if event.get('uid') else None,
|
||||||
'kind': 'calendar#event',
|
'kind': 'calendar#event',
|
||||||
'summary': str(event.get('summary')) if event.get('summary') else None,
|
'summary': str(event.get('summary')) if event.get('summary') else None,
|
||||||
'description': str(event.get('description')) if event.get('description') else None,
|
'description': str(event.get('description'))
|
||||||
|
if event.get('description')
|
||||||
|
else None,
|
||||||
'status': str(event.get('status')).lower() if event.get('status') else None,
|
'status': str(event.get('status')).lower() if event.get('status') else None,
|
||||||
'responseStatus': str(event.get('partstat')).lower() if event.get('partstat') else None,
|
'responseStatus': str(event.get('partstat')).lower()
|
||||||
|
if event.get('partstat')
|
||||||
|
else None,
|
||||||
'location': str(event.get('location')) if event.get('location') else None,
|
'location': str(event.get('location')) if event.get('location') else None,
|
||||||
'htmlLink': str(event.get('url')) if event.get('url') else None,
|
'htmlLink': str(event.get('url')) if event.get('url') else None,
|
||||||
'organizer': {
|
'organizer': {
|
||||||
'email': str(event.get('organizer')).replace('MAILTO:', ''),
|
'email': str(event.get('organizer')).replace('MAILTO:', ''),
|
||||||
'displayName': event.get('organizer').params.get('cn')
|
'displayName': event.get('organizer').params.get('cn'),
|
||||||
} if event.get('organizer') else None,
|
}
|
||||||
|
if event.get('organizer')
|
||||||
|
else None,
|
||||||
'created': cls._convert_timestamp(event, 'created'),
|
'created': cls._convert_timestamp(event, 'created'),
|
||||||
'updated': cls._convert_timestamp(event, 'last-modified'),
|
'updated': cls._convert_timestamp(event, 'last-modified'),
|
||||||
'start': {
|
'start': {
|
||||||
'dateTime': cls._convert_timestamp(event, 'dtstart'),
|
'dateTime': cls._convert_timestamp(event, 'dtstart'),
|
||||||
'timeZone': 'UTC',
|
'timeZone': 'UTC',
|
||||||
},
|
},
|
||||||
|
|
||||||
'end': {
|
'end': {
|
||||||
'dateTime': cls._convert_timestamp(event, 'dtend'),
|
'dateTime': cls._convert_timestamp(event, 'dtend'),
|
||||||
'timeZone': 'UTC',
|
'timeZone': 'UTC',
|
||||||
|
@ -76,7 +75,7 @@ class CalendarIcalPlugin(Plugin, CalendarInterface):
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def get_upcoming_events(self, max_results=10, only_participating=True):
|
def get_upcoming_events(self, *_, only_participating=True, **__):
|
||||||
"""
|
"""
|
||||||
Get the upcoming events. See
|
Get the upcoming events. See
|
||||||
:func:`~platypush.plugins.calendar.CalendarPlugin.get_upcoming_events`.
|
:func:`~platypush.plugins.calendar.CalendarPlugin.get_upcoming_events`.
|
||||||
|
@ -86,8 +85,9 @@ class CalendarIcalPlugin(Plugin, CalendarInterface):
|
||||||
|
|
||||||
events = []
|
events = []
|
||||||
response = requests.get(self.url)
|
response = requests.get(self.url)
|
||||||
assert response.ok, \
|
assert response.ok, "HTTP error while getting events from {}: {}".format(
|
||||||
"HTTP error while getting events from {}: {}".format(self.url, response.text)
|
self.url, response.text
|
||||||
|
)
|
||||||
|
|
||||||
calendar = Calendar.from_ical(response.text)
|
calendar = Calendar.from_ical(response.text)
|
||||||
for event in calendar.walk():
|
for event in calendar.walk():
|
||||||
|
@ -97,16 +97,24 @@ class CalendarIcalPlugin(Plugin, CalendarInterface):
|
||||||
event = self._translate_event(event)
|
event = self._translate_event(event)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
event['status'] != 'cancelled'
|
event['status'] != 'cancelled'
|
||||||
and event['end'].get('dateTime')
|
and event['end'].get('dateTime')
|
||||||
and event['end']['dateTime'] >= datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat()
|
and event['end']['dateTime']
|
||||||
and (
|
>= datetime.datetime.utcnow()
|
||||||
(only_participating
|
.replace(tzinfo=datetime.timezone.utc)
|
||||||
and event.get('responseStatus') in [None, 'accepted', 'tentative'])
|
.isoformat()
|
||||||
or not only_participating)
|
and (
|
||||||
|
(
|
||||||
|
only_participating
|
||||||
|
and event.get('responseStatus')
|
||||||
|
in [None, 'accepted', 'tentative']
|
||||||
|
)
|
||||||
|
or not only_participating
|
||||||
|
)
|
||||||
):
|
):
|
||||||
events.append(event)
|
events.append(event)
|
||||||
|
|
||||||
return events
|
return events
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
from platypush.plugins import Plugin
|
from platypush.plugins import Plugin
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,7 +19,8 @@ class GooglePlugin(Plugin):
|
||||||
|
|
||||||
5. Generate a credentials file for the needed scope::
|
5. Generate a credentials file for the needed scope::
|
||||||
|
|
||||||
python -m platypush.plugins.google.credentials 'https://www.googleapis.com/auth/gmail.compose' ~/client_secret.json
|
python -m platypush.plugins.google.credentials \
|
||||||
|
'https://www.googleapis.com/auth/gmail.compose' ~/client_secret.json
|
||||||
|
|
||||||
Requires:
|
Requires:
|
||||||
|
|
||||||
|
@ -32,7 +29,7 @@ class GooglePlugin(Plugin):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, scopes=None, *args, **kwargs):
|
def __init__(self, scopes=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialized the Google plugin with the required scopes.
|
Initialized the Google plugin with the required scopes.
|
||||||
|
|
||||||
|
@ -41,14 +38,13 @@ class GooglePlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from platypush.plugins.google.credentials import get_credentials
|
from platypush.plugins.google.credentials import get_credentials
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self._scopes = scopes or []
|
self._scopes = scopes or []
|
||||||
|
|
||||||
if self._scopes:
|
if self._scopes:
|
||||||
scopes = ' '.join(sorted(self._scopes))
|
scopes = ' '.join(sorted(self._scopes))
|
||||||
self.credentials = {
|
self.credentials = {scopes: get_credentials(scopes)}
|
||||||
scopes: get_credentials(scopes)
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
self.credentials = {}
|
self.credentials = {}
|
||||||
|
|
||||||
|
@ -57,7 +53,7 @@ class GooglePlugin(Plugin):
|
||||||
from apiclient import discovery
|
from apiclient import discovery
|
||||||
|
|
||||||
if scopes is None:
|
if scopes is None:
|
||||||
scopes = getattr(self, 'scopes') if hasattr(self, 'scopes') else []
|
scopes = getattr(self, 'scopes', [])
|
||||||
|
|
||||||
scopes = ' '.join(sorted(scopes))
|
scopes = ' '.join(sorted(scopes))
|
||||||
credentials = self.credentials[scopes]
|
credentials = self.credentials[scopes]
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from platypush.plugins import action
|
from platypush.plugins import action
|
||||||
|
@ -34,9 +30,17 @@ class GoogleCalendarPlugin(GooglePlugin, CalendarInterface):
|
||||||
|
|
||||||
now = datetime.datetime.utcnow().isoformat() + 'Z'
|
now = datetime.datetime.utcnow().isoformat() + 'Z'
|
||||||
service = self.get_service('calendar', 'v3')
|
service = self.get_service('calendar', 'v3')
|
||||||
result = service.events().list(calendarId='primary', timeMin=now,
|
result = (
|
||||||
maxResults=max_results, singleEvents=True,
|
service.events()
|
||||||
orderBy='startTime').execute()
|
.list(
|
||||||
|
calendarId='primary',
|
||||||
|
timeMin=now,
|
||||||
|
maxResults=max_results,
|
||||||
|
singleEvents=True,
|
||||||
|
orderBy='startTime',
|
||||||
|
)
|
||||||
|
.execute()
|
||||||
|
)
|
||||||
|
|
||||||
events = result.get('items', [])
|
events = result.get('items', [])
|
||||||
return events
|
return events
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
|
@ -81,8 +77,9 @@ class GoogleMailPlugin(GooglePlugin):
|
||||||
elif main_type == 'audio':
|
elif main_type == 'audio':
|
||||||
msg = MIMEAudio(content, _subtype=sub_type)
|
msg = MIMEAudio(content, _subtype=sub_type)
|
||||||
elif main_type == 'application':
|
elif main_type == 'application':
|
||||||
msg = MIMEApplication(content, _subtype=sub_type,
|
msg = MIMEApplication(
|
||||||
_encoder=encode_base64)
|
content, _subtype=sub_type, _encoder=encode_base64
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
msg = MIMEBase(main_type, sub_type)
|
msg = MIMEBase(main_type, sub_type)
|
||||||
msg.set_payload(content)
|
msg.set_payload(content)
|
||||||
|
@ -93,8 +90,7 @@ class GoogleMailPlugin(GooglePlugin):
|
||||||
|
|
||||||
service = self.get_service('gmail', 'v1')
|
service = self.get_service('gmail', 'v1')
|
||||||
body = {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}
|
body = {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}
|
||||||
message = (service.users().messages().send(
|
message = service.users().messages().send(userId='me', body=body).execute()
|
||||||
userId='me', body=body).execute())
|
|
||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
|
@ -108,4 +104,5 @@ class GoogleMailPlugin(GooglePlugin):
|
||||||
labels = results.get('labels', [])
|
labels = results.get('labels', [])
|
||||||
return labels
|
return labels
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Union, Optional
|
from typing import List, Union, Optional
|
||||||
|
|
||||||
|
@ -50,23 +46,29 @@ class GoogleMapsPlugin(GooglePlugin):
|
||||||
:type longitude: float
|
:type longitude: float
|
||||||
"""
|
"""
|
||||||
|
|
||||||
response = requests.get('https://maps.googleapis.com/maps/api/geocode/json',
|
response = requests.get(
|
||||||
params={
|
'https://maps.googleapis.com/maps/api/geocode/json',
|
||||||
'latlng': '{},{}'.format(latitude, longitude),
|
params={
|
||||||
'key': self.api_key,
|
'latlng': '{},{}'.format(latitude, longitude),
|
||||||
}).json()
|
'key': self.api_key,
|
||||||
|
},
|
||||||
|
).json()
|
||||||
|
|
||||||
address = dict(
|
address = {
|
||||||
(t, None) for t in ['street_number', 'street', 'locality', 'country', 'postal_code']
|
t: None
|
||||||
)
|
for t in ['street_number', 'street', 'locality', 'country', 'postal_code']
|
||||||
|
}
|
||||||
|
|
||||||
address['latitude'] = latitude
|
address['latitude'] = latitude
|
||||||
address['longitude'] = longitude
|
address['longitude'] = longitude
|
||||||
|
|
||||||
if 'results' in response and response['results']:
|
if 'results' in response and response['results']:
|
||||||
result = response['results'][0]
|
result = response['results'][0]
|
||||||
self.logger.info('Google Maps geocode response for latlng ({},{}): {}'.
|
self.logger.info(
|
||||||
format(latitude, longitude, result))
|
'Google Maps geocode response for latlng ({},{}): {}'.format(
|
||||||
|
latitude, longitude, result
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
address['address'] = result['formatted_address'].split(',')[0]
|
address['address'] = result['formatted_address'].split(',')[0]
|
||||||
for addr_component in result['address_components']:
|
for addr_component in result['address_components']:
|
||||||
|
@ -92,11 +94,13 @@ class GoogleMapsPlugin(GooglePlugin):
|
||||||
:type longitude: float
|
:type longitude: float
|
||||||
"""
|
"""
|
||||||
|
|
||||||
response = requests.get('https://maps.googleapis.com/maps/api/elevation/json',
|
response = requests.get(
|
||||||
params={
|
'https://maps.googleapis.com/maps/api/elevation/json',
|
||||||
'locations': '{},{}'.format(latitude, longitude),
|
params={
|
||||||
'key': self.api_key,
|
'locations': '{},{}'.format(latitude, longitude),
|
||||||
}).json()
|
'key': self.api_key,
|
||||||
|
},
|
||||||
|
).json()
|
||||||
|
|
||||||
elevation = None
|
elevation = None
|
||||||
|
|
||||||
|
@ -106,16 +110,20 @@ class GoogleMapsPlugin(GooglePlugin):
|
||||||
return {'elevation': elevation}
|
return {'elevation': elevation}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def get_travel_time(self, origins: List[str], destinations: List[str],
|
def get_travel_time(
|
||||||
departure_time: Optional[datetime_types] = None,
|
self,
|
||||||
arrival_time: Optional[datetime_types] = None,
|
origins: List[str],
|
||||||
units: str = 'metric',
|
destinations: List[str],
|
||||||
avoid: Optional[List[str]] = None,
|
departure_time: Optional[datetime_types] = None,
|
||||||
language: Optional[str] = None,
|
arrival_time: Optional[datetime_types] = None,
|
||||||
mode: Optional[str] = None,
|
units: str = 'metric',
|
||||||
traffic_model: Optional[str] = None,
|
avoid: Optional[List[str]] = None,
|
||||||
transit_mode: Optional[List[str]] = None,
|
language: Optional[str] = None,
|
||||||
transit_route_preference: Optional[str] = None):
|
mode: Optional[str] = None,
|
||||||
|
traffic_model: Optional[str] = None,
|
||||||
|
transit_mode: Optional[List[str]] = None,
|
||||||
|
transit_route_preference: Optional[str] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Get the estimated travel time between a set of departure points and a set of destinations.
|
Get the estimated travel time between a set of departure points and a set of destinations.
|
||||||
|
|
||||||
|
@ -194,17 +202,25 @@ class GoogleMapsPlugin(GooglePlugin):
|
||||||
'origins': '|'.join(origins),
|
'origins': '|'.join(origins),
|
||||||
'destinations': '|'.join(destinations),
|
'destinations': '|'.join(destinations),
|
||||||
'units': units,
|
'units': units,
|
||||||
**({'departure_time': to_datetime(departure_time)} if departure_time else {}),
|
**(
|
||||||
|
{'departure_time': to_datetime(departure_time)}
|
||||||
|
if departure_time
|
||||||
|
else {}
|
||||||
|
),
|
||||||
**({'arrival_time': to_datetime(arrival_time)} if arrival_time else {}),
|
**({'arrival_time': to_datetime(arrival_time)} if arrival_time else {}),
|
||||||
**({'avoid': '|'.join(avoid)} if avoid else {}),
|
**({'avoid': '|'.join(avoid)} if avoid else {}),
|
||||||
**({'language': language} if language else {}),
|
**({'language': language} if language else {}),
|
||||||
**({'mode': mode} if mode else {}),
|
**({'mode': mode} if mode else {}),
|
||||||
**({'traffic_model': traffic_model} if traffic_model else {}),
|
**({'traffic_model': traffic_model} if traffic_model else {}),
|
||||||
**({'transit_mode': transit_mode} if transit_mode else {}),
|
**({'transit_mode': transit_mode} if transit_mode else {}),
|
||||||
**({'transit_route_preference': transit_route_preference}
|
**(
|
||||||
if transit_route_preference else {}),
|
{'transit_route_preference': transit_route_preference}
|
||||||
|
if transit_route_preference
|
||||||
|
else {}
|
||||||
|
),
|
||||||
'key': self.api_key,
|
'key': self.api_key,
|
||||||
}).json()
|
},
|
||||||
|
).json()
|
||||||
|
|
||||||
assert not rs.get('error_message'), f'{rs["status"]}: {rs["error_message"]}'
|
assert not rs.get('error_message'), f'{rs["status"]}: {rs["error_message"]}'
|
||||||
rows = rs.get('rows', [])
|
rows = rs.get('rows', [])
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
from platypush.plugins import action
|
from platypush.plugins import action
|
||||||
from platypush.plugins.google import GooglePlugin
|
from platypush.plugins.google import GooglePlugin
|
||||||
|
|
||||||
|
@ -48,10 +44,12 @@ class GoogleYoutubePlugin(GooglePlugin):
|
||||||
:type max_results: int
|
:type max_results: int
|
||||||
|
|
||||||
:param kwargs: Any extra arguments that will be transparently passed to the YouTube API.
|
:param kwargs: Any extra arguments that will be transparently passed to the YouTube API.
|
||||||
See the `Getting started - parameters <https://developers.google.com/youtube/v3/docs/search/list#parameters>`_.
|
See the `Getting started - parameters
|
||||||
|
<https://developers.google.com/youtube/v3/docs/search/list#parameters>`_.
|
||||||
|
|
||||||
:return: A list of YouTube resources.
|
:return: A list of YouTube resources.
|
||||||
See the `Getting started - Resource <https://developers.google.com/youtube/v3/docs/search#resource>`_.
|
See the `Getting started - Resource
|
||||||
|
<https://developers.google.com/youtube/v3/docs/search#resource>`_.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
parts = parts or self._default_parts[:]
|
parts = parts or self._default_parts[:]
|
||||||
|
@ -63,9 +61,11 @@ class GoogleYoutubePlugin(GooglePlugin):
|
||||||
types = ','.join(types)
|
types = ','.join(types)
|
||||||
|
|
||||||
service = self.get_service('youtube', 'v3')
|
service = self.get_service('youtube', 'v3')
|
||||||
result = service.search().list(part=parts, q=query, type=types,
|
result = (
|
||||||
maxResults=max_results,
|
service.search()
|
||||||
**kwargs).execute()
|
.list(part=parts, q=query, type=types, maxResults=max_results, **kwargs)
|
||||||
|
.execute()
|
||||||
|
)
|
||||||
|
|
||||||
events = result.get('items', [])
|
events = result.get('items', [])
|
||||||
return events
|
return events
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
"""
|
|
||||||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from platypush.context import get_backend
|
from platypush.context import get_backend
|
||||||
from platypush.plugins import Plugin, action
|
from platypush.plugins import Plugin, action
|
||||||
|
|
||||||
|
|
||||||
class WiimotePlugin(Plugin):
|
class WiimotePlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
WiiMote plugin.
|
WiiMote plugin.
|
||||||
|
@ -20,7 +17,6 @@ class WiimotePlugin(Plugin):
|
||||||
def _get_wiimote(cls):
|
def _get_wiimote(cls):
|
||||||
return get_backend('wiimote').get_wiimote()
|
return get_backend('wiimote').get_wiimote()
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""
|
"""
|
||||||
|
@ -28,7 +24,6 @@ class WiimotePlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
self._get_wiimote()
|
self._get_wiimote()
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
|
@ -36,7 +31,6 @@ class WiimotePlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
get_backend('wiimote').close()
|
get_backend('wiimote').close()
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def rumble(self, secs):
|
def rumble(self, secs):
|
||||||
"""
|
"""
|
||||||
|
@ -47,7 +41,6 @@ class WiimotePlugin(Plugin):
|
||||||
time.sleep(secs)
|
time.sleep(secs)
|
||||||
wm.rumble = False
|
wm.rumble = False
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def state(self):
|
def state(self):
|
||||||
"""
|
"""
|
||||||
|
@ -55,23 +48,22 @@ class WiimotePlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
return get_backend('wiimote').get_state()
|
return get_backend('wiimote').get_state()
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_leds(self, leds):
|
def set_leds(self, leds):
|
||||||
"""
|
"""
|
||||||
Set the LEDs state on the controller
|
Set the LEDs state on the controller
|
||||||
|
|
||||||
:param leds: Iterable with the new states to be applied to the LEDs. Example: [1, 0, 0, 0] or (False, True, False, False)
|
:param leds: Iterable with the new states to be applied to the LEDs.
|
||||||
|
Example: [1, 0, 0, 0] or (False, True, False, False)
|
||||||
:type leds: list
|
:type leds: list
|
||||||
"""
|
"""
|
||||||
|
|
||||||
new_led = 0
|
new_led = 0
|
||||||
for i, led in enumerate(leds):
|
for i, led in enumerate(leds):
|
||||||
if led:
|
if led:
|
||||||
new_led |= (1 << i)
|
new_led |= 1 << i
|
||||||
|
|
||||||
self._get_wiimote().led = new_led
|
self._get_wiimote().led = new_led
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -30,7 +30,7 @@ setup(
|
||||||
name="platypush",
|
name="platypush",
|
||||||
version="0.50.2",
|
version="0.50.2",
|
||||||
author="Fabio Manganiello",
|
author="Fabio Manganiello",
|
||||||
author_email="info@fabiomanganiello.com",
|
author_email="fabio@manganiello.tech",
|
||||||
description="Platypush service",
|
description="Platypush service",
|
||||||
license="MIT",
|
license="MIT",
|
||||||
python_requires='>= 3.6',
|
python_requires='>= 3.6',
|
||||||
|
|
Loading…
Reference in a new issue